├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── 01_BUG_REPORT.md │ ├── 02_FEATURE_REQUEST.md │ ├── 03_CODEBASE_IMPROVEMENT.md │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── crates └── starknet-types-core │ ├── Cargo.toml │ ├── Makefile │ ├── README.md │ ├── benches │ └── criterion_hashes.rs │ └── src │ ├── curve │ ├── affine_point.rs │ ├── curve_errors.rs │ ├── mod.rs │ └── projective_point.rs │ ├── felt │ ├── alloc_impls.rs │ ├── arbitrary.rs │ ├── felt_arbitrary.rs │ ├── mod.rs │ ├── non_zero.rs │ ├── num_traits_impl.rs │ ├── papyrus_serialization.rs │ ├── parity_scale_codec.rs │ ├── prime_bigint.rs │ ├── primitive_conversions.rs │ ├── secret_felt.rs │ └── serde.rs │ ├── hash │ ├── blake2s.rs │ ├── mod.rs │ ├── pedersen.rs │ ├── poseidon.rs │ └── traits.rs │ ├── lib.rs │ ├── qm31 │ ├── mod.rs │ └── num_traits_impl.rs │ ├── short_string │ └── mod.rs │ └── u256 │ ├── mod.rs │ ├── num_traits_impl.rs │ ├── primitive_conversions.rs │ └── tests │ ├── from_dec_str.rs │ ├── from_hex_str.rs │ └── mod.rs ├── ensure_no_std ├── .cargo │ └── config.toml ├── Cargo.toml ├── rust-toolchain.toml └── src │ └── main.rs ├── fuzz ├── Cargo.toml ├── Makefile ├── README.md └── felt │ ├── Cargo.toml │ ├── README.md │ └── fuzz_targets │ ├── add_sub.rs │ ├── conversions.rs │ ├── div.rs │ └── mul.rs └── rust-toolchain.toml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @pefontana @dan-starkware @gabrielbosio 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help types-rs to improve 4 | title: "bug: " 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | # Bug Report 10 | 11 | **types-rs version:** 12 | 13 | 14 | 15 | **Current behavior:** 16 | 17 | 18 | 19 | **Expected behavior:** 20 | 21 | 22 | 23 | **Steps to reproduce:** 24 | 25 | 26 | 27 | **Related code:** 28 | 29 | 30 | 31 | ``` 32 | insert short code snippets here 33 | ``` 34 | 35 | **Other information:** 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "feat: " 5 | labels: "enhancement" 6 | assignees: "" 7 | --- 8 | 9 | # Feature Request 10 | 11 | **Describe the Feature Request** 12 | 13 | 14 | 15 | **Describe Preferred Solution** 16 | 17 | 18 | 19 | **Describe Alternatives** 20 | 21 | 22 | 23 | **Related Code** 24 | 25 | 26 | 27 | **Additional Context** 28 | 29 | 30 | 31 | **If the feature request is approved, would you be willing to submit a PR?** 32 | _(Help can be provided if you need assistance submitting a PR)_ 33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03_CODEBASE_IMPROVEMENT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Codebase improvement 3 | about: 4 | Provide your feedback for the existing codebase. Suggest a better solution for 5 | algorithms, development tools, etc. 6 | title: "dev: " 7 | labels: "enhancement" 8 | assignees: "" 9 | --- 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: types-rs Community Support 5 | url: https://github.com/starknet-io/types-rs/discussions 6 | about: Please ask and answer questions here. 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Pull Request type 4 | 5 | 6 | 7 | Please add the labels corresponding to the type of changes your PR introduces: 8 | 9 | - Bugfix 10 | - Feature 11 | - Code style update (formatting, renaming) 12 | - Refactoring (no functional changes, no API changes) 13 | - Build-related changes 14 | - Documentation content changes 15 | - Testing 16 | - Other (please describe): 17 | 18 | ## What is the current behavior? 19 | 20 | 21 | 22 | Resolves: #NA 23 | 24 | ## What is the new behavior? 25 | 26 | 27 | 28 | - 29 | - 30 | - 31 | 32 | ## Does this introduce a breaking change? 33 | 34 | 35 | 36 | 37 | ## Other information 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - "main" 5 | pull_request: 6 | 7 | name: "Build feature combinations" 8 | 9 | jobs: 10 | feature-powerset: 11 | name: "Build ${{ matrix.package }} features" 12 | runs-on: "ubuntu-latest" 13 | 14 | strategy: 15 | matrix: 16 | package: 17 | - "starknet-types-core" 18 | 19 | steps: 20 | - uses: actions/checkout@v5 21 | - name: Install toolchain 22 | run: rustup show 23 | 24 | - name: "Install cargo-hack" 25 | run: | 26 | cargo install --locked cargo-hack 27 | 28 | - name: "Build all feature combinations" 29 | run: | 30 | cargo hack build --package ${{ matrix.package }} --feature-powerset 31 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # just for manual publishing 2 | on: workflow_dispatch 3 | 4 | name: Publish 5 | 6 | jobs: 7 | publish: 8 | name: Publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v5 12 | - name: Install toolchain 13 | run: rustup show 14 | 15 | - run: cargo publish --token "$CARGO_REGISTRY_TOKEN" -p starknet-types-core 16 | env: 17 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Test 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v5 11 | - name: Install toolchain 12 | run: rustup show 13 | 14 | - name: Run cargo fmt 15 | run: cargo fmt -- --check 16 | 17 | - name: Run cargo clippy 18 | run: cargo clippy --all-features -- -D warnings 19 | 20 | - name: Run cargo clippy alloc 21 | run: > 22 | cargo clippy --no-default-features --features alloc,serde -- -D warnings 23 | - name: Run cargo test 24 | run: cargo test --all-features 25 | 26 | - name: Run cargo test alloc 27 | run: cargo test --no-default-features --features alloc,serde 28 | 29 | no-std-build: 30 | name: Test no_std support 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v5 34 | - name: Check wasm compatibility 35 | run: |- 36 | cd ensure_no_std 37 | rustup show 38 | cargo build -r 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | .idea/* 17 | 18 | # Added by cargo 19 | 20 | /target 21 | .DS_Store 22 | 23 | corpus/ 24 | artifacts/ 25 | 26 | .idea 27 | *.DS_Store 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/starknet-types-core", 4 | ] 5 | 6 | exclude = ["ensure_no_std", "fuzz"] 7 | resolver = "2" 8 | 9 | [workspace.package] 10 | authors = [ 11 | "Pedro Fontana <@pefontana>", 12 | "Mario Rugiero <@Oppen>", 13 | "Lucas Levy <@LucasLvy>", 14 | "Shahar Papini <@spapinistarkware>", 15 | "Abdelhamid Bakhta <@abdelhamidbakhta>", 16 | "Dan Brownstein <@dan-starkware>", 17 | "Federico Carrone <@unbalancedparentheses>", 18 | "Jonathan Lei <@xJonathanLEI>", 19 | "Maciej Kamiński <@maciejka>", 20 | "Timothée Delabrouille <@tdelabro>" 21 | ] 22 | edition = "2021" 23 | repository = "https://github.com/starknet-io/types-rs" 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2025 Starknet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # types-rs 2 | 3 | 🐺 Starknet Rust types 🦀 4 | 5 | [![GitHub Workflow Status](https://github.com/starknet-io/types-rs/actions/workflows/test.yml/badge.svg)](https://github.com/starknet-io/types-rs/actions/workflows/test.yml) 6 | [![Project license](https://img.shields.io/github/license/starknet-io/types-rs.svg?style=flat-square)](LICENSE) 7 | [![Pull Requests welcome](https://img.shields.io/badge/PRs-welcome-ff69b4.svg?style=flat-square)](https://github.com/starknet-io/types-rs/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) 8 | [![Rust docs](https://docs.rs/starknet-types-core/badge.svg)](https://docs.rs/starknet-types-core) 9 | [![Rust crate](https://img.shields.io/crates/v/starknet-types-core.svg)](https://crates.io/crates/starknet-types-core) 10 | 11 | This repository is an initiative by a group of maintainers to address the fragmentation in the Starknet Rust ecosystem and improve its interoperability. The key motivation behind this repository is to standardize the representation of the `Felt` (Field Element) type in Rust, which is massively used as the core and atomic type of the Cairo language and its Virtual Machine. 12 | 13 | The `Felt` type is currently defined in many different crates and repositories, resulting in increased code complexity and performance issues due to the need for numerous conversions. This repository aims to mitigate these issues by providing a universal `Felt` type in Rust. 14 | 15 | ## Crates 16 | 17 | This repository hosts one crates: 18 | 19 | - `starknet-types-core`: This crate provides the universal Felt (Field Element) type for Cairo and STARK proofs. It also focuses on Starknet types related to computation and execution, requiring performance and optimization for specific arithmetic and cryptographic operations. 20 | 21 | ## Usage 22 | 23 | You can include any of these crates in your library via crates.io or as a git dependency. For example, to include the `starknet-types-core` crate, add the following to your `Cargo.toml`: 24 | 25 | As a crates.io dependency: 26 | 27 | ```toml 28 | [dependencies] 29 | starknet-types-core = "0.1.0" 30 | ``` 31 | 32 | As a git dependency: 33 | 34 | ```toml 35 | [dependencies] 36 | starknet-types-core = { git = "https://github.com/starknet-io/types-rs.git", version = "0.1.0", default-features = false, features = ["serde"] } 37 | ``` 38 | 39 | ## Build from source 40 | 41 | To build the crates from source, clone the repository and run the following command: 42 | 43 | ```bash 44 | cargo build --release 45 | ``` 46 | 47 | ## Testing 48 | 49 | To run the tests, clone the repository and run the following command: 50 | 51 | ```bash 52 | cargo test 53 | ``` 54 | 55 | ## Contributing 56 | 57 | Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) for more information. 58 | 59 | ## License 60 | 61 | This repository is licensed under the MIT License, see [LICENSE](LICENSE) for more information. 62 | -------------------------------------------------------------------------------- /crates/starknet-types-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "starknet-types-core" 3 | version = "0.4.0" 4 | edition = "2021" 5 | license = "MIT" 6 | homepage = "https://github.com/starknet-io/types-rs" 7 | repository = "https://github.com/starknet-io/types-rs" 8 | categories = ["mathematics", "cryptography"] 9 | keywords = ["stark", "zkp", "cairo"] 10 | description = "Core types representation for Starknet" 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | lambdaworks-math = { version = "0.13.0", default-features = false } 15 | num-traits = { version = "0.2", default-features = false } 16 | num-bigint = { version = "0.4", default-features = false } 17 | num-integer = { version = "0.1", default-features = false } 18 | 19 | # Optional 20 | arbitrary = { version = "1.3", optional = true } 21 | blake2 = { version = "0.10.6", default-features = false, optional = true } 22 | digest = { version = "0.10.7", optional = true } 23 | serde = { version = "1", optional = true, default-features = false, features = [ 24 | "alloc", "derive" 25 | ] } 26 | lambdaworks-crypto = { version = "0.13.0", default-features = false, optional = true } 27 | parity-scale-codec = { version = "3.6", default-features = false, optional = true } 28 | lazy_static = { version = "1.5", default-features = false, optional = true } 29 | zeroize = { version = "1.8.1", default-features = false, optional = true } 30 | subtle = { version = "2.6.1", default-features = false, optional = true } 31 | rand = { version = "0.9.2", default-features = false, optional = true } 32 | 33 | [features] 34 | default = ["std", "serde", "curve", "num-traits"] 35 | std = [ 36 | "alloc", 37 | "lambdaworks-math/std", 38 | "num-traits/std", 39 | "num-bigint/std", 40 | "num-integer/std", 41 | "serde?/std", 42 | "lambdaworks-crypto?/std", 43 | "zeroize?/std", 44 | "rand?/std" 45 | ] 46 | alloc = ["zeroize?/alloc"] 47 | curve = [] 48 | hash = ["dep:lambdaworks-crypto", "dep:blake2", "dep:digest"] 49 | arbitrary = ["std", "dep:arbitrary"] 50 | parity-scale-codec = ["dep:parity-scale-codec"] 51 | serde = ["alloc", "dep:serde"] 52 | prime-bigint = ["dep:lazy_static"] 53 | num-traits = [] 54 | papyrus-serialization = ["std"] 55 | secret-felt = ["alloc", "dep:zeroize", "dep:subtle", "subtle/const-generics", "subtle/core_hint_black_box", "dep:rand", "rand/alloc"] 56 | 57 | [dev-dependencies] 58 | proptest = { version = "1.5", default-features = false, features = [ 59 | "alloc", 60 | "proptest-macro", 61 | ] } 62 | regex = "1.11" 63 | serde_test = "1" 64 | criterion = "0.5" 65 | rand_chacha = "0.9" 66 | rand = "0.9.2" 67 | rstest = "0.24" 68 | lazy_static = { version = "1.5", default-features = false } 69 | bincode = "1" 70 | 71 | [[bench]] 72 | name = "criterion_hashes" 73 | harness = false 74 | -------------------------------------------------------------------------------- /crates/starknet-types-core/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build test bench 2 | 3 | build: 4 | cargo build --release 5 | 6 | test: 7 | cargo test 8 | 9 | bench: 10 | cargo bench --bench criterion_hashes --features hash 11 | -------------------------------------------------------------------------------- /crates/starknet-types-core/README.md: -------------------------------------------------------------------------------- 1 | # starknet-types-core 2 | 3 | Core types representation for Starknet. 4 | 5 | ## Overview 6 | 7 | The `starknet-types-core` crate provides: 8 | * The universal `Felt` (Field Element) type for Cairo and STARK proofs. It was created to reduce the fragmentation in the Starknet Rust ecosystem by providing a standardized representation of the `Felt` type. 9 | 10 | ## Features 11 | 12 | ### Always on 13 | - Standardized `Felt` type: Simplify your codebase by using our standardized `Felt` type. Optimized for performance: The `Felt` type has been optimized for high-performance applications. 14 | - No_std support ✅ 15 | 16 | ### Serde 17 | - Provides a Serialization and Deserialization implementations for the `Felt` type 18 | - No_std support ✅ 19 | 20 | ### Parity Scale Codec 21 | - Provides Serialization and Deserialization implementations for the `Felt` type within the Parity serialization framework 22 | - No_std support ✅ 23 | 24 | ### Arbitrary 25 | - Provides an Arbitrary implementations for the `Felt` type 26 | 27 | ### Curve 28 | - Add the `AffinePoint` and `ProjectivePoint` structs, which represent points on the Stark curve for performing elliptic curve operations. 29 | - No_std support ✅ 30 | 31 | ### Hash 32 | - Implements Pedersen hashing for Felts and Felts array 33 | 34 | ## Examples 35 | 36 | Here are some examples of how to use the `starknet-types-core` types: 37 | 38 | ```rust 39 | let felt = Felt::from(18); 40 | let projective_point = ProjectivePoint::new(Felt::from(0), Felt::from(1), Felt::from(0)); 41 | let affine_point = AffinePoint::new(Felt::from(0), Felt::from(1)).unwrap(); 42 | ``` 43 | 44 | ## Usage 45 | 46 | Include `starknet-types-core` in your library by adding the following to your `Cargo.toml`: 47 | 48 | ```toml 49 | [dependencies] 50 | starknet-types-core = { version = "0.0.3", git = "https://github.com/starknet-io/types-rs", default-features = false, features = [ 51 | "alloc", 52 | "serde", 53 | "arbitrary", 54 | "curve", 55 | "hash", 56 | ] } 57 | ``` 58 | 59 | ## Build from source 60 | 61 | Clone the repository and navigate to the starknet-types-core directory. Then run: 62 | 63 | ```bash 64 | cargo build --release 65 | ``` 66 | 67 | ## Testing 68 | 69 | Clone the repository and navigate to the starknet-types-core directory. Then run: 70 | 71 | ```bash 72 | cargo test 73 | ``` 74 | 75 | ## Contributing 76 | 77 | Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) for more information. 78 | 79 | ## License 80 | 81 | This repository is licensed under the MIT License, see [LICENSE](LICENSE) for more information. 82 | -------------------------------------------------------------------------------- /crates/starknet-types-core/benches/criterion_hashes.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; 2 | use rand::{Rng, RngCore, SeedableRng}; 3 | use rand_chacha::ChaCha8Rng; 4 | 5 | use starknet_types_core::felt::Felt; 6 | use starknet_types_core::hash::StarkHash; 7 | use starknet_types_core::hash::{Pedersen, Poseidon}; 8 | 9 | const SEED: u64 = 3; 10 | 11 | fn rnd_felt(rng: &mut ChaCha8Rng) -> Felt { 12 | let mut felt: [u8; 32] = Default::default(); 13 | let first_non_zero = rng.gen_range(0..32); 14 | rng.fill_bytes(&mut felt[first_non_zero..32]); 15 | Felt::from_bytes_be(&felt) 16 | } 17 | 18 | fn rnd_felts(rng: &mut ChaCha8Rng, n: usize) -> Vec { 19 | (0..n).map(|_| rnd_felt(rng)).collect() 20 | } 21 | 22 | fn rnd_pair_of_felts(rng: &mut ChaCha8Rng) -> (Felt, Felt) { 23 | (rnd_felt(rng), rnd_felt(rng)) 24 | } 25 | 26 | fn poseidon_hash(c: &mut Criterion) { 27 | let mut rng = ChaCha8Rng::seed_from_u64(SEED); 28 | let mut group = c.benchmark_group("Poseidon"); 29 | 30 | group.bench_function("hash", |bench| { 31 | bench.iter_batched( 32 | || rnd_pair_of_felts(&mut rng), 33 | |(x, y)| black_box(Poseidon::hash(&x, &y)), 34 | BatchSize::SmallInput, 35 | ); 36 | }); 37 | } 38 | 39 | fn poseidon_hash_array(c: &mut Criterion) { 40 | let mut rng = ChaCha8Rng::seed_from_u64(SEED); 41 | 42 | let mut group = c.benchmark_group("Poseidon"); 43 | 44 | for n in [10, 100, 1000, 10000].iter() { 45 | group.bench_with_input(BenchmarkId::new("hash_array", n), n, |bench, &n| { 46 | bench.iter_batched( 47 | || rnd_felts(&mut rng, n), 48 | |felts| black_box(Poseidon::hash_array(&felts)), 49 | BatchSize::SmallInput, 50 | ); 51 | }); 52 | } 53 | } 54 | 55 | fn pedersen_hash(c: &mut Criterion) { 56 | let mut rng = ChaCha8Rng::seed_from_u64(SEED); 57 | let mut group = c.benchmark_group("Pedersen"); 58 | 59 | group.bench_function("hash", |bench| { 60 | bench.iter_batched( 61 | || rnd_pair_of_felts(&mut rng), 62 | |(x, y)| black_box(Pedersen::hash(&x, &y)), 63 | BatchSize::SmallInput, 64 | ); 65 | }); 66 | } 67 | 68 | fn pedersen_hash_array(c: &mut Criterion) { 69 | let mut rng = ChaCha8Rng::seed_from_u64(SEED); 70 | 71 | let mut group = c.benchmark_group("Pedersen"); 72 | 73 | for n in [10, 100, 1000, 10000].iter() { 74 | group.bench_with_input(BenchmarkId::new("hash_array", n), n, |bench, &n| { 75 | bench.iter_batched( 76 | || rnd_felts(&mut rng, n), 77 | |felts| black_box(Pedersen::hash_array(&felts)), 78 | BatchSize::SmallInput, 79 | ); 80 | }); 81 | } 82 | } 83 | 84 | criterion_group!( 85 | hashes, 86 | pedersen_hash, 87 | pedersen_hash_array, 88 | poseidon_hash, 89 | poseidon_hash_array 90 | ); 91 | criterion_main!(hashes); 92 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/curve/affine_point.rs: -------------------------------------------------------------------------------- 1 | use crate::{curve::curve_errors::CurveError, felt::Felt}; 2 | use lambdaworks_math::{ 3 | cyclic_group::IsGroup, 4 | elliptic_curve::{ 5 | short_weierstrass::{ 6 | curves::stark_curve::StarkCurve, point::ShortWeierstrassProjectivePoint, 7 | traits::IsShortWeierstrass, 8 | }, 9 | traits::{FromAffine, IsEllipticCurve}, 10 | }, 11 | }; 12 | 13 | /// Represents a point on the Stark elliptic curve. 14 | /// Doc: https://docs.starkware.co/starkex/crypto/stark-curve.html 15 | #[repr(transparent)] 16 | #[derive(Debug, Clone, PartialEq, Eq)] 17 | pub struct AffinePoint(pub(crate) ShortWeierstrassProjectivePoint); 18 | 19 | impl AffinePoint { 20 | pub fn new(x: Felt, y: Felt) -> Result { 21 | Ok(Self(ShortWeierstrassProjectivePoint::from_affine( 22 | x.0, y.0, 23 | )?)) 24 | } 25 | 26 | /// Constructs a new affine point without checking whether the coordinates specify a point in the subgroup. 27 | /// This method should be used with caution, as it does not validate whether the provided coordinates 28 | /// correspond to a valid point on the curve. 29 | pub const fn new_unchecked(x: Felt, y: Felt) -> AffinePoint { 30 | Self(ShortWeierstrassProjectivePoint::new_unchecked([ 31 | x.0, 32 | y.0, 33 | Felt::ONE.0, 34 | ])) 35 | } 36 | 37 | /// Construct new affine point from the `x` coordinate and the parity bit `y_parity`. 38 | /// If `y_parity` is false, choose the y-coordinate with even parity. 39 | /// If `y_parity` is true, choose the y-coordinate with odd parity. 40 | pub fn new_from_x(x: &Felt, y_parity: bool) -> Option { 41 | // right hand side of the stark curve equation `y^2 = x^3 + α*x + β (mod p)`. 42 | let rhs = x * x * x + Felt(StarkCurve::a()) * x + Felt(StarkCurve::b()); 43 | 44 | let (root_1, root_2) = rhs.0.sqrt()?; 45 | 46 | let root_1_le_bits = root_1.to_bits_le(); 47 | let first_bit = root_1_le_bits.first()?; 48 | 49 | let y = if *first_bit == y_parity { 50 | root_1 51 | } else { 52 | root_2 53 | }; 54 | 55 | // the curve equation is already satisfied above and is safe to create a new unchecked point 56 | Some(Self::new_unchecked(*x, Felt(y))) 57 | } 58 | 59 | /// The point at infinity. 60 | pub fn identity() -> AffinePoint { 61 | Self(ShortWeierstrassProjectivePoint::neutral_element()) 62 | } 63 | 64 | pub fn is_identity(&self) -> bool { 65 | self == &Self::identity() 66 | } 67 | 68 | /// Returns the `x` coordinate of the point. 69 | pub fn x(&self) -> Felt { 70 | Felt(*self.0.to_affine().x()) 71 | } 72 | 73 | /// Returns the `y` coordinate of the point. 74 | pub fn y(&self) -> Felt { 75 | Felt(*self.0.to_affine().y()) 76 | } 77 | 78 | // Returns the generator point of the StarkCurve 79 | pub fn generator() -> Self { 80 | AffinePoint(StarkCurve::generator()) 81 | } 82 | } 83 | 84 | impl core::ops::Neg for &AffinePoint { 85 | type Output = AffinePoint; 86 | 87 | fn neg(self) -> AffinePoint { 88 | AffinePoint(self.0.neg()) 89 | } 90 | } 91 | 92 | impl core::ops::Add for AffinePoint { 93 | type Output = AffinePoint; 94 | 95 | fn add(self, rhs: Self) -> Self::Output { 96 | AffinePoint(self.0.operate_with(&rhs.0)) 97 | } 98 | } 99 | 100 | impl core::ops::Mul for &AffinePoint { 101 | type Output = AffinePoint; 102 | 103 | // Add the point (`self`) to itself for `scalar` many times 104 | fn mul(self, rhs: Felt) -> AffinePoint { 105 | AffinePoint(self.0.operate_with_self(rhs.0.representative())) 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod test { 111 | use super::*; 112 | use std::ops::Neg; 113 | 114 | #[test] 115 | fn affine_point_new_unchecked() { 116 | let a = AffinePoint::new( 117 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 118 | Felt::from_hex_unwrap( 119 | "0x11a2681208d7c128580162110d9e6ddb0bd34e42ace22dcc7f52f9939e11df6", 120 | ), 121 | ) 122 | .unwrap(); 123 | 124 | let b = AffinePoint::new_unchecked( 125 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 126 | Felt::from_hex_unwrap( 127 | "0x11a2681208d7c128580162110d9e6ddb0bd34e42ace22dcc7f52f9939e11df6", 128 | ), 129 | ); 130 | assert!(a.eq(&b)); 131 | } 132 | 133 | #[test] 134 | fn affine_point_identity() { 135 | let identity = AffinePoint::identity(); 136 | 137 | assert!(identity.is_identity()); 138 | assert_eq!(identity.x(), Felt::from(0)); 139 | assert_eq!(identity.y(), Felt::from(1)); 140 | 141 | let a = AffinePoint::new( 142 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 143 | Felt::from_hex_unwrap( 144 | "0x11a2681208d7c128580162110d9e6ddb0bd34e42ace22dcc7f52f9939e11df6", 145 | ), 146 | ); 147 | assert!(!a.unwrap().is_identity()); 148 | } 149 | 150 | #[test] 151 | fn affine_neg() { 152 | assert_eq!( 153 | -&AffinePoint::new( 154 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 155 | Felt::from_hex_unwrap( 156 | "0x11a2681208d7c128580162110d9e6ddb0bd34e42ace22dcc7f52f9939e11df6" 157 | ), 158 | ) 159 | .unwrap(), 160 | AffinePoint::new( 161 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 162 | Felt::from_hex_unwrap( 163 | "0x6e5d97edf7283fe7a7fe9deef2619224f42cb1bd531dd23380ad066c61ee20b" 164 | ), 165 | ) 166 | .unwrap() 167 | ); 168 | assert_eq!(-&AffinePoint::identity(), AffinePoint::identity()); 169 | } 170 | 171 | #[test] 172 | fn affine_add() { 173 | let p = AffinePoint::new( 174 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 175 | Felt::from_hex_unwrap( 176 | "0x6e5d97edf7283fe7a7fe9deef2619224f42cb1bd531dd23380ad066c61ee20b", 177 | ), 178 | ) 179 | .unwrap(); 180 | 181 | assert_eq!( 182 | p.clone() + p, 183 | AffinePoint::new( 184 | Felt::from_hex_unwrap( 185 | "0x23a1c9a32dd397fb1e7f758b9089757c1223057aea1d8b52cbec583ad74eaab", 186 | ), 187 | Felt::from_hex_unwrap( 188 | "0x466880caf4086bac129ae52ee98ddf75b2b394ae7c7ed1a19d9c61aa1f69f62", 189 | ), 190 | ) 191 | .unwrap() 192 | ); 193 | } 194 | 195 | #[test] 196 | fn affine_mul() { 197 | let p = AffinePoint::new( 198 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 199 | Felt::from_hex_unwrap( 200 | "0x6e5d97edf7283fe7a7fe9deef2619224f42cb1bd531dd23380ad066c61ee20b", 201 | ), 202 | ) 203 | .unwrap(); 204 | 205 | assert_eq!( 206 | &p * Felt::from(2), 207 | AffinePoint::new( 208 | Felt::from_hex_unwrap( 209 | "0x23a1c9a32dd397fb1e7f758b9089757c1223057aea1d8b52cbec583ad74eaab", 210 | ), 211 | Felt::from_hex_unwrap( 212 | "0x466880caf4086bac129ae52ee98ddf75b2b394ae7c7ed1a19d9c61aa1f69f62", 213 | ), 214 | ) 215 | .unwrap() 216 | ); 217 | } 218 | 219 | #[test] 220 | fn affine_new_from_x_odd() { 221 | let p = AffinePoint::new( 222 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 223 | Felt::from_hex_unwrap( 224 | "0x6e5d97edf7283fe7a7fe9deef2619224f42cb1bd531dd23380ad066c61ee20b", 225 | ), 226 | ) 227 | .unwrap(); 228 | 229 | assert_eq!(p, AffinePoint::new_from_x(&p.x(), true).unwrap()); 230 | } 231 | 232 | #[test] 233 | fn affine_new_from_x_even() { 234 | let p = AffinePoint::new( 235 | Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"), 236 | Felt::from_hex_unwrap( 237 | "0x6e5d97edf7283fe7a7fe9deef2619224f42cb1bd531dd23380ad066c61ee20b", 238 | ), 239 | ) 240 | .unwrap(); 241 | 242 | assert_eq!(p.neg(), AffinePoint::new_from_x(&p.x(), false).unwrap()); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/curve/curve_errors.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | use lambdaworks_math::elliptic_curve::traits::EllipticCurveError; 3 | 4 | #[derive(Debug, PartialEq, Eq)] 5 | pub enum CurveError { 6 | EllipticCurveError(EllipticCurveError), 7 | } 8 | 9 | impl From for CurveError { 10 | fn from(error: EllipticCurveError) -> CurveError { 11 | CurveError::EllipticCurveError(error) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/curve/mod.rs: -------------------------------------------------------------------------------- 1 | mod affine_point; 2 | mod curve_errors; 3 | mod projective_point; 4 | 5 | pub use self::affine_point::*; 6 | pub use self::curve_errors::*; 7 | pub use self::projective_point::*; 8 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/curve/projective_point.rs: -------------------------------------------------------------------------------- 1 | use crate::curve::affine_point::AffinePoint; 2 | use crate::curve::curve_errors::CurveError; 3 | use crate::felt::Felt; 4 | use core::ops; 5 | use lambdaworks_math::cyclic_group::IsGroup; 6 | use lambdaworks_math::elliptic_curve::short_weierstrass::curves::stark_curve::StarkCurve; 7 | use lambdaworks_math::elliptic_curve::short_weierstrass::point::ShortWeierstrassProjectivePoint; 8 | use lambdaworks_math::elliptic_curve::traits::EllipticCurveError::InvalidPoint; 9 | use lambdaworks_math::elliptic_curve::traits::FromAffine; 10 | use lambdaworks_math::unsigned_integer::traits::IsUnsignedInteger; 11 | 12 | /// Represents a projective point on the Stark elliptic curve. 13 | #[repr(transparent)] 14 | #[derive(Debug, Clone, PartialEq, Eq)] 15 | pub struct ProjectivePoint(pub(crate) ShortWeierstrassProjectivePoint); 16 | 17 | impl ProjectivePoint { 18 | pub fn new(x: Felt, y: Felt, z: Felt) -> Result { 19 | Ok(Self(ShortWeierstrassProjectivePoint::new([x.0, y.0, z.0])?)) 20 | } 21 | 22 | /// Creates a new short Weierstrass projective point, assuming the coordinates are valid. 23 | /// 24 | /// The coordinates are valid if the equation `y^2 * z = x^3 + a * x * z^2 + b * z^3` 25 | /// holds, Where `a` and `b` are the short Weierstrass coefficients of the Stark 26 | /// curve. Furthermore, the coordinates in the form `[0, _, 0]` all satisfy the 27 | /// equation, and should be always transformed to the infinity value `[0, 1, 0]`. 28 | /// 29 | /// SAFETY: Failing to guarantee this assumptions could lead to a runtime panic 30 | /// while operating with the value. 31 | pub const fn new_unchecked(x: Felt, y: Felt, z: Felt) -> ProjectivePoint { 32 | Self(ShortWeierstrassProjectivePoint::new_unchecked([ 33 | x.0, y.0, z.0, 34 | ])) 35 | } 36 | 37 | /// The point at infinity. 38 | pub fn identity() -> ProjectivePoint { 39 | Self(ShortWeierstrassProjectivePoint::neutral_element()) 40 | } 41 | 42 | pub fn is_identity(&self) -> bool { 43 | self == &Self::identity() 44 | } 45 | 46 | /// Creates the same point in affine coordinates. That is, 47 | /// returns (x * inv_z, y* inv_z, 1) 48 | /// where `self` is the point (x, y, z) and inv_z is the multiplicative inverse of z 49 | pub fn to_affine(&self) -> Result { 50 | if self.z() == Felt::ZERO { 51 | return Err(CurveError::EllipticCurveError(InvalidPoint)); 52 | } 53 | Ok(AffinePoint(self.0.to_affine())) 54 | } 55 | 56 | pub fn from_affine(x: Felt, y: Felt) -> Result { 57 | Ok(Self( 58 | ShortWeierstrassProjectivePoint::from_affine(x.0, y.0) 59 | .map_err(CurveError::EllipticCurveError)?, 60 | )) 61 | } 62 | 63 | /// Constructs a new projective point without checking whether the coordinates specify a point in the subgroup. 64 | /// This method should be used with caution, as it does not validate whether the provided coordinates 65 | /// correspond to a valid point on the curve. 66 | pub fn from_affine_unchecked(x: Felt, y: Felt) -> Self { 67 | Self(ShortWeierstrassProjectivePoint::new_unchecked([ 68 | x.0, 69 | y.0, 70 | Felt::ONE.0, 71 | ])) 72 | } 73 | 74 | /// Returns the `x` coordinate of the point. 75 | pub fn x(&self) -> Felt { 76 | Felt(*self.0.x()) 77 | } 78 | 79 | /// Returns the `y` coordinate of the point. 80 | pub fn y(&self) -> Felt { 81 | Felt(*self.0.y()) 82 | } 83 | 84 | /// Returns the `z` coordinate of the point. 85 | pub fn z(&self) -> Felt { 86 | Felt(*self.0.z()) 87 | } 88 | 89 | pub fn double(&self) -> Self { 90 | Self(self.0.double()) 91 | } 92 | } 93 | 94 | impl TryFrom for ProjectivePoint { 95 | type Error = CurveError; 96 | 97 | fn try_from(affine_point: AffinePoint) -> Result { 98 | Self::from_affine(affine_point.x(), affine_point.y()) 99 | } 100 | } 101 | 102 | impl ops::Add<&ProjectivePoint> for &ProjectivePoint { 103 | type Output = ProjectivePoint; 104 | 105 | fn add(self, rhs: &ProjectivePoint) -> ProjectivePoint { 106 | ProjectivePoint(self.0.operate_with(&rhs.0)) 107 | } 108 | } 109 | 110 | impl ops::AddAssign<&ProjectivePoint> for ProjectivePoint { 111 | fn add_assign(&mut self, rhs: &ProjectivePoint) { 112 | self.0 = self.0.operate_with(&rhs.0); 113 | } 114 | } 115 | 116 | impl ops::Add<&AffinePoint> for &ProjectivePoint { 117 | type Output = ProjectivePoint; 118 | 119 | fn add(self, rhs: &AffinePoint) -> ProjectivePoint { 120 | ProjectivePoint(self.0.operate_with_affine(&rhs.0)) 121 | } 122 | } 123 | 124 | impl ops::AddAssign<&AffinePoint> for ProjectivePoint { 125 | fn add_assign(&mut self, rhs: &AffinePoint) { 126 | self.0 = self.0.operate_with_affine(&rhs.0); 127 | } 128 | } 129 | 130 | impl ops::Add for ProjectivePoint { 131 | type Output = ProjectivePoint; 132 | 133 | fn add(self, rhs: AffinePoint) -> ProjectivePoint { 134 | ProjectivePoint(self.0.operate_with_affine(&rhs.0)) 135 | } 136 | } 137 | 138 | impl ops::AddAssign for ProjectivePoint { 139 | fn add_assign(&mut self, rhs: AffinePoint) { 140 | self.0 = self.0.operate_with_affine(&rhs.0); 141 | } 142 | } 143 | 144 | impl ops::Add for ProjectivePoint { 145 | type Output = ProjectivePoint; 146 | 147 | fn add(self, rhs: ProjectivePoint) -> ProjectivePoint { 148 | ProjectivePoint(self.0.operate_with(&rhs.0)) 149 | } 150 | } 151 | 152 | impl ops::AddAssign for ProjectivePoint { 153 | fn add_assign(&mut self, rhs: ProjectivePoint) { 154 | self.0 = self.0.operate_with(&rhs.0); 155 | } 156 | } 157 | 158 | impl core::ops::Neg for &ProjectivePoint { 159 | type Output = ProjectivePoint; 160 | 161 | fn neg(self) -> ProjectivePoint { 162 | ProjectivePoint(self.0.neg()) 163 | } 164 | } 165 | 166 | impl core::ops::Sub<&ProjectivePoint> for &ProjectivePoint { 167 | type Output = ProjectivePoint; 168 | 169 | fn sub(self, rhs: &ProjectivePoint) -> ProjectivePoint { 170 | self + &-rhs 171 | } 172 | } 173 | 174 | impl core::ops::SubAssign<&ProjectivePoint> for ProjectivePoint { 175 | fn sub_assign(&mut self, rhs: &ProjectivePoint) { 176 | *self += &-rhs; 177 | } 178 | } 179 | 180 | impl core::ops::Sub for ProjectivePoint { 181 | type Output = ProjectivePoint; 182 | 183 | fn sub(self, rhs: ProjectivePoint) -> ProjectivePoint { 184 | &self - &rhs 185 | } 186 | } 187 | 188 | impl core::ops::SubAssign for ProjectivePoint { 189 | fn sub_assign(&mut self, rhs: ProjectivePoint) { 190 | *self -= &rhs; 191 | } 192 | } 193 | 194 | impl ops::Mul for &ProjectivePoint { 195 | type Output = ProjectivePoint; 196 | 197 | fn mul(self, rhs: Felt) -> ProjectivePoint { 198 | ProjectivePoint(self.0.operate_with_self(rhs.0.representative())) 199 | } 200 | } 201 | 202 | impl ops::Mul for &ProjectivePoint 203 | where 204 | T: IsUnsignedInteger, 205 | { 206 | type Output = ProjectivePoint; 207 | 208 | fn mul(self, rhs: T) -> ProjectivePoint { 209 | ProjectivePoint(self.0.operate_with_self(rhs)) 210 | } 211 | } 212 | 213 | #[cfg(test)] 214 | mod test { 215 | use super::*; 216 | 217 | #[test] 218 | fn try_from_affine() { 219 | let projective_point = ProjectivePoint::new_unchecked( 220 | Felt::from_dec_str( 221 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 222 | ) 223 | .unwrap(), 224 | Felt::from_dec_str( 225 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 226 | ) 227 | .unwrap(), 228 | Felt::from(1), 229 | ); 230 | let affine_point = projective_point.clone().to_affine().unwrap(); 231 | 232 | assert_eq!( 233 | TryInto::::try_into(affine_point).unwrap(), 234 | projective_point 235 | ); 236 | } 237 | 238 | #[test] 239 | fn try_from_affine_invalid_point() { 240 | let affine_point = AffinePoint::new_unchecked( 241 | Felt::from_dec_str( 242 | "4739451078007766457464989774322083649278607533249481151382481072868806602", 243 | ) 244 | .unwrap(), 245 | Felt::from_dec_str( 246 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 247 | ) 248 | .unwrap(), 249 | ); 250 | 251 | assert!(matches!( 252 | TryInto::::try_into(affine_point), 253 | Err(CurveError::EllipticCurveError(_)) 254 | )); 255 | } 256 | 257 | #[test] 258 | fn from_affine_unchecked() { 259 | let a = AffinePoint::new( 260 | Felt::from_dec_str( 261 | "3324833730090626974525872402899302150520188025637965566623476530814354734325", 262 | ) 263 | .unwrap(), 264 | Felt::from_dec_str( 265 | "3147007486456030910661996439995670279305852583596209647900952752170983517249", 266 | ) 267 | .unwrap(), 268 | ) 269 | .unwrap(); 270 | 271 | assert_eq!( 272 | ProjectivePoint::from_affine_unchecked(a.x(), a.y()), 273 | ProjectivePoint::from_affine(a.x(), a.y()).unwrap() 274 | ); 275 | } 276 | 277 | #[test] 278 | fn projective_point_identity() { 279 | let identity = ProjectivePoint::identity(); 280 | assert!(identity.is_identity()); 281 | assert_eq!( 282 | identity, 283 | ProjectivePoint::new_unchecked(Felt::from(0), Felt::from(1), Felt::from(0)) 284 | ); 285 | 286 | assert_eq!( 287 | identity.to_affine(), 288 | Err(CurveError::EllipticCurveError(InvalidPoint)) 289 | ); 290 | 291 | let a = ProjectivePoint::new_unchecked( 292 | Felt::from_dec_str( 293 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 294 | ) 295 | .unwrap(), 296 | Felt::from_dec_str( 297 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 298 | ) 299 | .unwrap(), 300 | Felt::from(1), 301 | ); 302 | assert!(!a.is_identity()); 303 | } 304 | 305 | #[test] 306 | // Results checked against starknet-rs https://github.com/xJonathanLEI/starknet-rs/ 307 | fn add_operations() { 308 | let projective_point_1 = ProjectivePoint::new_unchecked( 309 | Felt::from_dec_str( 310 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 311 | ) 312 | .unwrap(), 313 | Felt::from_dec_str( 314 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 315 | ) 316 | .unwrap(), 317 | Felt::from(1), 318 | ); 319 | let projective_point_2 = projective_point_1.clone(); 320 | let result = (&projective_point_1 + &projective_point_2) 321 | .to_affine() 322 | .unwrap(); 323 | 324 | assert_eq!( 325 | result, 326 | AffinePoint::new( 327 | Felt::from_dec_str( 328 | "3324833730090626974525872402899302150520188025637965566623476530814354734325", 329 | ) 330 | .unwrap(), 331 | Felt::from_dec_str( 332 | "3147007486456030910661996439995670279305852583596209647900952752170983517249", 333 | ) 334 | .unwrap() 335 | ) 336 | .unwrap() 337 | ) 338 | } 339 | 340 | #[test] 341 | // Results checked against starknet-rs https://github.com/xJonathanLEI/starknet-rs/ 342 | fn add_operations_with_affine() { 343 | let projective_point = ProjectivePoint::new_unchecked( 344 | Felt::from_dec_str( 345 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 346 | ) 347 | .unwrap(), 348 | Felt::from_dec_str( 349 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 350 | ) 351 | .unwrap(), 352 | Felt::from(1), 353 | ); 354 | let affine_point = projective_point.clone().to_affine().unwrap(); 355 | let result = (&projective_point + &affine_point).to_affine().unwrap(); 356 | 357 | assert_eq!( 358 | result, 359 | AffinePoint::new( 360 | Felt::from_dec_str( 361 | "3324833730090626974525872402899302150520188025637965566623476530814354734325", 362 | ) 363 | .unwrap(), 364 | Felt::from_dec_str( 365 | "3147007486456030910661996439995670279305852583596209647900952752170983517249", 366 | ) 367 | .unwrap() 368 | ) 369 | .unwrap() 370 | ) 371 | } 372 | 373 | #[test] 374 | // Results checked against starknet-rs https://github.com/xJonathanLEI/starknet-rs/ 375 | fn add_operations_with_affine_no_pointer() { 376 | let projective_point = ProjectivePoint::new_unchecked( 377 | Felt::from_dec_str( 378 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 379 | ) 380 | .unwrap(), 381 | Felt::from_dec_str( 382 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 383 | ) 384 | .unwrap(), 385 | Felt::from(1), 386 | ); 387 | let affine_point = projective_point.clone().to_affine().unwrap(); 388 | let result = (projective_point + affine_point).to_affine().unwrap(); 389 | 390 | assert_eq!( 391 | result, 392 | AffinePoint::new( 393 | Felt::from_dec_str( 394 | "3324833730090626974525872402899302150520188025637965566623476530814354734325", 395 | ) 396 | .unwrap(), 397 | Felt::from_dec_str( 398 | "3147007486456030910661996439995670279305852583596209647900952752170983517249", 399 | ) 400 | .unwrap() 401 | ) 402 | .unwrap() 403 | ) 404 | } 405 | 406 | #[test] 407 | // Results checked against starknet-rs https://github.com/xJonathanLEI/starknet-rs/ 408 | fn add_assign_operations() { 409 | let mut projective_point_1 = ProjectivePoint::new_unchecked( 410 | Felt::from_dec_str( 411 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 412 | ) 413 | .unwrap(), 414 | Felt::from_dec_str( 415 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 416 | ) 417 | .unwrap(), 418 | Felt::from(1), 419 | ); 420 | let projective_point_2 = projective_point_1.clone(); 421 | projective_point_1 += &projective_point_2; 422 | 423 | let result = projective_point_1.to_affine().unwrap(); 424 | 425 | assert_eq!( 426 | result.x(), 427 | Felt::from_dec_str( 428 | "3324833730090626974525872402899302150520188025637965566623476530814354734325", 429 | ) 430 | .unwrap() 431 | ); 432 | 433 | assert_eq!( 434 | result.y(), 435 | Felt::from_dec_str( 436 | "3147007486456030910661996439995670279305852583596209647900952752170983517249", 437 | ) 438 | .unwrap() 439 | ); 440 | } 441 | 442 | #[test] 443 | // Results checked against starknet-rs https://github.com/xJonathanLEI/starknet-rs/ 444 | fn add_assign_operations_with_affine() { 445 | let mut projective_point = ProjectivePoint::new_unchecked( 446 | Felt::from_dec_str( 447 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 448 | ) 449 | .unwrap(), 450 | Felt::from_dec_str( 451 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 452 | ) 453 | .unwrap(), 454 | Felt::from(1), 455 | ); 456 | let affine_point = projective_point.clone().to_affine().unwrap(); 457 | projective_point += &affine_point; 458 | 459 | let result = projective_point.to_affine().unwrap(); 460 | 461 | assert_eq!( 462 | result.x(), 463 | Felt::from_dec_str( 464 | "3324833730090626974525872402899302150520188025637965566623476530814354734325", 465 | ) 466 | .unwrap() 467 | ); 468 | 469 | assert_eq!( 470 | result.y(), 471 | Felt::from_dec_str( 472 | "3147007486456030910661996439995670279305852583596209647900952752170983517249", 473 | ) 474 | .unwrap() 475 | ); 476 | } 477 | 478 | #[test] 479 | // Results checked against starknet-rs https://github.com/xJonathanLEI/starknet-rs/ 480 | fn add_assign_operations_with_affine_no_pointer() { 481 | let mut projective_point = ProjectivePoint::new_unchecked( 482 | Felt::from_dec_str( 483 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 484 | ) 485 | .unwrap(), 486 | Felt::from_dec_str( 487 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 488 | ) 489 | .unwrap(), 490 | Felt::from(1), 491 | ); 492 | let affine_point = projective_point.clone().to_affine().unwrap(); 493 | projective_point += affine_point; 494 | 495 | let result = projective_point.to_affine().unwrap(); 496 | 497 | assert_eq!( 498 | result.x(), 499 | Felt::from_dec_str( 500 | "3324833730090626974525872402899302150520188025637965566623476530814354734325", 501 | ) 502 | .unwrap() 503 | ); 504 | 505 | assert_eq!( 506 | result.y(), 507 | Felt::from_dec_str( 508 | "3147007486456030910661996439995670279305852583596209647900952752170983517249", 509 | ) 510 | .unwrap() 511 | ); 512 | } 513 | 514 | #[test] 515 | // Results checked against starknet-rs https://github.com/xJonathanLEI/starknet-rs/ 516 | fn mul_operations() { 517 | let identity = ProjectivePoint::identity(); 518 | 519 | assert_eq!(&identity * 11_u16, identity); 520 | assert_eq!( 521 | &identity * Felt::from_dec_str("8731298391798138132780",).unwrap(), 522 | identity 523 | ); 524 | 525 | let projective_point_1 = ProjectivePoint::new_unchecked( 526 | Felt::from_dec_str( 527 | "685118385380464480289795596422487144864558069280897344382334516257395969277", 528 | ) 529 | .unwrap(), 530 | Felt::from_dec_str( 531 | "2157469546853095472290556201984093730375838368522549154974787195581425752638", 532 | ) 533 | .unwrap(), 534 | Felt::from(1), 535 | ); 536 | 537 | let result = (&projective_point_1 * 1812_u32).to_affine().unwrap(); 538 | 539 | assert_eq!( 540 | result, 541 | AffinePoint::new( 542 | Felt::from_dec_str( 543 | "3440268448213322209285127313797148367487473316555419755705577898182859853039", 544 | ) 545 | .unwrap(), 546 | Felt::from_dec_str( 547 | "1596323783766236796787317367695486687781666659527154739146733884430376982452", 548 | ) 549 | .unwrap() 550 | ) 551 | .unwrap() 552 | ) 553 | } 554 | 555 | #[test] 556 | fn mul_by_scalar_operations_with_felt() { 557 | let a = ProjectivePoint::new_unchecked( 558 | Felt::from_dec_str( 559 | "685118385380464480289795596422487144864558069280897344382334516257395969277", 560 | ) 561 | .unwrap(), 562 | Felt::from_dec_str( 563 | "2157469546853095472290556201984093730375838368522549154974787195581425752638", 564 | ) 565 | .unwrap(), 566 | Felt::from(1), 567 | ); 568 | let b = Felt::from(1812); 569 | 570 | let mut expected = a.clone(); 571 | 572 | for _ in 0..1811 { 573 | expected += a.clone(); 574 | } 575 | 576 | assert_eq!((&a * b).to_affine().unwrap(), expected.to_affine().unwrap()); 577 | } 578 | 579 | #[test] 580 | // Results checked against starknet-rs https://github.com/xJonathanLEI/starknet-rs/ 581 | fn double_operations() { 582 | let projective_point = ProjectivePoint::new_unchecked( 583 | Felt::from_dec_str( 584 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 585 | ) 586 | .unwrap(), 587 | Felt::from_dec_str( 588 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 589 | ) 590 | .unwrap(), 591 | Felt::from(1), 592 | ); 593 | 594 | assert_eq!( 595 | projective_point.double().to_affine().unwrap(), 596 | AffinePoint::new( 597 | Felt::from_dec_str( 598 | "3324833730090626974525872402899302150520188025637965566623476530814354734325", 599 | ) 600 | .unwrap(), 601 | Felt::from_dec_str( 602 | "3147007486456030910661996439995670279305852583596209647900952752170983517249", 603 | ) 604 | .unwrap() 605 | ) 606 | .unwrap() 607 | ); 608 | } 609 | 610 | #[test] 611 | fn neg_operations() { 612 | let a = ProjectivePoint::new_unchecked( 613 | Felt::from_dec_str( 614 | "685118385380464480289795596422487144864558069280897344382334516257395969277", 615 | ) 616 | .unwrap(), 617 | Felt::from_dec_str( 618 | "2157469546853095472290556201984093730375838368522549154974787195581425752638", 619 | ) 620 | .unwrap(), 621 | Felt::from(1), 622 | ); 623 | 624 | let b = ProjectivePoint::new_unchecked( 625 | Felt::from_dec_str( 626 | "685118385380464480289795596422487144864558069280897344382334516257395969277", 627 | ) 628 | .unwrap(), 629 | -Felt::from_dec_str( 630 | "2157469546853095472290556201984093730375838368522549154974787195581425752638", 631 | ) 632 | .unwrap(), 633 | Felt::from(1), 634 | ); 635 | 636 | assert_eq!(-&a, b); 637 | } 638 | 639 | #[test] 640 | fn sub_operations_pointers() { 641 | let mut a = ProjectivePoint::new_unchecked( 642 | Felt::from_dec_str( 643 | "685118385380464480289795596422487144864558069280897344382334516257395969277", 644 | ) 645 | .unwrap(), 646 | Felt::from_dec_str( 647 | "2157469546853095472290556201984093730375838368522549154974787195581425752638", 648 | ) 649 | .unwrap(), 650 | Felt::from(1), 651 | ); 652 | let b = a.clone(); 653 | 654 | assert_eq!(&ProjectivePoint::identity() - &a, -&a); 655 | assert_eq!(&a - &a, ProjectivePoint::identity()); 656 | assert_eq!(&(&a - &a) + &a, a); 657 | a -= &b; 658 | assert_eq!(a, ProjectivePoint::identity()); 659 | } 660 | 661 | #[test] 662 | fn sub_operations() { 663 | let mut a = ProjectivePoint::new_unchecked( 664 | Felt::from_dec_str( 665 | "685118385380464480289795596422487144864558069280897344382334516257395969277", 666 | ) 667 | .unwrap(), 668 | Felt::from_dec_str( 669 | "2157469546853095472290556201984093730375838368522549154974787195581425752638", 670 | ) 671 | .unwrap(), 672 | Felt::from(1), 673 | ); 674 | let b = a.clone(); 675 | 676 | assert_eq!(ProjectivePoint::identity() - a.clone(), -&a); 677 | assert_eq!(a.clone() - a.clone(), ProjectivePoint::identity()); 678 | assert_eq!(a.clone() - a.clone() + a.clone(), a); 679 | a -= b; 680 | assert_eq!(a, ProjectivePoint::identity()); 681 | } 682 | } 683 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/alloc_impls.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use super::alloc; 4 | use super::Felt; 5 | 6 | impl Felt { 7 | /// Helper to represent the felt value as a zero-padded hexadecimal string. 8 | /// 9 | /// Equivalent to calling `format!("{self:#x}")`. 10 | pub fn to_hex_string(&self) -> alloc::string::String { 11 | alloc::format!("{self:#x}") 12 | } 13 | 14 | /// Helper to represent the felt value as a zero-padded hexadecimal string. 15 | /// 16 | /// The resulting string will consist of: 17 | /// 1. A`0x` prefix, 18 | /// 2. an amount of padding zeros so that the resulting string length is fixed (This amount may be 0), 19 | /// 3. the felt value represented in hexadecimal 20 | /// 21 | /// The resulting string is guaranted to be 66 chars long, which is enough to represent `Felt::MAX`: 22 | /// 2 chars for the `0x` prefix and 64 chars for the padded hexadecimal felt value. 23 | pub fn to_fixed_hex_string(&self) -> alloc::string::String { 24 | alloc::format!("{self:#066x}") 25 | } 26 | } 27 | 28 | /// Represents [Felt] in lowercase hexadecimal format. 29 | impl fmt::LowerHex for Felt { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | let hex = alloc::string::ToString::to_string(&self.0); 32 | let hex = hex.strip_prefix("0x").unwrap(); 33 | f.pad_integral(true, if f.alternate() { "0x" } else { "" }, hex) 34 | } 35 | } 36 | 37 | /// Represents [Felt] in uppercase hexadecimal format. 38 | impl fmt::UpperHex for Felt { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | let hex = alloc::string::ToString::to_string(&self.0); 41 | let hex = hex.strip_prefix("0x").unwrap().to_uppercase(); 42 | f.pad_integral(true, if f.alternate() { "0x" } else { "" }, &hex) 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | use proptest::prelude::*; 50 | 51 | proptest! { 52 | #[test] 53 | fn to_hex_string_is_same_as_format(ref x in any::()) { 54 | let y = alloc::format!("{x:#x}"); 55 | prop_assert_eq!(y, x.to_hex_string()); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/arbitrary.rs: -------------------------------------------------------------------------------- 1 | use crate::felt::Felt; 2 | use arbitrary::{self, Arbitrary, Unstructured}; 3 | use lambdaworks_math::field::element::FieldElement; 4 | use lambdaworks_math::unsigned_integer::element::UnsignedInteger; 5 | 6 | impl Arbitrary<'_> for Felt { 7 | // Creates an arbitrary `Felt` from unstructured input for fuzzing. 8 | // It uses the default implementation to create the internal limbs and then 9 | // uses the usual constructors from `lambdaworks-math`. 10 | fn arbitrary(u: &mut Unstructured) -> arbitrary::Result { 11 | let limbs = <[u64; 4]>::arbitrary(u)?; 12 | let uint = UnsignedInteger::from_limbs(limbs); 13 | let felt = FieldElement::new(uint); 14 | Ok(Felt(felt)) 15 | } 16 | 17 | #[inline] 18 | fn size_hint(depth: usize) -> (usize, Option) { 19 | <[u64; 4]>::size_hint(depth) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/felt_arbitrary.rs: -------------------------------------------------------------------------------- 1 | use lambdaworks_math::{field::element::FieldElement, unsigned_integer::element::UnsignedInteger}; 2 | use proptest::prelude::*; 3 | 4 | use crate::felt::Felt; 5 | const FIELD_HIGH: u128 = (1 << 123) + (17 << 64); // this is equal to 10633823966279327296825105735305134080 6 | const FIELD_LOW: u128 = 1; 7 | 8 | /// Returns a [`Strategy`] that generates any valid Felt 9 | /// This is used to generate input values for proptests 10 | fn any_felt() -> impl Strategy { 11 | (0..=FIELD_HIGH) 12 | // turn range into `impl Strategy` 13 | .prop_map(|x| x) 14 | // choose second 128-bit limb capped by first one 15 | .prop_flat_map(|high| { 16 | let low = if high == FIELD_HIGH { 17 | (0..FIELD_LOW).prop_map(|x| x).sboxed() 18 | } else { 19 | any::().sboxed() 20 | }; 21 | (Just(high), low) 22 | }) 23 | // turn (u128, u128) into limbs array and then into Felt 24 | .prop_map(|(high, low)| { 25 | let limbs = [ 26 | (high >> 64) as u64, 27 | (high & ((1 << 64) - 1)) as u64, 28 | (low >> 64) as u64, 29 | (low & ((1 << 64) - 1)) as u64, 30 | ]; 31 | FieldElement::new(UnsignedInteger::from_limbs(limbs)) 32 | }) 33 | .prop_map(Felt) 34 | } 35 | 36 | /// Returns a [`Strategy`] that generates any nonzero Felt 37 | /// This is used to generate input values for proptests 38 | pub fn nonzero_felt() -> impl Strategy { 39 | any_felt().prop_filter("is zero", |&x| x != Felt::ZERO) 40 | } 41 | 42 | impl Arbitrary for Felt { 43 | type Parameters = (); 44 | 45 | fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 46 | any_felt().sboxed() 47 | } 48 | 49 | type Strategy = SBoxedStrategy; 50 | } 51 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/non_zero.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use lambdaworks_math::{ 4 | field::{ 5 | element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, 6 | }, 7 | unsigned_integer::element::UnsignedInteger, 8 | }; 9 | 10 | use crate::felt::Felt; 11 | 12 | /// A non-zero [Felt]. 13 | #[repr(transparent)] 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | pub struct NonZeroFelt(pub(crate) FieldElement); 16 | 17 | impl NonZeroFelt { 18 | /// Create a [NonZeroFelt] as a constant. 19 | /// # Safety 20 | /// If the value is zero will panic. 21 | pub const fn from_raw(value: [u64; 4]) -> Self { 22 | if value[0] == 0 && value[1] == 0 && value[2] == 0 && value[3] == 0 { 23 | panic!("Felt is zero"); 24 | } 25 | let value = Felt::from_raw(value); 26 | Self(value.0) 27 | } 28 | 29 | /// [Felt] constant that's equal to 1. 30 | pub const ONE: Self = Self::from_felt_unchecked(Felt( 31 | FieldElement::::from_hex_unchecked("1"), 32 | )); 33 | 34 | /// [Felt] constant that's equal to 2. 35 | pub const TWO: Self = Self::from_felt_unchecked(Felt( 36 | FieldElement::::from_hex_unchecked("2"), 37 | )); 38 | 39 | /// [Felt] constant that's equal to 3. 40 | pub const THREE: Self = Self::from_felt_unchecked(Felt( 41 | FieldElement::::from_hex_unchecked("3"), 42 | )); 43 | 44 | /// Maximum value of [Felt]. Equals to 2^251 + 17 * 2^192. 45 | pub const MAX: Self = 46 | Self::from_felt_unchecked(Felt(FieldElement::::const_from_raw( 47 | UnsignedInteger::from_limbs([544, 0, 0, 32]), 48 | ))); 49 | 50 | /// Create a [NonZeroFelt] without checking it. If the [Felt] is indeed [Felt::ZERO] 51 | /// this can lead to undefined behaviour and big security issue. 52 | /// You should always use the [TryFrom] implementation 53 | pub const fn from_felt_unchecked(value: Felt) -> Self { 54 | Self(value.0) 55 | } 56 | } 57 | 58 | #[derive(Debug)] 59 | pub struct FeltIsZeroError; 60 | 61 | #[cfg(feature = "std")] 62 | impl std::error::Error for FeltIsZeroError {} 63 | 64 | impl fmt::Display for FeltIsZeroError { 65 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 66 | "tried to create NonZeroFelt from 0".fmt(f) 67 | } 68 | } 69 | 70 | impl From for Felt { 71 | fn from(value: NonZeroFelt) -> Self { 72 | Self(value.0) 73 | } 74 | } 75 | 76 | impl From<&NonZeroFelt> for Felt { 77 | fn from(value: &NonZeroFelt) -> Self { 78 | Self(value.0) 79 | } 80 | } 81 | 82 | impl AsRef for NonZeroFelt { 83 | fn as_ref(&self) -> &NonZeroFelt { 84 | self 85 | } 86 | } 87 | 88 | impl TryFrom for NonZeroFelt { 89 | type Error = FeltIsZeroError; 90 | 91 | fn try_from(value: Felt) -> Result { 92 | if value == Felt::ZERO { 93 | Err(FeltIsZeroError) 94 | } else { 95 | Ok(Self(value.0)) 96 | } 97 | } 98 | } 99 | 100 | impl TryFrom<&Felt> for NonZeroFelt { 101 | type Error = FeltIsZeroError; 102 | 103 | fn try_from(value: &Felt) -> Result { 104 | if *value == Felt::ZERO { 105 | Err(FeltIsZeroError) 106 | } else { 107 | Ok(Self(value.0)) 108 | } 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | #[cfg(feature = "alloc")] 115 | pub extern crate alloc; 116 | 117 | use proptest::prelude::*; 118 | 119 | use crate::felt::{felt_arbitrary::nonzero_felt, Felt, NonZeroFelt}; 120 | 121 | #[test] 122 | fn nonzerofelt_from_raw() { 123 | let one_raw = [ 124 | 576460752303422960, 125 | 18446744073709551615, 126 | 18446744073709551615, 127 | 18446744073709551585, 128 | ]; 129 | assert_eq!(NonZeroFelt::from_raw(one_raw), NonZeroFelt::ONE); 130 | let two_raw = [ 131 | 576460752303422416, 132 | 18446744073709551615, 133 | 18446744073709551615, 134 | 18446744073709551553, 135 | ]; 136 | assert_eq!(NonZeroFelt::from_raw(two_raw), NonZeroFelt::TWO); 137 | let nineteen_raw = [ 138 | 576460752303413168, 139 | 18446744073709551615, 140 | 18446744073709551615, 141 | 18446744073709551009, 142 | ]; 143 | assert_eq!( 144 | NonZeroFelt::from_raw(nineteen_raw), 145 | NonZeroFelt::try_from(Felt::from(19)).unwrap() 146 | ); 147 | } 148 | 149 | #[test] 150 | fn nonzerofelt_from_felt_unchecked() { 151 | assert_eq!( 152 | NonZeroFelt::from_felt_unchecked(Felt::from_hex_unwrap("9028392")), 153 | NonZeroFelt::try_from(Felt::from(0x9028392)).unwrap() 154 | ); 155 | assert_eq!( 156 | NonZeroFelt::from_felt_unchecked(Felt::from_hex_unwrap("1")), 157 | NonZeroFelt::try_from(Felt::from(1)).unwrap() 158 | ); 159 | assert_eq!( 160 | NonZeroFelt::from_felt_unchecked(Felt::from_hex_unwrap("0x2")), 161 | NonZeroFelt::try_from(Felt::from(2)).unwrap() 162 | ); 163 | assert_eq!( 164 | NonZeroFelt::from_felt_unchecked(Felt::from_hex_unwrap("0x0000000003")), 165 | NonZeroFelt::try_from(Felt::from(3)).unwrap() 166 | ); 167 | assert_eq!( 168 | NonZeroFelt::from_felt_unchecked(Felt::from_hex_unwrap("000004")), 169 | NonZeroFelt::try_from(Felt::from(4)).unwrap() 170 | ); 171 | assert_eq!( 172 | NonZeroFelt::from_felt_unchecked(Felt::from_hex_unwrap("0x05b")), 173 | NonZeroFelt::try_from(Felt::from(91)).unwrap() 174 | ); 175 | assert_eq!( 176 | NonZeroFelt::from_felt_unchecked(Felt::from_hex_unwrap("A")), 177 | NonZeroFelt::try_from(Felt::from(10)).unwrap() 178 | ); 179 | } 180 | 181 | #[test] 182 | #[should_panic(expected = "Felt is zero")] 183 | fn nonzerofelt_is_zero_from_raw() { 184 | NonZeroFelt::from_raw([0; 4]); 185 | } 186 | 187 | #[test] 188 | fn non_zero_felt_from_zero_should_fail() { 189 | assert!(NonZeroFelt::try_from(Felt::ZERO).is_err()); 190 | } 191 | 192 | proptest! { 193 | #[test] 194 | fn non_zero_felt_new_is_ok_when_not_zero(x in nonzero_felt()) { 195 | prop_assert!(NonZeroFelt::try_from(x).is_ok()); 196 | prop_assert_eq!(NonZeroFelt::try_from(x).unwrap().0, x.0); 197 | } 198 | 199 | #[test] 200 | fn non_zero_is_not_zero(x in nonzero_felt()) { 201 | prop_assert!(x != Felt::ZERO) 202 | } 203 | 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/num_traits_impl.rs: -------------------------------------------------------------------------------- 1 | use super::Felt; 2 | use num_bigint::{ToBigInt, ToBigUint}; 3 | use num_traits::{FromPrimitive, Inv, One, Pow, ToPrimitive, Zero}; 4 | 5 | impl ToBigInt for Felt { 6 | /// Converts the value of `self` to a [`BigInt`]. 7 | /// 8 | /// Safe to unwrap, will always return `Some`. 9 | fn to_bigint(&self) -> Option { 10 | Some(self.to_bigint()) 11 | } 12 | } 13 | 14 | impl ToBigUint for Felt { 15 | /// Converts the value of `self` to a [`BigUint`]. 16 | /// 17 | /// Safe to unwrap, will always return `Some`. 18 | fn to_biguint(&self) -> Option { 19 | Some(self.to_biguint()) 20 | } 21 | } 22 | 23 | impl FromPrimitive for Felt { 24 | fn from_i64(value: i64) -> Option { 25 | Some(value.into()) 26 | } 27 | 28 | fn from_u64(value: u64) -> Option { 29 | Some(value.into()) 30 | } 31 | 32 | fn from_i128(value: i128) -> Option { 33 | Some(value.into()) 34 | } 35 | 36 | fn from_u128(value: u128) -> Option { 37 | Some(value.into()) 38 | } 39 | } 40 | 41 | // TODO: we need to decide whether we want conversions to signed primitives 42 | // will support converting the high end of the field to negative. 43 | impl ToPrimitive for Felt { 44 | fn to_u64(&self) -> Option { 45 | self.to_u128().and_then(|x| u64::try_from(x).ok()) 46 | } 47 | 48 | fn to_i64(&self) -> Option { 49 | self.to_u128().and_then(|x| i64::try_from(x).ok()) 50 | } 51 | 52 | fn to_u128(&self) -> Option { 53 | match self.0.representative().limbs { 54 | [0, 0, hi, lo] => Some((lo as u128) | ((hi as u128) << 64)), 55 | _ => None, 56 | } 57 | } 58 | 59 | fn to_i128(&self) -> Option { 60 | self.to_u128().and_then(|x| i128::try_from(x).ok()) 61 | } 62 | } 63 | 64 | impl Zero for Felt { 65 | fn is_zero(&self) -> bool { 66 | *self == Felt::ZERO 67 | } 68 | 69 | fn zero() -> Felt { 70 | Felt::ZERO 71 | } 72 | } 73 | 74 | impl One for Felt { 75 | fn one() -> Self { 76 | Felt::ONE 77 | } 78 | } 79 | 80 | impl Inv for Felt { 81 | type Output = Option; 82 | 83 | fn inv(self) -> Self::Output { 84 | self.inverse() 85 | } 86 | } 87 | 88 | impl Pow for Felt { 89 | type Output = Self; 90 | 91 | fn pow(self, rhs: u8) -> Self::Output { 92 | Self(self.0.pow(rhs as u128)) 93 | } 94 | } 95 | impl Pow for Felt { 96 | type Output = Self; 97 | 98 | fn pow(self, rhs: u16) -> Self::Output { 99 | Self(self.0.pow(rhs as u128)) 100 | } 101 | } 102 | impl Pow for Felt { 103 | type Output = Self; 104 | 105 | fn pow(self, rhs: u32) -> Self::Output { 106 | Self(self.0.pow(rhs as u128)) 107 | } 108 | } 109 | impl Pow for Felt { 110 | type Output = Self; 111 | 112 | fn pow(self, rhs: u64) -> Self::Output { 113 | Self(self.0.pow(rhs as u128)) 114 | } 115 | } 116 | impl Pow for Felt { 117 | type Output = Self; 118 | 119 | fn pow(self, rhs: u128) -> Self::Output { 120 | Self(self.0.pow(rhs)) 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | 128 | #[test] 129 | fn zero_is_zero() { 130 | assert!(Felt::ZERO.is_zero()); 131 | } 132 | 133 | #[test] 134 | fn one_is_one() { 135 | assert!(Felt::ONE.is_one()); 136 | } 137 | 138 | #[test] 139 | fn default_is_zero() { 140 | assert!(Felt::default().is_zero()); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/papyrus_serialization.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | 3 | use super::Felt; 4 | /// Storage efficient serialization for field elements. 5 | impl Felt { 6 | // Felt encoding constants. 7 | const CHOOSER_FULL: u8 = 15; 8 | const CHOOSER_HALF: u8 = 14; 9 | pub fn serialize(&self, res: &mut impl std::io::Write) -> Result<(), Error> { 10 | let self_as_bytes = self.to_bytes_be(); 11 | // We use the fact that bytes[0] < 0x10 and encode the size of the felt in the 4 most 12 | // significant bits of the serialization, which we call `chooser`. We assume that 128 bit 13 | // felts are prevalent (because of how uint256 is encoded in felts). 14 | 15 | // The first i for which nibbles 2i+1, 2i+2 are nonzero. Note that the first nibble is 16 | // always 0. 17 | let mut first_index = 31; 18 | // Find first non zero byte 19 | if let Some((index, value)) = self_as_bytes 20 | .iter() 21 | .enumerate() 22 | .find(|(_index, value)| value != &&0u8) 23 | { 24 | if value < &16 { 25 | // Can encode the chooser and the value on a single byte. 26 | first_index = index; 27 | } else { 28 | // The chooser is encoded with the first nibble of the value. 29 | first_index = index - 1; 30 | } 31 | }; 32 | let chooser = if first_index < 15 { 33 | // For 34 up to 63 nibble felts: chooser == 15, serialize using 32 bytes. 34 | first_index = 0; 35 | Felt::CHOOSER_FULL 36 | } else if first_index < 18 { 37 | // For 28 up to 33 nibble felts: chooser == 14, serialize using 17 bytes. 38 | first_index = 15; 39 | Felt::CHOOSER_HALF 40 | } else { 41 | // For up to 27 nibble felts: serialize the lower 1 + (chooser * 2) nibbles of the felt 42 | // using chooser + 1 bytes. 43 | (31 - first_index) as u8 44 | }; 45 | res.write_all(&[(chooser << 4) | self_as_bytes[first_index]])?; 46 | res.write_all(&self_as_bytes[first_index + 1..])?; 47 | Ok(()) 48 | } 49 | 50 | /// Storage efficient deserialization for field elements. 51 | pub fn deserialize(bytes: &mut impl std::io::Read) -> Option { 52 | let mut res = [0u8; 32]; 53 | 54 | bytes.read_exact(&mut res[..1]).ok()?; 55 | let first = res[0]; 56 | let chooser: u8 = first >> 4; 57 | let first = first & 0x0f; 58 | 59 | let first_index = if chooser == Felt::CHOOSER_FULL { 60 | 0 61 | } else if chooser == Felt::CHOOSER_HALF { 62 | 15 63 | } else { 64 | (31 - chooser) as usize 65 | }; 66 | res[0] = 0; 67 | res[first_index] = first; 68 | bytes.read_exact(&mut res[first_index + 1..]).ok()?; 69 | Some(Self::from_bytes_be(&res)) 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | /// Tests proper serialization and deserialization of felts using `parity-scale-codec`. 78 | #[test] 79 | fn hash_serde() { 80 | fn enc_len(n_nibbles: usize) -> usize { 81 | match n_nibbles { 82 | 0..=27 => n_nibbles / 2 + 1, 83 | 28..=33 => 17, 84 | _ => 32, 85 | } 86 | } 87 | 88 | // 64 nibbles are invalid. 89 | for n_nibbles in 0..64 { 90 | let mut bytes = [0u8; 32]; 91 | // Set all nibbles to 0xf. 92 | for i in 0..n_nibbles { 93 | bytes[31 - (i >> 1)] |= 15 << (4 * (i & 1)); 94 | } 95 | let h = Felt::from_bytes_be(&bytes); 96 | let mut res = Vec::new(); 97 | assert!(h.serialize(&mut res).is_ok()); 98 | assert_eq!(res.len(), enc_len(n_nibbles)); 99 | let mut reader = &res[..]; 100 | let d = Felt::deserialize(&mut reader).unwrap(); 101 | assert_eq!(Felt::from_bytes_be(&bytes), d); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/parity_scale_codec.rs: -------------------------------------------------------------------------------- 1 | use crate::felt::Felt; 2 | 3 | /// Allows transparent binary serialization of Felts with `parity_scale_codec`. 4 | impl parity_scale_codec::Encode for Felt { 5 | fn encode_to(&self, dest: &mut T) { 6 | dest.write(&self.to_bytes_be()); 7 | } 8 | } 9 | 10 | /// Allows transparent binary deserialization of Felts with `parity_scale_codec` 11 | impl parity_scale_codec::Decode for Felt { 12 | fn decode( 13 | input: &mut I, 14 | ) -> Result { 15 | let mut buf: [u8; 32] = [0; 32]; 16 | input.read(&mut buf)?; 17 | Ok(Felt::from_bytes_be(&buf)) 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | 25 | #[test] 26 | fn parity_scale_codec_serialization() { 27 | use parity_scale_codec::{Decode, Encode}; 28 | 29 | // use an endianness-asymetric number to test that byte order is correct in serialization 30 | let initial_felt = Felt::from_hex("0xabcdef123").unwrap(); 31 | 32 | // serialize the felt 33 | let serialized_felt = initial_felt.encode(); 34 | 35 | // deserialize the felt 36 | let deserialized_felt = Felt::decode(&mut &serialized_felt[..]).unwrap(); 37 | 38 | // check that the deserialized felt is the same as the initial one 39 | assert_eq!( 40 | initial_felt, deserialized_felt, 41 | "mismatch between original and deserialized felts" 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/prime_bigint.rs: -------------------------------------------------------------------------------- 1 | use crate::felt::Felt; 2 | use lazy_static::lazy_static; 3 | use num_bigint::{BigInt, BigUint}; 4 | use num_traits::Num; 5 | 6 | lazy_static! { 7 | pub static ref CAIRO_PRIME_BIGINT: BigInt = BigInt::from_str_radix( 8 | "800000000000011000000000000000000000000000000000000000000000001", 9 | 16 10 | ) 11 | .unwrap(); 12 | } 13 | 14 | impl Felt { 15 | pub fn prime() -> BigUint { 16 | (*CAIRO_PRIME_BIGINT).to_biguint().unwrap() 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::*; 23 | 24 | #[test] 25 | fn prime() { 26 | assert_eq!(Felt::prime(), CAIRO_PRIME_BIGINT.to_biguint().unwrap()); 27 | } 28 | 29 | #[test] 30 | fn bigints_to_felt() { 31 | let one = &*CAIRO_PRIME_BIGINT + BigInt::from(1_u32); 32 | assert_eq!(Felt::from(&one.to_biguint().unwrap()), Felt::from(1)); 33 | assert_eq!(Felt::from(&one), Felt::from(1)); 34 | 35 | let zero = &*CAIRO_PRIME_BIGINT * 99_u32; 36 | assert_eq!(Felt::from(&zero.to_biguint().unwrap()), Felt::from(0)); 37 | assert_eq!(Felt::from(&zero), Felt::from(0)); 38 | 39 | assert_eq!( 40 | Felt::from(&BigInt::from(-1)), 41 | Felt::from_hex("0x800000000000011000000000000000000000000000000000000000000000000") 42 | .unwrap() 43 | ); 44 | 45 | let numbers_str = [ 46 | "0x0", 47 | "0x1", 48 | "0x10", 49 | "0x8000000000000110000000000", 50 | "0xffffffffffffff", 51 | "0xffffffffefff12380777abcd", 52 | ]; 53 | 54 | for number_str in numbers_str { 55 | assert_eq!( 56 | Felt::from(&BigInt::from_str_radix(&number_str[2..], 16).unwrap()), 57 | Felt::from_hex(number_str).unwrap() 58 | ); 59 | assert_eq!( 60 | Felt::from(&BigUint::from_str_radix(&number_str[2..], 16).unwrap()), 61 | Felt::from_hex(number_str).unwrap() 62 | ) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/primitive_conversions.rs: -------------------------------------------------------------------------------- 1 | use lambdaworks_math::{field::element::FieldElement, unsigned_integer::element::UnsignedInteger}; 2 | 3 | use super::Felt; 4 | 5 | // Bool <-> Felt 6 | 7 | impl From for Felt { 8 | fn from(value: bool) -> Felt { 9 | match value { 10 | true => Felt::ONE, 11 | false => Felt::ZERO, 12 | } 13 | } 14 | } 15 | 16 | // From for Felt 17 | 18 | macro_rules! impl_from { 19 | ($from:ty, $with:ty) => { 20 | impl From<$from> for Felt { 21 | fn from(value: $from) -> Self { 22 | (value as $with).into() 23 | } 24 | } 25 | }; 26 | } 27 | 28 | impl_from!(u8, u128); 29 | impl_from!(u16, u128); 30 | impl_from!(u32, u128); 31 | impl_from!(u64, u128); 32 | impl_from!(usize, u128); 33 | impl_from!(i8, i128); 34 | impl_from!(i16, i128); 35 | impl_from!(i32, i128); 36 | impl_from!(i64, i128); 37 | impl_from!(isize, i128); 38 | 39 | impl From for Felt { 40 | fn from(value: u128) -> Felt { 41 | Self(FieldElement::from(&UnsignedInteger::from(value))) 42 | } 43 | } 44 | 45 | impl From for Felt { 46 | fn from(value: i128) -> Felt { 47 | let mut res = Self(FieldElement::from(&UnsignedInteger::from( 48 | value.unsigned_abs(), 49 | ))); 50 | if value.is_negative() { 51 | res = -res; 52 | } 53 | res 54 | } 55 | } 56 | 57 | // TryFrom for primitive 58 | 59 | #[derive(Debug, Copy, Clone)] 60 | pub struct PrimitiveFromFeltError; 61 | 62 | #[cfg(feature = "std")] 63 | impl std::error::Error for PrimitiveFromFeltError {} 64 | 65 | impl core::fmt::Display for PrimitiveFromFeltError { 66 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 67 | write!(f, "Failed to convert `Felt` into primitive type") 68 | } 69 | } 70 | 71 | macro_rules! impl_try_felt_into_unsigned { 72 | ($into: ty) => { 73 | impl TryFrom for $into { 74 | type Error = PrimitiveFromFeltError; 75 | 76 | fn try_from(value: Felt) -> Result<$into, Self::Error> { 77 | let bytes_be = value.to_bytes_le(); 78 | let (bytes_to_return, bytes_to_check) = 79 | bytes_be.split_at(core::mem::size_of::<$into>()); 80 | 81 | if bytes_to_check.iter().all(|&v| v == 0) { 82 | Ok(<$into>::from_le_bytes(bytes_to_return.try_into().unwrap())) 83 | } else { 84 | Err(PrimitiveFromFeltError) 85 | } 86 | } 87 | } 88 | }; 89 | } 90 | 91 | impl_try_felt_into_unsigned!(u8); 92 | impl_try_felt_into_unsigned!(u16); 93 | impl_try_felt_into_unsigned!(u32); 94 | impl_try_felt_into_unsigned!(u64); 95 | impl_try_felt_into_unsigned!(usize); 96 | impl_try_felt_into_unsigned!(u128); 97 | 98 | const MINUS_TWO_BYTES_REPR: [u8; 32] = [ 99 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 100 | 255, 255, 255, 255, 255, 16, 0, 0, 0, 0, 0, 0, 8, 101 | ]; 102 | 103 | macro_rules! impl_try_felt_into_signed { 104 | ($into: ty) => { 105 | impl TryFrom for $into { 106 | type Error = PrimitiveFromFeltError; 107 | 108 | // Let's call `s` the `size_of_type`. 109 | // 110 | // There is 3 ways the value we are looking for can be encoded in the Felt bytes: 111 | // 1. Positive numbers: 112 | // Bytes [s..] are all set to zero, and byte [s] value must not exceed 127 113 | // 2. Negative numbers: 114 | // Bytes [s..] should match `MINUS_TWO_BYTES_REPR`, and byte [s] value must not be less than 128 115 | // 3. -1: 116 | // Bytes are those of Felt::MAX 117 | fn try_from(value: Felt) -> Result { 118 | // We'll split the conversion in 3 case: 119 | // Felt >= 0 120 | // Felt < -1 121 | // Felt == -1 122 | 123 | // Get the size of the type we'll convert the felt into 124 | let size_of_type = core::mem::size_of::<$into>(); 125 | // Convert the felt as little endian bytes 126 | let bytes_le = value.to_bytes_le(); 127 | 128 | // Case 1: Felt >= 0 129 | // Our felt type can hold values up to roughly 2**252 bits which is encoded in 32 bytes. 130 | // The type we'll convert the value into can hold `size_of_type` (= { 1, 2, 4, 8, 16 }) bytes. 131 | // If all the bytes after the last byte that our target type can fit are 0 it means that the 132 | // number is positive. 133 | // The target type is a signed type which means that the leftmost bit of the last byte 134 | // (remember we're in little endian) is used for the sign (0 for positive numbers) 135 | // so the last byte can hold a value up to 0b01111111 136 | if bytes_le[size_of_type..].iter().all(|&v| v == 0) 137 | && bytes_le[size_of_type - 1] <= 0b01111111 138 | { 139 | Ok(<$into>::from_le_bytes( 140 | bytes_le[..size_of_type].try_into().unwrap(), 141 | )) 142 | } 143 | // Case 2: Felt < -1 144 | // Similarly to how we checked that the number was positive by checking the bytes after the 145 | // `size_of_type` byte we check that all the bytes after correspond to the bytes of a negative felt 146 | // The leftmost bit is use for the sign, as it's a negative number it has to be 1. 147 | else if bytes_le[size_of_type..] == MINUS_TWO_BYTES_REPR[size_of_type..] 148 | && bytes_le[size_of_type - 1] >= 0b10000000 149 | { 150 | // We take the `size_of_type` first bytes because they contain the useful value. 151 | let offsetted_value = 152 | <$into>::from_le_bytes(bytes_le[..size_of_type].try_into().unwrap()); 153 | 154 | // Quite conveniently the byte representation of the `size_of` least significant bytes 155 | // is the one of the negative value we are looking for +1. 156 | // So we just have to remove 1 to get the actual value. 157 | offsetted_value.checked_sub(1).ok_or(PrimitiveFromFeltError) 158 | // Case 3: Felt == -1 159 | // This is the little endian representation of Felt::MAX. And Felt::MAX is exactly -1 160 | // [ 161 | // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 162 | // 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 8, 163 | // ] 164 | // The only valid Felt with those most significant bytes is Felt::MAX. 165 | // All other arrays with those most significant bytes are invalid Felts because they would be >= Prime. 166 | } else if bytes_le[24..] == [17, 0, 0, 0, 0, 0, 0, 8] { 167 | return Ok(-1); 168 | } else { 169 | Err(PrimitiveFromFeltError) 170 | } 171 | } 172 | } 173 | }; 174 | } 175 | 176 | impl_try_felt_into_signed!(i8); 177 | impl_try_felt_into_signed!(i16); 178 | impl_try_felt_into_signed!(i32); 179 | impl_try_felt_into_signed!(i64); 180 | impl_try_felt_into_signed!(isize); 181 | impl_try_felt_into_signed!(i128); 182 | 183 | #[cfg(test)] 184 | mod tests { 185 | use crate::felt::Felt; 186 | #[test] 187 | fn from_and_try_from_works_both_way_for_valid_unsigned_values() { 188 | // u8 189 | let u8_max_value = u8::MAX; 190 | assert_eq!( 191 | u8_max_value, 192 | u8::try_from(Felt::from(u8_max_value)).unwrap() 193 | ); 194 | let u8_42_value = 42u8; 195 | assert_eq!(u8_42_value, u8::try_from(Felt::from(u8_42_value)).unwrap()); 196 | let u8_zero_value = 0u8; 197 | assert_eq!( 198 | u8_zero_value, 199 | u8::try_from(Felt::from(u8_zero_value)).unwrap() 200 | ); 201 | // u16 202 | let u16_max_value = u16::MAX; 203 | assert_eq!( 204 | u16_max_value, 205 | u16::try_from(Felt::from(u16_max_value)).unwrap() 206 | ); 207 | let u16_42_value = 42u16; 208 | assert_eq!( 209 | u16_42_value, 210 | u16::try_from(Felt::from(u16_42_value)).unwrap() 211 | ); 212 | let u16_zero_value = 0u16; 213 | assert_eq!( 214 | u16_zero_value, 215 | u16::try_from(Felt::from(u16_zero_value)).unwrap() 216 | ); 217 | // u32 218 | let u32_max_value = u32::MAX; 219 | assert_eq!( 220 | u32_max_value, 221 | u32::try_from(Felt::from(u32_max_value)).unwrap() 222 | ); 223 | let u32_42_value = 42u32; 224 | assert_eq!( 225 | u32_42_value, 226 | u32::try_from(Felt::from(u32_42_value)).unwrap() 227 | ); 228 | let u32_zero_value = 0u32; 229 | assert_eq!( 230 | u32_zero_value, 231 | u32::try_from(Felt::from(u32_zero_value)).unwrap() 232 | ); 233 | // u64 234 | let u64_max_value = u64::MAX; 235 | assert_eq!( 236 | u64_max_value, 237 | u64::try_from(Felt::from(u64_max_value)).unwrap() 238 | ); 239 | let u64_42_value = 42u64; 240 | assert_eq!( 241 | u64_42_value, 242 | u64::try_from(Felt::from(u64_42_value)).unwrap() 243 | ); 244 | let u64_zero_value = 0u64; 245 | assert_eq!( 246 | u64_zero_value, 247 | u64::try_from(Felt::from(u64_zero_value)).unwrap() 248 | ); 249 | // u128 250 | let u128_max_value = u128::MAX; 251 | assert_eq!( 252 | u128_max_value, 253 | u128::try_from(Felt::from(u128_max_value)).unwrap() 254 | ); 255 | let u128_42_value = 42u128; 256 | assert_eq!( 257 | u128_42_value, 258 | u128::try_from(Felt::from(u128_42_value)).unwrap() 259 | ); 260 | let u128_zero_value = 0u128; 261 | assert_eq!( 262 | u128_zero_value, 263 | u128::try_from(Felt::from(u128_zero_value)).unwrap() 264 | ); 265 | // usize 266 | let usize_max_value = usize::MAX; 267 | assert_eq!( 268 | usize_max_value, 269 | usize::try_from(Felt::from(usize_max_value)).unwrap() 270 | ); 271 | let usize_42_value = 42usize; 272 | assert_eq!( 273 | usize_42_value, 274 | usize::try_from(Felt::from(usize_42_value)).unwrap() 275 | ); 276 | let usize_zero_value = 0usize; 277 | assert_eq!( 278 | usize_zero_value, 279 | usize::try_from(Felt::from(usize_zero_value)).unwrap() 280 | ); 281 | } 282 | 283 | #[test] 284 | fn from_and_try_from_works_both_way_for_all_valid_i8_and_i16_values() { 285 | // i8 286 | for i in i8::MIN..i8::MAX { 287 | assert_eq!(i, i8::try_from(Felt::from(i)).unwrap()); 288 | } 289 | // i16 290 | for i in i16::MIN..i16::MAX { 291 | assert_eq!(i, i16::try_from(Felt::from(i)).unwrap()); 292 | } 293 | // For the others types it would be too long to test every value exhaustively, 294 | // so we only check keys values in `from_and_try_from_works_both_way_for_valid_signed_values` 295 | } 296 | 297 | #[test] 298 | fn from_and_try_from_works_both_way_for_valid_signed_values() { 299 | // i32 300 | let i32_max = i32::MAX; 301 | assert_eq!(i32_max, i32::try_from(Felt::from(i32_max)).unwrap()); 302 | let i32_min = i32::MIN; 303 | assert_eq!(i32_min, i32::try_from(Felt::from(i32_min)).unwrap()); 304 | let i32_zero = 0i32; 305 | assert_eq!(i32_zero, i32::try_from(Felt::from(i32_zero)).unwrap()); 306 | let i32_minus_one = -1i32; 307 | assert_eq!( 308 | i32_minus_one, 309 | i32::try_from(Felt::from(i32_minus_one)).unwrap() 310 | ); 311 | // i64 312 | let i64_max = i64::MAX; 313 | assert_eq!(i64_max, i64::try_from(Felt::from(i64_max)).unwrap()); 314 | let i64_min = i64::MIN; 315 | assert_eq!(i64_min, i64::try_from(Felt::from(i64_min)).unwrap()); 316 | let i64_zero = 0i64; 317 | assert_eq!(i64_zero, i64::try_from(Felt::from(i64_zero)).unwrap()); 318 | let i64_minus_one = -1i64; 319 | assert_eq!( 320 | i64_minus_one, 321 | i64::try_from(Felt::from(i64_minus_one)).unwrap() 322 | ); 323 | // isize 324 | let isize_max = isize::MAX; 325 | assert_eq!(isize_max, isize::try_from(Felt::from(isize_max)).unwrap()); 326 | let isize_min = isize::MIN; 327 | assert_eq!(isize_min, isize::try_from(Felt::from(isize_min)).unwrap()); 328 | let isize_zero = 0isize; 329 | assert_eq!(isize_zero, isize::try_from(Felt::from(isize_zero)).unwrap()); 330 | let isize_minus_one = -1isize; 331 | assert_eq!( 332 | isize_minus_one, 333 | isize::try_from(Felt::from(isize_minus_one)).unwrap() 334 | ); 335 | // i128 336 | let i128_max = i128::MAX; 337 | assert_eq!(i128_max, Felt::from(i128_max).try_into().unwrap()); 338 | let i128_min = i128::MIN; 339 | assert_eq!(i128_min, Felt::from(i128_min).try_into().unwrap()); 340 | let i128_zero = 0i128; 341 | assert_eq!(i128_zero, Felt::from(i128_zero).try_into().unwrap()); 342 | let i128_minus_one = -1i128; 343 | assert_eq!( 344 | i128_minus_one, 345 | Felt::from(i128_minus_one).try_into().unwrap() 346 | ); 347 | } 348 | 349 | #[test] 350 | fn try_from_fail_for_out_of_boud_values() { 351 | for i in (i8::MAX as i16 + 1)..i16::MAX { 352 | let f = Felt::from(i); 353 | assert!(i8::try_from(f).is_err()); 354 | } 355 | for i in i16::MIN..(i8::MIN as i16 - 1) { 356 | let f = Felt::from(i); 357 | assert!(i8::try_from(f).is_err()); 358 | } 359 | // It would be too expansive to check all values of larger types, 360 | // so we just check some key ones 361 | 362 | let over_i128_max = Felt::from(i128::MAX) + 1; 363 | assert!(i8::try_from(over_i128_max).is_err()); 364 | assert!(i16::try_from(over_i128_max).is_err()); 365 | assert!(i32::try_from(over_i128_max).is_err()); 366 | assert!(i64::try_from(over_i128_max).is_err()); 367 | assert!(isize::try_from(over_i128_max).is_err()); 368 | assert!(i128::try_from(over_i128_max).is_err()); 369 | 370 | let under_i128_min = Felt::from(i128::MIN) - 1; 371 | assert!(i8::try_from(under_i128_min).is_err()); 372 | assert!(i16::try_from(under_i128_min).is_err()); 373 | assert!(i32::try_from(under_i128_min).is_err()); 374 | assert!(i64::try_from(under_i128_min).is_err()); 375 | assert!(isize::try_from(under_i128_min).is_err()); 376 | assert!(i128::try_from(under_i128_min).is_err()); 377 | let under_i128_min = Felt::from(i128::MIN) - 2; 378 | assert!(i8::try_from(under_i128_min).is_err()); 379 | assert!(i16::try_from(under_i128_min).is_err()); 380 | assert!(i32::try_from(under_i128_min).is_err()); 381 | assert!(i64::try_from(under_i128_min).is_err()); 382 | assert!(isize::try_from(under_i128_min).is_err()); 383 | assert!(i128::try_from(under_i128_min).is_err()); 384 | } 385 | 386 | #[test] 387 | fn felt_from_into_bool() { 388 | assert!(Felt::from(true) == Felt::ONE); 389 | assert!(Felt::from(false) == Felt::ZERO); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/secret_felt.rs: -------------------------------------------------------------------------------- 1 | use crate::felt::{Felt, FromStrError}; 2 | use rand::{CryptoRng, RngCore}; 3 | use subtle::ConstantTimeEq; 4 | use zeroize::{Zeroize, Zeroizing}; 5 | 6 | #[cfg(not(feature = "std"))] 7 | use super::alloc::{boxed::Box, string::String, vec::Vec}; 8 | 9 | /// A wrapper for a [Felt] that ensures the value is securely zeroized when dropped. 10 | /// 11 | /// This type provides secure handling of sensitive [Felt] values (like private keys) 12 | /// by ensuring that the memory is properly cleared when the value is no longer needed. 13 | #[derive(Eq)] 14 | pub struct SecretFelt(Box); 15 | 16 | impl zeroize::DefaultIsZeroes for Felt {} 17 | 18 | impl Zeroize for SecretFelt { 19 | fn zeroize(&mut self) { 20 | self.0.zeroize(); 21 | } 22 | } 23 | 24 | impl Drop for SecretFelt { 25 | fn drop(&mut self) { 26 | self.zeroize(); 27 | } 28 | } 29 | 30 | impl SecretFelt { 31 | /// Creates a new [SecretFelt] from a [Felt] value and zeroize the original. 32 | /// 33 | /// It takes a mutable reference to a [Felt] value, creates a copy, 34 | /// and then zeroize the original value to ensure it doesn't remain in memory. 35 | /// 36 | /// # Warning 37 | /// 38 | /// Avoid moving the secret [Felt] in memory and avoid intermediate 39 | /// operations between the [Felt] creation and the [SecretFelt] initialization 40 | /// in order to not leave any copies of the value in memory 41 | /// 42 | /// # Example 43 | /// 44 | /// ``` 45 | /// use starknet_types_core::felt::{Felt, secret_felt::SecretFelt}; 46 | /// 47 | /// let mut private_key = Felt::from_hex_unwrap("0x2d39148a92f479fb077389d"); 48 | /// let secret_felt = SecretFelt::from_felt(&mut private_key); 49 | /// // private_key is now zeroized (set to Felt::ZERO) 50 | /// ``` 51 | pub fn from_felt(secret_felt: &mut Felt) -> Self { 52 | let boxed_copy = Box::new(*secret_felt); 53 | secret_felt.zeroize(); 54 | Self(boxed_copy) 55 | } 56 | 57 | /// Creates a new [SecretFelt] from a hex String and zeroized the original String. 58 | /// 59 | /// 60 | /// # Warning 61 | /// Make sure the String is initialized in a secure way. 62 | /// e.g. read from a file. 63 | /// 64 | /// # Example 65 | /// ``` 66 | /// use std::fs; 67 | /// use starknet_types_core::felt::secret_felt::SecretFelt; 68 | /// use std::str::FromStr; 69 | /// 70 | /// let mut private_key = String::from_str("0x2d39148a92f479fb077389d").unwrap(); 71 | /// let secret_felt = SecretFelt::from_hex_string(&mut private_key).unwrap(); 72 | /// ``` 73 | pub fn from_hex_string(hex: &mut String) -> Result { 74 | let secret_felt = Felt::from_hex(hex)?; 75 | hex.zeroize(); 76 | Ok(Self(Box::new(secret_felt))) 77 | } 78 | 79 | /// Creates a new [SecretFelt] from its big-endian representation in a Vec of length 32. 80 | /// Internally it uses [from_bytes_be](Felt::from_bytes_be). 81 | /// The input will be zeroized after calling this function 82 | pub fn from_bytes_be(secret: &mut [u8; 32]) -> Self { 83 | let secret_felt = Self(Box::new(Felt::from_bytes_be(secret))); 84 | secret.zeroize(); 85 | secret_felt 86 | } 87 | 88 | /// Creates a new [SecretFelt] from its little-endian representation in a Vec of length 32. 89 | /// Internally it uses [from_bytes_le](Felt::from_bytes_le). 90 | /// The input will be zeroized after calling this function 91 | pub fn from_bytes_le(secret: &mut [u8; 32]) -> Self { 92 | let secret_felt = Self(Box::new(Felt::from_bytes_le(secret))); 93 | secret.zeroize(); 94 | secret_felt 95 | } 96 | 97 | /// Create a new [SecretFelt] from cryptographically secure PRNG 98 | /// 99 | /// # Example 100 | /// ``` 101 | /// use starknet_types_core::felt::secret_felt::SecretFelt; 102 | /// use rand_chacha::ChaCha20Rng; 103 | /// use rand::SeedableRng; 104 | /// 105 | /// let rng = ChaCha20Rng::from_os_rng(); 106 | /// let secret_key = SecretFelt::from_random(rng); 107 | /// ``` 108 | pub fn from_random(mut rng: T) -> Self 109 | where 110 | T: RngCore + CryptoRng, 111 | { 112 | let mut buffer = [0u8; 32]; 113 | rng.fill_bytes(&mut buffer); 114 | 115 | let secret_felt = Self(Box::new(Felt::from_bytes_be(&buffer))); 116 | buffer.zeroize(); 117 | 118 | secret_felt 119 | } 120 | 121 | /// Returns a safe copy of the inner value. 122 | /// 123 | /// # Warning 124 | /// 125 | /// Be careful not to copy the value elsewhere, as that would defeat 126 | /// the security guarantees of this type. 127 | pub fn inner_value(&self) -> Zeroizing { 128 | Zeroizing::new(*self.0.clone()) 129 | } 130 | } 131 | 132 | /// Constant time equality check for [SecretFelt] 133 | impl PartialEq for SecretFelt { 134 | fn eq(&self, other: &Self) -> bool { 135 | let mut self_limbs = self.0 .0.representative().limbs; 136 | let mut other_limbs = other.0 .0.representative().limbs; 137 | 138 | let is_eq: bool = self_limbs.ct_eq(&other_limbs).into(); 139 | 140 | self_limbs.zeroize(); 141 | other_limbs.zeroize(); 142 | 143 | is_eq 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod test { 149 | use crate::felt::{secret_felt::SecretFelt, Felt}; 150 | use core::mem::size_of; 151 | use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; 152 | use std::{ops::Deref, str::FromStr}; 153 | use zeroize::Zeroize; 154 | 155 | #[test] 156 | fn test_zeroize_secret_felt() { 157 | let mut signing_key = SecretFelt::from_random(ChaCha20Rng::seed_from_u64(1)); 158 | signing_key.zeroize(); 159 | 160 | // Get a pointer to the inner Felt 161 | let ptr = signing_key.inner_value().deref() as *const Felt as *const u8; 162 | let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::()) }; 163 | 164 | // Check that the memory is zeroed 165 | assert_eq!( 166 | Felt::from_bytes_be_slice(after_zeroize), 167 | Felt::ZERO, 168 | "Memory was not properly zeroized" 169 | ); 170 | } 171 | 172 | #[test] 173 | fn test_zeroize_original() { 174 | let mut private_key = Felt::from_hex_unwrap( 175 | "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 176 | ); 177 | let mut signing_key = SecretFelt::from_felt(&mut private_key); 178 | signing_key.zeroize(); 179 | 180 | // Get a pointer to the original memory 181 | let ptr = private_key.as_ref() as *const Felt as *const u8; 182 | let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::()) }; 183 | 184 | // Check that original value was zeroized 185 | assert_eq!( 186 | Felt::from_bytes_be_slice(after_zeroize), 187 | Felt::ZERO, 188 | "Original value was not properly zeroized" 189 | ); 190 | } 191 | 192 | #[test] 193 | fn test_zeroize_hex_string() { 194 | let mut private_key = 195 | String::from_str("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") 196 | .unwrap(); 197 | 198 | let mut signing_key = SecretFelt::from_hex_string(&mut private_key).unwrap(); 199 | signing_key.zeroize(); 200 | 201 | let ptr = private_key.as_ptr() as *const Felt as *const u8; 202 | let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::()) }; 203 | 204 | assert_eq!( 205 | Felt::from_bytes_be_slice(after_zeroize), 206 | Felt::ZERO, 207 | "Original value was not properly zeroized" 208 | ); 209 | } 210 | 211 | #[test] 212 | fn test_zeroize_on_drop() { 213 | let mut private_key = Felt::from_hex_unwrap( 214 | "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 215 | ); 216 | 217 | // make a copy, the initial Felt will be zeroized 218 | let pk_copy = private_key; 219 | 220 | let raw_ptr; 221 | { 222 | let signing_key = SecretFelt::from_felt(&mut private_key); 223 | 224 | let inner_value = *signing_key.0; 225 | raw_ptr = &inner_value as *const Felt as *const u8; 226 | 227 | // Verify it's not zero before dropping 228 | let before_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::()) }; 229 | assert!( 230 | !before_drop.iter().all(|&b| b == 0), 231 | "Memory should not be zeroed yet" 232 | ); 233 | } // At this point, signing_key has been dropped and zeroized 234 | 235 | // Check that the memory is zeroed after drop 236 | let after_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::()) }; 237 | 238 | let felt_after_drop = Felt::from_bytes_be_slice(after_drop); 239 | 240 | // Memory is not zero because the compiler reuse that memory slot 241 | // but should not be equal to the initial value 242 | assert_ne!(pk_copy, felt_after_drop); 243 | } 244 | 245 | #[test] 246 | fn test_inner_value() { 247 | let mut private_key = Felt::from_hex_unwrap( 248 | "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 249 | ); 250 | 251 | // make a copy, the initial Felt will be zeroized 252 | let pk_copy = private_key; 253 | 254 | let raw_ptr; 255 | { 256 | let signing_key = SecretFelt::from_felt(&mut private_key); 257 | 258 | let inner_felt = signing_key.inner_value(); 259 | 260 | assert_eq!(*inner_felt, pk_copy); 261 | 262 | raw_ptr = inner_felt.as_ref() as *const Felt as *const u8; 263 | } // inner_value should be zeroized when is out of scope 264 | 265 | let after_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::()) }; 266 | let felt_after_drop = Felt::from_bytes_be_slice(after_drop); 267 | 268 | // Memory is not zero because the compiler reuse that memory slot 269 | // but should not be equal to the initial value 270 | assert_ne!(pk_copy, felt_after_drop); 271 | } 272 | 273 | #[test] 274 | fn test_partial_eq() { 275 | let mut private_key1 = [255u8; 32]; 276 | let mut private_key2 = [255u8; 32]; 277 | let mut private_key3 = [254u8; 32]; 278 | 279 | let signing_key1 = SecretFelt::from_bytes_be(&mut private_key1); 280 | let signing_key2 = SecretFelt::from_bytes_be(&mut private_key2); 281 | let signing_key3 = SecretFelt::from_bytes_be(&mut private_key3); 282 | 283 | assert!(signing_key1.eq(&signing_key2)); 284 | assert!(signing_key1.ne(&signing_key3)); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/felt/serde.rs: -------------------------------------------------------------------------------- 1 | pub extern crate alloc; 2 | 3 | use alloc::format; 4 | use alloc::string::String; 5 | use core::fmt; 6 | use lambdaworks_math::field::{ 7 | element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, 8 | }; 9 | use serde::{ 10 | de::{self}, 11 | Deserialize, Serialize, 12 | }; 13 | 14 | use super::Felt; 15 | 16 | impl Serialize for Felt { 17 | fn serialize(&self, serializer: S) -> Result 18 | where 19 | S: ::serde::Serializer, 20 | { 21 | if serializer.is_human_readable() { 22 | serializer.serialize_str(&format!("{:#x}", self)) 23 | } else { 24 | let be_bytes = self.to_bytes_be(); 25 | let first_significant_byte_index = be_bytes.iter().position(|&b| b != 0).unwrap_or(31); 26 | serializer.serialize_bytes(&be_bytes[first_significant_byte_index..]) 27 | } 28 | } 29 | } 30 | 31 | impl<'de> Deserialize<'de> for Felt { 32 | fn deserialize(deserializer: D) -> Result 33 | where 34 | D: ::serde::Deserializer<'de>, 35 | { 36 | if deserializer.is_human_readable() { 37 | deserializer.deserialize_str(FeltVisitor) 38 | } else { 39 | deserializer.deserialize_bytes(FeltVisitor) 40 | } 41 | } 42 | } 43 | 44 | struct FeltVisitor; 45 | 46 | impl de::Visitor<'_> for FeltVisitor { 47 | type Value = Felt; 48 | 49 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | // The message below is append to “This Visitor expects to receive …” 51 | write!( 52 | formatter, 53 | "a 32 byte array ([u8;32]) or a hexadecimal string." 54 | ) 55 | } 56 | 57 | fn visit_str(self, value: &str) -> Result 58 | where 59 | E: de::Error, 60 | { 61 | // Strip the '0x' prefix from the encoded hex string 62 | value 63 | .strip_prefix("0x") 64 | .and_then(|v| FieldElement::::from_hex(v).ok()) 65 | .map(Felt) 66 | .ok_or(String::from("expected hex string to be prefixed by '0x'")) 67 | .map_err(de::Error::custom) 68 | } 69 | 70 | fn visit_bytes(self, value: &[u8]) -> Result 71 | where 72 | E: de::Error, 73 | { 74 | if value.len() > 32 { 75 | return Err(de::Error::invalid_length(value.len(), &self)); 76 | } 77 | 78 | let mut buffer = [0u8; 32]; 79 | buffer[32 - value.len()..].copy_from_slice(value); 80 | Ok(Felt::from_bytes_be(&buffer)) 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::*; 87 | use bincode::Options; 88 | use proptest::prelude::*; 89 | use serde_test::{assert_tokens, Configure, Token}; 90 | 91 | #[test] 92 | fn serde() { 93 | assert_tokens(&Felt::ZERO.readable(), &[Token::String("0x0")]); 94 | assert_tokens(&Felt::TWO.readable(), &[Token::String("0x2")]); 95 | assert_tokens(&Felt::THREE.readable(), &[Token::String("0x3")]); 96 | assert_tokens( 97 | &Felt::MAX.readable(), 98 | &[Token::String( 99 | "0x800000000000011000000000000000000000000000000000000000000000000", 100 | )], 101 | ); 102 | 103 | assert_tokens(&Felt::ZERO.compact(), &[Token::Bytes(&[0; 1])]); 104 | assert_tokens(&Felt::TWO.compact(), &[Token::Bytes(&[2])]); 105 | assert_tokens(&Felt::THREE.compact(), &[Token::Bytes(&[3])]); 106 | static MAX: [u8; 32] = [ 107 | 8, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 108 | 0, 0, 0, 109 | ]; 110 | assert_tokens(&Felt::MAX.compact(), &[Token::Bytes(&MAX)]); 111 | assert_tokens( 112 | &Felt::from_hex_unwrap("0xbabe").compact(), 113 | &[Token::Bytes(&[0xba, 0xbe])], 114 | ); 115 | assert_tokens( 116 | &Felt::from_hex_unwrap("0xba000000be").compact(), 117 | &[Token::Bytes(&[0xba, 0, 0, 0, 0xbe])], 118 | ); 119 | assert_tokens( 120 | &Felt::from_hex_unwrap("0xbabe0000").compact(), 121 | &[Token::Bytes(&[0xba, 0xbe, 0, 0])], 122 | ); 123 | } 124 | 125 | #[test] 126 | fn backward_compatible_deserialization() { 127 | static TWO_SERIALIZED_USING_PREVIOUS_IMPLEMENTATION: [u8; 33] = [ 128 | 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129 | 0, 0, 0, 2, 130 | ]; 131 | 132 | let options = bincode::DefaultOptions::new(); 133 | let deserialized = options 134 | .deserialize(&TWO_SERIALIZED_USING_PREVIOUS_IMPLEMENTATION) 135 | .unwrap(); 136 | assert_eq!(Felt::TWO, deserialized); 137 | } 138 | 139 | proptest! { 140 | #[test] 141 | fn compact_round_trip(ref x in any::()) { 142 | let serialized = bincode::serialize(&x).unwrap(); 143 | let deserialized: Felt = bincode::deserialize(&serialized).unwrap(); 144 | prop_assert_eq!(x, &deserialized); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/hash/blake2s.rs: -------------------------------------------------------------------------------- 1 | //! Blake2s-256 hash implementation for Starknet field elements. 2 | //! 3 | //! This module implements Blake2s-256 hashing for `Felt` values following the 4 | //! Cairo specification. The implementation includes specific data serialization 5 | //! choices that match the Cairo reference implementation. 6 | //! 7 | //! ## Data Serialization 8 | //! 9 | //! Each `Felt` is encoded into 32-bit words based on its magnitude: 10 | //! - **Small values** (< 2^63): encoded as 2 words from the last 8 bytes 11 | //! - **Large values** (≥ 2^63): encoded as 8 words from the full 32-byte representation, 12 | //! with the MSB of the first word set as a marker 13 | //! 14 | //! The resulting words are serialized in little-endian byte order before hashing. 15 | //! The Blake2s-256 digest is then packed into a `Felt` using all 256 bits modulo the field prime. 16 | //! 17 | //! ## Reference Implementation 18 | //! 19 | //! This implementation follows the Cairo specification: 20 | //! - [Cairo Blake2s implementation](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/cairo_blake2s/blake2s.cairo) 21 | 22 | use crate::felt::Felt; 23 | use blake2::Blake2s256; 24 | use digest::Digest; 25 | 26 | extern crate alloc; 27 | use alloc::vec::Vec; 28 | 29 | use super::traits::StarkHash; 30 | 31 | pub struct Blake2Felt252; 32 | 33 | impl StarkHash for Blake2Felt252 { 34 | fn hash(felt_0: &Felt, felt_1: &Felt) -> Felt { 35 | Self::hash_array(&[*felt_0, *felt_1]) 36 | } 37 | 38 | fn hash_array(felts: &[Felt]) -> Felt { 39 | Self::encode_felt252_data_and_calc_blake_hash(felts) 40 | } 41 | 42 | fn hash_single(felt: &Felt) -> Felt { 43 | Self::hash_array(&[*felt]) 44 | } 45 | } 46 | 47 | impl Blake2Felt252 { 48 | /// Threshold for choosing the encoding strategy of a `Felt`. 49 | /// Values below `SMALL_THRESHOLD` are encoded as two `u32`s, 50 | /// while values at or above it are encoded as eight `u32`s 51 | /// (`SMALL_THRESHOLD` equals `2**63`). 52 | pub const SMALL_THRESHOLD: Felt = Felt::from_hex_unwrap("8000000000000000"); 53 | 54 | /// Encodes each `Felt` into 32-bit words: 55 | /// - **Small** values `< 2^63` get **2** words: `[ high_32_bits, low_32_bits ]` from the last 8 56 | /// bytes of the 256-bit BE representation. 57 | /// - **Large** values `>= 2^63` get **8** words: the full 32-byte big-endian split, **with** the 58 | /// MSB of the first word set as a marker (`+2^255`). 59 | /// 60 | /// # Returns 61 | /// A flat `Vec` containing all the unpacked words, in the same order. 62 | pub fn encode_felts_to_u32s(felts: &[Felt]) -> Vec { 63 | // MSB mask for the first u32 in the 8-limb case. 64 | const BIG_MARKER: u32 = 1 << 31; 65 | // Number of bytes per u32. 66 | const BYTES_PER_U32: usize = 4; 67 | 68 | let mut unpacked_u32s = Vec::new(); 69 | for felt in felts { 70 | let felt_as_be_bytes = felt.to_bytes_be(); 71 | if *felt < Self::SMALL_THRESHOLD { 72 | // small: 2 limbs only, extracts the 8 LSBs. 73 | let high = u32::from_be_bytes(felt_as_be_bytes[24..28].try_into().unwrap()); 74 | let low = u32::from_be_bytes(felt_as_be_bytes[28..32].try_into().unwrap()); 75 | unpacked_u32s.push(high); 76 | unpacked_u32s.push(low); 77 | } else { 78 | // big: 8 limbs, big‐endian order. 79 | let start = unpacked_u32s.len(); 80 | 81 | for chunk in felt_as_be_bytes.chunks_exact(BYTES_PER_U32) { 82 | unpacked_u32s.push(u32::from_be_bytes(chunk.try_into().unwrap())); 83 | } 84 | // set the MSB of the very first limb as the Cairo hint does with "+ 2**255". 85 | unpacked_u32s[start] |= BIG_MARKER; 86 | } 87 | } 88 | unpacked_u32s 89 | } 90 | 91 | /// Packs the first 8 little-endian 32-bit words (32 bytes) of `bytes` 92 | /// into a single 252-bit Felt. 93 | fn pack_256_le_to_felt(bytes: &[u8]) -> Felt { 94 | assert!(bytes.len() >= 32, "need at least 32 bytes to pack 8 words"); 95 | 96 | // 1) copy your 32-byte LE-hash into the low 32 bytes of a 32-byte buffer. 97 | let mut buf = [0u8; 32]; 98 | buf[..32].copy_from_slice(&bytes[..32]); 99 | 100 | // 2) interpret the whole 32-byte buffer as a little-endian Felt. 101 | Felt::from_bytes_le(&buf) 102 | } 103 | 104 | /// Hashes the data with Blake2s-256 and packs the result into a Felt. 105 | pub fn hash_and_pack_to_felt(data: &[u8]) -> Felt { 106 | let mut hasher = Blake2s256::new(); 107 | hasher.update(data); 108 | let hash32 = hasher.finalize(); 109 | Self::pack_256_le_to_felt(hash32.as_slice()) 110 | } 111 | 112 | /// Encodes a slice of `Felt` values into 32-bit words exactly as Cairo's 113 | /// [`encode_felt252_to_u32s`](https://github.com/starkware-libs/cairo-lang/blob/ab8be40403a7634ba296c467b26b8bd945ba5cfa/src/starkware/cairo/common/cairo_blake2s/blake2s.cairo) 114 | /// hint does, then hashes the resulting byte stream with Blake2s-256 and 115 | /// returns the full 256-bit digest as a `Felt`. 116 | pub fn encode_felt252_data_and_calc_blake_hash(data: &[Felt]) -> Felt { 117 | // 1) Unpack each Felt into 2 or 8 u32. 118 | let u32_words = Self::encode_felts_to_u32s(data); 119 | 120 | // 2) Serialize the u32 limbs into a little-endian byte stream. 121 | let mut byte_stream = Vec::with_capacity(u32_words.len() * 4); 122 | for word in u32_words { 123 | byte_stream.extend_from_slice(&word.to_le_bytes()); 124 | } 125 | 126 | // 3) Compute Blake2s-256 over the bytes and pack the result into a Felt. 127 | Self::hash_and_pack_to_felt(&byte_stream) 128 | } 129 | } 130 | 131 | #[cfg(test)] 132 | mod tests { 133 | use super::*; 134 | use crate::felt::Felt; 135 | use rstest::rstest; 136 | 137 | /// Test two-limb encoding for a small Felt (< 2^63) into high and low 32-bit words. 138 | #[test] 139 | fn test_encode_felts_to_u32s_small() { 140 | // Last 8 bytes of 0x1122334455667788 in big-endian are [0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88]. 141 | // High word = 0x11223344, low word = 0x55667788. 142 | let val = Felt::from_hex_unwrap("1122334455667788"); 143 | let words = Blake2Felt252::encode_felts_to_u32s(&[val]); 144 | assert_eq!(words, vec![0x11223344, 0x55667788]); 145 | } 146 | 147 | /// Test eight-limb encoding and MSB marker for a big Felt (>= 2^63). 148 | #[test] 149 | fn test_encode_felts_to_u32s_big() { 150 | // The value 2^63 (0x8000000000000000) triggers the "big" path. 151 | let val = Felt::from_hex_unwrap("8000000000000000"); 152 | let words = Blake2Felt252::encode_felts_to_u32s(&[val]); 153 | 154 | // Expected limbs: split full 32-byte BE into eight 32-bit words. 155 | // The seventh BE chunk [0x80,0x00,0x00,0x00] becomes 0x80000000 at index 6. 156 | // Additionally, set the MSB marker (bit 31) on the first word. 157 | let mut expected = vec![0u32; 8]; 158 | expected[6] = 0x8000_0000; 159 | expected[0] |= 1 << 31; 160 | assert_eq!(words, expected); 161 | } 162 | 163 | /// Test packing of a 32-byte little-endian buffer into a 256-bit Felt. 164 | #[test] 165 | fn test_pack_256_le_to_felt_basic() { 166 | // Test with small values that won't trigger modular reduction. 167 | let mut buf = [0u8; 32]; 168 | buf[0] = 0x01; 169 | buf[1] = 0x02; 170 | buf[2] = 0x03; 171 | buf[3] = 0x04; 172 | // Leave the rest as zeros. 173 | 174 | let f = Blake2Felt252::pack_256_le_to_felt(&buf); 175 | let out = f.to_bytes_le(); 176 | 177 | // For small values, the first few bytes should match exactly. 178 | assert_eq!(out[0], 0x01); 179 | assert_eq!(out[1], 0x02); 180 | assert_eq!(out[2], 0x03); 181 | assert_eq!(out[3], 0x04); 182 | 183 | // Test that the packing formula works correctly for a simple case. 184 | let expected = Felt::from(0x01) 185 | + Felt::from(0x02) * Felt::from(1u64 << 8) 186 | + Felt::from(0x03) * Felt::from(1u64 << 16) 187 | + Felt::from(0x04) * Felt::from(1u64 << 24); 188 | assert_eq!(f, expected); 189 | 190 | // Test with a value that exceeds the field prime P to verify modular reduction. 191 | // Create a 32-byte buffer with all 0xFF bytes, representing 2^256 - 1. 192 | let max_buf = [0xFF_u8; 32]; 193 | let f_max = Blake2Felt252::pack_256_le_to_felt(&max_buf); 194 | 195 | // The result should be (2^256 - 1) mod P. 196 | // Since 2^256 = Felt::TWO.pow(256), we can compute this value directly. 197 | // This tests that modular reduction works correctly when exceeding the field prime. 198 | let two_pow_256_minus_one = Felt::TWO.pow(256u32) - Felt::ONE; 199 | assert_eq!(f_max, two_pow_256_minus_one); 200 | } 201 | 202 | /// Test that pack_256_le_to_felt panics when input is shorter than 32 bytes. 203 | #[test] 204 | #[should_panic(expected = "need at least 32 bytes to pack 8 words")] 205 | fn test_pack_256_le_to_felt_too_short() { 206 | let too_short = [0u8; 31]; 207 | Blake2Felt252::pack_256_le_to_felt(&too_short); 208 | } 209 | 210 | /// Test that hashing a single zero Felt produces the expected 256-bit Blake2s digest. 211 | #[test] 212 | fn test_hash_single_zero() { 213 | let zero = Felt::from_hex_unwrap("0"); 214 | let hash = Blake2Felt252::hash_single(&zero); 215 | let expected = Felt::from_hex_unwrap( 216 | "5768af071a2f8df7c9df9dc4ca0e7a1c5908d5eff88af963c3264f412dbdf43", 217 | ); 218 | assert_eq!(hash, expected); 219 | } 220 | 221 | /// Test that hashing an array of Felts [1, 2] produces the expected 256-bit Blake2s digest. 222 | #[test] 223 | fn test_hash_array_one_two() { 224 | let one = Felt::from_hex_unwrap("1"); 225 | let two = Felt::from_hex_unwrap("2"); 226 | let hash = Blake2Felt252::hash_array(&[one, two]); 227 | let expected = Felt::from_hex_unwrap( 228 | "5534c03a14b214436366f30e9c77b6e56c8835de7dc5aee36957d4384cce66d", 229 | ); 230 | assert_eq!(hash, expected); 231 | } 232 | 233 | /// Test the encode_felt252_data_and_calc_blake_hash function 234 | /// with the same result as the Cairo v0.14 version. 235 | #[rstest] 236 | #[case::empty(vec![], "874258848688468311465623299960361657518391155660316941922502367727700287818")] 237 | #[case::boundary_under_2_63(vec![Felt::from((1u64 << 63) - 1)], "94160078030592802631039216199460125121854007413180444742120780261703604445")] 238 | #[case::boundary_at_2_63(vec![Felt::from(1u64 << 63)], "318549634615606806810268830802792194529205864650702991817600345489579978482")] 239 | #[case::very_large_felt(vec![Felt::from_hex_unwrap("800000000000011000000000000000000000000000000000000000000000000")], "3505594194634492896230805823524239179921427575619914728883524629460058657521")] 240 | #[case::mixed_small_large(vec![Felt::from(42), Felt::from(1u64 << 63), Felt::from(1337)], "1127477916086913892828040583976438888091205536601278656613505514972451246501")] 241 | #[case::max_u64(vec![Felt::from(u64::MAX)], "3515074221976790747383295076946184515593027667350620348239642126105984996390")] 242 | fn test_encode_felt252_data_and_calc_blake_hash( 243 | #[case] input: Vec, 244 | #[case] expected_result: &str, 245 | ) { 246 | let result = Blake2Felt252::encode_felt252_data_and_calc_blake_hash(&input); 247 | let expected = Felt::from_dec_str(expected_result).unwrap(); 248 | assert_eq!( 249 | result, expected, 250 | "rust_implementation: {result:?} != cairo_implementation: {expected:?}" 251 | ); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/hash/mod.rs: -------------------------------------------------------------------------------- 1 | mod blake2s; 2 | mod pedersen; 3 | mod poseidon; 4 | mod traits; 5 | 6 | pub use self::blake2s::*; 7 | pub use self::pedersen::*; 8 | pub use self::poseidon::*; 9 | pub use self::traits::*; 10 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/hash/pedersen.rs: -------------------------------------------------------------------------------- 1 | use crate::felt::Felt; 2 | use lambdaworks_crypto::hash::pedersen::{Pedersen as PedersenLambdaworks, PedersenStarkCurve}; 3 | use lambdaworks_math::field::{ 4 | element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, 5 | }; 6 | 7 | use super::traits::StarkHash; 8 | 9 | pub struct Pedersen; 10 | 11 | impl StarkHash for Pedersen { 12 | /// Computes the Pedersen hash of two Felts, as defined 13 | /// in 14 | fn hash(felt_0: &Felt, felt_1: &Felt) -> Felt { 15 | Felt(PedersenStarkCurve::hash(&felt_0.0, &felt_1.0)) 16 | } 17 | 18 | /// Computes the Pedersen hash of an array of Felts, as defined 19 | /// in 20 | /// 21 | /// Warning: there is room for collision as: 22 | /// Pedersen::hash_array([value]) and Pedersen::hash(Pedersen::hash(0, value), 1) will return the same values 23 | fn hash_array(felts: &[Felt]) -> Felt { 24 | let data_len = Felt::from(felts.len()); 25 | let current_hash: FieldElement = felts 26 | .iter() 27 | .fold(FieldElement::zero(), |current_hash, felt| { 28 | PedersenStarkCurve::hash(¤t_hash, &felt.0) 29 | }); 30 | Felt(PedersenStarkCurve::hash(¤t_hash, &data_len.0)) 31 | } 32 | 33 | /// Computes the Pedersen hash of a single Felt 34 | /// 35 | /// Warning: there is room for collision as: 36 | /// Pedersen::hash_single(value) and Pedersen::hash(value, 0) will return the same values 37 | fn hash_single(felt: &Felt) -> Felt { 38 | Felt(PedersenStarkCurve::hash(&felt.0, &Felt::from(0).0)) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn test_pedersen_hash_single() { 48 | let x = 49 | Felt::from_hex("0x03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb") 50 | .unwrap(); 51 | assert_eq!( 52 | Pedersen::hash_single(&x), 53 | Felt::from_hex("0x460ded9dacd215bcfc43f1b30b2a02690378e00f82a2283617d47d948c7b7f1") 54 | .unwrap() 55 | ) 56 | } 57 | 58 | #[test] 59 | fn test_pedersen_hash_collision() { 60 | let x = 61 | Felt::from_hex("0x03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb") 62 | .unwrap(); 63 | assert_eq!( 64 | Pedersen::hash_single(&x), 65 | Pedersen::hash(&x, &Felt::from(0)) 66 | ) 67 | } 68 | 69 | #[test] 70 | fn test_pedersen_hash() { 71 | let x = 72 | Felt::from_hex("0x03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb") 73 | .unwrap(); 74 | let y = 75 | Felt::from_hex("0x0208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a") 76 | .unwrap(); 77 | 78 | assert_eq!( 79 | Pedersen::hash(&x, &y), 80 | Felt::from_hex("0x030e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662") 81 | .unwrap() 82 | ); 83 | } 84 | 85 | #[test] 86 | fn test_pedersen_hash_array() { 87 | let a = Felt::from_hex("0xaa").unwrap(); 88 | let b = Felt::from_hex("0xbb").unwrap(); 89 | let c = Felt::from_hex("0xcc").unwrap(); 90 | let expected = 91 | Felt::from_hex("0x10808e8929644950878c4f71326e47c6b584d9cfea2de0415daf8def0f5e89f") 92 | .unwrap(); 93 | assert_eq!(Pedersen::hash_array(&[a, b, c]), expected); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/hash/poseidon.rs: -------------------------------------------------------------------------------- 1 | use crate::felt::Felt; 2 | use lambdaworks_crypto::hash::poseidon::{ 3 | starknet::PoseidonCairoStark252, Poseidon as PoseidonLambdaworks, 4 | }; 5 | use lambdaworks_math::field::{ 6 | element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, 7 | }; 8 | 9 | use super::traits::StarkHash; 10 | 11 | pub struct Poseidon; 12 | 13 | impl StarkHash for Poseidon { 14 | /// Computes the Poseidon hash of two Felts, as defined 15 | /// in 16 | fn hash(felt_0: &Felt, felt_1: &Felt) -> Felt { 17 | Felt(PoseidonCairoStark252::hash(&felt_0.0, &felt_1.0)) 18 | } 19 | /// Computes the Poseidon hash of an array of Felts, as defined 20 | /// in 21 | fn hash_array(felts: &[Felt]) -> Felt { 22 | // Non-copy but less dangerous than transmute 23 | // https://doc.rust-lang.org/std/mem/fn.transmute.html#alternatives 24 | Felt(PoseidonCairoStark252::hash_many(unsafe { 25 | core::slice::from_raw_parts( 26 | felts.as_ptr() as *const FieldElement, 27 | felts.len(), 28 | ) 29 | })) 30 | } 31 | 32 | fn hash_single(felt: &Felt) -> Felt { 33 | Felt(PoseidonCairoStark252::hash_single(&felt.0)) 34 | } 35 | } 36 | 37 | impl Poseidon { 38 | /// Computes the Hades permutation over a mutable state of 3 Felts, as defined 39 | /// in 40 | pub fn hades_permutation(state: &mut [Felt; 3]) { 41 | let mut state_inner = [state[0].0, state[1].0, state[2].0]; 42 | PoseidonCairoStark252::hades_permutation(&mut state_inner); 43 | for i in 0..3 { 44 | state[i] = Felt(state_inner[i]); 45 | } 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | 53 | #[test] 54 | fn test_poseidon_single() { 55 | let x = Felt::from_hex("0x9").unwrap(); 56 | 57 | assert_eq!( 58 | Poseidon::hash_single(&x), 59 | Felt::from_hex("0x3bb3b91c714cb47003947f36dadc98326176963c434cd0a10320b8146c948b3") 60 | .unwrap() 61 | ); 62 | } 63 | 64 | #[test] 65 | fn test_poseidon_hash() { 66 | let x = 67 | Felt::from_hex("0x03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb") 68 | .unwrap(); 69 | let y = 70 | Felt::from_hex("0x0208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a") 71 | .unwrap(); 72 | 73 | assert_eq!( 74 | Poseidon::hash(&x, &y), 75 | Felt::from_hex("0x67c6a2e2d0c7867f97444ae17956dbc89d40ad22255bb06f5f6c515958926ed") 76 | .unwrap() 77 | ); 78 | } 79 | 80 | #[test] 81 | fn test_poseidon_hash_array() { 82 | let a = Felt::from_hex("0xaa").unwrap(); 83 | let b = Felt::from_hex("0xbb").unwrap(); 84 | let c = Felt::from_hex("0xcc").unwrap(); 85 | let expected = 86 | Felt::from_hex("0x2742e049f7e1613e4a014efeec0d742882a798ae0af8b8dd730358c23848775") 87 | .unwrap(); 88 | assert_eq!(Poseidon::hash_array(&[a, b, c]), expected); 89 | } 90 | 91 | #[test] 92 | fn test_hades_permutation() { 93 | let mut state = [ 94 | Felt::from_hex("0x9").unwrap(), 95 | Felt::from_hex("0xb").unwrap(), 96 | Felt::from_hex("0x2").unwrap(), 97 | ]; 98 | let expected = [ 99 | Felt::from_hex("0x510f3a3faf4084e3b1e95fd44c30746271b48723f7ea9c8be6a9b6b5408e7e6") 100 | .unwrap(), 101 | Felt::from_hex("0x4f511749bd4101266904288021211333fb0a514cb15381af087462fa46e6bd9") 102 | .unwrap(), 103 | Felt::from_hex("0x186f6dd1a6e79cb1b66d505574c349272cd35c07c223351a0990410798bb9d8") 104 | .unwrap(), 105 | ]; 106 | Poseidon::hades_permutation(&mut state); 107 | 108 | assert_eq!(state, expected); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/hash/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::felt::Felt; 2 | 3 | pub trait StarkHash { 4 | /// Computes the hash of two Felt 5 | fn hash(felt_0: &Felt, felt_1: &Felt) -> Felt; 6 | 7 | /// Computes the hash of an array of Felts, 8 | /// as defined in 9 | fn hash_array(felts: &[Felt]) -> Felt; 10 | 11 | fn hash_single(felt: &Felt) -> Felt; 12 | } 13 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | #[cfg(feature = "curve")] 4 | pub mod curve; 5 | #[cfg(feature = "hash")] 6 | pub mod hash; 7 | 8 | pub mod felt; 9 | pub mod qm31; 10 | 11 | #[cfg(any(feature = "std", feature = "alloc"))] 12 | pub mod short_string; 13 | pub mod u256; 14 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/qm31/mod.rs: -------------------------------------------------------------------------------- 1 | //! A value in the Degree-4 (quadruple) extension of the Mersenne 31 field. 2 | //! 3 | //! The Marsenne 31 field is used by the Stwo prover. 4 | 5 | use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub}; 6 | 7 | use lambdaworks_math::field::{ 8 | element::FieldElement, 9 | errors::FieldError, 10 | fields::mersenne31::{ 11 | extensions::Degree4ExtensionField, 12 | field::{Mersenne31Field, MERSENNE_31_PRIME_FIELD_ORDER}, 13 | }, 14 | traits::IsField, 15 | }; 16 | 17 | #[cfg(feature = "num-traits")] 18 | mod num_traits_impl; 19 | 20 | use crate::felt::Felt; 21 | 22 | /// A value in the Degree-4 (quadruple) extension of the Mersenne 31 (M31) field. 23 | /// 24 | /// Each QM31 value is represented by two values in the Degree-2 (complex) 25 | /// extension, and each of these is represented by two values in the base 26 | /// field. Thus, a QM31 is represented by four M31 coordinates. 27 | /// 28 | /// An M31 coordinate fits in 31 bits, as it has a maximum value of: `(1 << 31) - 1`. 29 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 30 | pub struct QM31(pub FieldElement); 31 | 32 | #[derive(Debug, Clone, Copy)] 33 | pub struct InvalidQM31Packing(pub Felt); 34 | 35 | #[cfg(feature = "std")] 36 | impl std::error::Error for InvalidQM31Packing {} 37 | 38 | #[cfg(feature = "std")] 39 | impl std::fmt::Display for InvalidQM31Packing { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | write!(f, "felt is not a packed QM31: {}", self.0) 42 | } 43 | } 44 | 45 | impl QM31 { 46 | /// Creates a QM31 from four M31 elements. 47 | pub fn from_coefficients(a: u32, b: u32, c: u32, d: u32) -> Self { 48 | Self(Degree4ExtensionField::const_from_coefficients( 49 | Mersenne31Field::from_base_type(a), 50 | Mersenne31Field::from_base_type(b), 51 | Mersenne31Field::from_base_type(c), 52 | Mersenne31Field::from_base_type(d), 53 | )) 54 | } 55 | 56 | /// Extracts M31 elements from a QM31. 57 | pub fn to_coefficients(&self) -> (u32, u32, u32, u32) { 58 | // Take CM31 coordinates from QM31. 59 | let [a, b] = self.0.value(); 60 | 61 | // Take M31 coordinates from both CM31. 62 | let [c1, c2] = a.value(); 63 | let [c3, c4] = b.value(); 64 | 65 | ( 66 | c1.representative(), 67 | c2.representative(), 68 | c3.representative(), 69 | c4.representative(), 70 | ) 71 | } 72 | 73 | /// Packs the [QM31] into a [Felt]. 74 | /// 75 | /// Stores the four M31 coordinates in the first 144 bits of a Felt. Each 76 | /// coordinate takes 36 bits, and the resulting felt is equal to: 77 | /// `C1 + C2 << 36 + C3 << 72 + C4 << 108` 78 | /// 79 | /// Why the stride between coordinates is 36 instead of 31? In Stwo, Felt 80 | /// elements are stored in memory as 28 M31s, each representing 9 bits 81 | /// (that representation is efficient for multiplication). 36 is the first 82 | /// multiple of 9 that is greater than 31. 83 | pub fn pack_into_felt(&self) -> Felt { 84 | let (c1, c2, c3, c4) = self.to_coefficients(); 85 | 86 | // Pack as: c1 + c2 << 36 + c3 << 36*2 + c4 << 36*3. 87 | let lo = c1 as u128 + ((c2 as u128) << 36); 88 | let hi = c3 as u128 + ((c4 as u128) << 36); 89 | let mut felt_bytes = [0u8; 32]; 90 | felt_bytes[0..9].copy_from_slice(&lo.to_le_bytes()[0..9]); 91 | felt_bytes[9..18].copy_from_slice(&hi.to_le_bytes()[0..9]); 92 | Felt::from_bytes_le(&felt_bytes) 93 | } 94 | 95 | /// Unpacks a [QM31] from the [Felt] 96 | /// 97 | /// See the method [QM31::pack_into_felt] for a detailed explanation on the 98 | /// packing format. 99 | pub fn unpack_from_felt(felt: &Felt) -> Result { 100 | const MASK_36: u64 = (1 << 36) - 1; 101 | const MASK_8: u64 = (1 << 8) - 1; 102 | 103 | let digits = felt.to_le_digits(); 104 | 105 | // The QM31 is packed in the first 144 bits, 106 | // the remaining bits must be zero. 107 | if digits[3] != 0 || digits[2] >= 1 << 16 { 108 | return Err(InvalidQM31Packing(*felt)); 109 | } 110 | 111 | // Unpack as: c1 + c2 << 36 + c3 << 36*2 + c4 << 36*3. 112 | let c1 = digits[0] & MASK_36; 113 | let c2 = (digits[0] >> 36) + ((digits[1] & MASK_8) << 28); 114 | let c3 = (digits[1] >> 8) & MASK_36; 115 | let c4 = (digits[1] >> 44) + (digits[2] << 20); 116 | 117 | // Even though we use 36 bits for each coordinate, 118 | // the maximum value is still the field prime. 119 | for c in [c1, c2, c3, c4] { 120 | if c >= MERSENNE_31_PRIME_FIELD_ORDER as u64 { 121 | return Err(InvalidQM31Packing(*felt)); 122 | } 123 | } 124 | 125 | Ok(QM31(Degree4ExtensionField::const_from_coefficients( 126 | c1 as u32, c2 as u32, c3 as u32, c4 as u32, 127 | ))) 128 | } 129 | 130 | /// Multiplicative inverse inside field. 131 | pub fn inverse(&self) -> Result { 132 | Ok(Self(self.0.inv()?)) 133 | } 134 | } 135 | 136 | impl Add for QM31 { 137 | type Output = QM31; 138 | 139 | fn add(self, rhs: Self) -> Self::Output { 140 | Self(self.0.add(rhs.0)) 141 | } 142 | } 143 | impl Sub for QM31 { 144 | type Output = QM31; 145 | 146 | fn sub(self, rhs: Self) -> Self::Output { 147 | Self(self.0.sub(rhs.0)) 148 | } 149 | } 150 | impl Mul for QM31 { 151 | type Output = QM31; 152 | 153 | fn mul(self, rhs: Self) -> Self::Output { 154 | Self(self.0.mul(rhs.0)) 155 | } 156 | } 157 | impl Div for QM31 { 158 | type Output = Result; 159 | 160 | fn div(self, rhs: Self) -> Self::Output { 161 | Ok(Self(self.0.div(rhs.0)?)) 162 | } 163 | } 164 | impl AddAssign for QM31 { 165 | fn add_assign(&mut self, rhs: Self) { 166 | self.0.add_assign(rhs.0); 167 | } 168 | } 169 | impl MulAssign for QM31 { 170 | fn mul_assign(&mut self, rhs: Self) { 171 | self.0.mul_assign(rhs.0); 172 | } 173 | } 174 | impl Neg for QM31 { 175 | type Output = QM31; 176 | 177 | fn neg(self) -> Self::Output { 178 | Self(self.0.neg()) 179 | } 180 | } 181 | 182 | #[cfg(test)] 183 | mod test { 184 | use lambdaworks_math::field::fields::mersenne31::{ 185 | extensions::Degree4ExtensionField, field::MERSENNE_31_PRIME_FIELD_ORDER, 186 | }; 187 | use num_bigint::BigInt; 188 | 189 | use crate::{felt::Felt, qm31::QM31}; 190 | 191 | #[test] 192 | fn qm31_packing_and_unpacking() { 193 | const MAX: u32 = MERSENNE_31_PRIME_FIELD_ORDER - 1; 194 | 195 | let cases = [ 196 | [1, 2, 3, 4], 197 | [MAX, 0, 0, 0], 198 | [MAX, MAX, 0, 0], 199 | [MAX, MAX, MAX, 0], 200 | [MAX, MAX, MAX, MAX], 201 | ]; 202 | 203 | for [c1, c2, c3, c4] in cases { 204 | let qm31 = QM31(Degree4ExtensionField::const_from_coefficients( 205 | c1, c2, c3, c4, 206 | )); 207 | let packed_qm31 = qm31.pack_into_felt(); 208 | let unpacked_qm31 = QM31::unpack_from_felt(&packed_qm31).unwrap(); 209 | 210 | assert_eq!(qm31, unpacked_qm31) 211 | } 212 | } 213 | 214 | #[test] 215 | fn qm31_packing() { 216 | const MAX: u32 = MERSENNE_31_PRIME_FIELD_ORDER - 1; 217 | 218 | let cases = [ 219 | [1, 2, 3, 4], 220 | [MAX, 0, 0, 0], 221 | [MAX, MAX, 0, 0], 222 | [MAX, MAX, MAX, 0], 223 | [MAX, MAX, MAX, MAX], 224 | ]; 225 | 226 | for [c1, c2, c3, c4] in cases { 227 | let qm31 = QM31(Degree4ExtensionField::const_from_coefficients( 228 | c1, c2, c3, c4, 229 | )); 230 | let packed_qm31 = qm31.pack_into_felt(); 231 | 232 | let expected_packing = BigInt::from(c1) 233 | + (BigInt::from(c2) << 36) 234 | + (BigInt::from(c3) << 72) 235 | + (BigInt::from(c4) << 108); 236 | 237 | assert_eq!(packed_qm31, Felt::from(expected_packing)) 238 | } 239 | } 240 | 241 | #[test] 242 | fn qm31_invalid_packing() { 243 | const MAX: u64 = MERSENNE_31_PRIME_FIELD_ORDER as u64 - 1; 244 | 245 | let cases = [ 246 | [MAX + 1, 0, 0, 0], 247 | [0, MAX + 1, 0, 0], 248 | [0, 0, MAX + 1, 0], 249 | [0, 0, 0, MAX + 1], 250 | ]; 251 | 252 | for [c1, c2, c3, c4] in cases { 253 | let invalid_packing = Felt::from( 254 | BigInt::from(c1) 255 | + (BigInt::from(c2) << 36) 256 | + (BigInt::from(c3) << 72) 257 | + (BigInt::from(c4) << 108), 258 | ); 259 | 260 | QM31::unpack_from_felt(&invalid_packing).unwrap_err(); 261 | } 262 | } 263 | 264 | #[test] 265 | fn qm31_packing_with_high_bits() { 266 | let invalid_packing = Felt::from(BigInt::from(1) << 200); 267 | 268 | QM31::unpack_from_felt(&invalid_packing).unwrap_err(); 269 | } 270 | 271 | /// Tests the QM31 packing when some coefficients have a value of PRIME. 272 | /// 273 | /// If we try to create an M31 with a value of PRIME, it won't be reduced 274 | /// to 0 internally. This tests verifies that a PRIME coefficient is being 275 | /// packed as its representative value, instead of the raw value. 276 | #[test] 277 | fn qm31_packing_with_prime_coefficients() { 278 | const PRIME: u32 = MERSENNE_31_PRIME_FIELD_ORDER; 279 | 280 | let cases = [ 281 | [PRIME, 0, 0, 0], 282 | [0, PRIME, 0, 0], 283 | [0, 0, PRIME, 0], 284 | [0, 0, 0, PRIME], 285 | ]; 286 | 287 | for [c1, c2, c3, c4] in cases { 288 | let qm31 = QM31::from_coefficients(c1, c2, c3, c4); 289 | let packed_qm31 = qm31.pack_into_felt(); 290 | 291 | let expected_packing = BigInt::from(c1 % PRIME) 292 | + (BigInt::from(c2 % PRIME) << 36) 293 | + (BigInt::from(c3 % PRIME) << 72) 294 | + (BigInt::from(c4 % PRIME) << 108); 295 | 296 | assert_eq!(packed_qm31, Felt::from(expected_packing)) 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/qm31/num_traits_impl.rs: -------------------------------------------------------------------------------- 1 | use lambdaworks_math::field::{ 2 | element::FieldElement, errors::FieldError, 3 | fields::mersenne31::extensions::Degree4ExtensionField, 4 | }; 5 | use num_traits::{Inv, One, Pow, Zero}; 6 | 7 | use super::QM31; 8 | 9 | impl Zero for QM31 { 10 | fn zero() -> Self { 11 | Self(FieldElement::::zero()) 12 | } 13 | 14 | fn is_zero(&self) -> bool { 15 | self == &Self::zero() 16 | } 17 | } 18 | impl One for QM31 { 19 | fn one() -> Self { 20 | Self(FieldElement::::one()) 21 | } 22 | } 23 | impl Inv for QM31 { 24 | type Output = Result; 25 | 26 | fn inv(self) -> Self::Output { 27 | self.inverse() 28 | } 29 | } 30 | impl Pow for QM31 { 31 | type Output = Self; 32 | 33 | fn pow(self, rhs: u8) -> Self::Output { 34 | Self(self.0.pow(rhs as u128)) 35 | } 36 | } 37 | impl Pow for QM31 { 38 | type Output = Self; 39 | 40 | fn pow(self, rhs: u16) -> Self::Output { 41 | Self(self.0.pow(rhs as u128)) 42 | } 43 | } 44 | impl Pow for QM31 { 45 | type Output = Self; 46 | 47 | fn pow(self, rhs: u32) -> Self::Output { 48 | Self(self.0.pow(rhs as u128)) 49 | } 50 | } 51 | impl Pow for QM31 { 52 | type Output = Self; 53 | 54 | fn pow(self, rhs: u64) -> Self::Output { 55 | Self(self.0.pow(rhs as u128)) 56 | } 57 | } 58 | impl Pow for QM31 { 59 | type Output = Self; 60 | 61 | fn pow(self, rhs: u128) -> Self::Output { 62 | Self(self.0.pow(rhs)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/short_string/mod.rs: -------------------------------------------------------------------------------- 1 | //! Cairo short string 2 | //! 3 | //! The cairo language make it possible to create `Felt` values at compile time from so-called "short string". 4 | //! See https://docs.starknet.io/archive/cairo-101/strings/ for more information and syntax. 5 | //! 6 | //! This modules allows to mirror this behaviour in Rust, by leveraging type safety. 7 | //! A `ShortString` is string that have been checked and is guaranted to be convertible into a valid `Felt`. 8 | //! It checks that the `String` only contains ascii characters and is no longer than 31 characters. 9 | //! 10 | //! The convesion to `Felt` is done by using the internal ascii short string as bytes and parse those as a big endian number. 11 | 12 | #[cfg(not(feature = "std"))] 13 | use crate::felt::alloc::string::{String, ToString}; 14 | use crate::felt::Felt; 15 | 16 | /// A cairo short string 17 | /// 18 | /// Allow for safe conversion of cairo short string `String` into `Felt`, 19 | /// as it is guaranted that the value it contains can be represented as a felt. 20 | #[repr(transparent)] 21 | #[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] 22 | pub struct ShortString(String); 23 | 24 | impl core::fmt::Display for ShortString { 25 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 26 | self.0.fmt(f) 27 | } 28 | } 29 | 30 | impl From for Felt { 31 | fn from(ss: ShortString) -> Self { 32 | let bytes = ss.0.as_bytes(); 33 | 34 | let mut buffer = [0u8; 32]; 35 | // `ShortString` initialization guarantee that the string is ascii and its len doesn't exceed 31. 36 | // Which mean that its bytes representation won't either exceed 31 bytes. 37 | // So, this won't panic. 38 | buffer[(32 - bytes.len())..].copy_from_slice(bytes); 39 | 40 | // The conversion will never fail 41 | Felt::from_bytes_be(&buffer) 42 | } 43 | } 44 | 45 | #[derive(Debug, Clone)] 46 | #[cfg_attr(test, derive(PartialEq, Eq))] 47 | pub enum TryShortStringFromStringError { 48 | TooLong, 49 | NonAscii, 50 | } 51 | 52 | impl core::fmt::Display for TryShortStringFromStringError { 53 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 54 | match self { 55 | TryShortStringFromStringError::TooLong => "string to long", 56 | TryShortStringFromStringError::NonAscii => "string contains non ascii characters", 57 | } 58 | .fmt(f) 59 | } 60 | } 61 | 62 | #[cfg(feature = "std")] 63 | impl std::error::Error for TryShortStringFromStringError {} 64 | 65 | impl TryFrom for ShortString { 66 | type Error = TryShortStringFromStringError; 67 | 68 | fn try_from(value: String) -> Result { 69 | if value.len() > 31 { 70 | return Err(TryShortStringFromStringError::TooLong); 71 | } 72 | if !value.as_str().is_ascii() { 73 | return Err(TryShortStringFromStringError::NonAscii); 74 | } 75 | 76 | Ok(ShortString(value)) 77 | } 78 | } 79 | 80 | impl Felt { 81 | /// Create a felt value from a cairo short string. 82 | /// 83 | /// The string must contains only ascii characters 84 | /// and its length must not exceed 31. 85 | /// 86 | /// The returned felt value be that of the input raw bytes. 87 | pub fn parse_cairo_short_string(string: &str) -> Result { 88 | let bytes = string.as_bytes(); 89 | if !bytes.is_ascii() { 90 | return Err(TryShortStringFromStringError::NonAscii); 91 | } 92 | if bytes.len() > 31 { 93 | return Err(TryShortStringFromStringError::TooLong); 94 | } 95 | 96 | let mut buffer = [0u8; 32]; 97 | buffer[(32 - bytes.len())..].copy_from_slice(bytes); 98 | 99 | // The conversion will never fail 100 | Ok(Felt::from_bytes_be(&buffer)) 101 | } 102 | } 103 | 104 | impl TryFrom<&str> for ShortString { 105 | type Error = TryShortStringFromStringError; 106 | 107 | fn try_from(value: &str) -> Result { 108 | if value.len() > 31 { 109 | return Err(TryShortStringFromStringError::TooLong); 110 | } 111 | if !value.is_ascii() { 112 | return Err(TryShortStringFromStringError::NonAscii); 113 | } 114 | 115 | Ok(ShortString(value.to_string())) 116 | } 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use super::*; 122 | 123 | #[test] 124 | fn ok() { 125 | for (string, expected_felt) in [ 126 | (String::default(), Felt::ZERO), 127 | (String::from("aa"), Felt::from_hex_unwrap("0x6161")), 128 | ( 129 | String::from("approve"), 130 | Felt::from_hex_unwrap("0x617070726f7665"), 131 | ), 132 | ( 133 | String::from("SN_SEPOLIA"), 134 | Felt::from_raw([ 135 | 507980251676163170, 136 | 18446744073709551615, 137 | 18446744073708869172, 138 | 1555806712078248243, 139 | ]), 140 | ), 141 | ] { 142 | let felt = Felt::parse_cairo_short_string(&string).unwrap(); 143 | let short_string = ShortString::try_from(string.clone()).unwrap(); 144 | 145 | assert_eq!(felt, expected_felt); 146 | assert_eq!(short_string.0, string); 147 | assert_eq!(Felt::from(short_string), expected_felt); 148 | } 149 | } 150 | 151 | #[test] 152 | fn ko_too_long() { 153 | let ok_string = String::from("This is a 31 characters string."); 154 | assert!(Felt::parse_cairo_short_string(&ok_string).is_ok()); 155 | assert!(ShortString::try_from(ok_string).is_ok()); 156 | 157 | let ko_string = String::from("This is a 32 characters string.."); 158 | 159 | assert_eq!( 160 | Felt::parse_cairo_short_string(&ko_string), 161 | Err(TryShortStringFromStringError::TooLong) 162 | ); 163 | assert_eq!( 164 | ShortString::try_from(ko_string), 165 | Err(TryShortStringFromStringError::TooLong) 166 | ); 167 | } 168 | 169 | #[test] 170 | fn ko_non_ascii() { 171 | let string = String::from("What a nice emoji 💫"); 172 | 173 | assert_eq!( 174 | Felt::parse_cairo_short_string(&string), 175 | Err(TryShortStringFromStringError::NonAscii) 176 | ); 177 | assert_eq!( 178 | ShortString::try_from(string), 179 | Err(TryShortStringFromStringError::NonAscii) 180 | ); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/u256/mod.rs: -------------------------------------------------------------------------------- 1 | //! A Cairo-like u256 type. 2 | //! 3 | //! This `U256` type purpose is not to be used to perform arithmetic operations, 4 | //! but rather to offer a handy interface to convert from and to Cairo's u256 values. 5 | //! Indeed, the Cairo language represent u256 values as a two felts struct, 6 | //! representing the `low` and `high` 128 bits of the value. 7 | //! We mirror this representation, allowing for efficient serialization/deserializatin. 8 | //! 9 | //! We recommend you create From/Into implementation to bridge the gap between your favourite u256 type, 10 | //! and the one provided by this crate. 11 | 12 | #[cfg(feature = "num-traits")] 13 | mod num_traits_impl; 14 | mod primitive_conversions; 15 | #[cfg(test)] 16 | mod tests; 17 | 18 | use core::{fmt::Debug, str::FromStr}; 19 | 20 | use crate::felt::{Felt, PrimitiveFromFeltError}; 21 | 22 | /// Error types that can occur when parsing a string into a U256. 23 | #[derive(Debug)] 24 | pub enum FromStrError { 25 | /// The string contain too many characters to be the representation of a valid u256 value. 26 | StringTooLong, 27 | /// The parsed value exceeds the maximum representable value for U256. 28 | ValueTooBig, 29 | /// The string contains invalid characters for the expected format. 30 | Invalid, 31 | /// Underlying u128 parsing failed. 32 | Parse(::Err), 33 | } 34 | 35 | impl core::fmt::Display for FromStrError { 36 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 37 | match self { 38 | FromStrError::ValueTooBig => core::fmt::Display::fmt("value too big for u256", f), 39 | FromStrError::Invalid => core::fmt::Display::fmt("invalid characters", f), 40 | FromStrError::Parse(e) => { 41 | // Avoid using format as it requires `alloc` 42 | core::fmt::Display::fmt("invalid string: ", f)?; 43 | core::fmt::Display::fmt(e, f) 44 | } 45 | FromStrError::StringTooLong => { 46 | core::fmt::Display::fmt("too many characters to be a valid u256 representation", f) 47 | } 48 | } 49 | } 50 | } 51 | 52 | #[cfg(feature = "std")] 53 | impl std::error::Error for FromStrError {} 54 | 55 | /// A 256-bit unsigned integer represented as two 128-bit components. 56 | /// 57 | /// The internal representation uses big-endian ordering where `high` contains 58 | /// the most significant 128 bits and `low` contains the least significant 128 bits. 59 | /// This reflects the way u256 are represented in the Cairo language. 60 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 61 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 62 | pub struct U256 { 63 | high: u128, 64 | low: u128, 65 | } 66 | 67 | impl U256 { 68 | /// Returns the high 128 bits of the U256 value. 69 | pub fn high(&self) -> u128 { 70 | self.high 71 | } 72 | 73 | /// Returns the low 128 bits of the U256 value. 74 | pub fn low(&self) -> u128 { 75 | self.low 76 | } 77 | 78 | /// Constructs a U256 from explicit high and low 128-bit components. 79 | /// 80 | /// This is the most direct way to create a U256 when you already have 81 | /// the component values separated. 82 | pub fn from_parts(high: u128, low: u128) -> Self { 83 | Self { low, high } 84 | } 85 | 86 | /// Attempts to construct a U256 from two Felt values representing high and low parts. 87 | /// 88 | /// This conversion can fail if either Felt value cannot be represented as a u128, 89 | /// which would indicate the Felt contains a value outside the valid range. 90 | pub fn try_from_felt_parts(high: Felt, low: Felt) -> Result { 91 | Ok(Self { 92 | high: high.try_into()?, 93 | low: low.try_into()?, 94 | }) 95 | } 96 | 97 | /// Attempts to construct a U256 from decimal string representations of high and low parts. 98 | /// 99 | /// Both strings must be valid decimal representations that fit within u128 range. 100 | pub fn try_from_dec_str_parts(high: &str, low: &str) -> Result::Err> { 101 | Ok(Self { 102 | high: high.parse()?, 103 | low: low.parse()?, 104 | }) 105 | } 106 | 107 | /// Attempts to construct a U256 from hexadecimal string representations of high and low parts. 108 | /// 109 | /// Both strings must be valid hexadecimal (prefixed or not) representations that fit within u128 range. 110 | pub fn try_from_hex_str_parts(high: &str, low: &str) -> Result::Err> { 111 | let high = if high.starts_with("0x") || high.starts_with("0X") { 112 | &high[2..] 113 | } else { 114 | high 115 | }; 116 | let low = if low.starts_with("0x") || low.starts_with("0X") { 117 | &low[2..] 118 | } else { 119 | low 120 | }; 121 | 122 | Ok(Self { 123 | high: u128::from_str_radix(high, 16)?, 124 | low: u128::from_str_radix(low, 16)?, 125 | }) 126 | } 127 | 128 | /// Parses a hexadecimal string into a U256 value. 129 | /// 130 | /// Accepts strings with or without "0x"/"0X" prefixes and handles leading zero removal. 131 | /// The implementation automatically determines the split between high and low components 132 | /// based on string length, with values over 32 hex digits requiring high component usage. 133 | pub fn from_hex_str(hex_str: &str) -> Result { 134 | // Remove prefix 135 | let string_without_prefix = if hex_str.starts_with("0x") || hex_str.starts_with("0X") { 136 | &hex_str[2..] 137 | } else { 138 | hex_str 139 | }; 140 | 141 | if string_without_prefix.is_empty() { 142 | return Err(FromStrError::Invalid); 143 | } 144 | 145 | // Remove leading zero 146 | let string_without_zero_padding = string_without_prefix.trim_start_matches('0'); 147 | 148 | let (high, low) = if string_without_zero_padding.is_empty() { 149 | // The string was uniquely made out of of `0` 150 | (0, 0) 151 | } else if string_without_zero_padding.len() > 64 { 152 | return Err(FromStrError::StringTooLong); 153 | } else if string_without_zero_padding.len() > 32 { 154 | // The 32 last characters are the `low` u128 bytes, 155 | // all the other ones are the `high` u128 bytes. 156 | let delimiter_index = string_without_zero_padding.len() - 32; 157 | ( 158 | u128::from_str_radix(&string_without_zero_padding[0..delimiter_index], 16) 159 | .map_err(FromStrError::Parse)?, 160 | u128::from_str_radix(&string_without_zero_padding[delimiter_index..], 16) 161 | .map_err(FromStrError::Parse)?, 162 | ) 163 | } else { 164 | // There is no `high` bytes. 165 | ( 166 | 0, 167 | u128::from_str_radix(string_without_zero_padding, 16) 168 | .map_err(FromStrError::Parse)?, 169 | ) 170 | }; 171 | 172 | Ok(U256 { high, low }) 173 | } 174 | 175 | /// Parses a decimal string into a `u256`. 176 | /// 177 | /// Custom arithmetic is executed in order to efficiently parse the input as two `u128` values. 178 | /// 179 | /// This implementation performs digit-by-digit multiplication to handle values 180 | /// that exceed u128 range. The algorithm uses overflow detection to prevent 181 | /// silent wraparound and ensures accurate representation of large decimal numbers. 182 | /// Values with more than 78 decimal digits are rejected as they exceed U256 capacity. 183 | pub fn from_dec_str(dec_str: &str) -> Result { 184 | if dec_str.is_empty() { 185 | return Err(FromStrError::Invalid); 186 | } 187 | 188 | // Ignore leading zeros 189 | let string_without_zero_padding = dec_str.trim_start_matches('0'); 190 | 191 | let (high, low) = if string_without_zero_padding.is_empty() { 192 | // The string was uniquely made out of of `0` 193 | (0, 0) 194 | } else if string_without_zero_padding.len() > 78 { 195 | return Err(FromStrError::StringTooLong); 196 | } else { 197 | let mut low = 0u128; 198 | let mut high = 0u128; 199 | 200 | // b is ascii value of the char less the ascii value of the char '0' 201 | // which happen to be equal to the number represented by the char. 202 | // b = ascii(char) - ascii('0') 203 | for b in string_without_zero_padding 204 | .bytes() 205 | .map(|b| b.wrapping_sub(b'0')) 206 | { 207 | // Using `wrapping_sub` all non 0-9 characters will yield a value greater than 9. 208 | if b > 9 { 209 | return Err(FromStrError::Invalid); 210 | } 211 | 212 | // We use a [long multiplication](https://en.wikipedia.org/wiki/Multiplication_algorithm#Long_multiplication) 213 | // algorithm to perform the computation. 214 | // The idea is that if 215 | // `v = (high << 128) + low` 216 | // then 217 | // `v * 10 = ((high * 10) << 128) + low * 10` 218 | 219 | // Compute `high * 10`, return error on overflow. 220 | let (new_high, did_overflow) = high.overflowing_mul(10); 221 | if did_overflow { 222 | return Err(FromStrError::ValueTooBig); 223 | } 224 | // Now we want to compute `low * 10`, but in case it overflows, we want to carry rather than error. 225 | // To do so, we perform another long multiplication to get both the result and carry values, 226 | // this time breaking the u128 (low) value into two u64 (low_low and low_high), 227 | // perform multiplication on each part individually, extracting an eventual carry, and finally 228 | // combining them back. 229 | // 230 | // Any overflow on the high part will result in an error. 231 | // Any overflow on the low part should be handled by carrying the extra amount to the high part. 232 | let (new_low, carry) = { 233 | let low_low = low as u64; 234 | let low_high = (low >> 64) as u64; 235 | 236 | // Both of those values cannot overflow, as they are u64 stored into a u128. 237 | // Instead they will just start using the highest half part of their bytes. 238 | let low_low = (low_low as u128) * 10; 239 | let low_high = (low_high as u128) * 10; 240 | 241 | // The carry of the multiplication per 10 is in the highest 64 bytes of the `low_high` part. 242 | let carry_mul_10 = low_high >> 64; 243 | // We shift back the bytes, erasing any carry we may have. 244 | let low_high_without_carry = low_high << 64; 245 | 246 | // By adding back the two low parts together we get its new value. 247 | let (new_low, did_overflow) = low_low.overflowing_add(low_high_without_carry); 248 | // I couldn't come up with a value where `did_overflow` is true, 249 | // but better safe than sorry 250 | (new_low, carry_mul_10 + if did_overflow { 1 } else { 0 }) 251 | }; 252 | 253 | // Add carry to high if it exists. 254 | let new_high = if carry != 0 { 255 | let (new_high, did_overflow) = new_high.overflowing_add(carry); 256 | // Error if it overflows. 257 | if did_overflow { 258 | return Err(FromStrError::ValueTooBig); 259 | } 260 | new_high 261 | } else { 262 | new_high 263 | }; 264 | 265 | // Add the new digit to low. 266 | let (new_low, did_overflow) = new_low.overflowing_add(b.into()); 267 | 268 | // Add one to high if the previous operation overflowed. 269 | if did_overflow { 270 | let (new_high, did_overflow) = new_high.overflowing_add(1); 271 | // Error if it overflows. 272 | if did_overflow { 273 | return Err(FromStrError::ValueTooBig); 274 | } 275 | high = new_high; 276 | } else { 277 | high = new_high; 278 | } 279 | 280 | low = new_low 281 | } 282 | 283 | (high, low) 284 | }; 285 | 286 | Ok(U256 { high, low }) 287 | } 288 | } 289 | 290 | impl FromStr for U256 { 291 | type Err = FromStrError; 292 | 293 | /// Parses a string into a U256 by detecting the format automatically. 294 | /// 295 | /// Strings beginning with "0x" or "0X" are treated as hexadecimal, 296 | /// while all other strings are interpreted as decimal. 297 | fn from_str(s: &str) -> Result { 298 | if s.starts_with("0x") || s.starts_with("0X") { 299 | Self::from_hex_str(s) 300 | } else { 301 | Self::from_dec_str(s) 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/u256/num_traits_impl.rs: -------------------------------------------------------------------------------- 1 | use num_bigint::{BigInt, BigUint, ToBigInt, ToBigUint}; 2 | use num_traits::{FromPrimitive, ToPrimitive}; 3 | 4 | use super::U256; 5 | 6 | impl ToBigUint for U256 { 7 | fn to_biguint(&self) -> Option { 8 | let mut buffer = [0u8; 32]; 9 | 10 | buffer[..16].copy_from_slice(&self.high.to_be_bytes()); 11 | buffer[16..].copy_from_slice(&self.low.to_be_bytes()); 12 | 13 | Some(BigUint::from_bytes_be(&buffer)) 14 | } 15 | } 16 | 17 | impl ToBigInt for U256 { 18 | fn to_bigint(&self) -> Option { 19 | self.to_biguint().map(|v| v.into()) 20 | } 21 | } 22 | 23 | impl FromPrimitive for U256 { 24 | fn from_i64(value: i64) -> Option { 25 | value.try_into().ok() 26 | } 27 | 28 | fn from_u64(value: u64) -> Option { 29 | Some(value.into()) 30 | } 31 | fn from_i128(value: i128) -> Option { 32 | value.try_into().ok() 33 | } 34 | 35 | fn from_u128(value: u128) -> Option { 36 | Some(value.into()) 37 | } 38 | } 39 | 40 | impl ToPrimitive for U256 { 41 | fn to_i64(&self) -> Option { 42 | (*self).try_into().ok() 43 | } 44 | 45 | fn to_u64(&self) -> Option { 46 | (*self).try_into().ok() 47 | } 48 | 49 | fn to_i128(&self) -> Option { 50 | (*self).try_into().ok() 51 | } 52 | 53 | fn to_u128(&self) -> Option { 54 | (*self).try_into().ok() 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use core::str::FromStr; 61 | 62 | use super::*; 63 | 64 | #[test] 65 | fn to_bigint() { 66 | let big_value = 67 | "115000000000000000000000000000000000000000000000000000000000000000000000000000"; 68 | let bigint = BigInt::from_str(big_value).unwrap(); 69 | let u256 = U256::from_dec_str(big_value).unwrap(); 70 | let converted = u256.to_bigint().unwrap(); 71 | assert_eq!(bigint, converted); 72 | } 73 | #[test] 74 | fn to_biguint() { 75 | let big_value = 76 | "115000000000000000000000000000000000000000000000000000000000000000000000000000"; 77 | let biguint = BigInt::from_str(big_value).unwrap(); 78 | let u256 = U256::from_dec_str(big_value).unwrap(); 79 | let converted = u256.to_bigint().unwrap(); 80 | assert_eq!(biguint, converted); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/u256/primitive_conversions.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Display; 2 | 3 | use super::U256; 4 | 5 | macro_rules! impl_from_uint { 6 | ($from:ty) => { 7 | impl From<$from> for U256 { 8 | fn from(value: $from) -> U256 { 9 | U256 { 10 | high: 0, 11 | low: value.into(), 12 | } 13 | } 14 | } 15 | }; 16 | } 17 | 18 | impl_from_uint!(u8); 19 | impl_from_uint!(u16); 20 | impl_from_uint!(u32); 21 | impl_from_uint!(u64); 22 | impl_from_uint!(u128); 23 | 24 | macro_rules! impl_try_from_int { 25 | ($from:ty) => { 26 | impl TryFrom<$from> for U256 { 27 | type Error = core::num::TryFromIntError; 28 | 29 | fn try_from(value: $from) -> Result { 30 | Ok(U256 { 31 | high: 0, 32 | low: value.try_into()?, 33 | }) 34 | } 35 | } 36 | }; 37 | } 38 | 39 | impl_try_from_int!(i8); 40 | impl_try_from_int!(i16); 41 | impl_try_from_int!(i32); 42 | impl_try_from_int!(i64); 43 | impl_try_from_int!(i128); 44 | 45 | #[derive(Debug)] 46 | pub struct TryFromU256Error; 47 | 48 | impl Display for TryFromU256Error { 49 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 50 | "out of range u256 type conversion attempted".fmt(f) 51 | } 52 | } 53 | 54 | #[cfg(feature = "std")] 55 | impl std::error::Error for TryFromU256Error {} 56 | 57 | macro_rules! impl_try_from_u256 { 58 | ($to:ty) => { 59 | impl TryFrom for $to { 60 | type Error = TryFromU256Error; 61 | 62 | fn try_from(value: U256) -> Result { 63 | if value.high != 0 { 64 | Err(TryFromU256Error) 65 | } else { 66 | value.low.try_into().map_err(|_| TryFromU256Error) 67 | } 68 | } 69 | } 70 | }; 71 | } 72 | 73 | impl_try_from_u256!(u8); 74 | impl_try_from_u256!(u16); 75 | impl_try_from_u256!(u32); 76 | impl_try_from_u256!(u64); 77 | impl_try_from_u256!(u128); 78 | impl_try_from_u256!(i8); 79 | impl_try_from_u256!(i16); 80 | impl_try_from_u256!(i32); 81 | impl_try_from_u256!(i64); 82 | impl_try_from_u256!(i128); 83 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/u256/tests/from_dec_str.rs: -------------------------------------------------------------------------------- 1 | use crate::u256::{FromStrError, U256}; 2 | 3 | #[test] 4 | fn test_from_dec_str_zero_values() { 5 | // Test "0" 6 | let result = U256::from_dec_str("0").unwrap(); 7 | assert_eq!(result.low(), 0); 8 | assert_eq!(result.high(), 0); 9 | 10 | // Test multiple zeros 11 | let result = U256::from_dec_str("000").unwrap(); 12 | assert_eq!(result.low(), 0); 13 | assert_eq!(result.high(), 0); 14 | 15 | let result = U256::from_dec_str("0000000000000000000000000000000000000000").unwrap(); 16 | assert_eq!(result.low(), 0); 17 | assert_eq!(result.high(), 0); 18 | } 19 | 20 | #[test] 21 | fn test_from_dec_str_invalid_empty_after_trimming() { 22 | // Test empty string 23 | assert!(matches!(U256::from_dec_str(""), Err(FromStrError::Invalid))); 24 | } 25 | 26 | #[test] 27 | fn test_from_dec_str_small_values() { 28 | // Test single digit 29 | let result = U256::from_dec_str("1").unwrap(); 30 | assert_eq!(result.low(), 1); 31 | assert_eq!(result.high(), 0); 32 | 33 | let result = U256::from_dec_str("9").unwrap(); 34 | assert_eq!(result.low(), 9); 35 | assert_eq!(result.high(), 0); 36 | 37 | // Test multiple digits 38 | let result = U256::from_dec_str("123").unwrap(); 39 | assert_eq!(result.low(), 123); 40 | assert_eq!(result.high(), 0); 41 | 42 | let result = U256::from_dec_str("999").unwrap(); 43 | assert_eq!(result.low(), 999); 44 | assert_eq!(result.high(), 0); 45 | } 46 | 47 | #[test] 48 | fn test_from_dec_str_max_u128_value() { 49 | let max_u128_str = "340282366920938463463374607431768211455"; 50 | let result = U256::from_dec_str(max_u128_str).unwrap(); 51 | assert_eq!(result.low(), u128::MAX); 52 | assert_eq!(result.high(), 0); 53 | 54 | let max_u128_plus_one_str = "340282366920938463463374607431768211456"; 55 | let result = U256::from_dec_str(max_u128_plus_one_str).unwrap(); 56 | assert_eq!(result.low(), 0); 57 | assert_eq!(result.high(), 1); 58 | 59 | let max_u128_plus_two_str = "340282366920938463463374607431768211457"; 60 | let result = U256::from_dec_str(max_u128_plus_two_str).unwrap(); 61 | assert_eq!(result.low(), 1); 62 | assert_eq!(result.high(), 1); 63 | } 64 | 65 | #[test] 66 | fn test_from_dec_str_values_under_39_digits() { 67 | // Test values with less than 39 digits (should only use low part) 68 | let result = U256::from_dec_str("12345678901234567890123456789012345678").unwrap(); // 38 digits 69 | assert_eq!(result.low(), 12345678901234567890123456789012345678u128); 70 | assert_eq!(result.high(), 0); 71 | 72 | // Test a smaller value 73 | let result = U256::from_dec_str("1000000000000000000000000000000000000").unwrap(); // 37 digits 74 | assert_eq!(result.low(), 1000000000000000000000000000000000000u128); 75 | assert_eq!(result.high(), 0); 76 | } 77 | 78 | #[test] 79 | fn test_from_dec_str_values_39_digits_and_above() { 80 | // Test 39 digits (should use manual parsing) 81 | let result = U256::from_dec_str("123456789012345678901234567890123456789").unwrap(); // 39 digits 82 | // This should use the manual parsing logic 83 | assert!(result.low() > 0 || result.high() > 0); 84 | 85 | // Test a specific known value that exceeds u128::MAX 86 | let result = U256::from_dec_str("340282366920938463463374607431768211456").unwrap(); // u128::MAX + 1 87 | assert_eq!(result.low(), 0); 88 | assert_eq!(result.high(), 1); 89 | } 90 | 91 | #[test] 92 | fn test_from_dec_str_maximum_allowed_value() { 93 | // Test maximum value for u256 (78 digits) 94 | let max_u256_str = 95 | "115792089237316195423570985008687907853269984665640564039457584007913129639935"; 96 | let result = U256::from_dec_str(max_u256_str).unwrap(); 97 | assert_eq!(result.low(), u128::MAX); 98 | assert_eq!(result.high(), u128::MAX); 99 | 100 | let max_u256_plus_one_str = 101 | "115792089237316195423570985008687907853269984665640564039457584007913129639936"; 102 | assert!(matches!( 103 | U256::from_dec_str(max_u256_plus_one_str), 104 | Err(FromStrError::ValueTooBig) 105 | )); 106 | } 107 | 108 | #[test] 109 | fn test_from_dec_str_too_long() { 110 | // Test 79 digits (too big) 111 | let too_big = "1157920892373161954235709850086879078532699846656405640394575840079131296399350"; 112 | assert!(matches!( 113 | U256::from_dec_str(too_big), 114 | Err(FromStrError::StringTooLong) 115 | )); 116 | 117 | // Test even longer string 118 | let very_long = "1".repeat(100); 119 | assert!(matches!( 120 | U256::from_dec_str(&very_long), 121 | Err(FromStrError::StringTooLong) 122 | )); 123 | } 124 | 125 | #[test] 126 | fn test_from_dec_str_invalid_characters_lower_only() { 127 | // Test invalid decimal characters 128 | assert!(matches!( 129 | U256::from_dec_str("123a"), 130 | Err(FromStrError::Invalid) 131 | )); 132 | 133 | assert!(matches!( 134 | U256::from_dec_str("12.3"), 135 | Err(FromStrError::Invalid) 136 | )); 137 | 138 | assert!(matches!( 139 | U256::from_dec_str("12-3"), 140 | Err(FromStrError::Invalid) 141 | )); 142 | 143 | assert!(matches!( 144 | U256::from_dec_str("12+3"), 145 | Err(FromStrError::Invalid) 146 | )); 147 | 148 | assert!(matches!( 149 | U256::from_dec_str("12 3"), 150 | Err(FromStrError::Invalid) 151 | )); 152 | 153 | // Test characters outside 0-9 range 154 | assert!(matches!( 155 | U256::from_dec_str("12:3"), // ':' is ASCII 58, '0' is 48, so b':' - b'0' = 10 156 | Err(FromStrError::Invalid) 157 | )); 158 | 159 | assert!(matches!( 160 | U256::from_dec_str("12/3"), // '/' is ASCII 47, '0' is 48, so b'/' - b'0' = 255 (wrapping) 161 | Err(FromStrError::Invalid) 162 | )); 163 | } 164 | 165 | #[test] 166 | fn test_from_dec_str_leading_zeros() { 167 | // Test leading zeros that should be trimmed 168 | let result = 169 | U256::from_dec_str("0000000000000000000000000000000000000000000000000000000000000001") 170 | .unwrap(); 171 | assert_eq!(result.low(), 1); 172 | assert_eq!(result.high(), 0); 173 | 174 | let result = 175 | U256::from_dec_str("00000000000000000000000000000000000000000000000000000000000000123") 176 | .unwrap(); 177 | assert_eq!(result.low(), 123); 178 | assert_eq!(result.high(), 0); 179 | 180 | // Test case where all characters are zeros 181 | let result = U256::from_dec_str("00000000000000000000000000000000").unwrap(); 182 | assert_eq!(result.low(), 0); 183 | assert_eq!(result.high(), 0); 184 | } 185 | 186 | #[test] 187 | fn test_from_dec_str_boundary_lengths() { 188 | // Test exactly 78 digits (maximum allowed) 189 | let max_78 = "111456789012345678901234567890123456789012345678901234567890123456789012345678"; 190 | let result = U256::from_dec_str(max_78).unwrap(); 191 | assert!(result.low() > 0 || result.high() > 0); 192 | 193 | // Test exactly 38 digits (just under 39) 194 | let digits_38 = "340282366920938463463374607431768211455"; 195 | let result = U256::from_dec_str(digits_38).unwrap(); 196 | assert_eq!(result.low(), 340282366920938463463374607431768211455u128); 197 | assert_eq!(result.high(), 0); 198 | 199 | // Test exactly 39 digits (boundary for manual parsing) 200 | let digits_39 = "123456789012345678901234567890123456789"; 201 | let result = U256::from_dec_str(digits_39).unwrap(); 202 | assert!(result.low() > 0 || result.high() > 0); 203 | } 204 | 205 | #[test] 206 | fn test_from_dec_str_overflow_detection() { 207 | // Test values that would cause overflow during manual parsing 208 | // This tests the overflow detection in the multiplication and addition steps 209 | 210 | // Test a value that's close to but not exceeding the limit 211 | let near_max = "115792089237316195423570985008687907853269984665640564039457584007913129639934"; 212 | let result = U256::from_dec_str(near_max).unwrap(); 213 | assert!(result.low() > 0 || result.high() > 0); 214 | 215 | // Test a value that would exceed u256 during intermediate calculations 216 | let overflow_test = 217 | "999999999999999999999999999999999999999999999999999999999999999999999999999999"; 218 | let x = U256::from_dec_str(overflow_test); 219 | assert!(matches!(x, Err(FromStrError::ValueTooBig))); 220 | } 221 | 222 | #[test] 223 | fn test_from_dec_str_specific_known_values() { 224 | // Test some specific known values to verify correctness 225 | 226 | // Test 10^38 (should use manual parsing) 227 | let ten_to_38 = "100000000000000000000000000000000000000"; // 1 followed by 38 zeros 228 | let result = U256::from_dec_str(ten_to_38).unwrap(); 229 | assert!(result.low() > 0 || result.high() > 0); 230 | 231 | // Test 2^128 (should be high=1, low=0) 232 | let two_to_128 = "340282366920938463463374607431768211456"; // 2^128 233 | let result = U256::from_dec_str(two_to_128).unwrap(); 234 | assert_eq!(result.low(), 0); 235 | assert_eq!(result.high(), 1); 236 | 237 | // Test 2^64 238 | let two_to_64 = "18446744073709551616"; // 2^64 239 | let result = U256::from_dec_str(two_to_64).unwrap(); 240 | assert_eq!(result.low(), 18446744073709551616u128); 241 | assert_eq!(result.high(), 0); 242 | } 243 | 244 | #[test] 245 | fn test_from_dec_str_parse_error_for_small_values() { 246 | // Test that invalid characters in small values (< 39 digits) return Parse error 247 | assert!(matches!( 248 | U256::from_dec_str("1234567890123456789012a"), 249 | Err(FromStrError::Invalid) 250 | )); 251 | 252 | assert!(matches!( 253 | U256::from_dec_str("1234567890123456789012.8"), 254 | Err(FromStrError::Invalid) 255 | )); 256 | } 257 | 258 | #[test] 259 | fn test_from_dec_str_parse_error_for_big_values() { 260 | // Test that invalid characters in small values (< 39 digits) return Parse error 261 | assert!(matches!( 262 | U256::from_dec_str( 263 | "11579208923731619542357098500868790785326998466564056403945758400791312963993a" 264 | ), 265 | Err(FromStrError::Invalid) 266 | )); 267 | 268 | assert!(matches!( 269 | U256::from_dec_str( 270 | "11579208923731619542357098500868790785326998466564056403945758400791312963993 " 271 | ), 272 | Err(FromStrError::Invalid) 273 | )); 274 | } 275 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/u256/tests/from_hex_str.rs: -------------------------------------------------------------------------------- 1 | use crate::u256::{FromStrError, U256}; 2 | 3 | #[test] 4 | fn test_from_hex_str_zero_values() { 5 | // Test "0x0" 6 | let result = U256::from_hex_str("0x0").unwrap(); 7 | assert_eq!(result.low(), 0); 8 | assert_eq!(result.high(), 0); 9 | 10 | // Test "0X0" (uppercase prefix) 11 | let result = U256::from_hex_str("0X0").unwrap(); 12 | assert_eq!(result.low(), 0); 13 | assert_eq!(result.high(), 0); 14 | 15 | // Test "0" (no prefix) 16 | let result = U256::from_hex_str("0").unwrap(); 17 | assert_eq!(result.low(), 0); 18 | assert_eq!(result.high(), 0); 19 | 20 | // Test multiple zeros with prefix 21 | let result = U256::from_hex_str("0x000").unwrap(); 22 | assert_eq!(result.low(), 0); 23 | assert_eq!(result.high(), 0); 24 | 25 | // Test multiple zeros without prefix 26 | let result = U256::from_hex_str("000").unwrap(); 27 | assert_eq!(result.low(), 0); 28 | assert_eq!(result.high(), 0); 29 | } 30 | 31 | #[test] 32 | fn test_from_hex_str_invalid_empty_after_prefix() { 33 | // Test empty string after removing prefix and zeros 34 | assert!(matches!( 35 | U256::from_hex_str("0x"), 36 | Err(FromStrError::Invalid) 37 | )); 38 | 39 | assert!(matches!( 40 | U256::from_hex_str("0X"), 41 | Err(FromStrError::Invalid) 42 | )); 43 | } 44 | 45 | #[test] 46 | fn test_from_hex_str_small_values() { 47 | // Test single digit 48 | let result = U256::from_hex_str("0x1").unwrap(); 49 | assert_eq!(result.low(), 1); 50 | assert_eq!(result.high(), 0); 51 | 52 | let result = U256::from_hex_str("0xf").unwrap(); 53 | assert_eq!(result.low(), 15); 54 | assert_eq!(result.high(), 0); 55 | 56 | let result = U256::from_hex_str("0xF").unwrap(); 57 | assert_eq!(result.low(), 15); 58 | assert_eq!(result.high(), 0); 59 | 60 | // Test without prefix 61 | let result = U256::from_hex_str("a").unwrap(); 62 | assert_eq!(result.low(), 10); 63 | assert_eq!(result.high(), 0); 64 | 65 | let result = U256::from_hex_str("A").unwrap(); 66 | assert_eq!(result.low(), 10); 67 | assert_eq!(result.high(), 0); 68 | } 69 | 70 | #[test] 71 | fn test_from_hex_str_max_low_value() { 72 | // Test maximum u128 value (32 hex chars) 73 | let max_u128_hex = "ffffffffffffffffffffffffffffffff"; 74 | let result = U256::from_hex_str(max_u128_hex).unwrap(); 75 | assert_eq!(result.low(), u128::MAX); 76 | assert_eq!(result.high(), 0); 77 | 78 | // Test without prefix 79 | let max_u128_hex = "0xffffffffffffffffffffffffffffffff"; 80 | let result = U256::from_hex_str(max_u128_hex).unwrap(); 81 | assert_eq!(result.low(), u128::MAX); 82 | assert_eq!(result.high(), 0); 83 | } 84 | 85 | #[test] 86 | fn test_from_hex_str_values_requiring_high() { 87 | // Test 33 hex chars (should use high part) 88 | let result = U256::from_hex_str("0x1ffffffffffffffffffffffffffffffff").unwrap(); 89 | assert_eq!(result.low(), u128::MAX); 90 | assert_eq!(result.high(), 1); 91 | 92 | // Test with leading zeros that get trimmed 93 | let result = 94 | U256::from_hex_str("0x00000000000000000000000000000001ffffffffffffffffffffffffffffffff") 95 | .unwrap(); 96 | assert_eq!(result.low(), u128::MAX); 97 | assert_eq!(result.high(), 1); 98 | 99 | // Test maximum value (64 hex chars) 100 | let max_hex = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 101 | let result = U256::from_hex_str(max_hex).unwrap(); 102 | assert_eq!(result.low(), u128::MAX); 103 | assert_eq!(result.high(), u128::MAX); 104 | 105 | // Test maximum value (64 hex chars) with leading zeros 106 | let max_hex = "0x0000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 107 | let result = U256::from_hex_str(max_hex).unwrap(); 108 | assert_eq!(result.low(), u128::MAX); 109 | assert_eq!(result.high(), u128::MAX); 110 | 111 | // Test 64 chars (maximum allowed) 112 | let max_64 = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; 113 | let result = U256::from_hex_str(max_64).unwrap(); 114 | assert_eq!( 115 | result.high(), 116 | u128::from_str_radix("0123456789abcdef0123456789abcdef", 16).unwrap() 117 | ); 118 | assert_eq!( 119 | result.low(), 120 | u128::from_str_radix("0123456789abcdef0123456789abcdef", 16).unwrap() 121 | ); 122 | 123 | // Test a 40-character hex string to verify correct splitting 124 | let hex_40 = "0x1234567890abcdef1234567890abcdef12345678"; 125 | let result = U256::from_hex_str(hex_40).unwrap(); 126 | 127 | // Should split at position 8 (40 - 32 = 8) 128 | let expected_high = u128::from_str_radix("12345678", 16).unwrap(); 129 | let expected_low = u128::from_str_radix("90abcdef1234567890abcdef12345678", 16).unwrap(); 130 | 131 | assert_eq!(result.high(), expected_high); 132 | assert_eq!(result.low(), expected_low); 133 | } 134 | 135 | #[test] 136 | fn test_from_hex_str_too_long() { 137 | // Test 65 hex chars (too big) 138 | let too_big = "0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 139 | assert!(matches!( 140 | U256::from_hex_str(too_big), 141 | Err(FromStrError::StringTooLong) 142 | )); 143 | 144 | // Test without prefix 145 | let too_big = "1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 146 | assert!(matches!( 147 | U256::from_hex_str(too_big), 148 | Err(FromStrError::StringTooLong) 149 | )); 150 | 151 | // Test even longer string 152 | let very_long = "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"; 153 | assert!(matches!( 154 | U256::from_hex_str(very_long), 155 | Err(FromStrError::StringTooLong) 156 | )); 157 | } 158 | 159 | #[test] 160 | fn test_from_hex_str_invalid_characters() { 161 | // Test invalid hex characters 162 | assert!(matches!( 163 | U256::from_hex_str("0xg"), 164 | Err(FromStrError::Parse(_)) 165 | )); 166 | 167 | assert!(matches!( 168 | U256::from_hex_str("0x123g"), 169 | Err(FromStrError::Parse(_)) 170 | )); 171 | 172 | assert!(matches!( 173 | U256::from_hex_str("0xz"), 174 | Err(FromStrError::Parse(_)) 175 | )); 176 | 177 | // Test without prefix 178 | assert!(matches!( 179 | U256::from_hex_str("g"), 180 | Err(FromStrError::Parse(_)) 181 | )); 182 | 183 | assert!(matches!( 184 | U256::from_hex_str("123g"), 185 | Err(FromStrError::Parse(_)) 186 | )); 187 | 188 | // Test invalid characters in high part 189 | assert!(matches!( 190 | U256::from_hex_str("0xg123456789abcdef123456789abcdef12"), 191 | Err(FromStrError::Parse(_)) 192 | )); 193 | 194 | // Test invalid characters in low part when high part is present 195 | assert!(matches!( 196 | U256::from_hex_str("0x1123456789abcdef123456789abcdefg2"), 197 | Err(FromStrError::Parse(_)) 198 | )); 199 | } 200 | 201 | #[test] 202 | fn test_from_hex_str_mixed_case() { 203 | // Test mixed case hex digits 204 | let result = U256::from_hex_str("0xaBcDeF123456789").unwrap(); 205 | assert_eq!( 206 | result.low(), 207 | u128::from_str_radix("aBcDeF123456789", 16).unwrap() 208 | ); 209 | assert_eq!(result.high(), 0); 210 | 211 | // Test mixed case in both parts 212 | let result = U256::from_hex_str("0xaBcDeF123456789aBcDeF123456789aBcDeF12").unwrap(); 213 | assert_eq!(result.high(), u128::from_str_radix("aBcDeF", 16).unwrap()); 214 | assert_eq!( 215 | result.low(), 216 | u128::from_str_radix("123456789aBcDeF123456789aBcDeF12", 16).unwrap() 217 | ); 218 | } 219 | 220 | #[test] 221 | fn test_from_hex_str_empty_string() { 222 | // Test empty string 223 | assert!(matches!(U256::from_hex_str(""), Err(FromStrError::Invalid))); 224 | } 225 | -------------------------------------------------------------------------------- /crates/starknet-types-core/src/u256/tests/mod.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use crate::felt::{Felt, PrimitiveFromFeltError}; 4 | 5 | use super::U256; 6 | 7 | mod from_dec_str; 8 | mod from_hex_str; 9 | 10 | #[test] 11 | fn from_parts() { 12 | let value = U256::from_parts(0x2, 0x1); 13 | assert_eq!(value.low(), 0x1); 14 | assert_eq!(value.high(), 0x2); 15 | assert_eq!(value.low, 0x1); 16 | assert_eq!(value.high, 0x2); 17 | } 18 | 19 | #[test] 20 | fn try_from_felt_parts() { 21 | let u128_max = Felt::from(u128::MAX); 22 | let u128_max_plus_one = u128_max + 1; 23 | 24 | assert!(U256::try_from_felt_parts(u128_max, u128_max).is_ok()); 25 | assert!(matches!( 26 | U256::try_from_felt_parts(u128_max, u128_max_plus_one), 27 | Err(PrimitiveFromFeltError) 28 | )); 29 | assert!(matches!( 30 | U256::try_from_felt_parts(Felt::MAX, u128_max), 31 | Err(PrimitiveFromFeltError) 32 | )); 33 | } 34 | 35 | #[test] 36 | fn try_from_dec_str_parts() { 37 | let valid_str = "123"; 38 | assert!(U256::try_from_dec_str_parts(valid_str, valid_str).is_ok()); 39 | let valid_str = 40 | "00000000000000000000000000000000000000000000000000000000000000000000000000000000000123"; 41 | assert!(U256::try_from_dec_str_parts(valid_str, valid_str).is_ok()); 42 | 43 | let invalid_str = ""; 44 | assert!(U256::try_from_dec_str_parts(valid_str, invalid_str).is_err()); 45 | let invalid_str = "10p"; 46 | assert!(U256::try_from_dec_str_parts(invalid_str, valid_str).is_err()); 47 | let invalid_str = "0x123"; 48 | assert!(U256::try_from_dec_str_parts(invalid_str, valid_str).is_err()); 49 | } 50 | #[test] 51 | fn try_from_hex_str_parts() { 52 | let valid_str = "123"; 53 | assert!(U256::try_from_hex_str_parts(valid_str, valid_str).is_ok()); 54 | let valid_str = "0x123"; 55 | assert!(U256::try_from_hex_str_parts(valid_str, valid_str).is_ok()); 56 | let valid_str = 57 | "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123"; 58 | assert!(U256::try_from_hex_str_parts(valid_str, valid_str).is_ok()); 59 | 60 | let invalid_str = ""; 61 | assert!(U256::try_from_hex_str_parts(valid_str, invalid_str).is_err()); 62 | let invalid_str = "10p"; 63 | assert!(U256::try_from_hex_str_parts(invalid_str, valid_str).is_err()); 64 | let invalid_str = "0x0x123"; 65 | assert!(U256::try_from_hex_str_parts(invalid_str, valid_str).is_err()); 66 | } 67 | 68 | #[test] 69 | fn from_str() { 70 | let input = "0x1234"; 71 | assert_eq!( 72 | U256::from_str(input).unwrap(), 73 | U256::from_hex_str(input).unwrap() 74 | ); 75 | let input = "0X1234"; 76 | assert_eq!( 77 | U256::from_str(input).unwrap(), 78 | U256::from_hex_str(input).unwrap() 79 | ); 80 | let input = "1234"; 81 | assert_eq!( 82 | U256::from_str(input).unwrap(), 83 | U256::from_dec_str(input).unwrap() 84 | ); 85 | let input = "1234ff"; 86 | assert!(U256::from_str(input).is_err()); 87 | let input = "0x0x1"; 88 | assert!(U256::from_str(input).is_err()); 89 | } 90 | 91 | #[test] 92 | fn ordering() { 93 | let value1 = U256::from_parts(2, 1); 94 | let value2 = U256::from_parts(1, 3); 95 | assert!(value2 < value1); 96 | assert_eq!(value2, value2); 97 | } 98 | 99 | #[test] 100 | fn from_primitives_impl() { 101 | let value: U256 = u8::MAX.into(); 102 | assert_eq!( 103 | value, 104 | U256 { 105 | high: 0, 106 | low: u8::MAX.into() 107 | } 108 | ); 109 | let value: U256 = u16::MAX.into(); 110 | assert_eq!( 111 | value, 112 | U256 { 113 | high: 0, 114 | low: u16::MAX.into() 115 | } 116 | ); 117 | let value: U256 = u32::MAX.into(); 118 | assert_eq!( 119 | value, 120 | U256 { 121 | high: 0, 122 | low: u32::MAX.into() 123 | } 124 | ); 125 | let value: U256 = u64::MAX.into(); 126 | assert_eq!( 127 | value, 128 | U256 { 129 | high: 0, 130 | low: u64::MAX.into() 131 | } 132 | ); 133 | let value: U256 = u128::MAX.into(); 134 | assert_eq!( 135 | value, 136 | U256 { 137 | high: 0, 138 | low: u128::MAX 139 | } 140 | ); 141 | 142 | // Try from positive signed values 143 | let value: U256 = i8::MAX.try_into().unwrap(); 144 | assert_eq!( 145 | value, 146 | U256 { 147 | high: 0, 148 | low: i8::MAX.try_into().unwrap() 149 | } 150 | ); 151 | let value: U256 = i16::MAX.try_into().unwrap(); 152 | assert_eq!( 153 | value, 154 | U256 { 155 | high: 0, 156 | low: i16::MAX.try_into().unwrap() 157 | } 158 | ); 159 | let value: U256 = i32::MAX.try_into().unwrap(); 160 | assert_eq!( 161 | value, 162 | U256 { 163 | high: 0, 164 | low: i32::MAX.try_into().unwrap() 165 | } 166 | ); 167 | let value: U256 = i64::MAX.try_into().unwrap(); 168 | assert_eq!( 169 | value, 170 | U256 { 171 | high: 0, 172 | low: i64::MAX.try_into().unwrap() 173 | } 174 | ); 175 | let value: U256 = i128::MAX.try_into().unwrap(); 176 | assert_eq!( 177 | value, 178 | U256 { 179 | high: 0, 180 | low: i128::MAX.try_into().unwrap() 181 | } 182 | ); 183 | 184 | // Try from negative values 185 | let res = U256::try_from(i8::MIN); 186 | assert!(res.is_err()); 187 | let res = U256::try_from(i16::MIN); 188 | assert!(res.is_err()); 189 | let res = U256::try_from(i32::MIN); 190 | assert!(res.is_err()); 191 | let res = U256::try_from(i64::MIN); 192 | assert!(res.is_err()); 193 | let res = U256::try_from(i128::MIN); 194 | assert!(res.is_err()); 195 | } 196 | -------------------------------------------------------------------------------- /ensure_no_std/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | -------------------------------------------------------------------------------- /ensure_no_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ensure_no_std" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | starknet-types-core = { path = "../crates/starknet-types-core", default-features = false, features = [ 8 | "alloc", 9 | "serde", 10 | "curve", 11 | "parity-scale-codec", 12 | "num-traits", 13 | "hash", 14 | ] } 15 | wee_alloc = "0.4.5" 16 | 17 | 18 | [profile.dev] 19 | panic = "abort" 20 | 21 | [profile.release] 22 | panic = "abort" 23 | -------------------------------------------------------------------------------- /ensure_no_std/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.87.0" 3 | components = ["rustfmt", "clippy"] 4 | targets = ["wasm32-unknown-unknown"] 5 | profile = "minimal" 6 | -------------------------------------------------------------------------------- /ensure_no_std/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::panic::PanicInfo; 5 | 6 | /// This function is called on panic. 7 | #[panic_handler] 8 | fn panic(_info: &PanicInfo) -> ! { 9 | loop {} 10 | } 11 | 12 | #[no_mangle] 13 | pub extern "C" fn _start() -> ! { 14 | loop {} 15 | } 16 | 17 | #[global_allocator] 18 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 19 | 20 | #[allow(unused_imports)] 21 | use starknet_types_core; 22 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["felt"] 3 | 4 | resolver = "2" 5 | 6 | [workspace.package] 7 | authors = [ 8 | "Lucas Levy <@LucasLvy>", 9 | ] 10 | edition = "2021" 11 | repository = "https://github.com/starknet-io/types-rs" 12 | 13 | [workspace.dependencies] 14 | starknet-types-core = { path = "../crates/starknet-types-core", default-features = false, features = ["arbitrary"] } 15 | libfuzzer-sys = "0.4" 16 | -------------------------------------------------------------------------------- /fuzz/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: fuzz-add fuzz-div fuzz-mul fuzz-conversions 2 | 3 | fuzz-add_sub: 4 | cargo +nightly fuzz run add_sub_fuzzer --fuzz-dir=./felt 5 | 6 | fuzz-div: 7 | cargo +nightly fuzz run div_fuzzer --fuzz-dir=./felt 8 | 9 | fuzz-mul: 10 | cargo +nightly fuzz run mul_fuzzer --fuzz-dir=./felt 11 | 12 | fuzz-conversions: 13 | cargo +nightly fuzz run conversions_fuzzer --fuzz-dir=./felt 14 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing 2 | 3 | This directory contains the fuzzing infrastructure for the `types-rs` project. 4 | 5 | ## Setup 6 | ``` 7 | cargo install cargo-fuzz 8 | ``` 9 | 10 | ## Running the fuzzers 11 | * cd into one of the directories, e.g., `cd felt` 12 | * list the available fuzz targets, e.g., `cargo fuzz list --fuzz-dir=.` 13 | * run the fuzzer, e.g., `cargo +nightly fuzz run add_fuzzer --fuzz-dir=.` -------------------------------------------------------------------------------- /fuzz/felt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzing_felt" 3 | version = "0.0.1" 4 | edition = "2021" 5 | license = "MIT" 6 | homepage = "https://github.com/starknet-io/types-rs" 7 | repository = "https://github.com/starknet-io/types-rs" 8 | categories = ["types", "math", "crypto", "fuzzing"] 9 | keywords = ["stark", "zkp", "cairo", "fuzzing"] 10 | description = "Fuzzing crate for the starknet common felt type" 11 | readme = "README.md" 12 | 13 | [package.metadata] 14 | cargo-fuzz = true 15 | 16 | [dependencies] 17 | starknet-types-core = { path = "../../crates/starknet-types-core", default-features = false, features = ["arbitrary", "alloc"] } 18 | libfuzzer-sys.workspace = true 19 | num-bigint = "0.4.4" 20 | num-traits = "0.2.18" 21 | lambdaworks-math = { version = "0.10.0", default-features = false } 22 | 23 | [[bin]] 24 | name = "add_sub_fuzzer" 25 | path = "fuzz_targets/add_sub.rs" 26 | test = false 27 | doc = false 28 | 29 | [[bin]] 30 | name = "mul_fuzzer" 31 | path = "fuzz_targets/mul.rs" 32 | test = false 33 | doc = false 34 | [[bin]] 35 | name = "conversions_fuzzer" 36 | path = "fuzz_targets/conversions.rs" 37 | test = false 38 | doc = false 39 | bench = false 40 | 41 | [[bin]] 42 | name = "div_fuzzer" 43 | path = "fuzz_targets/div.rs" 44 | test = false 45 | doc = false 46 | bench = false 47 | -------------------------------------------------------------------------------- /fuzz/felt/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starknet-io/types-rs/61d82f0d20e1aac8b8e5c48a84fda101717940ac/fuzz/felt/README.md -------------------------------------------------------------------------------- /fuzz/felt/fuzz_targets/add_sub.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use starknet_types_core::felt::Felt; 4 | 5 | fuzz_target!(|data: (Felt, Felt)| { 6 | let zero = Felt::ZERO; 7 | let one = Felt::ONE; 8 | let max = Felt::MAX; 9 | let (a, b) = data; 10 | 11 | // Check a + 0 = a 12 | assert_eq!(a + zero, a); 13 | assert_eq!(b + zero, b); 14 | 15 | // Check a - 0 = a 16 | assert_eq!(a - zero, a); 17 | assert_eq!(b - zero, b); 18 | 19 | // Check a - a = 0 20 | assert_eq!(a - a, zero); 21 | assert_eq!(b - b, zero); 22 | 23 | // Check a + (-a) = 0 24 | assert_eq!(a + (-a), zero); 25 | assert_eq!(b + (-b), zero); 26 | 27 | // Check a + b = a - (-b) 28 | assert_eq!(a + b, a - (-b)); 29 | 30 | // Check a + a = a - (-a) 31 | assert_eq!(a + a, a - (-a)); 32 | assert_eq!(b + b, b - (-b)); 33 | 34 | // Check a + a = 2 * a 35 | assert_eq!(a + a, Felt::TWO * a); 36 | assert_eq!(b + b, Felt::TWO * b); 37 | 38 | // Check a + b = b + a 39 | assert_eq!(a + b, b + a); 40 | 41 | // Check (a + b) + b = a + (b + b) 42 | assert_eq!((a + b) + b, a + (b + b)); 43 | 44 | // Check a + max = a - 1 45 | assert_eq!(a + max, a - one); 46 | assert_eq!(b + max, b - one); 47 | 48 | // Check 0 - a = max - a + 1 49 | assert_eq!(zero - a, max - a + one); 50 | assert_eq!(zero - b, max - b + one); 51 | 52 | // Check a + b = (a.to_biguint() + b.to_biguint()) % PRIME 53 | assert_eq!(a + b, Felt::from(a.to_biguint() + b.to_biguint())); 54 | 55 | // a.double = a + a 56 | assert_eq!(a.double(), a + a); 57 | assert_eq!(b.double(), b + b); 58 | }); 59 | -------------------------------------------------------------------------------- /fuzz/felt/fuzz_targets/conversions.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use starknet_types_core::felt::Felt; 4 | 5 | use num_bigint::BigUint; 6 | use num_traits::{One, Zero}; 7 | 8 | use lambdaworks_math::unsigned_integer::element::UnsignedInteger; 9 | 10 | fn pad_left_with_0(v: Vec) -> Vec { 11 | let mut padded = vec![0; 32_usize.saturating_sub(v.len())]; 12 | padded.extend(v); 13 | padded 14 | } 15 | 16 | fn pad_right_with_0(v: Vec) -> Vec { 17 | let padded = vec![0; 32_usize.saturating_sub(v.len())]; 18 | [v, padded].concat() 19 | } 20 | 21 | fuzz_target!(|felt: Felt| { 22 | // to_bytes_be, to_bytes_le 23 | assert_eq!(felt, Felt::from_bytes_be(&felt.to_bytes_be())); 24 | assert_eq!(felt, Felt::from_bytes_le(&felt.to_bytes_le())); 25 | assert_eq!(felt, Felt::from_bytes_be_slice(&felt.to_bytes_be())); 26 | assert_eq!(felt, Felt::from_bytes_le_slice(&felt.to_bytes_le())); 27 | 28 | // to_bits_be 29 | let felt_as_bits = &felt.to_bits_be(); 30 | let felt_as_buint = felt.to_biguint(); 31 | for i in 0..256 { 32 | let bit = (&felt_as_buint & (BigUint::one() << i)) != BigUint::zero(); 33 | assert_eq!(felt_as_bits[255 - i], bit); 34 | } 35 | 36 | // to_bits_le 37 | let felt_as_bits = &felt.to_bits_le(); 38 | for i in 0..256 { 39 | let bit = (&felt_as_buint & (BigUint::one() << i)) != BigUint::zero(); 40 | assert_eq!(felt_as_bits[i], bit); 41 | } 42 | 43 | // to_hex_string, to_fixed_hex_string, from_hex_unwrap 44 | assert_eq!(felt, Felt::from_hex_unwrap(&felt.to_hex_string())); 45 | assert_eq!(felt, Felt::from_hex_unwrap(&felt.to_fixed_hex_string())); 46 | assert_eq!(66, felt.to_fixed_hex_string().len()); 47 | 48 | // from_dec_str 49 | assert_eq!( 50 | felt, 51 | Felt::from_dec_str(&felt_as_buint.to_string()).unwrap() 52 | ); 53 | 54 | // to_bytes_be, to_bytes_le, to_big_uint 55 | assert_eq!( 56 | felt.to_bytes_be().to_vec(), 57 | pad_left_with_0(felt_as_buint.to_bytes_be()) 58 | ); 59 | assert_eq!( 60 | felt.to_bytes_le().to_vec(), 61 | pad_right_with_0(felt_as_buint.to_bytes_le()) 62 | ); 63 | 64 | // to_raw, from_raw 65 | assert_eq!(felt, Felt::from_raw(felt.to_raw())); 66 | 67 | // to_be_digits, to_le_digits 68 | assert_eq!( 69 | UnsignedInteger::<4>::from_hex(&felt.to_hex_string()).unwrap(), 70 | UnsignedInteger::<4>::from_limbs(felt.to_be_digits()) 71 | ); 72 | let mut le_digits_reversed = felt.to_le_digits(); 73 | le_digits_reversed.reverse(); 74 | assert_eq!( 75 | UnsignedInteger::<4>::from_hex(&felt.to_hex_string()).unwrap(), 76 | UnsignedInteger::<4>::from_limbs(le_digits_reversed) 77 | ); 78 | 79 | // TryForm unsigned primitives 80 | if felt <= Felt::from(u8::MAX) { 81 | assert_eq!(felt, Felt::from(u8::try_from(felt).unwrap())); 82 | } else { 83 | assert!(u8::try_from(felt).is_err()); 84 | } 85 | if felt <= Felt::from(u16::MAX) { 86 | assert_eq!(felt, Felt::from(u16::try_from(felt).unwrap())); 87 | } else { 88 | assert!(u16::try_from(felt).is_err()); 89 | } 90 | if felt <= Felt::from(u32::MAX) { 91 | assert_eq!(felt, Felt::from(u32::try_from(felt).unwrap())); 92 | } else { 93 | assert!(u32::try_from(felt).is_err()); 94 | } 95 | if felt <= Felt::from(u64::MAX) { 96 | assert_eq!(felt, Felt::from(u64::try_from(felt).unwrap())); 97 | } else { 98 | assert!(u64::try_from(felt).is_err()); 99 | } 100 | if felt <= Felt::from(usize::MAX) { 101 | assert_eq!(felt, Felt::from(usize::try_from(felt).unwrap())); 102 | } else { 103 | assert!(usize::try_from(felt).is_err()); 104 | } 105 | if felt <= Felt::from(u128::MAX) { 106 | assert_eq!(felt, Felt::from(u128::try_from(felt).unwrap())); 107 | } else { 108 | assert!(u128::try_from(felt).is_err()); 109 | } 110 | 111 | // TryFrom signed primitives 112 | if felt <= Felt::from(i8::MAX) || felt >= Felt::from(i8::MIN) { 113 | assert_eq!(felt, Felt::from(i8::try_from(felt).unwrap())); 114 | } else { 115 | assert!(i8::try_from(felt).is_err()); 116 | } 117 | if felt <= Felt::from(i16::MAX) || felt >= Felt::from(i16::MIN) { 118 | assert_eq!(felt, Felt::from(i16::try_from(felt).unwrap())); 119 | } else { 120 | assert!(i16::try_from(felt).is_err()); 121 | } 122 | if felt <= Felt::from(i32::MAX) || felt >= Felt::from(i32::MIN) { 123 | assert_eq!(felt, Felt::from(i32::try_from(felt).unwrap())); 124 | } else { 125 | assert!(i32::try_from(felt).is_err()); 126 | } 127 | if felt <= Felt::from(i64::MAX) || felt >= Felt::from(i64::MIN) { 128 | assert_eq!(felt, Felt::from(i64::try_from(felt).unwrap())); 129 | } else { 130 | assert!(i64::try_from(felt).is_err()); 131 | } 132 | if felt <= Felt::from(isize::MAX) || felt >= Felt::from(isize::MIN) { 133 | assert_eq!(felt, Felt::from(isize::try_from(felt).unwrap())); 134 | } else { 135 | assert!(isize::try_from(felt).is_err()); 136 | } 137 | if felt <= Felt::from(i128::MAX) || felt >= Felt::from(i128::MIN) { 138 | assert_eq!(felt, Felt::from(i128::try_from(felt).unwrap())); 139 | } else { 140 | assert!(i128::try_from(felt).is_err()); 141 | } 142 | }); 143 | -------------------------------------------------------------------------------- /fuzz/felt/fuzz_targets/div.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use starknet_types_core::felt::{Felt, NonZeroFelt}; 5 | 6 | fuzz_target!(|data: (Felt, Felt)| { 7 | let zero = Felt::ZERO; 8 | let one = Felt::ONE; 9 | 10 | let (a, b) = data; 11 | 12 | // Check a/a = 1 13 | if a != zero { 14 | let a_non_zero = NonZeroFelt::try_from(a).unwrap(); 15 | assert_eq!(a.field_div(&a_non_zero), one); 16 | } 17 | 18 | // Check a / 1 = a 19 | assert_eq!(a.field_div(&NonZeroFelt::try_from(one).unwrap()), a); 20 | 21 | // Check a * a^-1 = 1 22 | if a != zero { 23 | assert_eq!(one, a * a.inverse().unwrap(),); 24 | } 25 | 26 | // Check a / b = a * b^-1 27 | if b != zero { 28 | let b_non_zero = NonZeroFelt::try_from(b).unwrap(); 29 | let b_inverse = b.inverse().unwrap(); 30 | assert_eq!(a.field_div(&b_non_zero), a * b_inverse); 31 | } 32 | 33 | // Check (a / b) / b = a / (b * b) 34 | if b != zero { 35 | let b_non_zero = NonZeroFelt::try_from(b).unwrap(); 36 | let b_times_b_non_zero = NonZeroFelt::try_from(b * b).unwrap(); 37 | assert_eq!( 38 | a.field_div(&b_non_zero).field_div(&b_non_zero), 39 | a.field_div(&b_times_b_non_zero), 40 | ); 41 | } 42 | 43 | // Check a / b = (a.to_biguint() * b^-1.to_biguint()) % PRIME 44 | if b != zero { 45 | let b_non_zero = NonZeroFelt::try_from(b).unwrap(); 46 | let b_inverse = b.inverse().unwrap(); 47 | assert_eq!( 48 | a.field_div(&b_non_zero), 49 | Felt::from(a.to_biguint() * b_inverse.to_biguint()), 50 | ); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /fuzz/felt/fuzz_targets/mul.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use starknet_types_core::felt::Felt; 4 | 5 | fuzz_target!(|data: (Felt, Felt)| { 6 | let zero = Felt::ZERO; 7 | let one = Felt::ONE; 8 | let (a, b) = data; 9 | 10 | // Check a * 1 = a 11 | assert_eq!(a * one, a); 12 | assert_eq!(b * one, b); 13 | 14 | // Check 1 * a = a 15 | assert_eq!(one * a, a); 16 | assert_eq!(one * b, b); 17 | 18 | // Check a * 0 = 0 19 | assert_eq!(a * zero, zero); 20 | assert_eq!(b * zero, zero); 21 | 22 | // Check 0 * a = a 23 | assert_eq!(zero * a, zero); 24 | assert_eq!(zero * b, zero); 25 | 26 | // Check a * max = max - a + 1 27 | assert_eq!(a * Felt::MAX, Felt::MAX - a + 1); 28 | 29 | // Check a * b = b * a 30 | assert_eq!(a * b, b * a); 31 | 32 | // Check a * b = (a.to_biguint() * b.to_biguint()) % PRIME 33 | assert_eq!(a * b, Felt::from(a.to_biguint() * b.to_biguint())); 34 | 35 | assert_eq!(a.square(), a * a); 36 | assert_eq!(b.square(), b * b); 37 | }); 38 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.87.0" 3 | components = ["rustfmt", "clippy", "rust-analyzer"] 4 | profile = "minimal" 5 | --------------------------------------------------------------------------------