From d0804dd151047ba025d8b17c10a0e3e883032d3d Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 27 Jun 2023 00:29:50 +0300 Subject: [PATCH] feat: Add feature `lambdaworks-felt` to `felt` & `cairo-vm` crates (#1281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * Manually implement some common derives Also comment `from` impls for primitive numbers and (De)Serialize derives * Implement FromPrimitive * Implement ToPrimitive * Add BitAnd/Xor/Or implementations * Implement bit shift operators * Remove Signed implementation * Remove Integer impl * Impl Bounded * Impl from_str_radix * Add iter_u64_digits impl * Add Add impls * Add Sub impls * Patch arbitrary * Fix some warnings * Implement parse_bytes * Implement utility methods for tests * Fix test compilation errors * Add From impl for signed primitive nums * Impl From * Re-add bits fn * Impl Signed * Impl (De)Serialize * Fix compile errors and clippy suggestions * Pin cairo 1 compiler version * Reorder impls * Fix compile error * Fix various errors (tests pass!) * Remove to_bytes_be * Fix panicky from_bytes_be * Fix is_positive * Fix str conversions * Add documentation * Fix from_i64 accepting negative numbers * Use BigUint in from_bytes_be * Remove unneded field macro * Pin lambdaworks commit * Fix keccak * Fix from_i64 condition was reversed * Change Debug::fmt to return number in decimal * Appease clippy in felt crate * Silence clippy warning (for now) * Fix nostd error * Update lambdaworks to latest revision * Update rust version in CI * Change sqrt for lambdaworks' * Manually build BigDigits on to_biguint * Use a bigger number of iterations for square bench * Update lambdaworks-math revision * Fix sqrt tests and Shl/Shr impl (+add tests) * Update Cargo.lock * Appease clippy and fix Shr * Square input instead of using prop_assume * Revert the change to Shr * Appease clippy * Use bits instead of shl in range check * Remove `FeltBigInt` * Add lamdaworks-benchmarks.sh * Add lamdaworks-benchmarks.md * Update lamdaworks-benchmarks.md * Update commits * Update .md commits * Looooong benchmark * Remove bigbox clippy allow * Update lambdaworks to latest, and change AddAssign * Remove `-P` option in `xargs` In the measurements we got through `perf`, there were 3 to 4 times more page faults compared to base. This can be explained by the runner using swap memory because of the increase in memory usage. We can fix it by reducing the amount of processes ran in parallel by xargs (2 -> 1) * Change `to_(b|l)e_bytes` to not use lw primitives * Change comments in `Add<&Felt252> for u64` * Fix: wasn't indexing properly :P * Override default `div_mod_floor` impl for Felt252 * Disable swap memory before benchmarking * Reduce number of warmups and runs * Optimize `assert_le_felt` (used in math_integration_benchmark) * Use constant for zero() function * Extract division by constant * Use BigUint in assert_le_felt This part uses comparisons and integer division (that use `to_biguint`), so it's better to use `BigUints` directly. * Avoid calling `BigInt::abs` * Update changelog * Add tests for felt * Add TODO * Add other texts * Update lambdaworks to latest * Revert hyperfine arguments to main * Remove unneeded clones and into_owneds * Remove unneeded references and clones * Add BREAKING note to changelog * Make Felt252::one just copy a constant * Impl From for Felt252 * Change some uses of get_ref with get_mut_ref Using `get_ref` and later updating the variable with an `exec_scopes.insert_value(...)` causes two lookups in a hashmap, along with two creation of `String` from a slice. This change reduces it to just a single lookup and `String` creation. * Unify mem*_continue_* functions * Run benchmarks sequentially to avoid mem issues * Use div_mod_floor instead of div and mod * Use BigUint for non-modular calculations * Add TODO * Include both lib.rs * Fix lib_bigint_felt * Add test-lambdaworks-felt workflow * Fix failling example * Move extern crate import to lib.rs * Update changelog * Fix changelog * Fix example * Remove benchmark docs * Remove clone * Move crate-level attribute to lib.rs * Fix changelog * Remove blank line in toml * Use one line cfg directives * Remove reference * Restore clone * Fix doc test * Add `lambdaworks-felt` feature to vm crate * Add instructions to (de)activate the new feature * Use different matrix group for lambdaworks felt in CI * Move the sections a bit * Update lambdaworks-math version to 0.1.1 * Invert the part talking about features --------- Co-authored-by: Pedro Fontana Co-authored-by: TomΓ‘ <47506558+MegaRedHand@users.noreply.github.com> Co-authored-by: Mario Rugiero --- .github/workflows/bench.yml | 6 +- .github/workflows/hint_accountant.yml | 2 +- .github/workflows/hyperfine.yml | 4 +- .github/workflows/iai_main.yml | 6 +- .github/workflows/iai_pr.yml | 12 +- .github/workflows/publish.yml | 7 +- .github/workflows/rust.yml | 7 +- CHANGELOG.md | 6 + Cargo.lock | 77 +- README.md | 16 +- deps/parse-hyperlinks/src/lib.rs | 2 +- felt/Cargo.toml | 2 + ...{arbitrary.rs => arbitrary_bigint_felt.rs} | 0 felt/src/arbitrary_lambdaworks.rs | 47 + felt/src/bigint_felt.rs | 8 +- felt/src/lib.rs | 1665 +--------------- felt/src/lib_bigint_felt.rs | 1661 ++++++++++++++++ felt/src/lib_lambdaworks.rs | 1688 +++++++++++++++++ vm/Cargo.toml | 4 +- .../builtin_hint_processor_definition.rs | 19 +- .../cairo_keccak/keccak_hints.rs | 4 +- .../builtin_hint_processor/ec_utils.rs | 12 +- .../builtin_hint_processor/keccak_utils.rs | 33 +- .../builtin_hint_processor/math_utils.rs | 94 +- .../memcpy_hint_utils.rs | 39 +- .../builtin_hint_processor/memset_utils.rs | 16 +- .../builtin_hint_processor/uint256_utils.rs | 8 +- .../hint_processor_utils.rs | 17 +- vm/src/types/program.rs | 2 +- .../vm/runners/builtin_runner/range_check.rs | 2 +- 30 files changed, 3647 insertions(+), 1819 deletions(-) rename felt/src/{arbitrary.rs => arbitrary_bigint_felt.rs} (100%) create mode 100644 felt/src/arbitrary_lambdaworks.rs create mode 100644 felt/src/lib_bigint_felt.rs create mode 100644 felt/src/lib_lambdaworks.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 14beedcd4a..3fe9ae3a79 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -14,11 +14,9 @@ jobs: build: runs-on: ubuntu-20.04 steps: - - name: Install Rust 1.66.1 - uses: actions-rs/toolchain@v1 + - name: Install Rust + uses: dtolnay/rust-toolchain@1.69.0 with: - toolchain: 1.66.1 - override: true components: rustfmt, clippy - uses: actions/checkout@v3 - name: Python3 Build diff --git a/.github/workflows/hint_accountant.yml b/.github/workflows/hint_accountant.yml index 87ea94c036..a73d2bb272 100644 --- a/.github/workflows/hint_accountant.yml +++ b/.github/workflows/hint_accountant.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.66.1 + uses: dtolnay/rust-toolchain@1.69.0 - name: Set up Cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/.github/workflows/hyperfine.yml b/.github/workflows/hyperfine.yml index f80fc333ff..bfeb794c0d 100644 --- a/.github/workflows/hyperfine.yml +++ b/.github/workflows/hyperfine.yml @@ -74,9 +74,7 @@ jobs: - name: Install Rust if: ${{ steps.cache.outputs.cache-hit != 'true' }} - uses: dtolnay/rust-toolchain@stable - with: - toolchain: 1.66.1 + uses: dtolnay/rust-toolchain@1.69.0 - name: Checkout if: ${{ steps.cache.outputs.cache-hit != 'true' }} diff --git a/.github/workflows/iai_main.yml b/.github/workflows/iai_main.yml index ac19394879..6c382e650e 100644 --- a/.github/workflows/iai_main.yml +++ b/.github/workflows/iai_main.yml @@ -9,11 +9,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.66.1 - override: true - profile: minimal + uses: dtolnay/rust-toolchain@1.69.0 - name: Python3 Build uses: actions/setup-python@v4 with: diff --git a/.github/workflows/iai_pr.yml b/.github/workflows/iai_pr.yml index 25b7bb063f..83ee415a1b 100644 --- a/.github/workflows/iai_pr.yml +++ b/.github/workflows/iai_pr.yml @@ -9,11 +9,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.66.1 - override: true - profile: minimal + uses: dtolnay/rust-toolchain@1.69.0 - name: Python3 Build uses: actions/setup-python@v4 with: @@ -46,11 +42,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.66.1 - override: true - profile: minimal + uses: dtolnay/rust-toolchain@1.69.0 - name: Python3 Build uses: actions/setup-python@v4 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7fb6b34c56..943713cbd8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,11 +13,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@1.69.0 - name: Publish crate cairo-take_until_unbalanced env: CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} @@ -34,4 +30,3 @@ jobs: env: CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: cargo publish --token ${CRATES_TOKEN} --all-features -p cairo-vm - diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index da68ee1bb0..c7b9870c02 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -170,6 +170,7 @@ jobs: strategy: fail-fast: false matrix: + special-features: ["", "lambdaworks-felt"] target: [ test, test-no_std, test-wasm ] # TODO: features name: Run tests @@ -221,14 +222,14 @@ jobs: # FIXME: we need to update the Makefile to do this correctly case ${{ matrix.target }} in 'test') - cargo llvm-cov nextest --lcov --output-path lcov-${{ matrix.target }}.info --workspace --features "cairo-1-hints, test_utils" + cargo llvm-cov nextest --lcov --output-path lcov-${{ matrix.target }}.info --workspace --features "cairo-1-hints, test_utils, ${{ matrix.special_features }}" ;; 'test-no_std') - cargo llvm-cov nextest --lcov --output-path lcov-${{ matrix.target }}.info --workspace --no-default-features + cargo llvm-cov nextest --lcov --output-path lcov-${{ matrix.target }}.info --workspace --no-default-features --features "${{ matrix.special_features }}" ;; 'test-wasm') # NOTE: release mode is needed to avoid "too many locals" error - wasm-pack test --release --node vm --no-default-features + wasm-pack test --release --node vm --no-default-features --features "${{ matrix.special_features }}" ;; esac diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f6917a1ad..1573323634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ #### Upcoming Changes +* feat: Add feature `lambdaworks-felt` to `felt` & `cairo-vm` crates [#1218](https://github.com/lambdaclass/cairo-rs/pull/1281) + + Changes under this feature: + * `Felt252` now uses _lambdaworks_' `FieldElement` internally + * BREAKING: some methods of `Felt252` were removed, namely: `modpow` and `to_bytes_be` + #### [0.7.0] - 2023-6-26 * BREAKING: Integrate `RunResources` logic into `HintProcessor` trait [#1274](https://github.com/lambdaclass/cairo-rs/pull/1274) diff --git a/Cargo.lock b/Cargo.lock index f6ddc863e8..db142159b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,6 +177,15 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section", +] + [[package]] name = "atty" version = "0.2.14" @@ -282,6 +291,7 @@ dependencies = [ name = "cairo-felt" version = "0.7.0" dependencies = [ + "lambdaworks-math", "lazy_static", "num-bigint", "num-integer", @@ -961,6 +971,12 @@ dependencies = [ "itertools", ] +[[package]] +name = "critical-section" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -1358,6 +1374,15 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1377,6 +1402,19 @@ dependencies = [ "serde", ] +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin 0.9.8", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.3.3" @@ -1571,13 +1609,35 @@ dependencies = [ "regex", ] +[[package]] +name = "lambdaworks-gpu" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b233fcb8213965c77f67c07c3e8294539701a2b5fd55a4e05b48ab2210098063" +dependencies = [ + "rand", + "thiserror", +] + +[[package]] +name = "lambdaworks-math" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000838fc32d61770baebf9e3068606722258cee1490c43f72b1d751bc3c8994d" +dependencies = [ + "heapless", + "lambdaworks-gpu", + "rand", + "thiserror", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] @@ -2427,6 +2487,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "sprs" version = "0.7.1" @@ -2438,6 +2507,12 @@ dependencies = [ "num-traits 0.1.43", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "starknet-crypto" version = "0.5.1" diff --git a/README.md b/README.md index b708c2ccb4..88f03ffa45 100644 --- a/README.md +++ b/README.md @@ -81,14 +81,26 @@ These dependencies are only necessary in order to run the original VM, compile C ## πŸš€ Usage -### Running cairo-rs +### Adding cairo-rs as a dependency + +You can add the following to your rust project's `Cargo.toml`: + +```toml +cairo-vm = { version = '0.7.0', features = ["lambdaworks-felt"] } +``` + +The `features = ["lambdaworks-felt"]` part adds usage of [`lambdaworks-math`](https://github.com/lambdaclass/lambdaworks) as the backend for `Felt252`. This improves performance by more than 20%, and will be the default in the future. + +### Running cairo-rs from CLI To run programs from the command line, first compile the repository from the cairo-vm-cli folder: ```bash -cd cairo-vm-cli; cargo build --release; cd .. +cd cairo-vm-cli; cargo build --release -F lambdaworks-felt; cd .. ``` +The `-F lambdaworks-felt` part adds usage of [`lambdaworks-math`](https://github.com/lambdaclass/lambdaworks) as the backend for `Felt252`. This improves performance by more than 20%, and will be the default in the future. + Once the binary is built, it can be found in `target/release/` under the name `cairo-rvm-cli`. To compile a program, use `cairo-compile [path_to_the_.cairo_file] --output [desired_path_of_the_compiled_.json_file]`. For example: diff --git a/deps/parse-hyperlinks/src/lib.rs b/deps/parse-hyperlinks/src/lib.rs index 8da099d0c5..5bbbc7dd10 100644 --- a/deps/parse-hyperlinks/src/lib.rs +++ b/deps/parse-hyperlinks/src/lib.rs @@ -15,7 +15,7 @@ use nom::IResult; /// ``` /// use nom::bytes::complete::tag; /// use nom::sequence::delimited; -/// use parse_hyperlinks::take_until_unbalanced; +/// use cairo_take_until_unbalanced::take_until_unbalanced; /// /// let mut parser = delimited(tag("<"), take_until_unbalanced('<', '>'), tag(">")); /// assert_eq!(parser("<inside>abc"), Ok(("abc", "inside"))); diff --git a/felt/Cargo.toml b/felt/Cargo.toml index 51a4b2a8f0..37686c2141 100644 --- a/felt/Cargo.toml +++ b/felt/Cargo.toml @@ -9,6 +9,7 @@ description = "Field elements representation for the Cairo VM" default = ["std"] std = [] alloc = [] +lambdaworks-felt = ["dep:lambdaworks-math"] [dependencies] num-integer = { version = "0.1.45", default-features = false } @@ -18,6 +19,7 @@ lazy_static = { version = "1.4.0", default-features = false, features = [ "spin_no_std", ] } serde = { version = "1.0", features = ["derive"], default-features = false } +lambdaworks-math = { version = "0.1.1", default-features = false, optional=true } [dev-dependencies] proptest = "1.1.0" diff --git a/felt/src/arbitrary.rs b/felt/src/arbitrary_bigint_felt.rs similarity index 100% rename from felt/src/arbitrary.rs rename to felt/src/arbitrary_bigint_felt.rs diff --git a/felt/src/arbitrary_lambdaworks.rs b/felt/src/arbitrary_lambdaworks.rs new file mode 100644 index 0000000000..11b9df1aa4 --- /dev/null +++ b/felt/src/arbitrary_lambdaworks.rs @@ -0,0 +1,47 @@ +use lambdaworks_math::{field::element::FieldElement, unsigned_integer::element::UnsignedInteger}; +use num_traits::Zero; +use proptest::prelude::*; + +use crate::{Felt252, FIELD_HIGH, FIELD_LOW}; + +/// Returns a [`Strategy`] that generates any valid Felt252 +fn any_felt252() -> impl Strategy { + (0..=FIELD_HIGH) + // turn range into `impl Strategy` + .prop_map(|x| x) + // choose second 128-bit limb capped by first one + .prop_flat_map(|high| { + let low = if high == FIELD_HIGH { + (0..FIELD_LOW).prop_map(|x| x).sboxed() + } else { + any::().sboxed() + }; + (Just(high), low) + }) + // turn (u128, u128) into limbs array and then into Felt252 + .prop_map(|(high, low)| { + let limbs = [ + (high >> 64) as u64, + (high & ((1 << 64) - 1)) as u64, + (low >> 64) as u64, + (low & ((1 << 64) - 1)) as u64, + ]; + FieldElement::new(UnsignedInteger::from_limbs(limbs)) + }) + .prop_map(|value| Felt252 { value }) +} + +/// Returns a [`Strategy`] that generates any nonzero Felt252 +pub fn nonzero_felt252() -> impl Strategy { + any_felt252().prop_filter("is zero", |x| !x.is_zero()) +} + +impl Arbitrary for Felt252 { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + any_felt252().sboxed() + } + + type Strategy = SBoxedStrategy; +} diff --git a/felt/src/bigint_felt.rs b/felt/src/bigint_felt.rs index f4fa998d6a..2b036dfa12 100644 --- a/felt/src/bigint_felt.rs +++ b/felt/src/bigint_felt.rs @@ -12,7 +12,7 @@ use core::{ }, }; -use crate::{FeltOps, ParseFeltError}; +use crate::{lib_bigint_felt::FeltOps, ParseFeltError}; pub const FIELD_HIGH: u128 = (1 << 123) + (17 << 64); // this is equal to 10633823966279327296825105735305134080 pub const FIELD_LOW: u128 = 1; @@ -840,12 +840,6 @@ impl fmt::Debug for FeltBigInt { } } -impl fmt::Display for ParseFeltError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{ParseFeltError:?}") - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/felt/src/lib.rs b/felt/src/lib.rs index 6d9f09b9e2..d4a8d1e56f 100644 --- a/felt/src/lib.rs +++ b/felt/src/lib.rs @@ -1,1665 +1,38 @@ #![cfg_attr(not(feature = "std"), no_std)] + #[allow(unused_imports)] #[macro_use] #[cfg(all(not(feature = "std"), feature = "alloc"))] pub extern crate alloc; +#[cfg(all(test, not(feature = "lambdaworks-felt")))] +mod arbitrary_bigint_felt; +#[cfg(all(test, feature = "lambdaworks-felt"))] +mod arbitrary_lambdaworks; +#[cfg(not(feature = "lambdaworks-felt"))] mod bigint_felt; +#[cfg(not(feature = "lambdaworks-felt"))] +mod lib_bigint_felt; +#[cfg(feature = "lambdaworks-felt")] +mod lib_lambdaworks; -#[cfg(test)] -pub mod arbitrary; - -use bigint_felt::{FeltBigInt, FIELD_HIGH, FIELD_LOW}; -use num_bigint::{BigInt, BigUint, U64Digits}; -use num_integer::Integer; -use num_traits::{Bounded, FromPrimitive, Num, One, Pow, Signed, ToPrimitive, Zero}; -use serde::{Deserialize, Serialize}; +use core::fmt; -use core::{ - convert::Into, - fmt, - iter::Sum, - ops::{ - Add, AddAssign, BitAnd, BitOr, BitXor, Div, Mul, MulAssign, Neg, Rem, Shl, Shr, ShrAssign, - Sub, SubAssign, - }, -}; +#[cfg(feature = "lambdaworks-felt")] +pub use lib_lambdaworks::Felt252; -#[cfg(all(not(feature = "std"), feature = "alloc"))] -use alloc::{string::String, vec::Vec}; +#[cfg(not(feature = "lambdaworks-felt"))] +pub use lib_bigint_felt::Felt252; pub const PRIME_STR: &str = "0x800000000000011000000000000000000000000000000000000000000000001"; // in decimal, this is equal to 3618502788666131213697322783095070105623107215331596699973092056135872020481 - -pub(crate) trait FeltOps { - fn new>>(value: T) -> Self; - - fn modpow( - &self, - exponent: &FeltBigInt, - modulus: &FeltBigInt, - ) -> Self; - - fn iter_u64_digits(&self) -> U64Digits; - - #[cfg(any(feature = "std", feature = "alloc"))] - fn to_signed_bytes_le(&self) -> Vec; - - #[cfg(any(feature = "std", feature = "alloc"))] - fn to_bytes_be(&self) -> Vec; - - fn parse_bytes(buf: &[u8], radix: u32) -> Option>; - - fn from_bytes_be(bytes: &[u8]) -> Self; - - #[cfg(any(feature = "std", feature = "alloc"))] - fn to_str_radix(&self, radix: u32) -> String; - - /// Converts [`Felt252`] into a [`BigInt`] number in the range: `(- FIELD / 2, FIELD / 2)`. - /// - /// # Examples - /// - /// ``` - /// # use crate::cairo_felt::Felt252; - /// # use num_bigint::BigInt; - /// # use num_traits::Bounded; - /// let positive = Felt252::new(5); - /// assert_eq!(positive.to_bigint(), Into::::into(5)); - /// - /// let negative = Felt252::max_value(); - /// assert_eq!(negative.to_bigint(), Into::::into(-1)); - /// ``` - fn to_signed_felt(&self) -> BigInt; - - // Converts [`Felt252`]'s representation directly into a [`BigInt`]. - // Equivalent to doing felt.to_biguint().to_bigint(). - fn to_bigint(&self) -> BigInt; - - /// Converts [`Felt252`] into a [`BigUint`] number. - /// - /// # Examples - /// - /// ``` - /// # use crate::cairo_felt::Felt252; - /// # use num_bigint::BigUint; - /// # use num_traits::{Num, Bounded}; - /// let positive = Felt252::new(5); - /// assert_eq!(positive.to_biguint(), Into::::into(5_u32)); - /// - /// let negative = Felt252::max_value(); - /// assert_eq!(negative.to_biguint(), BigUint::from_str_radix("800000000000011000000000000000000000000000000000000000000000000", 16).unwrap()); - /// ``` - fn to_biguint(&self) -> BigUint; - - fn bits(&self) -> u64; - - fn prime() -> BigUint; -} - -#[macro_export] -macro_rules! felt_str { - ($val: expr) => { - $crate::Felt252::parse_bytes($val.as_bytes(), 10_u32).expect("Couldn't parse bytes") - }; - ($val: expr, $opt: expr) => { - $crate::Felt252::parse_bytes($val.as_bytes(), $opt as u32).expect("Couldn't parse bytes") - }; -} +pub const FIELD_HIGH: u128 = (1 << 123) + (17 << 64); // this is equal to 10633823966279327296825105735305134080 +pub const FIELD_LOW: u128 = 1; #[derive(Clone, Debug, PartialEq, Eq)] pub struct ParseFeltError; -#[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Deserialize, Default, Serialize)] -pub struct Felt252 { - value: FeltBigInt, -} - -macro_rules! from_num { - ($type:ty) => { - impl From<$type> for Felt252 { - fn from(value: $type) -> Self { - Self { - value: value.into(), - } - } - } - }; -} - -from_num!(i8); -from_num!(i16); -from_num!(i32); -from_num!(i64); -from_num!(i128); -from_num!(isize); -from_num!(u8); -from_num!(u16); -from_num!(u32); -from_num!(u64); -from_num!(u128); -from_num!(usize); -from_num!(BigInt); -from_num!(&BigInt); -from_num!(BigUint); -from_num!(&BigUint); - -impl Felt252 { - pub fn new>(value: T) -> Self { - value.into() - } - pub fn modpow(&self, exponent: &Felt252, modulus: &Felt252) -> Self { - Self { - value: self.value.modpow(&exponent.value, &modulus.value), - } - } - pub fn iter_u64_digits(&self) -> U64Digits { - self.value.iter_u64_digits() - } - - pub fn to_le_bytes(&self) -> [u8; 32] { - let mut res = [0u8; 32]; - let mut iter = self.iter_u64_digits(); - let (d0, d1, d2, d3) = ( - iter.next().unwrap_or_default().to_le_bytes(), - iter.next().unwrap_or_default().to_le_bytes(), - iter.next().unwrap_or_default().to_le_bytes(), - iter.next().unwrap_or_default().to_le_bytes(), - ); - res[..8].copy_from_slice(&d0); - res[8..16].copy_from_slice(&d1); - res[16..24].copy_from_slice(&d2); - res[24..].copy_from_slice(&d3); - res - } - - pub fn to_be_bytes(&self) -> [u8; 32] { - let mut bytes = self.to_le_bytes(); - bytes.reverse(); - bytes - } - - pub fn to_le_digits(&self) -> [u64; 4] { - let mut iter = self.iter_u64_digits(); - [ - iter.next().unwrap_or_default(), - iter.next().unwrap_or_default(), - iter.next().unwrap_or_default(), - iter.next().unwrap_or_default(), - ] - } - - #[cfg(any(feature = "std", feature = "alloc"))] - pub fn to_signed_bytes_le(&self) -> Vec { - self.value.to_signed_bytes_le() - } - #[cfg(any(feature = "std", feature = "alloc"))] - pub fn to_bytes_be(&self) -> Vec { - self.value.to_bytes_be() - } - - pub fn parse_bytes(buf: &[u8], radix: u32) -> Option { - Some(Self { - value: FeltBigInt::parse_bytes(buf, radix)?, - }) - } - pub fn from_bytes_be(bytes: &[u8]) -> Self { - Self { - value: FeltBigInt::from_bytes_be(bytes), - } - } - #[cfg(any(feature = "std", feature = "alloc"))] - pub fn to_str_radix(&self, radix: u32) -> String { - self.value.to_str_radix(radix) - } - - pub fn to_signed_felt(&self) -> BigInt { - #[allow(deprecated)] - self.value.to_signed_felt() - } - - pub fn to_bigint(&self) -> BigInt { - #[allow(deprecated)] - self.value.to_bigint() - } - - pub fn to_biguint(&self) -> BigUint { - #[allow(deprecated)] - self.value.to_biguint() - } - pub fn sqrt(&self) -> Self { - // Based on Tonelli-Shanks' algorithm for finding square roots - // and sympy's library implementation of said algorithm. - if self.is_zero() || self.is_one() { - return self.clone(); - } - - let max_felt = Felt252::max_value(); - let trailing_prime = Felt252::max_value() >> 192; // 0x800000000000011 - - let a = self.pow(&trailing_prime); - let d = (&Felt252::new(3_i32)).pow(&trailing_prime); - let mut m = Felt252::zero(); - let mut exponent = Felt252::one() << 191_u32; - let mut adm; - for i in 0..192_u32 { - adm = &a * &(&d).pow(&m); - adm = (&adm).pow(&exponent); - exponent >>= 1; - // if adm ≑ -1 (mod CAIRO_PRIME) - if adm == max_felt { - m += Felt252::one() << i; - } - } - let root_1 = self.pow(&((trailing_prime + 1_u32) >> 1)) * (&d).pow(&(m >> 1)); - let root_2 = &max_felt - &root_1 + 1_usize; - if root_1 < root_2 { - root_1 - } else { - root_2 - } - } - - pub fn bits(&self) -> u64 { - self.value.bits() - } - - pub fn prime() -> BigUint { - FeltBigInt::prime() - } -} - -impl Add for Felt252 { - type Output = Self; - fn add(self, rhs: Self) -> Self { - Self { - value: self.value + rhs.value, - } - } -} - -impl<'a> Add for &'a Felt252 { - type Output = Felt252; - fn add(self, rhs: Self) -> Self::Output { - Self::Output { - value: &self.value + &rhs.value, - } - } -} - -impl<'a> Add<&'a Felt252> for Felt252 { - type Output = Self; - fn add(self, rhs: &Self) -> Self::Output { - Self::Output { - value: self.value + &rhs.value, - } - } -} - -impl Add for Felt252 { - type Output = Self; - fn add(self, rhs: u32) -> Self { - Self { - value: self.value + rhs, - } - } -} - -impl Add for Felt252 { - type Output = Self; - fn add(self, rhs: usize) -> Self { - Self { - value: self.value + rhs, - } - } -} - -impl<'a> Add for &'a Felt252 { - type Output = Felt252; - fn add(self, rhs: usize) -> Self::Output { - Self::Output { - value: &self.value + rhs, - } - } -} - -impl Add for &Felt252 { - type Output = Felt252; - fn add(self, rhs: u64) -> Self::Output { - Self::Output { - value: &self.value + rhs, - } - } -} - -// This is special cased and optimized compared to the obvious implementation -// due to `pc_update` relying on this, which makes it a major bottleneck for -// execution. Testing for this function is extensive, comprised of explicit -// edge and special cases testing and property tests, all comparing to the -// more intuitive `(rhs + self).to_u64()` implementation. -// This particular implementation is much more complex than a slightly more -// intuitive one based on a single match. However, this is 8-62% faster -// depending on the case being bencharked, with an average of 32%, so it's -// worth it. -impl Add<&Felt252> for u64 { - type Output = Option; - - fn add(self, rhs: &Felt252) -> Option { - const PRIME_DIGITS_LE_HI: (u64, u64, u64) = - (0x0000000000000000, 0x0000000000000000, 0x0800000000000011); - const PRIME_MINUS_U64_MAX_DIGITS_LE_HI: (u64, u64, u64) = - (0xffffffffffffffff, 0xffffffffffffffff, 0x0800000000000010); - - // Iterate through the 64 bits digits in little-endian order to - // characterize how the sum will behave. - let mut rhs_digits = rhs.iter_u64_digits(); - // No digits means `rhs` is `0`, so the sum is simply `self`. - let Some(low) = rhs_digits.next() else { - return Some(self); - }; - // A single digit means this is effectively the sum of two `u64` numbers. - let Some(h0) = rhs_digits.next() else { - return self.checked_add(low) - }; - // Now we need to compare the 3 most significant digits. - // There are two relevant cases from now on, either `rhs` behaves like a - // substraction of a `u64` or the result of the sum falls out of range. - let (h1, h2) = (rhs_digits.next()?, rhs_digits.next()?); - match (h0, h1, h2) { - // The 3 MSB only match the prime for Felt252::max_value(), which is -1 - // in the signed field, so this is equivalent to substracting 1 to `self`. - #[allow(clippy::suspicious_arithmetic_impl)] - PRIME_DIGITS_LE_HI => self.checked_sub(1), - // For the remaining values between `[-u64::MAX..0]` (where `{0, -1}` have - // already been covered) the MSB matches that of `PRIME - u64::MAX`. - // Because we're in the negative number case, we count down. Because `0` - // and `-1` correspond to different MSBs, `0` and `1` in the LSB are less - // than `-u64::MAX`, the smallest value we can add to (read, substract it's - // magnitude from) a `u64` number, meaning we exclude them from the valid - // case. - // For the remaining range, we make take the absolute value module-2 while - // correcting by substracting `1` (note we actually substract `2` because - // the absolute value itself requires substracting `1`. - #[allow(clippy::suspicious_arithmetic_impl)] - PRIME_MINUS_U64_MAX_DIGITS_LE_HI if low >= 2 => { - (self).checked_sub(u64::MAX - (low - 2)) - } - // Any other case will result in an addition that is out of bounds, so - // the addition fails, returning `None`. - _ => None, - } - } -} - -impl AddAssign for Felt252 { - fn add_assign(&mut self, rhs: Self) { - self.value += rhs.value; - } -} - -impl<'a> AddAssign<&'a Felt252> for Felt252 { - fn add_assign(&mut self, rhs: &Self) { - self.value += &rhs.value; - } -} - -impl Sum for Felt252 { - fn sum>(iter: I) -> Self { - iter.fold(Felt252::zero(), |mut acc, x| { - acc += x; - acc - }) - } -} - -impl Neg for Felt252 { - type Output = Self; - fn neg(self) -> Self { - Self { - value: self.value.neg(), - } - } -} - -impl<'a> Neg for &'a Felt252 { - type Output = Felt252; - fn neg(self) -> Self::Output { - Self::Output { - value: (&self.value).neg(), - } - } -} - -impl Sub for Felt252 { - type Output = Self; - fn sub(self, rhs: Self) -> Self { - Self { - value: self.value - rhs.value, - } - } -} - -impl<'a> Sub for &'a Felt252 { - type Output = Felt252; - fn sub(self, rhs: Self) -> Self::Output { - Self::Output { - value: &self.value - &rhs.value, - } - } -} - -impl<'a> Sub<&'a Felt252> for Felt252 { - type Output = Self; - fn sub(self, rhs: &Self) -> Self { - Self { - value: self.value - &rhs.value, - } - } -} - -impl Sub<&Felt252> for usize { - type Output = Felt252; - fn sub(self, rhs: &Self::Output) -> Self::Output { - Self::Output { - value: self - &rhs.value, - } - } -} - -impl SubAssign for Felt252 { - fn sub_assign(&mut self, rhs: Self) { - self.value -= rhs.value - } -} - -impl<'a> SubAssign<&'a Felt252> for Felt252 { - fn sub_assign(&mut self, rhs: &Self) { - self.value -= &rhs.value; - } -} - -impl Sub for Felt252 { - type Output = Self; - fn sub(self, rhs: u32) -> Self { - Self { - value: self.value - rhs, - } - } -} - -impl<'a> Sub for &'a Felt252 { - type Output = Felt252; - fn sub(self, rhs: u32) -> Self::Output { - Self::Output { - value: &self.value - rhs, - } - } -} - -impl Sub for Felt252 { - type Output = Self; - fn sub(self, rhs: usize) -> Self { - Self { - value: self.value - rhs, - } - } -} - -impl Mul for Felt252 { - type Output = Self; - fn mul(self, rhs: Self) -> Self { - Self { - value: self.value * rhs.value, - } - } -} - -impl<'a> Mul for &'a Felt252 { - type Output = Felt252; - fn mul(self, rhs: Self) -> Self::Output { - Self::Output { - value: &self.value * &rhs.value, - } - } -} - -impl<'a> Mul<&'a Felt252> for Felt252 { - type Output = Self; - fn mul(self, rhs: &Self) -> Self { - Self { - value: self.value * &rhs.value, - } - } -} - -impl<'a> MulAssign<&'a Felt252> for Felt252 { - fn mul_assign(&mut self, rhs: &Self) { - self.value *= &rhs.value; - } -} - -impl Pow for Felt252 { - type Output = Self; - fn pow(self, rhs: u32) -> Self { - Self { - value: self.value.pow(rhs), - } - } -} - -impl<'a> Pow for &'a Felt252 { - type Output = Felt252; - fn pow(self, rhs: u32) -> Self::Output { - Self::Output { - value: (&self.value).pow(rhs), - } - } -} - -impl<'a> Pow<&'a Felt252> for &'a Felt252 { - type Output = Felt252; - fn pow(self, rhs: &'a Felt252) -> Self::Output { - Self::Output { - value: (&self.value).pow(&rhs.value), - } - } -} - -impl Div for Felt252 { - type Output = Self; - fn div(self, rhs: Self) -> Self { - Self { - value: self.value / rhs.value, - } - } -} - -impl<'a> Div for &'a Felt252 { - type Output = Felt252; - fn div(self, rhs: Self) -> Self::Output { - Self::Output { - value: &self.value / &rhs.value, - } - } -} - -impl<'a> Div for &'a Felt252 { - type Output = Felt252; - fn div(self, rhs: Self::Output) -> Self::Output { - Self::Output { - value: &self.value / rhs.value, - } - } -} - -impl Rem for Felt252 { - type Output = Self; - fn rem(self, rhs: Self) -> Self { - Self { - value: self.value % rhs.value, - } - } -} - -impl<'a> Rem<&'a Felt252> for Felt252 { - type Output = Self; - fn rem(self, rhs: &Self) -> Self { - Self { - value: self.value % &rhs.value, - } - } -} - -impl Zero for Felt252 { - fn zero() -> Self { - Self { - value: FeltBigInt::zero(), - } - } - - fn is_zero(&self) -> bool { - self.value.is_zero() - } -} - -impl One for Felt252 { - fn one() -> Self { - Self { - value: FeltBigInt::one(), - } - } - - fn is_one(&self) -> bool { - self.value.is_one() - } -} - -impl Bounded for Felt252 { - fn min_value() -> Self { - Self { - value: FeltBigInt::min_value(), - } - } - - fn max_value() -> Self { - Self { - value: FeltBigInt::max_value(), - } - } -} - -impl Num for Felt252 { - type FromStrRadixErr = ParseFeltError; - fn from_str_radix(string: &str, radix: u32) -> Result { - Ok(Self { - value: FeltBigInt::from_str_radix(string, radix)?, - }) - } -} - -impl Integer for Felt252 { - fn div_floor(&self, rhs: &Self) -> Self { - Self { - value: self.value.div_floor(&rhs.value), - } - } - - fn div_rem(&self, other: &Self) -> (Self, Self) { - let (div, rem) = self.value.div_rem(&other.value); - (Self { value: div }, Self { value: rem }) - } - - fn divides(&self, other: &Self) -> bool { - self.value.divides(&other.value) - } - - fn gcd(&self, other: &Self) -> Self { - Self { - value: self.value.gcd(&other.value), - } - } - - fn is_even(&self) -> bool { - self.value.is_even() - } - - fn is_multiple_of(&self, other: &Self) -> bool { - self.value.is_multiple_of(&other.value) - } - - fn is_odd(&self) -> bool { - self.value.is_odd() - } - - fn lcm(&self, other: &Self) -> Self { - Self { - value: self.value.lcm(&other.value), - } - } - - fn mod_floor(&self, rhs: &Self) -> Self { - Self { - value: self.value.mod_floor(&rhs.value), - } - } -} - -impl Signed for Felt252 { - fn abs(&self) -> Self { - Self { - value: self.value.abs(), - } - } - - fn abs_sub(&self, other: &Self) -> Self { - Self { - value: self.value.abs_sub(&other.value), - } - } - - fn signum(&self) -> Self { - Self { - value: self.value.signum(), - } - } - - fn is_positive(&self) -> bool { - self.value.is_positive() - } - - fn is_negative(&self) -> bool { - self.value.is_negative() - } -} - -impl Shl for Felt252 { - type Output = Self; - fn shl(self, rhs: u32) -> Self { - Self { - value: self.value << rhs, - } - } -} - -impl<'a> Shl for &'a Felt252 { - type Output = Felt252; - fn shl(self, rhs: u32) -> Self::Output { - Self::Output { - value: &self.value << rhs, - } - } -} - -impl Shl for Felt252 { - type Output = Self; - fn shl(self, rhs: usize) -> Self { - Self { - value: self.value << rhs, - } - } -} - -impl<'a> Shl for &'a Felt252 { - type Output = Felt252; - fn shl(self, rhs: usize) -> Self::Output { - Self::Output { - value: &self.value << rhs, - } - } -} - -impl Shr for Felt252 { - type Output = Self; - fn shr(self, rhs: u32) -> Self { - Self { - value: self.value >> rhs, - } - } -} - -impl<'a> Shr for &'a Felt252 { - type Output = Felt252; - fn shr(self, rhs: u32) -> Self::Output { - Self::Output { - value: &self.value >> rhs, - } - } -} - -impl ShrAssign for Felt252 { - fn shr_assign(&mut self, rhs: usize) { - self.value >>= rhs - } -} - -impl<'a> BitAnd for &'a Felt252 { - type Output = Felt252; - fn bitand(self, rhs: Self) -> Self::Output { - Self::Output { - value: &self.value & &rhs.value, - } - } -} - -impl<'a> BitAnd<&'a Felt252> for Felt252 { - type Output = Self; - fn bitand(self, rhs: &Self) -> Self { - Self { - value: self.value & &rhs.value, - } - } -} - -impl<'a> BitAnd for &'a Felt252 { - type Output = Felt252; - fn bitand(self, rhs: Self::Output) -> Self::Output { - Self::Output { - value: &self.value & rhs.value, - } - } -} - -impl<'a> BitOr for &'a Felt252 { - type Output = Felt252; - fn bitor(self, rhs: Self) -> Self::Output { - Self::Output { - value: &self.value | &rhs.value, - } - } -} - -impl<'a> BitXor for &'a Felt252 { - type Output = Felt252; - fn bitxor(self, rhs: Self) -> Self::Output { - Self::Output { - value: &self.value ^ &rhs.value, - } - } -} - -impl ToPrimitive for Felt252 { - fn to_u128(&self) -> Option { - self.value.to_u128() - } - - fn to_u64(&self) -> Option { - self.value.to_u64() - } - - fn to_i64(&self) -> Option { - self.value.to_i64() - } -} - -impl FromPrimitive for Felt252 { - fn from_u64(n: u64) -> Option { - FeltBigInt::from_u64(n).map(|n| Self { value: n }) - } - - fn from_i64(n: i64) -> Option { - FeltBigInt::from_i64(n).map(|n| Self { value: n }) - } -} - -impl fmt::Display for Felt252 { +impl fmt::Display for ParseFeltError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.value) - } -} - -impl fmt::Debug for Felt252 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.value) - } -} - -macro_rules! assert_felt_methods { - ($type:ty) => { - const _: () = { - fn assert_felt_ops() {} - fn assertion() { - assert_felt_ops::<$type>(); - } - }; - }; -} - -macro_rules! assert_felt_impl { - ($type:ty) => { - const _: () = { - fn assert_add() {} - fn assert_add_ref<'a, T: Add<&'a $type>>() {} - fn assert_add_u32>() {} - fn assert_add_usize>() {} - fn assert_add_assign() {} - fn assert_add_assign_ref<'a, T: AddAssign<&'a $type>>() {} - fn assert_sum>() {} - fn assert_neg() {} - fn assert_sub() {} - fn assert_sub_ref<'a, T: Sub<&'a $type>>() {} - fn assert_sub_assign() {} - fn assert_sub_assign_ref<'a, T: SubAssign<&'a $type>>() {} - fn assert_sub_u32>() {} - fn assert_sub_usize>() {} - fn assert_mul() {} - fn assert_mul_ref<'a, T: Mul<&'a $type>>() {} - fn assert_mul_assign_ref<'a, T: MulAssign<&'a $type>>() {} - fn assert_pow_u32>() {} - fn assert_pow_felt<'a, T: Pow<&'a $type>>() {} - fn assert_div() {} - fn assert_ref_div>() {} - fn assert_rem() {} - fn assert_rem_ref<'a, T: Rem<&'a $type>>() {} - fn assert_zero() {} - fn assert_one() {} - fn assert_bounded() {} - fn assert_num() {} - fn assert_integer() {} - fn assert_signed() {} - fn assert_shl_u32>() {} - fn assert_shl_usize>() {} - fn assert_shr_u32>() {} - fn assert_shr_assign_usize>() {} - fn assert_bitand() {} - fn assert_bitand_ref<'a, T: BitAnd<&'a $type>>() {} - fn assert_ref_bitand>() {} - fn assert_bitor() {} - fn assert_bitxor() {} - fn assert_from_primitive() {} - fn assert_to_primitive() {} - fn assert_display() {} - fn assert_debug() {} - - #[allow(dead_code)] - fn assert_all() { - assert_add::<$type>(); - assert_add::<&$type>(); - assert_add_ref::<$type>(); - assert_add_u32::<$type>(); - assert_add_usize::<$type>(); - assert_add_usize::<&$type>(); - assert_add_assign::<$type>(); - assert_add_assign_ref::<$type>(); - assert_sum::<$type>(); - assert_neg::<$type>(); - assert_neg::<&$type>(); - assert_sub::<$type>(); - assert_sub::<&$type>(); - assert_sub_ref::<$type>(); - assert_sub_assign::<$type>(); - assert_sub_assign_ref::<$type>(); - assert_sub_u32::<$type>(); - assert_sub_u32::<&$type>(); - assert_sub_usize::<$type>(); - assert_mul::<$type>(); - assert_mul::<&$type>(); - assert_mul_ref::<$type>(); - assert_mul_assign_ref::<$type>(); - assert_pow_u32::<$type>(); - assert_pow_felt::<&$type>(); - assert_div::<$type>(); - assert_div::<&$type>(); - assert_ref_div::<&$type>(); - assert_rem::<$type>(); - assert_rem_ref::<$type>(); - assert_zero::<$type>(); - assert_one::<$type>(); - assert_bounded::<$type>(); - assert_num::<$type>(); - assert_integer::<$type>(); - assert_signed::<$type>(); - assert_shl_u32::<$type>(); - assert_shl_u32::<&$type>(); - assert_shl_usize::<$type>(); - assert_shl_usize::<&$type>(); - assert_shr_u32::<$type>(); - assert_shr_u32::<&$type>(); - assert_shr_assign_usize::<$type>(); - assert_bitand::<&$type>(); - assert_bitand_ref::<$type>(); - assert_ref_bitand::<&$type>(); - assert_bitor::<&$type>(); - assert_bitxor::<&$type>(); - assert_from_primitive::<$type>(); - assert_to_primitive::<$type>(); - assert_display::<$type>(); - assert_debug::<$type>(); - } - }; - }; -} - -assert_felt_methods!(FeltBigInt); -assert_felt_impl!(FeltBigInt); -assert_felt_impl!(Felt252); - -#[cfg(test)] -mod test { - use super::*; - use crate::arbitrary::nonzero_felt252; - use core::cmp; - use rstest::rstest; - - use proptest::prelude::*; - - proptest! { - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 felt values that are randomly generated - // each time tests are run, that a new felt doesn't fall outside the range [0, p]. - // In this and some of the following tests, The value of {x} can be either [0] or a - // very large number, in order to try to overflow the value of {p} and thus ensure the - // modular arithmetic is working correctly. - fn new_in_range(ref x in any::<[u8; 40]>()) { - let x = Felt252::from_bytes_be(x); - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - prop_assert!(&x.to_biguint() < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn to_be_bytes(ref x in any::()) { - let bytes = x.to_be_bytes(); - let y = &Felt252::from_bytes_be(&bytes); - prop_assert_eq!(x, y); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn to_le_bytes(ref x in any::()) { - let mut bytes = x.to_le_bytes(); - // Convert to big endian for test - bytes.reverse(); - let y = &Felt252::from_bytes_be(&bytes); - prop_assert_eq!(x, y); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn to_le_digits(ref x in any::()) { - let digits: [u64; 4] = x.to_le_digits(); - let mut bytes: Vec<_> = digits - .into_iter() - .flat_map(|x| x.to_le_bytes()) - .collect(); - // Convert to big endian for test - bytes.reverse(); - let y = &Felt252::from_bytes_be(&bytes); - prop_assert_eq!(x, y); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn to_u128_ok(x in any::()) { - let y = Felt252::from(x); - let y = y.to_u128(); - prop_assert_eq!(Some(x), y); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn to_u128_out_of_range(x in nonzero_felt252()) { - let y = x + Felt252::from(u128::MAX); - let y = y.to_u128(); - prop_assert_eq!(None, y); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 felt values that are randomly - // generated each time tests are run, that a felt created using Felt252::from_bytes_be doesn't - // fall outside the range [0, p]. - // In this and some of the following tests, The value of {x} can be either [0] or a very large number, - // in order to try to overflow the value of {p} and thus ensure the modular arithmetic is working correctly. - fn from_bytes_be_in_range(ref x in any::<[u8; 40]>()) { - let x = Felt252::from_bytes_be(x); - let max_felt = Felt252::max_value(); - prop_assert!(x <= max_felt); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 felt values that are randomly generated each time - // tests are run, that the negative of a felt doesn't fall outside the range [0, p]. - fn neg_in_range(x in any::()) { - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let neg = -x.clone(); - let as_uint = &neg.to_biguint(); - prop_assert!(as_uint < p); - - // test reference variant - let neg = -&x; - let as_uint = &neg.to_biguint(); - prop_assert!(as_uint < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 {x} and {y} values that are randomly generated - // each time tests are run, that a subtraction between two felts {x} and {y} and doesn't fall - // outside the range [0, p]. The values of {x} and {y} can be either [0] or a very large number. - fn sub(ref x in any::(), ref y in any::()) { - let (x_int, y_int) = (&x.to_biguint(), &y.to_biguint()); - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let sub_xy = x - y; - prop_assert!(&sub_xy.to_biguint() < p); - prop_assert_eq!(Felt252::from(p + x_int - y_int), sub_xy); - - let sub_yx = y - x; - prop_assert!(&sub_yx.to_biguint() < p); - prop_assert_eq!(Felt252::from(p + y_int - x_int), sub_yx); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 {x} and {y} values that are randomly generated - // each time tests are run, that a subtraction with assignment between two felts {x} and {y} - // and doesn't fall outside the range [0, p]. The values of {x} and {y} can be either [0] or a very large number. - fn sub_assign_in_range(mut x in any::(), y in any::()) { - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - x -= y.clone(); - let as_uint = &x.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - - // test reference variant - x -= &y; - let as_uint = &x.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 {x} and {y} values that are randomly - // generated each time tests are run, that a multiplication between two felts {x} - // and {y} and doesn't fall outside the range [0, p]. The values of {x} and {y} - // can be either [0] or a very large number. - fn mul(ref x in any::(), ref y in any::()) { - let xy_int = x.to_biguint() * y.to_biguint(); - - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let (xy, yx) = (x * y, y * x); - prop_assert_eq!(&xy, &yx); - prop_assert_eq!(xy.to_biguint(), xy_int.mod_floor(p)); - prop_assert!(&xy.to_biguint() < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 pairs of {x} and {y} values that - // are randomly generated each time tests are run, that a multiplication with - // assignment between two felts {x} and {y} and doesn't fall outside the range [0, p]. - // The values of {x} and {y} can be either [0] or a very large number. - fn mul_assign_in_range(mut x in any::(), y in any::()) { - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - x *= &y; - let as_uint = &x.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 pairs of {x} and {y} values that are - // randomly generated each time tests are run, that the result of the division of - // {x} by {y} is the inverse multiplicative of {x} --that is, multiplying the result - // by {y} returns the original number {x}. The values of {x} and {y} can be either - // [0] or a very large number. - fn div_is_mul_inv(ref x in any::(), ref y in nonzero_felt252()) { - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - prop_assume!(!y.is_zero()); - - let q = x / y; - let as_uint = &q.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - prop_assert_eq!(&(q * y), x); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 {value}s that are randomly generated - // each time tests are run, that performing a bit shift to the left by {shift_amount} - // of bits (between 0 and 999) returns a result that is inside of the range [0, p]. - fn shift_left_in_range(value in any::(), shift_amount in 0..1000_u32){ - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let result = (value.clone() << shift_amount).to_biguint(); - prop_assert!(&result < p); - - let result = (&value << shift_amount).to_biguint(); - prop_assert!(&result < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 {value}s that are randomly - // generated each time tests are run, that performing a bit shift to the right - // by {shift_amount} of bits (between 0 and 999) returns a result that is inside of the range [0, p]. - fn shift_right_in_range(value in any::(), shift_amount in 0..1000_u32){ - let result = (value >> shift_amount).to_biguint(); - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - prop_assert!(&result < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 {value}s that are randomly generated - // each time tests are run, that performing a bit shift to the right by {shift_amount} - // of bits (between 0 and 999), with assignment, returns a result that is inside of the range [0, p]. - // "With assignment" means that the result of the operation is autommatically assigned - // to the variable value, replacing its previous content. - fn shift_right_assign_in_range(mut value in any::(), shift_amount in 0..1000_usize){ - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - value >>= shift_amount; - prop_assert!(value.to_biguint() < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property based test that ensures, for 100 pairs of values {x} and {y} - // generated at random each time tests are run, that performing a BitAnd - // operation between them returns a result that is inside of the range [0, p]. - fn bitand_in_range(x in any::(), y in any::()){ - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - let result = x & &y; - result.to_biguint(); - prop_assert!(result.to_biguint() < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property based test that ensures, for 100 pairs of values {x} and {y} - // generated at random each time tests are run, that performing a BitOr - // operation between them returns a result that is inside of the range [0, p]. - fn bitor_in_range(x in any::(), y in any::()){ - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - let result = &x | &y; - prop_assert!(result.to_biguint() < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property based test that ensures, for 100 pairs of values {x} and {y} - // generated at random each time tests are run, that performing a BitXor - // operation between them returns a result that is inside of the range [0, p]. - fn bitxor_in_range(x in any::(), y in any::()){ - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - let result = &x ^ &y; - prop_assert!(result.to_biguint() < p); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 values {x} that are randomly - // generated each time tests are run, that raising {x} to the {y}th power - // returns a result that is inside of the range [0, p]. - fn pow_in_range(base in any::(), exp in 0..100_u32){ - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let result = Pow::pow(base.clone(), exp); - let as_uint = &result.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - - // test reference variant - let result = Pow::pow(&base, exp); - let as_uint = &result.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property-based test that ensures, for 100 values {x} that are randomly - // generated each time tests are run, that raising {x} to the {y}th power - // returns a result that is inside of the range [0, p]. - fn pow_felt_in_range(base in any::(), exponent in any::()){ - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let result = Pow::pow(&base, &exponent); - let as_uint = result.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - - // test reference variant - let result: Felt252 = Pow::pow(&base, &exponent); - let as_uint = result.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property based test that ensures, for 100 pairs of values {x} and {y} - // generated at random each time tests are run, that performing a Sum operation - // between them returns a result that is inside of the range [0, p]. - fn sum_in_range(x in any::(), y in any::()){ - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let result = x + y; - let as_uint = &result.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property test to check that the remainder of a division between 100 pairs of - // values {x} and {y},generated at random each time tests are run, falls in the - // range [0, p]. x and y can either take the value of 0 or a large integer. - // In Cairo, the result of x / y is defined to always satisfy the equation - // (x / y) * y == x, so the remainder is 0 most of the time. - fn rem_in_range(x in any::(), y in nonzero_felt252()) { - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let result = x.clone() % y.clone(); - let as_uint = &result.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - - // test reference variant - let result = x % &y; - let as_uint = &result.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property based test that ensures, for 100 Felt252s {x} generated at - // random each time tests are run, that converting them into the u64 type - // returns a result that is inside of the range [0, p]. - fn from_u64_and_to_u64_primitive(x in any::()) { - let x_felt:Felt252 = Felt252::from_u64(x).unwrap(); - let x_u64:u64 = Felt252::to_u64(&x_felt).unwrap(); - - prop_assert_eq!(x, x_u64); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn from_i64_and_to_i64_primitive(x in any::()) { - let x: i64 = x as i64; - let x_felt:Felt252 = Felt252::from_i64(x).unwrap(); - let x_i64:i64 = Felt252::to_i64(&x_felt).unwrap(); - prop_assert_eq!(x, x_i64); - } - - #[test] - // Property test to check that lcm(x, y) works. Since we're operating in a prime field, lcm - // will just be the smaller number. - fn lcm_doesnt_panic(x in any::(), y in any::()) { - let lcm = x.lcm(&y); - prop_assert!(lcm == cmp::max(x, y)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Property test to check that is_multiple_of(x, y) works. Since we're operating in a prime field, is_multiple_of - // will always be true - fn is_multiple_of_doesnt_panic(x in any::(), y in any::()) { - prop_assert!(x.is_multiple_of(&y)); - } - - #[test] - fn divides_doesnt_panic(x in any::(), y in any::()) { - prop_assert!(x.divides(&y)); - } - - #[test] - fn gcd_doesnt_panic(x in any::(), y in any::()) { - let gcd1 = x.gcd(&y); - let gcd2 = y.gcd(&x); - prop_assert_eq!(gcd1, gcd2); - } - - #[test] - fn is_even(x in any::()) { - prop_assert_eq!(x.is_even(), x.to_biguint().is_even()); - } - - #[test] - fn is_odd(x in any::()) { - prop_assert_eq!(x.is_odd(), x.to_biguint().is_odd()); - } - - /// Tests the additive identity of the implementation of Zero trait for felts - /// - /// ```{.text} - /// x + 0 = x βˆ€ x - /// 0 + x = x βˆ€ x - /// ``` - #[test] - fn zero_additive_identity(ref x in any::()) { - let zero = Felt252::zero(); - prop_assert_eq!(x, &(x + &zero)); - prop_assert_eq!(x, &(&zero + x)); - } - - /// Tests the multiplicative identity of the implementation of One trait for felts - /// - /// ```{.text} - /// x * 1 = x βˆ€ x - /// 1 * x = x βˆ€ x - /// ``` - #[test] - fn one_multiplicative_identity(ref x in any::()) { - let one = Felt252::one(); - prop_assert_eq!(x, &(x * &one)); - prop_assert_eq!(x, &(&one * x)); - } - - #[test] - fn felt_is_always_positive(x in any::()) { - prop_assert!(x.is_positive()) - } - - #[test] - fn felt_is_never_negative(x in any::()) { - prop_assert!(!x.is_negative()) - } - - #[test] - fn non_zero_felt_signum_is_always_one(ref x in nonzero_felt252()) { - let one = Felt252::one(); - prop_assert_eq!(x.signum(), one) - } - - #[test] - fn sub_abs(x in any::(), y in any::()) { - let expected_abs_sub = if x > y {&x - &y} else {&y - &x}; - - prop_assert_eq!(x.abs_sub(&y), expected_abs_sub) - } - - #[test] - fn abs(x in any::()) { - prop_assert_eq!(&x, &x.abs()) - } - - #[test] - fn modpow_in_range(x in any::(), y in any::()) { - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let p_felt = Felt252::max_value(); - - let modpow = x.modpow(&y, &p_felt).to_biguint(); - prop_assert!(modpow < p, "{}", modpow); - } - - #[test] - fn sqrt_in_range(x in any::()) { - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let sqrt = x.sqrt().to_biguint(); - prop_assert!(sqrt < p, "{}", sqrt); - } - - #[test] - fn sqrt_is_inv_square(x in any::()) { - let x_sq = &x * &x; - let sqrt = x_sq.sqrt(); - - if sqrt != x { - prop_assert_eq!(Felt252::max_value() - sqrt + 1_usize, x); - } else { - prop_assert_eq!(sqrt, x); - } - } - - #[test] - fn add_to_u64(x in any::(), ref felt in any::()) { - let sum = (felt + x).to_u64(); - prop_assert_eq!(x + felt, sum); - } - - #[test] - fn add_to_u64_extremes(x in any::()) { - let big_zero = &Felt252::zero(); - let big_max = &Felt252::max_value(); - let big_min = &(big_zero + (i64::MIN as usize)); - - let sum_max = (big_max + x).to_u64(); - prop_assert_eq!(x + big_max, sum_max); - let sum_min = (big_min + x).to_u64(); - prop_assert_eq!(x + big_min, sum_min); - let sum_zero = (big_zero + x).to_u64(); - prop_assert_eq!(x + big_zero, sum_zero); - } - - #[test] - fn add_u32_in_range(x in any::(), y in any::()) { - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - let x_add_y = (x + y).to_biguint(); - prop_assert!(x_add_y < p, "{}", x_add_y); - } - - #[test] - fn add_u32_is_inv_sub(x in any::(), y in any::()) { - let expected_y = (x.clone() + y - x).to_u32().unwrap(); - prop_assert_eq!(expected_y, y, "{}", expected_y); - } - - #[test] - fn sub_u32_in_range(x in any::(), y in any::()) { - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - let x_sub_y = (x - y).to_biguint(); - prop_assert!(x_sub_y < p, "{}", x_sub_y); - } - - #[test] - fn sub_u32_is_inv_add(x in any::(), y in any::()) { - prop_assert_eq!(x.clone() - y + y, x) - } - - #[test] - fn sub_usize_in_range(x in any::(), y in any::()) { - let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - let x_sub_y = (x - y).to_biguint(); - prop_assert!(x_sub_y < p, "{}", x_sub_y); - } - - #[test] - fn sub_usize_is_inv_add(x in any::(), y in any::()) { - prop_assert_eq!(x.clone() - y + y, x) - } - - #[test] - fn add_in_range(x in any::(), y in any::()) { - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - let sub = x + y; - let as_uint = &sub.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - } - - #[test] - fn add_is_inv_sub(ref x in any::(), ref y in any::()) { - let expected_y = x + y - x; - prop_assert_eq!(&expected_y, y, "{}", y); - } - - #[test] - fn add_assign_in_range(mut x in any::(), y in any::()) { - let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - - x += y.clone(); - let as_uint = &x.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - - // test reference variant - x += &y; - let as_uint = &x.to_biguint(); - prop_assert!(as_uint < p, "{}", as_uint); - } - } - - #[rstest] - fn add_to_u64_edge_cases( - #[values(0, 1, u64::MAX)] x: u64, - #[values(-2, -1, 0, 1, 1i128.neg(), i64::MIN as i128, u64::MAX as i128, u64::MAX as i128 + 1, (u64::MAX as i128).neg())] - y: i128, - ) { - let y = Felt252::from(y); - assert_eq!(x + &y, (&y + x).to_u64()); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Checks that the result of adding two zeroes is zero - fn sum_zeros_in_range() { - let x = Felt252::new(0); - let y = Felt252::new(0); - let z = Felt252::new(0); - assert_eq!(x + y, z) - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Checks that the result of multiplying two zeroes is zero - fn mul_zeros_in_range() { - let x = Felt252::new(0); - let y = Felt252::new(0); - let z = Felt252::new(0); - assert_eq!(x * y, z) - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Checks that the result of performing a bit and operation between zeroes is zero - fn bit_and_zeros_in_range() { - let x = Felt252::new(0); - let y = Felt252::new(0); - let z = Felt252::new(0); - assert_eq!(&x & &y, z) - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Checks that the result of perfforming a bit or operation between zeroes is zero - fn bit_or_zeros_in_range() { - let x = Felt252::new(0); - let y = Felt252::new(0); - let z = Felt252::new(0); - assert_eq!(&x | &y, z) - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Checks that the result of perfforming a bit xor operation between zeroes is zero - fn bit_xor_zeros_in_range() { - let x = Felt252::new(0); - let y = Felt252::new(0); - let z = Felt252::new(0); - assert_eq!(&x ^ &y, z) - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Tests that the maximum value a Felt252 can take is equal to (prime - 1) - fn upper_bound() { - let prime = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); - let unit = BigUint::one(); - let felt_max_value = Felt252::max_value().to_biguint(); - assert_eq!(prime - unit, felt_max_value) - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - // Tests that the minimum value a Felt252 can take is equal to zero. - fn lower_bound() { - let zero = BigUint::zero(); - let felt_min_value = Felt252::min_value().to_biguint(); - assert_eq!(zero, felt_min_value) - } - - #[test] - fn zero_value() { - let zero = BigUint::zero(); - let felt_zero = Felt252::zero().to_biguint(); - assert_eq!(zero, felt_zero) - } - - #[test] - fn is_zero() { - let felt_zero = Felt252::zero(); - let felt_non_zero = Felt252::new(3); - assert!(felt_zero.is_zero()); - assert!(!felt_non_zero.is_zero()) - } - - #[test] - fn one_value() { - let one = BigUint::one(); - let felt_one = Felt252::one().to_biguint(); - assert_eq!(one, felt_one) - } - - #[test] - fn is_one() { - let felt_one = Felt252::one(); - let felt_non_one = Felt252::new(8); - assert!(felt_one.is_one()); - assert!(!felt_non_one.is_one()) - } - - #[test] - fn signum_of_zero_is_zero() { - let zero = Felt252::zero(); - assert_eq!(&zero.signum(), &zero) + write!(f, "{ParseFeltError:?}") } } diff --git a/felt/src/lib_bigint_felt.rs b/felt/src/lib_bigint_felt.rs new file mode 100644 index 0000000000..18df387924 --- /dev/null +++ b/felt/src/lib_bigint_felt.rs @@ -0,0 +1,1661 @@ +use crate::ParseFeltError; + +use crate::bigint_felt::{FeltBigInt, FIELD_HIGH, FIELD_LOW}; +use num_bigint::{BigInt, BigUint, U64Digits}; +use num_integer::Integer; +use num_traits::{Bounded, FromPrimitive, Num, One, Pow, Signed, ToPrimitive, Zero}; +use serde::{Deserialize, Serialize}; + +use core::{ + convert::Into, + fmt, + iter::Sum, + ops::{ + Add, AddAssign, BitAnd, BitOr, BitXor, Div, Mul, MulAssign, Neg, Rem, Shl, Shr, ShrAssign, + Sub, SubAssign, + }, +}; + +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::{string::String, vec::Vec}; + +pub(crate) trait FeltOps { + fn new>>(value: T) -> Self; + + fn modpow( + &self, + exponent: &FeltBigInt, + modulus: &FeltBigInt, + ) -> Self; + + fn iter_u64_digits(&self) -> U64Digits; + + #[cfg(any(feature = "std", feature = "alloc"))] + fn to_signed_bytes_le(&self) -> Vec; + + #[cfg(any(feature = "std", feature = "alloc"))] + fn to_bytes_be(&self) -> Vec; + + fn parse_bytes(buf: &[u8], radix: u32) -> Option>; + + fn from_bytes_be(bytes: &[u8]) -> Self; + + #[cfg(any(feature = "std", feature = "alloc"))] + fn to_str_radix(&self, radix: u32) -> String; + + /// Converts [`Felt252`] into a [`BigInt`] number in the range: `(- FIELD / 2, FIELD / 2)`. + /// + /// # Examples + /// + /// ``` + /// # use crate::cairo_felt::Felt252; + /// # use num_bigint::BigInt; + /// # use num_traits::Bounded; + /// let positive = Felt252::new(5); + /// assert_eq!(positive.to_signed_felt(), Into::::into(5)); + /// + /// let negative = Felt252::max_value(); + /// assert_eq!(negative.to_signed_felt(), Into::::into(-1)); + /// ``` + fn to_signed_felt(&self) -> BigInt; + + // Converts [`Felt252`]'s representation directly into a [`BigInt`]. + // Equivalent to doing felt.to_biguint().to_bigint(). + fn to_bigint(&self) -> BigInt; + + /// Converts [`Felt252`] into a [`BigUint`] number. + /// + /// # Examples + /// + /// ``` + /// # use crate::cairo_felt::Felt252; + /// # use num_bigint::BigUint; + /// # use num_traits::{Num, Bounded}; + /// let positive = Felt252::new(5); + /// assert_eq!(positive.to_biguint(), Into::::into(5_u32)); + /// + /// let negative = Felt252::max_value(); + /// assert_eq!(negative.to_biguint(), BigUint::from_str_radix("800000000000011000000000000000000000000000000000000000000000000", 16).unwrap()); + /// ``` + fn to_biguint(&self) -> BigUint; + + fn bits(&self) -> u64; + + fn prime() -> BigUint; +} + +#[macro_export] +macro_rules! felt_str { + ($val: expr) => { + $crate::Felt252::parse_bytes($val.as_bytes(), 10_u32).expect("Couldn't parse bytes") + }; + ($val: expr, $opt: expr) => { + $crate::Felt252::parse_bytes($val.as_bytes(), $opt as u32).expect("Couldn't parse bytes") + }; +} + +#[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Deserialize, Default, Serialize)] +pub struct Felt252 { + pub(crate) value: FeltBigInt, +} + +macro_rules! from_num { + ($type:ty) => { + impl From<$type> for Felt252 { + fn from(value: $type) -> Self { + Self { + value: value.into(), + } + } + } + }; +} + +from_num!(i8); +from_num!(i16); +from_num!(i32); +from_num!(i64); +from_num!(i128); +from_num!(isize); +from_num!(u8); +from_num!(u16); +from_num!(u32); +from_num!(u64); +from_num!(u128); +from_num!(usize); +from_num!(BigInt); +from_num!(&BigInt); +from_num!(BigUint); +from_num!(&BigUint); + +impl From for Felt252 { + fn from(flag: bool) -> Self { + if flag { + Self::one() + } else { + Self::zero() + } + } +} + +impl Felt252 { + pub fn new>(value: T) -> Self { + value.into() + } + pub fn modpow(&self, exponent: &Felt252, modulus: &Felt252) -> Self { + Self { + value: self.value.modpow(&exponent.value, &modulus.value), + } + } + pub fn iter_u64_digits(&self) -> U64Digits { + self.value.iter_u64_digits() + } + + pub fn to_le_bytes(&self) -> [u8; 32] { + let mut res = [0u8; 32]; + let mut iter = self.iter_u64_digits(); + let (d0, d1, d2, d3) = ( + iter.next().unwrap_or_default().to_le_bytes(), + iter.next().unwrap_or_default().to_le_bytes(), + iter.next().unwrap_or_default().to_le_bytes(), + iter.next().unwrap_or_default().to_le_bytes(), + ); + res[..8].copy_from_slice(&d0); + res[8..16].copy_from_slice(&d1); + res[16..24].copy_from_slice(&d2); + res[24..].copy_from_slice(&d3); + res + } + + pub fn to_be_bytes(&self) -> [u8; 32] { + let mut bytes = self.to_le_bytes(); + bytes.reverse(); + bytes + } + + pub fn to_le_digits(&self) -> [u64; 4] { + let mut iter = self.iter_u64_digits(); + [ + iter.next().unwrap_or_default(), + iter.next().unwrap_or_default(), + iter.next().unwrap_or_default(), + iter.next().unwrap_or_default(), + ] + } + + #[cfg(any(feature = "std", feature = "alloc"))] + pub fn to_signed_bytes_le(&self) -> Vec { + self.value.to_signed_bytes_le() + } + #[cfg(any(feature = "std", feature = "alloc"))] + pub fn to_bytes_be(&self) -> Vec { + self.value.to_bytes_be() + } + + pub fn parse_bytes(buf: &[u8], radix: u32) -> Option { + Some(Self { + value: FeltBigInt::parse_bytes(buf, radix)?, + }) + } + pub fn from_bytes_be(bytes: &[u8]) -> Self { + Self { + value: FeltBigInt::from_bytes_be(bytes), + } + } + #[cfg(any(feature = "std", feature = "alloc"))] + pub fn to_str_radix(&self, radix: u32) -> String { + self.value.to_str_radix(radix) + } + + pub fn to_signed_felt(&self) -> BigInt { + #[allow(deprecated)] + self.value.to_signed_felt() + } + + pub fn to_bigint(&self) -> BigInt { + #[allow(deprecated)] + self.value.to_bigint() + } + + pub fn to_biguint(&self) -> BigUint { + #[allow(deprecated)] + self.value.to_biguint() + } + pub fn sqrt(&self) -> Self { + // Based on Tonelli-Shanks' algorithm for finding square roots + // and sympy's library implementation of said algorithm. + if self.is_zero() || self.is_one() { + return self.clone(); + } + + let max_felt = Felt252::max_value(); + let trailing_prime = Felt252::max_value() >> 192; // 0x800000000000011 + + let a = self.pow(&trailing_prime); + let d = (&Felt252::new(3_i32)).pow(&trailing_prime); + let mut m = Felt252::zero(); + let mut exponent = Felt252::one() << 191_u32; + let mut adm; + for i in 0..192_u32 { + adm = &a * &(&d).pow(&m); + adm = (&adm).pow(&exponent); + exponent >>= 1; + // if adm ≑ -1 (mod CAIRO_PRIME) + if adm == max_felt { + m += Felt252::one() << i; + } + } + let root_1 = self.pow(&((trailing_prime + 1_u32) >> 1)) * (&d).pow(&(m >> 1)); + let root_2 = &max_felt - &root_1 + 1_usize; + if root_1 < root_2 { + root_1 + } else { + root_2 + } + } + + pub fn bits(&self) -> u64 { + self.value.bits() + } + + pub fn prime() -> BigUint { + FeltBigInt::prime() + } +} + +impl Add for Felt252 { + type Output = Self; + fn add(self, rhs: Self) -> Self { + Self { + value: self.value + rhs.value, + } + } +} + +impl<'a> Add for &'a Felt252 { + type Output = Felt252; + fn add(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value + &rhs.value, + } + } +} + +impl<'a> Add<&'a Felt252> for Felt252 { + type Output = Self; + fn add(self, rhs: &Self) -> Self::Output { + Self::Output { + value: self.value + &rhs.value, + } + } +} + +impl Add for Felt252 { + type Output = Self; + fn add(self, rhs: u32) -> Self { + Self { + value: self.value + rhs, + } + } +} + +impl Add for Felt252 { + type Output = Self; + fn add(self, rhs: usize) -> Self { + Self { + value: self.value + rhs, + } + } +} + +impl<'a> Add for &'a Felt252 { + type Output = Felt252; + fn add(self, rhs: usize) -> Self::Output { + Self::Output { + value: &self.value + rhs, + } + } +} + +impl Add for &Felt252 { + type Output = Felt252; + fn add(self, rhs: u64) -> Self::Output { + Self::Output { + value: &self.value + rhs, + } + } +} + +// This is special cased and optimized compared to the obvious implementation +// due to `pc_update` relying on this, which makes it a major bottleneck for +// execution. Testing for this function is extensive, comprised of explicit +// edge and special cases testing and property tests, all comparing to the +// more intuitive `(rhs + self).to_u64()` implementation. +// This particular implementation is much more complex than a slightly more +// intuitive one based on a single match. However, this is 8-62% faster +// depending on the case being bencharked, with an average of 32%, so it's +// worth it. +impl Add<&Felt252> for u64 { + type Output = Option; + + fn add(self, rhs: &Felt252) -> Option { + const PRIME_DIGITS_LE_HI: (u64, u64, u64) = + (0x0000000000000000, 0x0000000000000000, 0x0800000000000011); + const PRIME_MINUS_U64_MAX_DIGITS_LE_HI: (u64, u64, u64) = + (0xffffffffffffffff, 0xffffffffffffffff, 0x0800000000000010); + + // Iterate through the 64 bits digits in little-endian order to + // characterize how the sum will behave. + let mut rhs_digits = rhs.iter_u64_digits(); + // No digits means `rhs` is `0`, so the sum is simply `self`. + let Some(low) = rhs_digits.next() else { + return Some(self); + }; + // A single digit means this is effectively the sum of two `u64` numbers. + let Some(h0) = rhs_digits.next() else { + return self.checked_add(low) + }; + // Now we need to compare the 3 most significant digits. + // There are two relevant cases from now on, either `rhs` behaves like a + // substraction of a `u64` or the result of the sum falls out of range. + let (h1, h2) = (rhs_digits.next()?, rhs_digits.next()?); + match (h0, h1, h2) { + // The 3 MSB only match the prime for Felt252::max_value(), which is -1 + // in the signed field, so this is equivalent to substracting 1 to `self`. + #[allow(clippy::suspicious_arithmetic_impl)] + PRIME_DIGITS_LE_HI => self.checked_sub(1), + // For the remaining values between `[-u64::MAX..0]` (where `{0, -1}` have + // already been covered) the MSB matches that of `PRIME - u64::MAX`. + // Because we're in the negative number case, we count down. Because `0` + // and `-1` correspond to different MSBs, `0` and `1` in the LSB are less + // than `-u64::MAX`, the smallest value we can add to (read, substract it's + // magnitude from) a `u64` number, meaning we exclude them from the valid + // case. + // For the remaining range, we make take the absolute value module-2 while + // correcting by substracting `1` (note we actually substract `2` because + // the absolute value itself requires substracting `1`. + #[allow(clippy::suspicious_arithmetic_impl)] + PRIME_MINUS_U64_MAX_DIGITS_LE_HI if low >= 2 => { + (self).checked_sub(u64::MAX - (low - 2)) + } + // Any other case will result in an addition that is out of bounds, so + // the addition fails, returning `None`. + _ => None, + } + } +} + +impl AddAssign for Felt252 { + fn add_assign(&mut self, rhs: Self) { + self.value += rhs.value; + } +} + +impl<'a> AddAssign<&'a Felt252> for Felt252 { + fn add_assign(&mut self, rhs: &Self) { + self.value += &rhs.value; + } +} + +impl Sum for Felt252 { + fn sum>(iter: I) -> Self { + iter.fold(Felt252::zero(), |mut acc, x| { + acc += x; + acc + }) + } +} + +impl Neg for Felt252 { + type Output = Self; + fn neg(self) -> Self { + Self { + value: self.value.neg(), + } + } +} + +impl<'a> Neg for &'a Felt252 { + type Output = Felt252; + fn neg(self) -> Self::Output { + Self::Output { + value: (&self.value).neg(), + } + } +} + +impl Sub for Felt252 { + type Output = Self; + fn sub(self, rhs: Self) -> Self { + Self { + value: self.value - rhs.value, + } + } +} + +impl<'a> Sub for &'a Felt252 { + type Output = Felt252; + fn sub(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value - &rhs.value, + } + } +} + +impl<'a> Sub<&'a Felt252> for Felt252 { + type Output = Self; + fn sub(self, rhs: &Self) -> Self { + Self { + value: self.value - &rhs.value, + } + } +} + +impl Sub<&Felt252> for usize { + type Output = Felt252; + fn sub(self, rhs: &Self::Output) -> Self::Output { + Self::Output { + value: self - &rhs.value, + } + } +} + +impl SubAssign for Felt252 { + fn sub_assign(&mut self, rhs: Self) { + self.value -= rhs.value + } +} + +impl<'a> SubAssign<&'a Felt252> for Felt252 { + fn sub_assign(&mut self, rhs: &Self) { + self.value -= &rhs.value; + } +} + +impl Sub for Felt252 { + type Output = Self; + fn sub(self, rhs: u32) -> Self { + Self { + value: self.value - rhs, + } + } +} + +impl<'a> Sub for &'a Felt252 { + type Output = Felt252; + fn sub(self, rhs: u32) -> Self::Output { + Self::Output { + value: &self.value - rhs, + } + } +} + +impl Sub for Felt252 { + type Output = Self; + fn sub(self, rhs: usize) -> Self { + Self { + value: self.value - rhs, + } + } +} + +impl Mul for Felt252 { + type Output = Self; + fn mul(self, rhs: Self) -> Self { + Self { + value: self.value * rhs.value, + } + } +} + +impl<'a> Mul for &'a Felt252 { + type Output = Felt252; + fn mul(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value * &rhs.value, + } + } +} + +impl<'a> Mul<&'a Felt252> for Felt252 { + type Output = Self; + fn mul(self, rhs: &Self) -> Self { + Self { + value: self.value * &rhs.value, + } + } +} + +impl<'a> MulAssign<&'a Felt252> for Felt252 { + fn mul_assign(&mut self, rhs: &Self) { + self.value *= &rhs.value; + } +} + +impl Pow for Felt252 { + type Output = Self; + fn pow(self, rhs: u32) -> Self { + Self { + value: self.value.pow(rhs), + } + } +} + +impl<'a> Pow for &'a Felt252 { + type Output = Felt252; + fn pow(self, rhs: u32) -> Self::Output { + Self::Output { + value: (&self.value).pow(rhs), + } + } +} + +impl<'a> Pow<&'a Felt252> for &'a Felt252 { + type Output = Felt252; + fn pow(self, rhs: &'a Felt252) -> Self::Output { + Self::Output { + value: (&self.value).pow(&rhs.value), + } + } +} + +impl Div for Felt252 { + type Output = Self; + fn div(self, rhs: Self) -> Self { + Self { + value: self.value / rhs.value, + } + } +} + +impl<'a> Div for &'a Felt252 { + type Output = Felt252; + fn div(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value / &rhs.value, + } + } +} + +impl<'a> Div for &'a Felt252 { + type Output = Felt252; + fn div(self, rhs: Self::Output) -> Self::Output { + Self::Output { + value: &self.value / rhs.value, + } + } +} + +impl Rem for Felt252 { + type Output = Self; + fn rem(self, rhs: Self) -> Self { + Self { + value: self.value % rhs.value, + } + } +} + +impl<'a> Rem<&'a Felt252> for Felt252 { + type Output = Self; + fn rem(self, rhs: &Self) -> Self { + Self { + value: self.value % &rhs.value, + } + } +} + +impl Zero for Felt252 { + fn zero() -> Self { + Self { + value: FeltBigInt::zero(), + } + } + + fn is_zero(&self) -> bool { + self.value.is_zero() + } +} + +impl One for Felt252 { + fn one() -> Self { + Self { + value: FeltBigInt::one(), + } + } + + fn is_one(&self) -> bool { + self.value.is_one() + } +} + +impl Bounded for Felt252 { + fn min_value() -> Self { + Self { + value: FeltBigInt::min_value(), + } + } + + fn max_value() -> Self { + Self { + value: FeltBigInt::max_value(), + } + } +} + +impl Num for Felt252 { + type FromStrRadixErr = ParseFeltError; + fn from_str_radix(string: &str, radix: u32) -> Result { + Ok(Self { + value: FeltBigInt::from_str_radix(string, radix)?, + }) + } +} + +impl Integer for Felt252 { + fn div_floor(&self, rhs: &Self) -> Self { + Self { + value: self.value.div_floor(&rhs.value), + } + } + + fn div_rem(&self, other: &Self) -> (Self, Self) { + let (div, rem) = self.value.div_rem(&other.value); + (Self { value: div }, Self { value: rem }) + } + + fn divides(&self, other: &Self) -> bool { + self.value.divides(&other.value) + } + + fn gcd(&self, other: &Self) -> Self { + Self { + value: self.value.gcd(&other.value), + } + } + + fn is_even(&self) -> bool { + self.value.is_even() + } + + fn is_multiple_of(&self, other: &Self) -> bool { + self.value.is_multiple_of(&other.value) + } + + fn is_odd(&self) -> bool { + self.value.is_odd() + } + + fn lcm(&self, other: &Self) -> Self { + Self { + value: self.value.lcm(&other.value), + } + } + + fn mod_floor(&self, rhs: &Self) -> Self { + Self { + value: self.value.mod_floor(&rhs.value), + } + } +} + +impl Signed for Felt252 { + fn abs(&self) -> Self { + Self { + value: self.value.abs(), + } + } + + fn abs_sub(&self, other: &Self) -> Self { + Self { + value: self.value.abs_sub(&other.value), + } + } + + fn signum(&self) -> Self { + Self { + value: self.value.signum(), + } + } + + fn is_positive(&self) -> bool { + self.value.is_positive() + } + + fn is_negative(&self) -> bool { + self.value.is_negative() + } +} + +impl Shl for Felt252 { + type Output = Self; + fn shl(self, rhs: u32) -> Self { + Self { + value: self.value << rhs, + } + } +} + +impl<'a> Shl for &'a Felt252 { + type Output = Felt252; + fn shl(self, rhs: u32) -> Self::Output { + Self::Output { + value: &self.value << rhs, + } + } +} + +impl Shl for Felt252 { + type Output = Self; + fn shl(self, rhs: usize) -> Self { + Self { + value: self.value << rhs, + } + } +} + +impl<'a> Shl for &'a Felt252 { + type Output = Felt252; + fn shl(self, rhs: usize) -> Self::Output { + Self::Output { + value: &self.value << rhs, + } + } +} + +impl Shr for Felt252 { + type Output = Self; + fn shr(self, rhs: u32) -> Self { + Self { + value: self.value >> rhs, + } + } +} + +impl<'a> Shr for &'a Felt252 { + type Output = Felt252; + fn shr(self, rhs: u32) -> Self::Output { + Self::Output { + value: &self.value >> rhs, + } + } +} + +impl ShrAssign for Felt252 { + fn shr_assign(&mut self, rhs: usize) { + self.value >>= rhs + } +} + +impl<'a> BitAnd for &'a Felt252 { + type Output = Felt252; + fn bitand(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value & &rhs.value, + } + } +} + +impl<'a> BitAnd<&'a Felt252> for Felt252 { + type Output = Self; + fn bitand(self, rhs: &Self) -> Self { + Self { + value: self.value & &rhs.value, + } + } +} + +impl<'a> BitAnd for &'a Felt252 { + type Output = Felt252; + fn bitand(self, rhs: Self::Output) -> Self::Output { + Self::Output { + value: &self.value & rhs.value, + } + } +} + +impl<'a> BitOr for &'a Felt252 { + type Output = Felt252; + fn bitor(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value | &rhs.value, + } + } +} + +impl<'a> BitXor for &'a Felt252 { + type Output = Felt252; + fn bitxor(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value ^ &rhs.value, + } + } +} + +impl ToPrimitive for Felt252 { + fn to_u128(&self) -> Option { + self.value.to_u128() + } + + fn to_u64(&self) -> Option { + self.value.to_u64() + } + + fn to_i64(&self) -> Option { + self.value.to_i64() + } +} + +impl FromPrimitive for Felt252 { + fn from_u64(n: u64) -> Option { + FeltBigInt::from_u64(n).map(|n| Self { value: n }) + } + + fn from_i64(n: i64) -> Option { + FeltBigInt::from_i64(n).map(|n| Self { value: n }) + } +} + +impl fmt::Display for Felt252 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.value) + } +} + +impl fmt::Debug for Felt252 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.value) + } +} + +macro_rules! assert_felt_methods { + ($type:ty) => { + const _: () = { + fn assert_felt_ops() {} + fn assertion() { + assert_felt_ops::<$type>(); + } + }; + }; +} + +macro_rules! assert_felt_impl { + ($type:ty) => { + const _: () = { + fn assert_add() {} + fn assert_add_ref<'a, T: Add<&'a $type>>() {} + fn assert_add_u32>() {} + fn assert_add_usize>() {} + fn assert_add_assign() {} + fn assert_add_assign_ref<'a, T: AddAssign<&'a $type>>() {} + fn assert_sum>() {} + fn assert_neg() {} + fn assert_sub() {} + fn assert_sub_ref<'a, T: Sub<&'a $type>>() {} + fn assert_sub_assign() {} + fn assert_sub_assign_ref<'a, T: SubAssign<&'a $type>>() {} + fn assert_sub_u32>() {} + fn assert_sub_usize>() {} + fn assert_mul() {} + fn assert_mul_ref<'a, T: Mul<&'a $type>>() {} + fn assert_mul_assign_ref<'a, T: MulAssign<&'a $type>>() {} + fn assert_pow_u32>() {} + fn assert_pow_felt<'a, T: Pow<&'a $type>>() {} + fn assert_div() {} + fn assert_ref_div>() {} + fn assert_rem() {} + fn assert_rem_ref<'a, T: Rem<&'a $type>>() {} + fn assert_zero() {} + fn assert_one() {} + fn assert_bounded() {} + fn assert_num() {} + fn assert_integer() {} + fn assert_signed() {} + fn assert_shl_u32>() {} + fn assert_shl_usize>() {} + fn assert_shr_u32>() {} + fn assert_shr_assign_usize>() {} + fn assert_bitand() {} + fn assert_bitand_ref<'a, T: BitAnd<&'a $type>>() {} + fn assert_ref_bitand>() {} + fn assert_bitor() {} + fn assert_bitxor() {} + fn assert_from_primitive() {} + fn assert_to_primitive() {} + fn assert_display() {} + fn assert_debug() {} + + #[allow(dead_code)] + fn assert_all() { + assert_add::<$type>(); + assert_add::<&$type>(); + assert_add_ref::<$type>(); + assert_add_u32::<$type>(); + assert_add_usize::<$type>(); + assert_add_usize::<&$type>(); + assert_add_assign::<$type>(); + assert_add_assign_ref::<$type>(); + assert_sum::<$type>(); + assert_neg::<$type>(); + assert_neg::<&$type>(); + assert_sub::<$type>(); + assert_sub::<&$type>(); + assert_sub_ref::<$type>(); + assert_sub_assign::<$type>(); + assert_sub_assign_ref::<$type>(); + assert_sub_u32::<$type>(); + assert_sub_u32::<&$type>(); + assert_sub_usize::<$type>(); + assert_mul::<$type>(); + assert_mul::<&$type>(); + assert_mul_ref::<$type>(); + assert_mul_assign_ref::<$type>(); + assert_pow_u32::<$type>(); + assert_pow_felt::<&$type>(); + assert_div::<$type>(); + assert_div::<&$type>(); + assert_ref_div::<&$type>(); + assert_rem::<$type>(); + assert_rem_ref::<$type>(); + assert_zero::<$type>(); + assert_one::<$type>(); + assert_bounded::<$type>(); + assert_num::<$type>(); + assert_integer::<$type>(); + assert_signed::<$type>(); + assert_shl_u32::<$type>(); + assert_shl_u32::<&$type>(); + assert_shl_usize::<$type>(); + assert_shl_usize::<&$type>(); + assert_shr_u32::<$type>(); + assert_shr_u32::<&$type>(); + assert_shr_assign_usize::<$type>(); + assert_bitand::<&$type>(); + assert_bitand_ref::<$type>(); + assert_ref_bitand::<&$type>(); + assert_bitor::<&$type>(); + assert_bitxor::<&$type>(); + assert_from_primitive::<$type>(); + assert_to_primitive::<$type>(); + assert_display::<$type>(); + assert_debug::<$type>(); + } + }; + }; +} + +assert_felt_methods!(FeltBigInt); +assert_felt_impl!(FeltBigInt); +assert_felt_impl!(Felt252); + +#[cfg(test)] +mod test { + use super::*; + use crate::{arbitrary_bigint_felt::nonzero_felt252, PRIME_STR}; + use core::cmp; + use rstest::rstest; + + use proptest::prelude::*; + + proptest! { + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 felt values that are randomly generated + // each time tests are run, that a new felt doesn't fall outside the range [0, p]. + // In this and some of the following tests, The value of {x} can be either [0] or a + // very large number, in order to try to overflow the value of {p} and thus ensure the + // modular arithmetic is working correctly. + fn new_in_range(ref x in any::<[u8; 40]>()) { + let x = Felt252::from_bytes_be(x); + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + prop_assert!(&x.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_be_bytes(ref x in any::()) { + let bytes = x.to_be_bytes(); + let y = &Felt252::from_bytes_be(&bytes); + prop_assert_eq!(x, y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_le_bytes(ref x in any::()) { + let mut bytes = x.to_le_bytes(); + // Convert to big endian for test + bytes.reverse(); + let y = &Felt252::from_bytes_be(&bytes); + prop_assert_eq!(x, y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_le_digits(ref x in any::()) { + let digits: [u64; 4] = x.to_le_digits(); + let mut bytes: Vec<_> = digits + .into_iter() + .flat_map(|x| x.to_le_bytes()) + .collect(); + // Convert to big endian for test + bytes.reverse(); + let y = &Felt252::from_bytes_be(&bytes); + prop_assert_eq!(x, y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_u128_ok(x in any::()) { + let y = Felt252::from(x); + let y = y.to_u128(); + prop_assert_eq!(Some(x), y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_u128_out_of_range(x in nonzero_felt252()) { + let y = x + Felt252::from(u128::MAX); + let y = y.to_u128(); + prop_assert_eq!(None, y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 felt values that are randomly + // generated each time tests are run, that a felt created using Felt252::from_bytes_be doesn't + // fall outside the range [0, p]. + // In this and some of the following tests, The value of {x} can be either [0] or a very large number, + // in order to try to overflow the value of {p} and thus ensure the modular arithmetic is working correctly. + fn from_bytes_be_in_range(ref x in any::<[u8; 40]>()) { + let x = Felt252::from_bytes_be(x); + let max_felt = Felt252::max_value(); + prop_assert!(x <= max_felt); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 felt values that are randomly generated each time + // tests are run, that the negative of a felt doesn't fall outside the range [0, p]. + fn neg_in_range(x in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let neg = -x.clone(); + let as_uint = &neg.to_biguint(); + prop_assert!(as_uint < p); + + // test reference variant + let neg = -&x; + let as_uint = &neg.to_biguint(); + prop_assert!(as_uint < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {x} and {y} values that are randomly generated + // each time tests are run, that a subtraction between two felts {x} and {y} and doesn't fall + // outside the range [0, p]. The values of {x} and {y} can be either [0] or a very large number. + fn sub(ref x in any::(), ref y in any::()) { + let (x_int, y_int) = (&x.to_biguint(), &y.to_biguint()); + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let sub_xy = x - y; + prop_assert!(&sub_xy.to_biguint() < p); + prop_assert_eq!(Felt252::from(p + x_int - y_int), sub_xy); + + let sub_yx = y - x; + prop_assert!(&sub_yx.to_biguint() < p); + prop_assert_eq!(Felt252::from(p + y_int - x_int), sub_yx); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {x} and {y} values that are randomly generated + // each time tests are run, that a subtraction with assignment between two felts {x} and {y} + // and doesn't fall outside the range [0, p]. The values of {x} and {y} can be either [0] or a very large number. + fn sub_assign_in_range(mut x in any::(), y in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + x -= y.clone(); + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + x -= &y; + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {x} and {y} values that are randomly + // generated each time tests are run, that a multiplication between two felts {x} + // and {y} and doesn't fall outside the range [0, p]. The values of {x} and {y} + // can be either [0] or a very large number. + fn mul(ref x in any::(), ref y in any::()) { + let xy_int = x.to_biguint() * y.to_biguint(); + + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let (xy, yx) = (x * y, y * x); + prop_assert_eq!(&xy, &yx); + prop_assert_eq!(xy.to_biguint(), xy_int.mod_floor(p)); + prop_assert!(&xy.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 pairs of {x} and {y} values that + // are randomly generated each time tests are run, that a multiplication with + // assignment between two felts {x} and {y} and doesn't fall outside the range [0, p]. + // The values of {x} and {y} can be either [0] or a very large number. + fn mul_assign_in_range(mut x in any::(), y in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + x *= &y; + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 pairs of {x} and {y} values that are + // randomly generated each time tests are run, that the result of the division of + // {x} by {y} is the inverse multiplicative of {x} --that is, multiplying the result + // by {y} returns the original number {x}. The values of {x} and {y} can be either + // [0] or a very large number. + fn div_is_mul_inv(ref x in any::(), ref y in nonzero_felt252()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + prop_assume!(!y.is_zero()); + + let q = x / y; + let as_uint = &q.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + prop_assert_eq!(&(q * y), x); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {value}s that are randomly generated + // each time tests are run, that performing a bit shift to the left by {shift_amount} + // of bits (between 0 and 999) returns a result that is inside of the range [0, p]. + fn shift_left_in_range(value in any::(), shift_amount in 0..1000_u32){ + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = (value.clone() << shift_amount).to_biguint(); + prop_assert!(&result < p); + + let result = (&value << shift_amount).to_biguint(); + prop_assert!(&result < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {value}s that are randomly + // generated each time tests are run, that performing a bit shift to the right + // by {shift_amount} of bits (between 0 and 999) returns a result that is inside of the range [0, p]. + fn shift_right_in_range(value in any::(), shift_amount in 0..1000_u32){ + let result = (value >> shift_amount).to_biguint(); + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + prop_assert!(&result < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {value}s that are randomly generated + // each time tests are run, that performing a bit shift to the right by {shift_amount} + // of bits (between 0 and 999), with assignment, returns a result that is inside of the range [0, p]. + // "With assignment" means that the result of the operation is autommatically assigned + // to the variable value, replacing its previous content. + fn shift_right_assign_in_range(mut value in any::(), shift_amount in 0..1000_usize){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + value >>= shift_amount; + prop_assert!(value.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 pairs of values {x} and {y} + // generated at random each time tests are run, that performing a BitAnd + // operation between them returns a result that is inside of the range [0, p]. + fn bitand_in_range(x in any::(), y in any::()){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let result = x & &y; + result.to_biguint(); + prop_assert!(result.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 pairs of values {x} and {y} + // generated at random each time tests are run, that performing a BitOr + // operation between them returns a result that is inside of the range [0, p]. + fn bitor_in_range(x in any::(), y in any::()){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let result = &x | &y; + prop_assert!(result.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 pairs of values {x} and {y} + // generated at random each time tests are run, that performing a BitXor + // operation between them returns a result that is inside of the range [0, p]. + fn bitxor_in_range(x in any::(), y in any::()){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let result = &x ^ &y; + prop_assert!(result.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 values {x} that are randomly + // generated each time tests are run, that raising {x} to the {y}th power + // returns a result that is inside of the range [0, p]. + fn pow_in_range(base in any::(), exp in 0..100_u32){ + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = Pow::pow(base.clone(), exp); + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + let result = Pow::pow(&base, exp); + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 values {x} that are randomly + // generated each time tests are run, that raising {x} to the {y}th power + // returns a result that is inside of the range [0, p]. + fn pow_felt_in_range(base in any::(), exponent in any::()){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = Pow::pow(&base, &exponent); + let as_uint = result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + let result: Felt252 = Pow::pow(&base, &exponent); + let as_uint = result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 pairs of values {x} and {y} + // generated at random each time tests are run, that performing a Sum operation + // between them returns a result that is inside of the range [0, p]. + fn sum_in_range(x in any::(), y in any::()){ + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = x + y; + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property test to check that the remainder of a division between 100 pairs of + // values {x} and {y},generated at random each time tests are run, falls in the + // range [0, p]. x and y can either take the value of 0 or a large integer. + // In Cairo, the result of x / y is defined to always satisfy the equation + // (x / y) * y == x, so the remainder is 0 most of the time. + fn rem_in_range(x in any::(), y in nonzero_felt252()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = x.clone() % y.clone(); + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + let result = x % &y; + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 Felt252s {x} generated at + // random each time tests are run, that converting them into the u64 type + // returns a result that is inside of the range [0, p]. + fn from_u64_and_to_u64_primitive(x in any::()) { + let x_felt:Felt252 = Felt252::from_u64(x).unwrap(); + let x_u64:u64 = Felt252::to_u64(&x_felt).unwrap(); + + prop_assert_eq!(x, x_u64); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn from_i64_and_to_i64_primitive(x in any::()) { + let x: i64 = x as i64; + let x_felt:Felt252 = Felt252::from_i64(x).unwrap(); + let x_i64:i64 = Felt252::to_i64(&x_felt).unwrap(); + prop_assert_eq!(x, x_i64); + } + + #[test] + // Property test to check that lcm(x, y) works. Since we're operating in a prime field, lcm + // will just be the smaller number. + fn lcm_doesnt_panic(x in any::(), y in any::()) { + let lcm = x.lcm(&y); + prop_assert!(lcm == cmp::max(x, y)); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property test to check that is_multiple_of(x, y) works. Since we're operating in a prime field, is_multiple_of + // will always be true + fn is_multiple_of_doesnt_panic(x in any::(), y in any::()) { + prop_assert!(x.is_multiple_of(&y)); + } + + #[test] + fn divides_doesnt_panic(x in any::(), y in any::()) { + prop_assert!(x.divides(&y)); + } + + #[test] + fn gcd_doesnt_panic(x in any::(), y in any::()) { + let gcd1 = x.gcd(&y); + let gcd2 = y.gcd(&x); + prop_assert_eq!(gcd1, gcd2); + } + + #[test] + fn is_even(x in any::()) { + prop_assert_eq!(x.is_even(), x.to_biguint().is_even()); + } + + #[test] + fn is_odd(x in any::()) { + prop_assert_eq!(x.is_odd(), x.to_biguint().is_odd()); + } + + /// Tests the additive identity of the implementation of Zero trait for felts + /// + /// ```{.text} + /// x + 0 = x βˆ€ x + /// 0 + x = x βˆ€ x + /// ``` + #[test] + fn zero_additive_identity(ref x in any::()) { + let zero = Felt252::zero(); + prop_assert_eq!(x, &(x + &zero)); + prop_assert_eq!(x, &(&zero + x)); + } + + /// Tests the multiplicative identity of the implementation of One trait for felts + /// + /// ```{.text} + /// x * 1 = x βˆ€ x + /// 1 * x = x βˆ€ x + /// ``` + #[test] + fn one_multiplicative_identity(ref x in any::()) { + let one = Felt252::one(); + prop_assert_eq!(x, &(x * &one)); + prop_assert_eq!(x, &(&one * x)); + } + + #[test] + fn felt_is_always_positive(x in any::()) { + prop_assert!(x.is_positive()) + } + + #[test] + fn felt_is_never_negative(x in any::()) { + prop_assert!(!x.is_negative()) + } + + #[test] + fn non_zero_felt_signum_is_always_one(ref x in nonzero_felt252()) { + let one = Felt252::one(); + prop_assert_eq!(x.signum(), one) + } + + #[test] + fn sub_abs(x in any::(), y in any::()) { + let expected_abs_sub = if x > y {&x - &y} else {&y - &x}; + + prop_assert_eq!(x.abs_sub(&y), expected_abs_sub) + } + + #[test] + fn abs(x in any::()) { + prop_assert_eq!(&x, &x.abs()) + } + + #[test] + fn modpow_in_range(x in any::(), y in any::()) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let p_felt = Felt252::max_value(); + + let modpow = x.modpow(&y, &p_felt).to_biguint(); + prop_assert!(modpow < p, "{}", modpow); + } + + #[test] + fn sqrt_in_range(x in any::()) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let sqrt = x.sqrt().to_biguint(); + prop_assert!(sqrt < p, "{}", sqrt); + } + + #[test] + fn sqrt_is_inv_square(x in any::()) { + let x_sq = &x * &x; + let sqrt = x_sq.sqrt(); + + if sqrt != x { + prop_assert_eq!(Felt252::max_value() - sqrt + 1_usize, x); + } else { + prop_assert_eq!(sqrt, x); + } + } + + #[test] + fn add_to_u64(x in any::(), ref felt in any::()) { + let sum = (felt + x).to_u64(); + prop_assert_eq!(x + felt, sum); + } + + #[test] + fn add_to_u64_extremes(x in any::()) { + let big_zero = &Felt252::zero(); + let big_max = &Felt252::max_value(); + let big_min = &(big_zero + (i64::MIN as usize)); + + let sum_max = (big_max + x).to_u64(); + prop_assert_eq!(x + big_max, sum_max); + let sum_min = (big_min + x).to_u64(); + prop_assert_eq!(x + big_min, sum_min); + let sum_zero = (big_zero + x).to_u64(); + prop_assert_eq!(x + big_zero, sum_zero); + } + + #[test] + fn add_u32_in_range(x in any::(), y in any::()) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let x_add_y = (x + y).to_biguint(); + prop_assert!(x_add_y < p, "{}", x_add_y); + } + + #[test] + fn add_u32_is_inv_sub(x in any::(), y in any::()) { + let expected_y = (x.clone() + y - x).to_u32().unwrap(); + prop_assert_eq!(expected_y, y, "{}", expected_y); + } + + #[test] + fn sub_u32_in_range(x in any::(), y in any::()) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let x_sub_y = (x - y).to_biguint(); + prop_assert!(x_sub_y < p, "{}", x_sub_y); + } + + #[test] + fn sub_u32_is_inv_add(x in any::(), y in any::()) { + prop_assert_eq!(x.clone() - y + y, x) + } + + #[test] + fn sub_usize_in_range(x in any::(), y in any::()) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let x_sub_y = (x - y).to_biguint(); + prop_assert!(x_sub_y < p, "{}", x_sub_y); + } + + #[test] + fn sub_usize_is_inv_add(x in any::(), y in any::()) { + prop_assert_eq!(x.clone() - y + y, x) + } + + #[test] + fn add_in_range(x in any::(), y in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let sub = x + y; + let as_uint = &sub.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + fn add_is_inv_sub(ref x in any::(), ref y in any::()) { + let expected_y = x + y - x; + prop_assert_eq!(&expected_y, y, "{}", y); + } + + #[test] + fn add_assign_in_range(mut x in any::(), y in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + x += y.clone(); + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + x += &y; + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + } + + #[rstest] + fn add_to_u64_edge_cases( + #[values(0, 1, u64::MAX)] x: u64, + #[values(-2, -1, 0, 1, 1i128.neg(), i64::MIN as i128, u64::MAX as i128, u64::MAX as i128 + 1, (u64::MAX as i128).neg())] + y: i128, + ) { + let y = Felt252::from(y); + assert_eq!(x + &y, (&y + x).to_u64()); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of adding two zeroes is zero + fn sum_zeros_in_range() { + let x = Felt252::new(0); + let y = Felt252::new(0); + let z = Felt252::new(0); + assert_eq!(x + y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of multiplying two zeroes is zero + fn mul_zeros_in_range() { + let x = Felt252::new(0); + let y = Felt252::new(0); + let z = Felt252::new(0); + assert_eq!(x * y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of performing a bit and operation between zeroes is zero + fn bit_and_zeros_in_range() { + let x = Felt252::new(0); + let y = Felt252::new(0); + let z = Felt252::new(0); + assert_eq!(&x & &y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of perfforming a bit or operation between zeroes is zero + fn bit_or_zeros_in_range() { + let x = Felt252::new(0); + let y = Felt252::new(0); + let z = Felt252::new(0); + assert_eq!(&x | &y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of perfforming a bit xor operation between zeroes is zero + fn bit_xor_zeros_in_range() { + let x = Felt252::new(0); + let y = Felt252::new(0); + let z = Felt252::new(0); + assert_eq!(&x ^ &y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Tests that the maximum value a Felt252 can take is equal to (prime - 1) + fn upper_bound() { + let prime = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let unit = BigUint::one(); + let felt_max_value = Felt252::max_value().to_biguint(); + assert_eq!(prime - unit, felt_max_value) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Tests that the minimum value a Felt252 can take is equal to zero. + fn lower_bound() { + let zero = BigUint::zero(); + let felt_min_value = Felt252::min_value().to_biguint(); + assert_eq!(zero, felt_min_value) + } + + #[test] + fn zero_value() { + let zero = BigUint::zero(); + let felt_zero = Felt252::zero().to_biguint(); + assert_eq!(zero, felt_zero) + } + + #[test] + fn is_zero() { + let felt_zero = Felt252::zero(); + let felt_non_zero = Felt252::new(3); + assert!(felt_zero.is_zero()); + assert!(!felt_non_zero.is_zero()) + } + + #[test] + fn one_value() { + let one = BigUint::one(); + let felt_one = Felt252::one().to_biguint(); + assert_eq!(one, felt_one) + } + + #[test] + fn is_one() { + let felt_one = Felt252::one(); + let felt_non_one = Felt252::new(8); + assert!(felt_one.is_one()); + assert!(!felt_non_one.is_one()) + } + + #[test] + fn signum_of_zero_is_zero() { + let zero = Felt252::zero(); + assert_eq!(&zero.signum(), &zero) + } +} diff --git a/felt/src/lib_lambdaworks.rs b/felt/src/lib_lambdaworks.rs new file mode 100644 index 0000000000..635fb13abd --- /dev/null +++ b/felt/src/lib_lambdaworks.rs @@ -0,0 +1,1688 @@ +use core::{ + convert::Into, + fmt, + iter::Sum, + ops::{ + Add, AddAssign, BitAnd, BitOr, BitXor, Div, Mul, MulAssign, Neg, Rem, Shl, Shr, ShrAssign, + Sub, SubAssign, + }, +}; +use lambdaworks_math::{ + field::{ + element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, + }, + unsigned_integer::element::UnsignedInteger, +}; +use lazy_static::lazy_static; +use num_bigint::{BigInt, BigUint, Sign, ToBigInt}; +use num_integer::Integer; +use num_traits::{Bounded, FromPrimitive, Num, One, Pow, Signed, ToPrimitive, Zero}; +use serde::{Deserialize, Serialize}; + +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::{string::String, vec::Vec}; + +use crate::{ParseFeltError, FIELD_HIGH, FIELD_LOW}; + +lazy_static! { + pub static ref CAIRO_PRIME_BIGUINT: BigUint = + (Into::::into(FIELD_HIGH) << 128) + Into::::into(FIELD_LOW); + pub static ref SIGNED_FELT_MAX: BigUint = (&*CAIRO_PRIME_BIGUINT).shr(1_u32); + pub static ref CAIRO_SIGNED_PRIME: BigInt = CAIRO_PRIME_BIGUINT + .to_bigint() + .expect("Conversion BigUint -> BigInt can't fail"); +} + +#[macro_export] +macro_rules! felt_str { + ($val: expr) => { + $crate::Felt252::parse_bytes($val.as_bytes(), 10_u32).expect("Couldn't parse bytes") + }; + ($val: expr, $opt: expr) => { + $crate::Felt252::parse_bytes($val.as_bytes(), $opt as u32).expect("Couldn't parse bytes") + }; +} + +#[derive(Eq, Hash, PartialEq, Clone, Deserialize, Serialize)] +#[serde(from = "BigInt")] +#[serde(into = "BigInt")] +pub struct Felt252 { + pub(crate) value: FieldElement, +} + +// TODO: remove and change for transformation + compare +impl PartialOrd for Felt252 { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +// TODO: remove and change for transformation + compare +// Also, maybe this could be changed to compare against zero without changing montgomeryness +impl Ord for Felt252 { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.value + .representative() + .cmp(&other.value.representative()) + } +} + +impl Default for Felt252 { + fn default() -> Self { + Self { + value: FieldElement::zero(), + } + } +} + +macro_rules! from_num { + ($type:ty, $cast:ty) => { + impl From<$type> for Felt252 { + fn from(value: $type) -> Self { + let uplifted: $cast = value as $cast; + uplifted.into() + } + } + }; +} + +from_num!(isize, i64); +from_num!(i8, i64); +from_num!(i16, i64); +from_num!(i32, i64); + +// TODO: move to upstream? +impl From for Felt252 { + fn from(value: i64) -> Self { + let value = if !value.is_negative() { + FieldElement::new(UnsignedInteger::from_u64(value as u64)) + } else { + let abs_minus_one = UnsignedInteger::from_u64(-(value + 1) as u64); + FieldElement::zero() - FieldElement::one() - FieldElement::new(abs_minus_one) + }; + Self { value } + } +} + +// TODO: move to upstream? +impl From for Felt252 { + fn from(value: i128) -> Self { + let value = if !value.is_negative() { + FieldElement::new(UnsignedInteger::from_u128(value as u128)) + } else { + let abs_minus_one = UnsignedInteger::from_u128(-(value + 1) as u128); + FieldElement::zero() - FieldElement::one() - FieldElement::new(abs_minus_one) + }; + Self { value } + } +} + +from_num!(usize, u64); +from_num!(u8, u64); +from_num!(u16, u64); +from_num!(u32, u64); + +// TODO: move to upstream? +impl From for Felt252 { + fn from(value: u64) -> Self { + let value = FieldElement::new(UnsignedInteger::from_u64(value)); + Self { value } + } +} + +// TODO: move to upstream? +impl From for Felt252 { + fn from(value: u128) -> Self { + let value = FieldElement::new(UnsignedInteger::from_u128(value)); + Self { value } + } +} + +impl From for Felt252 { + fn from(flag: bool) -> Self { + if flag { + Self::one() + } else { + Self::zero() + } + } +} + +// TODO: bury BigUint? +impl From for Felt252 { + fn from(mut value: BigUint) -> Self { + if value >= *CAIRO_PRIME_BIGUINT { + value = value.mod_floor(&CAIRO_PRIME_BIGUINT); + } + let mut limbs = [0; 4]; + for (i, l) in (0..4).rev().zip(value.iter_u64_digits()) { + limbs[i] = l; + } + let value = FieldElement::new(UnsignedInteger::from_limbs(limbs)); + Self { value } + } +} + +// TODO: bury BigInt? +// NOTE: used for deserialization +impl From for Felt252 { + fn from(value: BigInt) -> Self { + let val = value.mod_floor(&CAIRO_PRIME_BIGUINT.to_bigint().expect("cannot fail")); + let mut limbs = [0; 4]; + for (i, l) in (0..4).rev().zip(val.iter_u64_digits()) { + limbs[i] = l; + } + let value = FieldElement::new(UnsignedInteger::from_limbs(limbs)); + Self { value } + } +} + +// NOTE: used for serialization +impl From for BigInt { + fn from(value: Felt252) -> Self { + value.to_bigint() + } +} + +impl Felt252 { + pub fn new>(value: T) -> Self { + value.into() + } + + pub fn iter_u64_digits(&self) -> impl Iterator { + self.value.representative().limbs.into_iter().rev() + } + + pub fn to_le_bytes(&self) -> [u8; 32] { + // TODO: upstream should return array + let mut bytes = [0; 32]; + let digits = self.to_le_digits(); + for (i, d) in digits.into_iter().enumerate() { + let idx = i * 8; + bytes[idx..(idx + 8)].copy_from_slice(&d.to_le_bytes()); + } + bytes + } + + pub fn to_be_bytes(&self) -> [u8; 32] { + // TODO: upstream should return array + let mut bytes = [0; 32]; + let digits = self.to_be_digits(); + for (i, d) in digits.into_iter().enumerate() { + let idx = i * 8; + bytes[idx..(idx + 8)].copy_from_slice(&d.to_be_bytes()); + } + bytes + } + + pub fn to_le_digits(&self) -> [u64; 4] { + let mut rep = self.value.representative(); + rep.limbs.reverse(); + rep.limbs + } + + pub fn to_be_digits(&self) -> [u64; 4] { + self.value.representative().limbs + } + + pub fn parse_bytes(bytes: &[u8], radix: u32) -> Option { + Some(BigInt::parse_bytes(bytes, radix)?.into()) + } + + pub fn from_bytes_be(bytes: &[u8]) -> Self { + Self::from(BigUint::from_bytes_be(bytes)) + } + + #[cfg(any(feature = "std", feature = "alloc"))] + pub fn to_str_radix(&self, radix: u32) -> String { + if radix == 16 { + let mut res = format!("{}", self.value); + res.replace_range(..2, ""); + res + } else { + self.to_biguint().to_str_radix(radix) + } + } + + /// Converts [`Felt252`] into a [`BigInt`] number in the range: `(- FIELD / 2, FIELD / 2)`. + /// + /// # Examples + /// + /// ``` + /// # use crate::cairo_felt::Felt252; + /// # use num_bigint::BigInt; + /// # use num_traits::Bounded; + /// let positive = Felt252::new(5); + /// assert_eq!(positive.to_signed_felt(), Into::::into(5)); + /// + /// let negative = Felt252::max_value(); + /// assert_eq!(negative.to_signed_felt(), Into::::into(-1)); + /// ``` + pub fn to_signed_felt(&self) -> BigInt { + let biguint = self.to_biguint(); + if biguint > *SIGNED_FELT_MAX { + BigInt::from_biguint(num_bigint::Sign::Minus, &*CAIRO_PRIME_BIGUINT - &biguint) + } else { + biguint.to_bigint().expect("cannot fail") + } + } + + // Converts [`Felt252`]'s representation directly into a [`BigInt`]. + // Equivalent to doing felt.to_biguint().to_bigint(). + pub fn to_bigint(&self) -> BigInt { + BigInt::from_biguint(Sign::Plus, self.to_biguint()) + } + + /// Converts [`Felt252`] into a [`BigUint`] number. + /// + /// # Examples + /// + /// ``` + /// # use crate::cairo_felt::Felt252; + /// # use num_bigint::BigUint; + /// # use num_traits::{Num, Bounded}; + /// let positive = Felt252::new(5); + /// assert_eq!(positive.to_biguint(), Into::::into(5_u32)); + /// + /// let negative = Felt252::max_value(); + /// assert_eq!(negative.to_biguint(), BigUint::from_str_radix("800000000000011000000000000000000000000000000000000000000000000", 16).unwrap()); + /// ``` + pub fn to_biguint(&self) -> BigUint { + let big_digits = self + .iter_u64_digits() + .flat_map(|limb| [limb as u32, (limb >> 32) as u32]) + .collect(); + BigUint::new(big_digits) + } + + pub fn sqrt(&self) -> Self { + // Safety: must be called with residual + let (root_1, root_2) = self.value.sqrt().unwrap(); + let value = FieldElement::new(root_1.representative().min(root_2.representative())); + Self { value } + } + + pub fn bits(&self) -> u64 { + // TODO: move upstream + let rep = self.value.representative(); + match rep.limbs { + [0, 0, 0, 0] => 0, + [0, 0, 0, l0] => u64::BITS - l0.leading_zeros(), + [0, 0, l1, _] => 2 * u64::BITS - l1.leading_zeros(), + [0, l2, _, _] => 3 * u64::BITS - l2.leading_zeros(), + [l3, _, _, _] => 4 * u64::BITS - l3.leading_zeros(), + } + .into() + } + + pub fn prime() -> BigUint { + CAIRO_PRIME_BIGUINT.clone() + } +} + +impl Add for Felt252 { + type Output = Self; + fn add(self, rhs: Self) -> Self { + Self { + value: self.value + rhs.value, + } + } +} + +impl<'a> Add for &'a Felt252 { + type Output = Felt252; + fn add(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value + &rhs.value, + } + } +} + +impl<'a> Add<&'a Felt252> for Felt252 { + type Output = Self; + fn add(self, rhs: &Self) -> Self::Output { + Self::Output { + value: self.value + &rhs.value, + } + } +} + +impl Add for Felt252 { + type Output = Self; + fn add(self, rhs: u32) -> Self { + let rhs = UnsignedInteger::from_u64(rhs.into()); + Self { + value: self.value + FieldElement::new(rhs), + } + } +} + +impl Add for Felt252 { + type Output = Self; + fn add(self, rhs: usize) -> Self { + let rhs = UnsignedInteger::from_u64(rhs as u64); + Self { + value: self.value + FieldElement::new(rhs), + } + } +} + +impl<'a> Add for &'a Felt252 { + type Output = Felt252; + fn add(self, rhs: usize) -> Self::Output { + let rhs = UnsignedInteger::from_u64(rhs as u64); + Self::Output { + value: &self.value + FieldElement::new(rhs), + } + } +} + +impl Add for &Felt252 { + type Output = Felt252; + fn add(self, rhs: u64) -> Self::Output { + let rhs = UnsignedInteger::from_u64(rhs); + Self::Output { + value: &self.value + FieldElement::new(rhs), + } + } +} + +// TODO: verify if this optimization causes a speed-up in the current implementation. +// This is special cased and optimized compared to the obvious implementation +// due to `pc_update` relying on this, which makes it a major bottleneck for +// execution. Testing for this function is extensive, comprised of explicit +// edge and special cases testing and property tests, all comparing to the +// more intuitive `(rhs + self).to_u64()` implementation. +impl Add<&Felt252> for u64 { + type Output = Option; + + fn add(self, rhs: &Felt252) -> Option { + const PRIME_DIGITS_BE_HI: [u64; 3] = + [0x0800000000000011, 0x0000000000000000, 0x0000000000000000]; + const PRIME_MINUS_U64_MAX_DIGITS_BE_HI: [u64; 3] = + [0x0800000000000010, 0xffffffffffffffff, 0xffffffffffffffff]; + + // Match with the 64 bits digits in big-endian order to + // characterize how the sum will behave. + match rhs.to_be_digits() { + // All digits are `0`, so the sum is simply `self`. + [0, 0, 0, 0] => Some(self), + // A single digit means this is effectively the sum of two `u64` numbers. + [0, 0, 0, low] => self.checked_add(low), + // Now we need to compare the 3 most significant digits. + // There are two relevant cases from now on, either `rhs` behaves like a + // substraction of a `u64` or the result of the sum falls out of range. + + // The 3 MSB only match the prime for Felt252::max_value(), which is -1 + // in the signed field, so this is equivalent to substracting 1 to `self`. + [hi @ .., _] if hi == PRIME_DIGITS_BE_HI => self.checked_sub(1), + + // For the remaining values between `[-u64::MAX..0]` (where `{0, -1}` have + // already been covered) the MSB matches that of `PRIME - u64::MAX`. + // Because we're in the negative number case, we count down. Because `0` + // and `-1` correspond to different MSBs, `0` and `1` in the LSB are less + // than `-u64::MAX`, the smallest value we can add to (read, substract its + // magnitude from) a `u64` number, meaning we exclude them from the valid + // case. + // For the remaining range, we take the absolute value module-2 while + // correcting by substracting `1` (note we actually substract `2` because + // the absolute value itself requires substracting `1`. + [hi @ .., low] if hi == PRIME_MINUS_U64_MAX_DIGITS_BE_HI && low >= 2 => { + (self).checked_sub(u64::MAX - (low - 2)) + } + // Any other case will result in an addition that is out of bounds, so + // the addition fails, returning `None`. + _ => None, + } + } +} + +impl AddAssign for Felt252 { + fn add_assign(&mut self, rhs: Self) { + self.value += rhs.value; + } +} + +impl<'a> AddAssign<&'a Felt252> for Felt252 { + fn add_assign(&mut self, rhs: &Self) { + // TODO: optimize and move upstream + self.value += rhs.value.clone(); + } +} + +impl Sum for Felt252 { + fn sum>(iter: I) -> Self { + iter.fold(Felt252::zero(), |mut acc, x| { + acc += x; + acc + }) + } +} + +impl Neg for Felt252 { + type Output = Self; + fn neg(self) -> Self { + Self { + value: self.value.neg(), + } + } +} + +impl<'a> Neg for &'a Felt252 { + type Output = Felt252; + fn neg(self) -> Self::Output { + Self::Output { + value: (&self.value).neg(), + } + } +} + +impl Sub for Felt252 { + type Output = Self; + fn sub(self, rhs: Self) -> Self { + Self { + value: self.value - rhs.value, + } + } +} + +impl<'a> Sub for &'a Felt252 { + type Output = Felt252; + fn sub(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value - &rhs.value, + } + } +} + +impl<'a> Sub<&'a Felt252> for Felt252 { + type Output = Self; + fn sub(self, rhs: &Self) -> Self { + Self { + value: self.value - &rhs.value, + } + } +} + +// a - b = a + (-b), but clippy doesn't know that +#[allow(clippy::suspicious_arithmetic_impl)] +impl Sub<&Felt252> for usize { + type Output = Felt252; + fn sub(self, rhs: &Self::Output) -> Self::Output { + let neg = Self::Output { + value: (&rhs.value).neg(), + }; + neg + self + } +} + +impl SubAssign for Felt252 { + fn sub_assign(&mut self, rhs: Self) { + // TODO: optimize and move to upstream + self.value = &self.value - rhs.value + } +} + +impl<'a> SubAssign<&'a Felt252> for Felt252 { + fn sub_assign(&mut self, rhs: &Self) { + // TODO: optimize and move to upstream + self.value = &self.value - &rhs.value + } +} + +impl Sub for Felt252 { + type Output = Self; + fn sub(self, rhs: u32) -> Self { + let value = self.value - FieldElement::new(UnsignedInteger::from_u64(rhs as u64)); + Self { value } + } +} + +impl<'a> Sub for &'a Felt252 { + type Output = Felt252; + fn sub(self, rhs: u32) -> Self::Output { + self.clone() - rhs + } +} + +impl Sub for Felt252 { + type Output = Self; + fn sub(self, rhs: usize) -> Self { + let value = self.value - FieldElement::new(UnsignedInteger::from_u64(rhs as u64)); + Self { value } + } +} + +impl Mul for Felt252 { + type Output = Self; + fn mul(self, rhs: Self) -> Self { + Self { + value: self.value * rhs.value, + } + } +} + +impl<'a> Mul for &'a Felt252 { + type Output = Felt252; + fn mul(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value * &rhs.value, + } + } +} + +impl<'a> Mul<&'a Felt252> for Felt252 { + type Output = Self; + fn mul(self, rhs: &Self) -> Self { + Self { + value: self.value * &rhs.value, + } + } +} + +impl<'a> MulAssign<&'a Felt252> for Felt252 { + fn mul_assign(&mut self, rhs: &Self) { + self.value = &self.value * &rhs.value; + } +} + +impl Pow for Felt252 { + type Output = Self; + fn pow(self, rhs: u32) -> Self { + Self { + value: self.value.pow(rhs), + } + } +} + +impl<'a> Pow for &'a Felt252 { + type Output = Felt252; + fn pow(self, rhs: u32) -> Self::Output { + Self::Output { + value: self.value.pow(rhs), + } + } +} + +impl<'a> Pow<&'a Felt252> for &'a Felt252 { + type Output = Felt252; + fn pow(self, rhs: &'a Felt252) -> Self::Output { + Self::Output { + value: self.value.pow(rhs.value.representative()), + } + } +} + +impl Div for Felt252 { + type Output = Self; + fn div(self, rhs: Self) -> Self { + Self { + value: self.value / rhs.value, + } + } +} + +impl<'a> Div for &'a Felt252 { + type Output = Felt252; + fn div(self, rhs: Self) -> Self::Output { + Self::Output { + value: &self.value / &rhs.value, + } + } +} + +impl<'a> Div for &'a Felt252 { + type Output = Felt252; + fn div(self, rhs: Self::Output) -> Self::Output { + Self::Output { + value: &self.value / rhs.value, + } + } +} + +impl Rem for Felt252 { + type Output = Self; + fn rem(self, _rhs: Self) -> Self { + Self::zero() + } +} + +impl<'a> Rem<&'a Felt252> for Felt252 { + type Output = Self; + fn rem(self, _rhs: &Self) -> Self { + Self::zero() + } +} + +impl Zero for Felt252 { + fn zero() -> Self { + Self { + value: FieldElement::from_raw(&Stark252PrimeField::ZERO), + } + } + + fn is_zero(&self) -> bool { + self.value == FieldElement::from_raw(&Stark252PrimeField::ZERO) + } +} + +impl One for Felt252 { + fn one() -> Self { + let value = FieldElement::from_raw(&Stark252PrimeField::ONE); + Self { value } + } + + fn is_one(&self) -> bool { + self.value == FieldElement::from_raw(&Stark252PrimeField::ONE) + } +} + +impl Bounded for Felt252 { + fn min_value() -> Self { + Self { + value: FieldElement::zero(), + } + } + + fn max_value() -> Self { + Self { + value: FieldElement::zero() - FieldElement::one(), + } + } +} + +impl Num for Felt252 { + type FromStrRadixErr = ParseFeltError; + fn from_str_radix(string: &str, radix: u32) -> Result { + let res = if radix == 16 { + let value = FieldElement::from_hex(string).map_err(|_| ParseFeltError)?; + Self { value } + } else { + let biguint = BigInt::from_str_radix(string, radix).map_err(|_| ParseFeltError)?; + biguint.into() + }; + Ok(res) + } +} + +impl Integer for Felt252 { + fn div_floor(&self, rhs: &Self) -> Self { + let (d, _) = self.div_rem(rhs); + d + } + + fn mod_floor(&self, rhs: &Self) -> Self { + let (_, m) = self.div_rem(rhs); + m + } + + fn div_rem(&self, other: &Self) -> (Self, Self) { + let (div, rem) = self.to_biguint().div_mod_floor(&other.to_biguint()); + (Self::from(div), Self::from(rem)) + } + + // NOTE: we overload because the default impl calls div_floor AND mod_floor. + fn div_mod_floor(&self, rhs: &Self) -> (Self, Self) { + // NOTE: for positive integers, to floor and truncate is the same, so div_rem == div_mod_floor. + self.div_rem(rhs) + } + + fn divides(&self, _other: &Self) -> bool { + !self.is_zero() + } + + fn gcd(&self, other: &Self) -> Self { + Self::from(self.to_biguint().gcd(&other.to_biguint())) + } + + fn is_even(&self) -> bool { + self.value.representative().limbs[3] & 1 == 0 + } + + fn is_multiple_of(&self, other: &Self) -> bool { + !other.is_zero() + } + + fn is_odd(&self) -> bool { + !self.is_even() + } + + fn lcm(&self, other: &Self) -> Self { + self.max(other).clone() + } +} + +impl Signed for Felt252 { + fn abs(&self) -> Self { + self.clone() + } + + fn abs_sub(&self, other: &Self) -> Self { + self.max(other) - self.min(other) + } + + fn signum(&self) -> Self { + if self.is_zero() { + Self::zero() + } else { + Self::one() + } + } + + fn is_positive(&self) -> bool { + !self.is_zero() + } + + fn is_negative(&self) -> bool { + false + } +} + +// ------------------- +// Bit-wise operations +// NOTE: these do bit shifting on the representative + +impl Shl for Felt252 { + type Output = Self; + fn shl(self, rhs: u32) -> Self { + &self << rhs + } +} + +impl<'a> Shl for &'a Felt252 { + type Output = Felt252; + #[allow(clippy::suspicious_arithmetic_impl)] + fn shl(self, rhs: u32) -> Self::Output { + // TODO: upstream should be able to receive usize + Felt252::from(2).pow(rhs) * self + } +} + +impl Shl for Felt252 { + type Output = Self; + fn shl(self, rhs: usize) -> Self { + &self << rhs + } +} + +impl<'a> Shl for &'a Felt252 { + type Output = Felt252; + fn shl(self, rhs: usize) -> Self::Output { + self << (rhs as u32) + } +} + +impl Shr for Felt252 { + type Output = Self; + fn shr(self, rhs: u32) -> Self { + &self >> rhs + } +} + +impl<'a> Shr for &'a Felt252 { + type Output = Felt252; + fn shr(self, rhs: u32) -> Self::Output { + self >> (rhs as usize) + } +} + +impl Shr for Felt252 { + type Output = Felt252; + fn shr(self, rhs: usize) -> Self::Output { + &self >> rhs + } +} + +impl<'a> Shr for &'a Felt252 { + type Output = Felt252; + fn shr(self, rhs: usize) -> Self::Output { + // TODO: upstream should do this check + if rhs >= 64 * 4 { + Felt252::zero() + } else { + let value = FieldElement::new(self.value.representative() >> rhs); + Self::Output { value } + } + } +} + +impl ShrAssign for Felt252 { + fn shr_assign(&mut self, rhs: usize) { + // TODO: optimize and move upstream + *self = self.clone() >> rhs; + } +} + +// TODO: move to upstream +impl<'a> BitAnd for &'a Felt252 { + type Output = Felt252; + fn bitand(self, rhs: Self) -> Self::Output { + self.clone() & rhs + } +} + +// TODO: move to upstream +impl<'a> BitAnd<&'a Felt252> for Felt252 { + type Output = Self; + fn bitand(self, rhs: &Self) -> Self { + rhs & self + } +} + +impl<'a> BitAnd for &'a Felt252 { + type Output = Felt252; + fn bitand(self, rhs: Self::Output) -> Self::Output { + // TODO: move to upstream + let a = self.value.representative(); + let b = rhs.value.representative(); + let value = FieldElement::new(a & b); + Self::Output { value } + } +} + +impl<'a> BitOr for &'a Felt252 { + type Output = Felt252; + fn bitor(self, rhs: Self) -> Self::Output { + // TODO: move to upstream + let mut a = self.value.representative(); + let b = rhs.value.representative(); + + for i in 0..a.limbs.len() { + a.limbs[i] |= b.limbs[i]; + } + let value = FieldElement::new(a); + // let value = FieldElement::new(a | b); + Self::Output { value } + } +} + +impl<'a> BitXor for &'a Felt252 { + type Output = Felt252; + fn bitxor(self, rhs: Self) -> Self::Output { + // TODO: move to upstream + let mut a = self.value.representative(); + let b = rhs.value.representative(); + + for i in 0..a.limbs.len() { + a.limbs[i] ^= b.limbs[i]; + } + let value = FieldElement::new(a); + // let value = FieldElement::new(a ^ b); + Self::Output { value } + } +} + +// TODO: move to upstream +impl ToPrimitive for Felt252 { + fn to_u128(&self) -> Option { + match self.value.representative().limbs { + [0, 0, high, low] => Some(((high as u128) << 64) | low as u128), + _ => None, + } + } + + fn to_u64(&self) -> Option { + match self.value.representative().limbs { + [0, 0, 0, val] => Some(val), + _ => None, + } + } + + fn to_i64(&self) -> Option { + // NOTE: result can't be negative + self.to_u64().as_ref().and_then(u64::to_i64) + } +} + +impl FromPrimitive for Felt252 { + fn from_u64(n: u64) -> Option { + Some(Felt252 { + value: FieldElement::from(n), + }) + } + + fn from_i64(n: i64) -> Option { + let res = (!n.is_negative()).then(|| FieldElement::from(n as u64)); + res.map(|value| Felt252 { value }) + } +} + +impl fmt::Display for Felt252 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_str_radix(10)) + } +} + +impl fmt::Debug for Felt252 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_str_radix(10)) + } +} + +#[cfg(test)] +mod test { + use core::cmp; + + use super::*; + use crate::{arbitrary_lambdaworks::nonzero_felt252, PRIME_STR}; + use num_integer::Integer; + use rstest::rstest; + + use proptest::prelude::*; + + proptest! { + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 felt values that are randomly generated + // each time tests are run, that a new felt doesn't fall outside the range [0, p]. + // In this and some of the following tests, The value of {x} can be either [0] or a + // very large number, in order to try to overflow the value of {p} and thus ensure the + // modular arithmetic is working correctly. + fn new_in_range(ref x in any::<[u8; 40]>()) { + let x = Felt252::from_bytes_be(x); + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + prop_assert!(&x.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_be_bytes(ref x in any::()) { + let bytes = x.to_be_bytes(); + let y = &Felt252::from_bytes_be(&bytes); + prop_assert_eq!(x, y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_le_bytes(ref x in any::()) { + let mut bytes = x.to_le_bytes(); + // Convert to big endian for test + bytes.reverse(); + let y = &Felt252::from_bytes_be(&bytes); + prop_assert_eq!(x, y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_le_digits(ref x in any::()) { + let digits: [u64; 4] = x.to_le_digits(); + let mut bytes: Vec<_> = digits + .into_iter() + .flat_map(|x| x.to_le_bytes()) + .collect(); + // Convert to big endian for test + bytes.reverse(); + let y = &Felt252::from_bytes_be(&bytes); + prop_assert_eq!(x, y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_u128_ok(x in any::()) { + let y = Felt252::from(x); + let y = y.to_u128(); + prop_assert_eq!(Some(x), y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn to_u128_out_of_range(x in nonzero_felt252()) { + let y = x + Felt252::from(u128::MAX); + let y = y.to_u128(); + prop_assert_eq!(None, y); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 felt values that are randomly + // generated each time tests are run, that a felt created using Felt252::from_bytes_be doesn't + // fall outside the range [0, p]. + // In this and some of the following tests, The value of {x} can be either [0] or a very large number, + // in order to try to overflow the value of {p} and thus ensure the modular arithmetic is working correctly. + fn from_bytes_be_in_range(ref x in any::<[u8; 40]>()) { + let x = Felt252::from_bytes_be(x); + let max_felt = Felt252::max_value(); + prop_assert!(x <= max_felt); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 felt values that are randomly generated each time + // tests are run, that the negative of a felt doesn't fall outside the range [0, p]. + fn neg_in_range(x in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let neg = -x.clone(); + let as_uint = &neg.to_biguint(); + prop_assert!(as_uint < p); + + // test reference variant + let neg = -&x; + let as_uint = &neg.to_biguint(); + prop_assert!(as_uint < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {x} and {y} values that are randomly generated + // each time tests are run, that a subtraction between two felts {x} and {y} and doesn't fall + // outside the range [0, p]. The values of {x} and {y} can be either [0] or a very large number. + fn sub(ref x in any::(), ref y in any::()) { + let (x_int, y_int) = (&x.to_biguint(), &y.to_biguint()); + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let sub_xy = x - y; + prop_assert!(&sub_xy.to_biguint() < p); + prop_assert_eq!(Felt252::from(p + x_int - y_int), sub_xy); + + let sub_yx = y - x; + prop_assert!(&sub_yx.to_biguint() < p); + prop_assert_eq!(Felt252::from(p + y_int - x_int), sub_yx); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {x} and {y} values that are randomly generated + // each time tests are run, that a subtraction with assignment between two felts {x} and {y} + // and doesn't fall outside the range [0, p]. The values of {x} and {y} can be either [0] or a very large number. + fn sub_assign_in_range(mut x in any::(), y in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + x -= y.clone(); + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + x -= &y; + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {x} and {y} values that are randomly + // generated each time tests are run, that a multiplication between two felts {x} + // and {y} and doesn't fall outside the range [0, p]. The values of {x} and {y} + // can be either [0] or a very large number. + fn mul(ref x in any::(), ref y in any::()) { + let xy_int = x.to_biguint() * y.to_biguint(); + + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let (xy, yx) = (x * y, y * x); + prop_assert_eq!(&xy, &yx); + prop_assert_eq!(xy.to_biguint(), xy_int.mod_floor(p)); + prop_assert!(&xy.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 pairs of {x} and {y} values that + // are randomly generated each time tests are run, that a multiplication with + // assignment between two felts {x} and {y} and doesn't fall outside the range [0, p]. + // The values of {x} and {y} can be either [0] or a very large number. + fn mul_assign_in_range(mut x in any::(), y in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + x *= &y; + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 pairs of {x} and {y} values that are + // randomly generated each time tests are run, that the result of the division of + // {x} by {y} is the inverse multiplicative of {x} --that is, multiplying the result + // by {y} returns the original number {x}. The values of {x} and {y} can be either + // [0] or a very large number. + fn div_is_mul_inv(ref x in any::(), ref y in nonzero_felt252()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + prop_assume!(!y.is_zero()); + + let q = x / y; + let as_uint = &q.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + prop_assert_eq!(&(q * y), x); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {value}s that are randomly generated + // each time tests are run, that performing a bit shift to the left by {shift_amount} + // of bits (between 0 and 999) returns a result that is inside of the range [0, p]. + fn shift_left_in_range(value in any::(), shift_amount in 0..1000_u32) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = (value.clone() << shift_amount).to_biguint(); + prop_assert!(&result < p); + + let result = (&value << shift_amount).to_biguint(); + prop_assert!(&result < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn shift_left_equals_old_shl(value in any::(), shift_amount in 0..1000_u32) { + let expected = (value.to_biguint() << shift_amount).mod_floor(&Felt252::prime()); + + let result = (&value << shift_amount).to_biguint(); + prop_assert_eq!(&result, &expected); + + let result = (value << shift_amount).to_biguint(); + prop_assert_eq!(&result, &expected); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {value}s that are randomly + // generated each time tests are run, that performing a bit shift to the right + // by {shift_amount} of bits (between 0 and 999) returns a result that is inside of the range [0, p]. + fn shift_right_in_range(value in any::(), shift_amount in 0..1000_u32){ + let result = (value >> shift_amount).to_biguint(); + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + prop_assert!(&result < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 {value}s that are randomly generated + // each time tests are run, that performing a bit shift to the right by {shift_amount} + // of bits (between 0 and 999), with assignment, returns a result that is inside of the range [0, p]. + // "With assignment" means that the result of the operation is autommatically assigned + // to the variable value, replacing its previous content. + fn shift_right_assign_in_range(mut value in any::(), shift_amount in 0..1000_usize) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + value >>= shift_amount; + prop_assert!(value.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn shift_right_equals_old_shr(value in any::(), shift_amount in 0..1000_u32) { + let expected = (value.to_biguint() >> shift_amount).mod_floor(&Felt252::prime()); + + let result = (&value >> shift_amount).to_biguint(); + prop_assert_eq!(&result, &expected); + + let result = (value >> shift_amount).to_biguint(); + prop_assert_eq!(&result, &expected); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 pairs of values {x} and {y} + // generated at random each time tests are run, that performing a BitAnd + // operation between them returns a result that is inside of the range [0, p]. + fn bitand_in_range(x in any::(), y in any::()){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let result = x & &y; + result.to_biguint(); + prop_assert!(result.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 pairs of values {x} and {y} + // generated at random each time tests are run, that performing a BitOr + // operation between them returns a result that is inside of the range [0, p]. + fn bitor_in_range(x in any::(), y in any::()){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let result = &x | &y; + prop_assert!(result.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 pairs of values {x} and {y} + // generated at random each time tests are run, that performing a BitXor + // operation between them returns a result that is inside of the range [0, p]. + fn bitxor_in_range(x in any::(), y in any::()){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let result = &x ^ &y; + prop_assert!(result.to_biguint() < p); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 values {x} that are randomly + // generated each time tests are run, that raising {x} to the {y}th power + // returns a result that is inside of the range [0, p]. + fn pow_in_range(base in any::(), exp in 0..100_u32){ + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = Pow::pow(base.clone(), exp); + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + let result = Pow::pow(&base, exp); + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property-based test that ensures, for 100 values {x} that are randomly + // generated each time tests are run, that raising {x} to the {y}th power + // returns a result that is inside of the range [0, p]. + fn pow_felt_in_range(base in any::(), exponent in any::()){ + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = Pow::pow(&base, &exponent); + let as_uint = result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + let result: Felt252 = Pow::pow(&base, &exponent); + let as_uint = result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 pairs of values {x} and {y} + // generated at random each time tests are run, that performing a Sum operation + // between them returns a result that is inside of the range [0, p]. + fn sum_in_range(x in any::(), y in any::()){ + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = x + y; + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property test to check that the remainder of a division between 100 pairs of + // values {x} and {y},generated at random each time tests are run, falls in the + // range [0, p]. x and y can either take the value of 0 or a large integer. + // In Cairo, the result of x / y is defined to always satisfy the equation + // (x / y) * y == x, so the remainder is 0 most of the time. + fn rem_in_range(x in any::(), y in nonzero_felt252()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let result = x.clone() % y.clone(); + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + let result = x % &y; + let as_uint = &result.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property based test that ensures, for 100 Felt252s {x} generated at + // random each time tests are run, that converting them into the u64 type + // returns a result that is inside of the range [0, p]. + fn from_u64_and_to_u64_primitive(x in any::()) { + let x_felt:Felt252 = Felt252::from_u64(x).unwrap(); + let x_u64:u64 = Felt252::to_u64(&x_felt).unwrap(); + + prop_assert_eq!(x, x_u64); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn from_i64_and_to_i64_primitive(x in any::()) { + let x = x.checked_abs().unwrap_or(0); + let x_felt: Felt252 = Felt252::from_i64(x).unwrap(); + let x_i64: i64 = Felt252::to_i64(&x_felt).unwrap(); + prop_assert_eq!(x, x_i64); + } + + #[test] + // Property test to check that lcm(x, y) works. Since we're operating in a prime field, lcm + // will just be the smaller number. + fn lcm_doesnt_panic(x in any::(), y in any::()) { + let lcm = x.lcm(&y); + prop_assert!(lcm == cmp::max(x, y)); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Property test to check that is_multiple_of(x, y) works. Since we're operating in a prime field, is_multiple_of + // will always be true + fn is_multiple_of_doesnt_panic(x in any::(), y in any::()) { + prop_assert!(x.is_multiple_of(&y)); + } + + #[test] + fn divides_doesnt_panic(x in any::(), y in any::()) { + prop_assert!(x.divides(&y)); + } + + #[test] + fn gcd_doesnt_panic(x in any::(), y in any::()) { + let gcd1 = x.gcd(&y); + let gcd2 = y.gcd(&x); + prop_assert_eq!(gcd1, gcd2); + } + + #[test] + fn is_even(x in any::()) { + prop_assert_eq!(x.is_even(), x.to_biguint().is_even()); + } + + #[test] + fn is_odd(x in any::()) { + prop_assert_eq!(x.is_odd(), x.to_biguint().is_odd()); + } + + /// Tests the additive identity of the implementation of Zero trait for felts + /// + /// ```{.text} + /// x + 0 = x βˆ€ x + /// 0 + x = x βˆ€ x + /// ``` + #[test] + fn zero_additive_identity(ref x in any::()) { + let zero = Felt252::zero(); + prop_assert_eq!(x, &(x + &zero)); + prop_assert_eq!(x, &(&zero + x)); + } + + /// Tests the multiplicative identity of the implementation of One trait for felts + /// + /// ```{.text} + /// x * 1 = x βˆ€ x + /// 1 * x = x βˆ€ x + /// ``` + #[test] + fn one_multiplicative_identity(ref x in any::()) { + let one = Felt252::one(); + prop_assert_eq!(x, &(x * &one)); + prop_assert_eq!(x, &(&one * x)); + } + + #[test] + fn felt_is_always_positive(x in any::()) { + prop_assume!(!x.is_zero()); + prop_assert!(x.is_positive()) + } + + #[test] + fn felt_is_never_negative(x in any::()) { + prop_assert!(!x.is_negative()) + } + + #[test] + fn non_zero_felt_signum_is_always_one(ref x in nonzero_felt252()) { + let one = Felt252::one(); + prop_assert_eq!(x.signum(), one) + } + + #[test] + fn sub_abs(x in any::(), y in any::()) { + let expected_abs_sub = if x > y {&x - &y} else {&y - &x}; + + prop_assert_eq!(x.abs_sub(&y), expected_abs_sub) + } + + #[test] + fn abs(x in any::()) { + prop_assert_eq!(&x, &x.abs()) + } + + #[test] + fn sqrt_in_range(x in any::()) { + // we use x = x' * x' so x has a square root + let x = &x * &x; + let p = Felt252::prime(); + + let sqrt = x.sqrt().to_biguint(); + prop_assert!(sqrt < p, "{}", sqrt); + } + + #[test] + fn sqrt_is_inv_square(x in any::()) { + // we use x = x' * x' so x has a square root + let x = &x * &x; + let x_sq = &x * &x; + let sqrt = x_sq.sqrt(); + + if sqrt != x { + prop_assert_eq!(Felt252::max_value() - sqrt + 1_usize, x); + } else { + prop_assert_eq!(sqrt, x); + } + } + + #[test] + fn add_to_u64(x in any::(), ref felt in any::()) { + let sum = (felt + x).to_u64(); + prop_assert_eq!(x + felt, sum); + } + + #[test] + fn add_to_u64_extremes(x in any::()) { + let big_zero = &Felt252::zero(); + let big_max = &Felt252::max_value(); + let big_min = &(big_zero + (i64::MIN as usize)); + + let sum_max = (big_max + x).to_u64(); + prop_assert_eq!(x + big_max, sum_max); + let sum_min = (big_min + x).to_u64(); + prop_assert_eq!(x + big_min, sum_min); + let sum_zero = (big_zero + x).to_u64(); + prop_assert_eq!(x + big_zero, sum_zero); + } + + #[test] + fn add_u32_in_range(x in any::(), y in any::()) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let x_add_y = (x + y).to_biguint(); + prop_assert!(x_add_y < p, "{}", x_add_y); + } + + #[test] + fn add_u32_is_inv_sub(x in any::(), y in any::()) { + let expected_y = (x.clone() + y - x).to_u32().unwrap(); + prop_assert_eq!(expected_y, y, "{}", expected_y); + } + + #[test] + fn sub_u32_in_range(x in any::(), y in any::()) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let x_sub_y = (x - y).to_biguint(); + prop_assert!(x_sub_y < p, "{}", x_sub_y); + } + + #[test] + fn sub_u32_is_inv_add(x in any::(), y in any::()) { + prop_assert_eq!(x.clone() - y + y, x) + } + + #[test] + fn sub_usize_in_range(x in any::(), y in any::()) { + let p = BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let x_sub_y = (x - y).to_biguint(); + prop_assert!(x_sub_y < p, "{}", x_sub_y); + } + + #[test] + fn sub_usize_is_inv_add(x in any::(), y in any::()) { + prop_assert_eq!(x.clone() - y + y, x) + } + + #[test] + fn add_in_range(x in any::(), y in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + let sub = x + y; + let as_uint = &sub.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + fn add_is_inv_sub(ref x in any::(), ref y in any::()) { + let expected_y = x + y - x; + prop_assert_eq!(&expected_y, y, "{}", y); + } + + #[test] + fn add_assign_in_range(mut x in any::(), y in any::()) { + let p = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + + x += y.clone(); + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + + // test reference variant + x += &y; + let as_uint = &x.to_biguint(); + prop_assert!(as_uint < p, "{}", as_uint); + } + + #[test] + fn felt_to_str(x in any::(), radix in 2_u32..37) { + let str_x = x.to_str_radix(radix); + let int_x = x.to_biguint(); + let expected = int_x.to_str_radix(radix); + prop_assert_eq!(str_x, expected); + } + + #[test] + fn bigint_from_felt(x in any::()) { + prop_assert_eq!(BigInt::from(x.clone()), x.to_bigint()); + } + + #[test] + fn to_signed_felt_is_negative(x in any::()) { + let int = BigInt::from(x); + let felt = Felt252::from(x); + prop_assert_eq!(felt.to_signed_felt(), int); + } + } + + #[rstest] + fn add_to_u64_edge_cases( + #[values(0, 1, u64::MAX)] x: u64, + #[values(-2, -1, 0, 1, 1i128.neg(), i64::MIN as i128, u64::MAX as i128, u64::MAX as i128 + 1, (u64::MAX as i128).neg())] + y: i128, + ) { + let y = Felt252::from(y); + assert_eq!(x + &y, (&y + x).to_u64()); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of adding two zeroes is zero + fn sum_zeros_in_range() { + let x = Felt252::zero(); + let y = Felt252::zero(); + let z = Felt252::zero(); + assert_eq!(x + y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of multiplying two zeroes is zero + fn mul_zeros_in_range() { + let x = Felt252::zero(); + let y = Felt252::zero(); + let z = Felt252::zero(); + assert_eq!(x * y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of performing a bit and operation between zeroes is zero + fn bit_and_zeros_in_range() { + let x = Felt252::zero(); + let y = Felt252::zero(); + let z = Felt252::zero(); + assert_eq!(&x & &y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of perfforming a bit or operation between zeroes is zero + fn bit_or_zeros_in_range() { + let x = Felt252::zero(); + let y = Felt252::zero(); + let z = Felt252::zero(); + assert_eq!(&x | &y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Checks that the result of perfforming a bit xor operation between zeroes is zero + fn bit_xor_zeros_in_range() { + let x = Felt252::zero(); + let y = Felt252::zero(); + let z = Felt252::zero(); + assert_eq!(&x ^ &y, z) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Tests that the maximum value a Felt252 can take is equal to (prime - 1) + fn upper_bound() { + let prime = &BigUint::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(); + let unit = BigUint::one(); + let felt_max_value = Felt252::max_value().to_biguint(); + assert_eq!(prime - unit, felt_max_value) + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // Tests that the minimum value a Felt252 can take is equal to zero. + fn lower_bound() { + let zero = BigUint::zero(); + let felt_min_value = Felt252::min_value().to_biguint(); + assert_eq!(zero, felt_min_value) + } + + #[test] + fn zero_value() { + let zero = BigUint::zero(); + let felt_zero = Felt252::zero().to_biguint(); + assert_eq!(zero, felt_zero) + } + + #[test] + fn is_zero() { + let felt_zero = Felt252::zero(); + let felt_non_zero = Felt252::new(3_u32); + assert!(felt_zero.is_zero()); + assert!(!felt_non_zero.is_zero()) + } + + #[test] + fn one_value() { + let one = BigUint::one(); + let felt_one = Felt252::one().to_biguint(); + assert_eq!(one, felt_one) + } + + #[test] + fn is_one() { + let felt_one = Felt252::one(); + let felt_non_one = Felt252::new(8_u32); + assert!(felt_one.is_one()); + assert!(!felt_non_one.is_one()) + } + + #[test] + fn signum_of_zero_is_zero() { + let zero = Felt252::zero(); + assert_eq!(&zero.signum(), &zero) + } + + #[test] + fn felt_from_str_radix_failed() { + let x = Felt252::from_str_radix("abcdefghijk", 16); + assert!(x.is_err()); + let res = x.unwrap_err().to_string(); + let expected = "ParseFeltError"; + assert_eq!(res, expected) + } + + #[test] + fn default_is_zero() { + assert_eq!(Felt252::default(), Felt252::zero()) + } +} diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 37ea8ccc2d..46981f4d46 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -21,7 +21,9 @@ std = [ "dep:num-prime", ] cairo-1-hints = ["dep:cairo-lang-starknet", "dep:cairo-lang-casm", "dep:ark-ff", "dep:ark-std"] - +lambdaworks-felt = [ + "felt/lambdaworks-felt" +] # Note that these features are not retro-compatible with the cairo Python VM. test_utils = [ diff --git a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index c9ca674f7a..4d639e03e3 100644 --- a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -54,10 +54,8 @@ use crate::{ unsafe_keccak_finalize, }, math_utils::*, - memcpy_hint_utils::{ - add_segment, enter_scope, exit_scope, memcpy_continue_copying, memcpy_enter_scope, - }, - memset_utils::{memset_continue_loop, memset_enter_scope}, + memcpy_hint_utils::{add_segment, enter_scope, exit_scope, memcpy_enter_scope}, + memset_utils::{memset_enter_scope, memset_step_loop}, poseidon_utils::{n_greater_than_10, n_greater_than_2}, pow_utils::pow, secp::{ @@ -241,15 +239,20 @@ impl HintProcessorLogic for BuiltinHintProcessor { hint_code::MEMSET_ENTER_SCOPE => { memset_enter_scope(vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking) } - hint_code::MEMCPY_CONTINUE_COPYING => memcpy_continue_copying( + hint_code::MEMCPY_CONTINUE_COPYING => memset_step_loop( vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking, + "continue_copying", + ), + hint_code::MEMSET_CONTINUE_LOOP => memset_step_loop( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + "continue_loop", ), - hint_code::MEMSET_CONTINUE_LOOP => { - memset_continue_loop(vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking) - } hint_code::SPLIT_FELT => split_felt(vm, &hint_data.ids_data, &hint_data.ap_tracking), hint_code::UNSIGNED_DIV_REM => { unsigned_div_rem(vm, &hint_data.ids_data, &hint_data.ap_tracking) diff --git a/vm/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs b/vm/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs index 94317ec8ad..a73a85c8b4 100644 --- a/vm/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs +++ b/vm/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs @@ -56,8 +56,8 @@ pub fn keccak_write_args( let low = low.as_ref(); let high = high.as_ref(); - let low_args = [low & Felt252::new(u64::MAX), low >> 64]; - let high_args = [high & Felt252::new(u64::MAX), high >> 64]; + let low_args = [low & Felt252::new(u64::MAX), low >> 64_u32]; + let high_args = [high & Felt252::new(u64::MAX), high >> 64_u32]; let low_args: Vec<_> = low_args.into_iter().map(MaybeRelocatable::from).collect(); vm.write_arg(inputs_ptr, &low_args) diff --git a/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs b/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs index a36cc82da3..5ea381e0f9 100644 --- a/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/ec_utils.rs @@ -65,7 +65,7 @@ pub fn random_ec_point_hint( let m = get_integer_from_var_name("m", vm, ids_data, ap_tracking)?; let bytes: Vec = [p.x, p.y, m, q.x, q.y] .iter() - .flat_map(|x| to_padded_bytes(x)) + .flat_map(|x| x.to_be_bytes()) .collect(); let (x, y) = random_ec_point_seeded(bytes)?; let s_addr = get_relocatable_from_var_name("s", vm, ids_data, ap_tracking)?; @@ -121,7 +121,7 @@ pub fn chained_ec_op_random_ec_point_hint( .iter() .chain(m_range.iter()) .chain(q_range.iter()) - .flat_map(|x| to_padded_bytes(x)) + .flat_map(|x| x.to_be_bytes()) .collect(); let (x, y) = random_ec_point_seeded(bytes)?; let s_addr = get_relocatable_from_var_name("s", vm, ids_data, ap_tracking)?; @@ -152,14 +152,6 @@ pub fn recover_y_hint( Ok(()) } -// Returns the Felt252 as a vec of bytes of len 32, pads left with zeros -fn to_padded_bytes(n: &Felt252) -> Vec { - let felt_to_bytes = n.to_bytes_be(); - let mut bytes: Vec = vec![0; 32 - felt_to_bytes.len()]; - bytes.extend(felt_to_bytes); - bytes -} - // Returns a random non-zero point on the elliptic curve // y^2 = x^3 + alpha * x + beta (mod field_prime). // The point is created deterministically from the seed. diff --git a/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs b/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs index e0aa586ee4..36c4b63679 100644 --- a/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/keccak_utils.rs @@ -1,4 +1,4 @@ -use crate::stdlib::{boxed::Box, cmp, collections::HashMap, ops::Shl, prelude::*}; +use crate::stdlib::{boxed::Box, cmp, collections::HashMap, prelude::*}; use crate::types::errors::math_errors::MathError; use crate::{ @@ -82,17 +82,12 @@ pub fn unsafe_keccak( let word = vm.get_integer(word_addr)?; let n_bytes = cmp::min(16, u64_length - byte_i); - if word.is_negative() || word.as_ref() >= &Felt252::one().shl(8 * (n_bytes as u32)) { + if word.is_negative() || word.as_ref() >= &(Felt252::one() << (8 * (n_bytes as u32))) { return Err(HintError::InvalidWordSize(Box::new(word.into_owned()))); } - let mut bytes = word.to_bytes_be(); - let mut bytes = { - let n_word_bytes = &bytes.len(); - left_pad(&mut bytes, n_bytes as usize - n_word_bytes) - }; - - keccak_input.append(&mut bytes); + let start = 32 - n_bytes as usize; + keccak_input.extend_from_slice(&word.to_be_bytes()[start..]); } let mut hasher = Keccak256::new(); @@ -157,12 +152,7 @@ pub fn unsafe_keccak_finalize( let range = vm.get_integer_range(start_ptr, n_elems)?; for word in range.into_iter() { - let mut bytes = word.to_bytes_be(); - let mut bytes = { - let n_word_bytes = &bytes.len(); - left_pad(&mut bytes, 16 - n_word_bytes) - }; - keccak_input.append(&mut bytes); + keccak_input.extend_from_slice(&word.to_be_bytes()[16..]); } let mut hasher = Keccak256::new(); @@ -181,13 +171,6 @@ pub fn unsafe_keccak_finalize( Ok(()) } -fn left_pad(bytes_vector: &mut [u8], n_zeros: usize) -> Vec { - let mut res: Vec = vec![0; n_zeros]; - res.extend(bytes_vector.iter()); - - res -} - // Implements hints of type : ids.output{num}_low = ids.output{num} & ((1 << 128) - 1) // ids.output{num}_high = ids.output{num} >> 128 pub fn split_output( @@ -200,7 +183,7 @@ pub fn split_output( let output_cow = get_integer_from_var_name(&output_name, vm, ids_data, ap_tracking)?; let output = output_cow.as_ref(); let low = output & Felt252::from(u128::MAX); - let high = output >> 128; + let high = output >> 128_u32; insert_value_from_var_name( &format!("output{}_high", num), high, @@ -286,8 +269,8 @@ pub fn split_output_mid_low_high( let binding = get_integer_from_var_name("output1", vm, ids_data, ap_tracking)?; let output1 = binding.as_ref(); let output1_low = output1 & Felt252::from((1u64 << (8 * 7)) - 1u64); - let tmp = output1 >> (8 * 7); - let output1_high = &tmp >> 128; + let tmp = output1 >> (8_u32 * 7); + let output1_high = &tmp >> 128_u32; let output1_mid = tmp & &Felt252::from(u128::MAX); insert_value_from_var_name("output1_high", output1_high, vm, ids_data, ap_tracking)?; insert_value_from_var_name("output1_mid", output1_mid, vm, ids_data, ap_tracking)?; diff --git a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs index be27605888..086374df13 100644 --- a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs @@ -30,7 +30,7 @@ use crate::{ }, }; use felt::Felt252; -use num_bigint::BigUint; +use num_bigint::{BigUint, Sign}; use num_integer::Integer; use num_traits::One; use num_traits::{Signed, Zero}; @@ -115,43 +115,45 @@ pub fn assert_le_felt( let prime_over_2_high = constants .get(PRIME_OVER_2_HIGH) .ok_or_else(|| HintError::MissingConstant(Box::new(PRIME_OVER_2_HIGH)))?; - let a = &get_integer_from_var_name("a", vm, ids_data, ap_tracking)? - .clone() - .into_owned(); - let b = &get_integer_from_var_name("b", vm, ids_data, ap_tracking)? - .clone() - .into_owned(); + let a = get_integer_from_var_name("a", vm, ids_data, ap_tracking)?.to_biguint(); + let b = get_integer_from_var_name("b", vm, ids_data, ap_tracking)?.to_biguint(); let range_check_ptr = get_ptr_from_var_name("range_check_ptr", vm, ids_data, ap_tracking)?; + // TODO: use UnsignedInteger for this + let prime_div2 = prime_div_constant(2)?; + let prime_div3 = prime_div_constant(3)?; + if a > b { - return Err(HintError::NonLeFelt252(Box::new((a.clone(), b.clone())))); + return Err(HintError::NonLeFelt252(Box::new(( + Felt252::from(a), + Felt252::from(b), + )))); } - let arc1 = b - a; - let arc2 = Felt252::zero() - Felt252::one() - b; - let mut lengths_and_indices = vec![(a, 0_i32), (&arc1, 1_i32), (&arc2, 2_i32)]; + let arc1 = &b - &a; + let arc2 = Felt252::prime() - 1_u32 - &b; + let mut lengths_and_indices = [(&a, 0_i32), (&arc1, 1_i32), (&arc2, 2_i32)]; lengths_and_indices.sort(); - if lengths_and_indices[0].0 > &div_prime_by_bound(Felt252::new(3_i32))? - || lengths_and_indices[1].0 > &div_prime_by_bound(Felt252::new(2_i32))? - { + // TODO: I believe this check can be removed + if lengths_and_indices[0].0 > &prime_div3 || lengths_and_indices[1].0 > &prime_div2 { return Err(HintError::ArcTooBig(Box::new(( - lengths_and_indices[0].0.clone(), - div_prime_by_bound(Felt252::new(3_i32))?, - lengths_and_indices[1].0.clone(), - div_prime_by_bound(Felt252::new(3_i32))?, + Felt252::from(lengths_and_indices[0].0.clone()), + Felt252::from(prime_div2), + Felt252::from(lengths_and_indices[1].0.clone()), + Felt252::from(prime_div3), )))); } let excluded = lengths_and_indices[2].1; exec_scopes.assign_or_update_variable("excluded", any_box!(Felt252::new(excluded))); - let (q_0, r_0) = (lengths_and_indices[0].0).div_mod_floor(prime_over_3_high); - let (q_1, r_1) = (lengths_and_indices[1].0).div_mod_floor(prime_over_2_high); + let (q_0, r_0) = (lengths_and_indices[0].0).div_mod_floor(&prime_over_3_high.to_biguint()); + let (q_1, r_1) = (lengths_and_indices[1].0).div_mod_floor(&prime_over_2_high.to_biguint()); - vm.insert_value((range_check_ptr + 1_i32)?, q_0)?; - vm.insert_value(range_check_ptr, r_0)?; - vm.insert_value((range_check_ptr + 3_i32)?, q_1)?; - vm.insert_value((range_check_ptr + 2_i32)?, r_1)?; + vm.insert_value(range_check_ptr, Felt252::from(r_0))?; + vm.insert_value((range_check_ptr + 1_i32)?, Felt252::from(q_0))?; + vm.insert_value((range_check_ptr + 2_i32)?, Felt252::from(r_1))?; + vm.insert_value((range_check_ptr + 3_i32)?, Felt252::from(q_1))?; Ok(()) } @@ -380,9 +382,12 @@ pub fn is_positive( let value = get_integer_from_var_name("value", vm, ids_data, ap_tracking)?; let value_as_int = value.to_signed_felt(); let range_check_builtin = vm.get_range_check_builtin()?; + + // Avoid using abs so we don't allocate a new BigInt + let (sign, abs_value) = value_as_int.into_parts(); //Main logic (assert a is positive) match &range_check_builtin._bound { - Some(bound) if value_as_int.abs() > bound.to_bigint() => { + Some(bound) if abs_value > bound.to_biguint() => { return Err(HintError::ValueOutsideValidRange(Box::new( value.into_owned(), ))) @@ -390,7 +395,7 @@ pub fn is_positive( _ => {} }; - let result = Felt252::from(value_as_int.is_positive() as u8); + let result = Felt252::from((sign == Sign::Plus) as u8); insert_value_from_var_name("is_positive", result, vm, ids_data, ap_tracking) } @@ -468,10 +473,10 @@ pub fn signed_div_rem( builtin_bound.clone(), )))); } - Some(builtin_bound) if bound.as_ref() > &builtin_bound.shr(1) => { + Some(builtin_bound) if bound.as_ref() > &(builtin_bound >> 1_u32) => { return Err(HintError::OutOfValidRange(Box::new(( bound.into_owned(), - builtin_bound.shr(1), + builtin_bound >> 1_u32, )))); } None if div.is_zero() => { @@ -682,7 +687,7 @@ pub fn is_quad_residue( if x.is_zero() || x.is_one() { insert_value_from_var_name("y", x.as_ref().clone(), vm, ids_data, ap_tracking) - } else if Pow::pow(x.as_ref(), &(Felt252::max_value() >> 1)).is_one() { + } else if Pow::pow(x.as_ref(), &(Felt252::max_value() >> 1_u32)).is_one() { insert_value_from_var_name("y", &x.sqrt(), vm, ids_data, ap_tracking) } else { insert_value_from_var_name( @@ -702,6 +707,12 @@ fn div_prime_by_bound(bound: Felt252) -> Result { Ok(Felt252::new(limit)) } +fn prime_div_constant(bound: u32) -> Result { + let prime: &BigUint = &CAIRO_PRIME; + let limit = prime / bound; + Ok(limit) +} + /* Implements hint: %{ ids.a_lsb = ids.a & 1 @@ -1800,6 +1811,29 @@ mod tests { ) } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn signed_div_rem_out_of_range_bound() { + let hint_code = "from starkware.cairo.common.math_utils import as_int, assert_integer\n\nassert_integer(ids.div)\nassert 0 < ids.div <= PRIME // range_check_builtin.bound, \\\n f'div={hex(ids.div)} is out of the valid range.'\n\nassert_integer(ids.bound)\nassert ids.bound <= range_check_builtin.bound // 2, \\\n f'bound={hex(ids.bound)} is out of the valid range.'\n\nint_value = as_int(ids.value, PRIME)\nq, ids.r = divmod(int_value, ids.div)\n\nassert -ids.bound <= q < ids.bound, \\\n f'{int_value} / {ids.div} = {q} is out of the range [{-ids.bound}, {ids.bound}).'\n\nids.biased_q = q + ids.bound"; + let mut vm = vm_with_range_check!(); + //Initialize fp + vm.run_context.fp = 6; + //Insert ids into memory + let bound = vm.get_range_check_builtin().unwrap()._bound.clone(); + vm.segments = segments![((1, 3), (5)), ((1, 4), 10)]; + vm.insert_value((1, 5).into(), bound.clone().unwrap()) + .unwrap(); + //Create ids + let ids_data = ids_data!["r", "biased_q", "range_check_ptr", "div", "value", "bound"]; + //Execute the hint + let builtin_bound = felt_str!("340282366920938463463374607431768211456"); + assert_matches!( + run_hint!(vm, ids_data, hint_code), + Err(HintError::OutOfValidRange(bx)) + if *bx == (bound.unwrap(), builtin_bound >> 1_u32) + ) + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn signed_div_rem_no_range_check_builtin() { @@ -2328,7 +2362,7 @@ mod tests { if x.is_zero() || x.is_one() { assert_eq!(vm.get_integer(Relocatable::from((1, 0))).unwrap().as_ref(), x); - } else if x.pow(&(Felt252::max_value() >> 1)).is_one() { + } else if x.pow(&(Felt252::max_value() >> 1_u32)).is_one() { assert_eq!(vm.get_integer(Relocatable::from((1, 0))).unwrap().into_owned(), x.sqrt()); } else { assert_eq!(vm.get_integer(Relocatable::from((1, 0))).unwrap().into_owned(), (x / Felt252::new(3)).sqrt()); diff --git a/vm/src/hint_processor/builtin_hint_processor/memcpy_hint_utils.rs b/vm/src/hint_processor/builtin_hint_processor/memcpy_hint_utils.rs index 83127d7412..150b712242 100644 --- a/vm/src/hint_processor/builtin_hint_processor/memcpy_hint_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/memcpy_hint_utils.rs @@ -2,17 +2,13 @@ use crate::stdlib::{any::Any, collections::HashMap, prelude::*}; use crate::{ hint_processor::{ - builtin_hint_processor::hint_utils::{ - get_integer_from_var_name, insert_value_from_var_name, insert_value_into_ap, - }, + builtin_hint_processor::hint_utils::{get_integer_from_var_name, insert_value_into_ap}, hint_processor_definition::HintReference, }, serde::deserialize_program::ApTracking, types::exec_scope::ExecutionScopes, vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, }; -use felt::Felt252; -use num_traits::{One, Zero}; //Implements hint: memory[ap] = segments.add() pub fn add_segment(vm: &mut VirtualMachine) -> Result<(), HintError> { @@ -46,38 +42,6 @@ pub fn memcpy_enter_scope( Ok(()) } -// Implements hint: -// %{ -// n -= 1 -// ids.continue_copying = 1 if n > 0 else 0 -// %} -pub fn memcpy_continue_copying( - vm: &mut VirtualMachine, - exec_scopes: &mut ExecutionScopes, - ids_data: &HashMap, - ap_tracking: &ApTracking, -) -> Result<(), HintError> { - // get `n` variable from vm scope - let n = exec_scopes.get_ref::("n")?; - // this variable will hold the value of `n - 1` - let new_n = n - 1; - // if it is positive, insert 1 in the address of `continue_copying` - // else, insert 0 - if new_n.is_zero() { - insert_value_from_var_name("continue_copying", &new_n, vm, ids_data, ap_tracking)?; - } else { - insert_value_from_var_name( - "continue_copying", - Felt252::one(), - vm, - ids_data, - ap_tracking, - )?; - } - exec_scopes.insert_value("n", new_n); - Ok(()) -} - #[cfg(test)] mod tests { use super::*; @@ -85,6 +49,7 @@ mod tests { use crate::utils::test_utils::*; use assert_matches::assert_matches; + use felt::Felt252; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/vm/src/hint_processor/builtin_hint_processor/memset_utils.rs b/vm/src/hint_processor/builtin_hint_processor/memset_utils.rs index 3673a49d05..b5182522e9 100644 --- a/vm/src/hint_processor/builtin_hint_processor/memset_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/memset_utils.rs @@ -12,7 +12,7 @@ use crate::{ vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, }; use felt::Felt252; -use num_traits::Signed; +use num_traits::{One, Signed}; // Implements hint: // %{ vm_enter_scope({'n': ids.n}) %} @@ -31,26 +31,26 @@ pub fn memset_enter_scope( /* Implements hint: %{ n -= 1 - ids.continue_loop = 1 if n > 0 else 0 + ids.`i_name` = 1 if n > 0 else 0 %} */ -pub fn memset_continue_loop( +pub fn memset_step_loop( vm: &mut VirtualMachine, exec_scopes: &mut ExecutionScopes, ids_data: &HashMap, ap_tracking: &ApTracking, + i_name: &'static str, ) -> Result<(), HintError> { // get `n` variable from vm scope - let n = exec_scopes.get_ref::("n")?; + let n = exec_scopes.get_mut_ref::("n")?; // this variable will hold the value of `n - 1` - let new_n = n - 1; + *n -= Felt252::one(); // if `new_n` is positive, insert 1 in the address of `continue_loop` // else, insert 0 - let should_continue = Felt252::new(new_n.is_positive() as i32); - insert_value_from_var_name("continue_loop", should_continue, vm, ids_data, ap_tracking)?; + let flag = Felt252::new(n.is_positive()); + insert_value_from_var_name(i_name, flag, vm, ids_data, ap_tracking)?; // Reassign `n` with `n - 1` // we do it at the end of the function so that the borrow checker doesn't complain - exec_scopes.insert_value("n", new_n); Ok(()) } diff --git a/vm/src/hint_processor/builtin_hint_processor/uint256_utils.rs b/vm/src/hint_processor/builtin_hint_processor/uint256_utils.rs index f1a6f71eb1..3c4b9352cc 100644 --- a/vm/src/hint_processor/builtin_hint_processor/uint256_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/uint256_utils.rs @@ -95,7 +95,7 @@ impl<'a> From<&BigUint> for Uint256<'a> { impl<'a> From for Uint256<'a> { fn from(value: Felt252) -> Self { let low = Felt252::new(u128::MAX) & &value; - let high = value >> 128; + let high = value >> 128_u32; Self::from_values(low, high) } } @@ -231,11 +231,7 @@ pub fn split_64( let a = get_integer_from_var_name("a", vm, ids_data, ap_tracking)?; let mut digits = a.iter_u64_digits(); let low = Felt252::new(digits.next().unwrap_or(0u64)); - let high = if digits.len() <= 1 { - Felt252::new(digits.next().unwrap_or(0u64)) - } else { - a.as_ref().shr(64_u32) - }; + let high = a.as_ref() >> 64_u32; insert_value_from_var_name("high", high, vm, ids_data, ap_tracking)?; insert_value_from_var_name("low", low, vm, ids_data, ap_tracking) } diff --git a/vm/src/hint_processor/cairo_1_hint_processor/hint_processor_utils.rs b/vm/src/hint_processor/cairo_1_hint_processor/hint_processor_utils.rs index e0b89b1d56..185ceb69b1 100644 --- a/vm/src/hint_processor/cairo_1_hint_processor/hint_processor_utils.rs +++ b/vm/src/hint_processor/cairo_1_hint_processor/hint_processor_utils.rs @@ -4,6 +4,7 @@ use crate::vm::errors::{hint_errors::HintError, vm_errors::VirtualMachineError}; use crate::vm::vm_core::VirtualMachine; use cairo_lang_casm::operand::{CellRef, DerefOrImmediate, Operation, Register, ResOperand}; use felt::Felt252; +use num_traits::Zero; /// Extracts a parameter assumed to be a buffer. pub(crate) fn extract_buffer(buffer: &ResOperand) -> Result<(&CellRef, Felt252), HintError> { let (cell, base_offset) = match buffer { @@ -99,7 +100,7 @@ pub(crate) fn res_operand_get_val( pub(crate) fn as_cairo_short_string(value: &Felt252) -> Option { let mut as_string = String::default(); let mut is_end = false; - for byte in value.to_bytes_be() { + for byte in value.to_be_bytes().into_iter().skip_while(Zero::is_zero) { if byte == 0 { is_end = true; } else if is_end || !byte.is_ascii() { @@ -110,3 +111,17 @@ pub(crate) fn as_cairo_short_string(value: &Felt252) -> Option { } Some(as_string) } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn simple_as_cairo_short_string() { + // Values extracted from cairo book example + let s = "Hello, Scarb!"; + let x = Felt252::new(5735816763073854913753904210465_u128); + assert!(s.is_ascii()); + let cairo_string = as_cairo_short_string(&x).expect("call to as_cairo_short_string failed"); + assert_eq!(cairo_string, s); + } +} diff --git a/vm/src/types/program.rs b/vm/src/types/program.rs index 321983dc4d..b353acdc97 100644 --- a/vm/src/types/program.rs +++ b/vm/src/types/program.rs @@ -185,7 +185,7 @@ impl TryFrom for Program { let data = value .bytecode .iter() - .map(|x| MaybeRelocatable::from(Felt252::from(&x.value))) + .map(|x| MaybeRelocatable::from(Felt252::from(x.value.clone()))) .collect(); //Hint data is going to be hosted processor-side, hints field will only store the pc where hints are located. // Only one pc will be stored, so the hint processor will be responsible for executing all hints for a given pc diff --git a/vm/src/vm/runners/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index b20b060088..6188a3b05d 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -96,7 +96,7 @@ impl RangeCheckBuiltinRunner { } else { Err(MemoryError::RangeCheckNumOutOfBounds(Box::new(( num.into_owned(), - Felt252::one().shl((N_PARTS * INNER_RC_BOUND_SHIFT) as u32), + Felt252::one() << ((N_PARTS * INNER_RC_BOUND_SHIFT) as u32), )))) } },