diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 4602e9c05f..b72036f574 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -45,32 +45,34 @@ jobs: run: | cargo install --force --locked --path . - - name: check build consistency - working-directory: examples/ecc - run: | - cargo openvm build - mv openvm/app.vmexe app1.vmexe - cargo openvm build - cmp app1.vmexe openvm/app.vmexe || (echo "Build is not deterministic!" && exit 1) - - name: Build and run book examples working-directory: examples run: | + set -e for dir in */; do if [ -f "${dir}Cargo.toml" ]; then echo "Building ${dir%/}" cd "$dir" - cargo openvm build && cargo openvm run + cargo openvm build + cargo openvm run cd .. fi done + - name: check build consistency + working-directory: examples/ecc + run: | + cargo openvm build + mv target/openvm/release/ecc-example.vmexe app1.vmexe + cargo openvm build + cmp app1.vmexe target/openvm/release/ecc-example.vmexe || (echo "Build is not deterministic!" && exit 1) + - name: Run app-level CLI commands working-directory: crates/cli run: | export RUST_BACKTRACE=1 cargo build - cargo run --bin cargo-openvm -- openvm keygen --config ./example/app_config.toml --output app.pk --vk-output app.vk + cargo run --bin cargo-openvm -- openvm keygen --config ./example/app_config.toml --output-dir . - name: Run CLI tests working-directory: crates/cli diff --git a/.github/workflows/extension-tests.yml b/.github/workflows/extension-tests.yml index bb251817d1..3141c6704e 100644 --- a/.github/workflows/extension-tests.yml +++ b/.github/workflows/extension-tests.yml @@ -76,8 +76,6 @@ jobs: FEATURE_ARGS="" if [[ "${{ matrix.extension.name }}" == "pairing" ]]; then FEATURE_ARGS="--features=bn254,bls12_381,halo2curves" - elif [[ "${{ matrix.extension.name }}" == "ecc" ]]; then - FEATURE_ARGS="--features=k256,p256" fi cargo nextest run --cargo-profile=fast $FEATURE_ARGS --no-tests=pass diff --git a/.github/workflows/guest-lib-tests.yml b/.github/workflows/guest-lib-tests.yml new file mode 100644 index 0000000000..1597b4fc99 --- /dev/null +++ b/.github/workflows/guest-lib-tests.yml @@ -0,0 +1,77 @@ +name: Guest Library Tests + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + paths: + - "crates/circuits/**" + - "crates/vm/**" + - "extensions/**" + - "guest-libs/**" + - "Cargo.toml" + - ".github/workflows/guest-lib-tests.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + OPENVM_FAST_TEST: "1" + +jobs: + integration-tests: + strategy: + matrix: + crate: + - { name: "sha2", path: "sha2" } + - { name: "keccak256", path: "keccak256" } + - { name: "ff_derive", path: "ff_derive" } + - { name: "k256", path: "k256" } + - { name: "p256", path: "p256" } + - { name: "ruint", path: "ruint" } + - { name: "pairing", path: "pairing" } + # Ensure tests run in parallel even if one fails + fail-fast: false + + runs-on: + - runs-on=${{ github.run_id }} + - runner=64cpu-linux-arm64 + - tag=crate-${{ matrix.crate.name }} + - extras=s3-cache + + steps: + - uses: runs-on/action@v1 + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + - "crates/circuits/**" + - "crates/vm/**" + - "extensions/**" + - "guest-libs/${{ matrix.crate.path }}/**" + - ".github/workflows/guest-lib-tests.yml" + - name: Skip if no changes + if: steps.filter.outputs.matched == 'false' + run: | + echo "No relevant changes, skipping tests." + exit 0 + + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - uses: taiki-e/install-action@nextest + + - name: Run ${{ matrix.crate.name }} guest library tests + working-directory: guest-libs/${{ matrix.crate.path }} + run: | + rustup component add rust-src --toolchain nightly-2025-02-14 + FEATURE_ARGS="" + if [[ "${{ matrix.crate.name }}" == "pairing" ]]; then + FEATURE_ARGS="--features=bn254,bls12_381" + fi + cargo nextest run --cargo-profile=fast $FEATURE_ARGS --no-tests=pass diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml index bb99d4ff4e..e41580948e 100644 --- a/.github/workflows/lints.yml +++ b/.github/workflows/lints.yml @@ -23,7 +23,7 @@ jobs: - uses: codespell-project/actions-codespell@v2 with: - skip: Cargo.lock,./book/pnpm-lock.yaml,*.txt,./crates/toolchain/openvm/src/memcpy.s,./crates/toolchain/openvm/src/memset.s,./audits/*.pdf, + skip: Cargo.lock,./book/pnpm-lock.yaml,*.txt,./crates/toolchain/openvm/src/memcpy.s,./crates/toolchain/openvm/src/memset.s,./audits/*.pdf,./guest-libs/ruint/* ignore_words_file: .codespellignore - uses: dtolnay/rust-toolchain@stable @@ -43,9 +43,10 @@ jobs: - name: Run clippy run: | - # list of features generated using: - # echo -e "\033[1;32mAll unique features across workspace:\033[0m" && cargo metadata --format-version=1 --no-deps | jq -r '.packages[].features | to_entries[] | .key' | sort -u | sed 's/^/• /' - cargo clippy --all-targets --all --tests --features "aggregation bench-metrics bls12_381 bn254 default entrypoint evm-prove evm-verify export-getrandom export-libm function-span getrandom halo2-compiler halo2curves heap-embedded-alloc k256 jemalloc jemalloc-prof nightly-features panic-handler parallel rust-runtime static-verifier std test-utils unstable" -- -D warnings + # list of all unique features across workspace generated using: + # cargo metadata --format-version=1 --no-deps | jq -r '.packages[].features | to_entries[] | .key' | sort -u | tr '\n' ' ' && echo "" + # (exclude mimalloc since it conflicts with jemalloc) + cargo clippy --all-targets --all --tests --features "aggregation bench-metrics bls12_381 bn254 build-binaries default entrypoint evm-prove evm-verify export-intrinsics export-libm function-span getrandom-unsupported halo2-compiler halo2curves heap-embedded-alloc jemalloc jemalloc-prof nightly-features panic-handler parallel profiling rust-runtime static-verifier std test-utils" -- -D warnings cargo clippy --all-targets --all --tests --no-default-features --features "mimalloc" -- -D warnings - name: Run fmt, clippy for guest @@ -56,16 +57,13 @@ jobs: if [ -f "$crate_path/Cargo.toml" ]; then echo "Running cargo fmt, clippy for $crate_path" cargo +nightly fmt --manifest-path "$crate_path/Cargo.toml" --all -- --check - if [[ "$crate_path" == *"extensions/ecc/tests/programs"* ]]; then - echo "Running cargo clippy with k256 and p256 features for $crate_path" - cargo clippy --manifest-path "$crate_path/Cargo.toml" --all-targets --features "std k256 p256" -- -D warnings - elif [[ "$crate_path" == *"extensions/pairing/tests/programs"* ]]; then + if [[ "$crate_path" == *"guest-libs/pairing/tests/programs"* ]]; then echo "Running cargo clippy with openvm_pairing_guest::bn254 feature for $crate_path" cargo clippy --manifest-path "$crate_path/Cargo.toml" --all-targets --features "std bn254" -- -D warnings echo "Running cargo clippy with openvm_pairing_guest::bls12_381 feature for $crate_path" cargo clippy --manifest-path "$crate_path/Cargo.toml" --all-targets --features "std bls12_381" -- -D warnings - elif [[ "$crate_path" == *"extensions/"* ]]; then - echo "Running cargo clippy for $crate_path" + elif [[ "$crate_path" == *"extensions/"* || "$crate_path" == *"guest-libs/"* ]]; then + echo "Running cargo clippy with std feature for $crate_path" cargo clippy --manifest-path "$crate_path/Cargo.toml" --all-targets --features "std" -- -D warnings else echo "Running cargo clippy for $crate_path" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..1e014d4030 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +## v1.1.2 (2025-05-08) + +- The solidity verifier contract no longer has any awareness of the OpenVM patch version. `{MAJOR_VERSION}.{MINOR_VERSION}` is the minimum information necessary to identify the verifier contract since any verifier contract changes will be accompanied by a minor version bump. + +## v1.1.1 (2025-05-03) + +- Adds `OpenVmHalo2Verifier` generation to the SDK which is a thin wrapper around the original `Halo2Verifier` contract exposing a more user-friendly interface. +- Updates the CLI to generate the new `OpenVmHalo2Verifier` contract during `cargo openvm setup`. +- Removes the ability to generate the old `Halo2Verifier` contract from the SDK and CLI. +- Changes the `EvmProof` struct to align with the interface of the `OpenVmHalo2Verifier` contract. +- Formats the verifier contract during generation for better readability on block explorers. +- For verifier contract compilation, explicitly sets the `solc` config via standard-json input for metadata consistency. + +## v1.1.0 (2025-05-02) + +### Security Fixes +- Fixes security vulnerability [OpenVM allows the byte decomposition of pc in AUIPC chip to overflow](https://github.com/advisories/GHSA-jf2r-x3j4-23m7) diff --git a/Cargo.lock b/Cargo.lock index dcc00c0729..4134a7d7f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,9 +34,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -82,7 +82,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" dependencies = [ - "alloy-primitives 1.1.2", + "alloy-primitives 1.2.0", "alloy-rlp", "crc", "serde", @@ -95,7 +95,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" dependencies = [ - "alloy-primitives 1.1.2", + "alloy-primitives 1.2.0", "alloy-rlp", "serde", ] @@ -106,23 +106,23 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" dependencies = [ - "alloy-primitives 1.1.2", + "alloy-primitives 1.2.0", "alloy-rlp", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "thiserror 2.0.12", ] [[package]] name = "alloy-eips" -version = "0.14.0" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609515c1955b33af3d78d26357540f68c5551a90ef58fd53def04f2aa074ec43" +checksum = "4f853de9ca1819f54de80de5d03bfc1bb7c9fafcf092b480a654447141bc354d" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", - "alloy-primitives 1.1.2", + "alloy-primitives 1.2.0", "alloy-rlp", "alloy-serde", "auto_impl", @@ -155,9 +155,9 @@ dependencies = [ "cfg-if", "const-hex", "derive_more 0.99.20", - "hex-literal", + "hex-literal 0.4.1", "itoa", - "ruint", + "ruint 1.15.0", "tiny-keccak", ] @@ -172,9 +172,9 @@ dependencies = [ "cfg-if", "const-hex", "derive_more 0.99.20", - "hex-literal", + "hex-literal 0.4.1", "itoa", - "ruint", + "ruint 1.15.0", "tiny-keccak", ] @@ -190,15 +190,15 @@ dependencies = [ "const-hex", "derive_more 2.0.1", "foldhash", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "indexmap 2.9.0", "itoa", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "keccak-asm", "paste", "proptest", "rand 0.8.5", - "ruint", + "ruint 1.15.0", "rustc-hash 2.1.1", "serde", "sha3", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "a326d47106039f38b811057215a92139f46eef7983a4b77b10930a0ea5685b1e" dependencies = [ "alloy-rlp", "bytes", @@ -217,15 +217,15 @@ dependencies = [ "const-hex", "derive_more 2.0.1", "foldhash", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "indexmap 2.9.0", "itoa", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "keccak-asm", "paste", "proptest", "rand 0.9.1", - "ruint", + "ruint 1.15.0", "rustc-hash 2.1.1", "serde", "sha3", @@ -251,16 +251,16 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "alloy-serde" -version = "0.14.0" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4dba6ff08916bc0a9cbba121ce21f67c0b554c39cf174bc7b9df6c651bd3c3b" +checksum = "906ce0190afeded19cb2e963cb8507c975a7862216b9e74f39bf91ddee6ae74b" dependencies = [ - "alloy-primitives 1.1.2", + "alloy-primitives 1.2.0", "serde", "serde_json", ] @@ -276,7 +276,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -293,7 +293,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "syn-solidity", "tiny-keccak", ] @@ -312,7 +312,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.101", + "syn 2.0.103", "syn-solidity", ] @@ -323,7 +323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d162f8524adfdfb0e4bd0505c734c985f3e2474eb022af32eef0d52a4f3935c" dependencies = [ "serde", - "winnow 0.7.10", + "winnow 0.7.11", ] [[package]] @@ -371,9 +371,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -386,33 +386,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -425,6 +425,21 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + [[package]] name = "ariadne" version = "0.2.0" @@ -441,24 +456,77 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" dependencies = [ - "ark-ec", + "ark-ec 0.5.0", "ark-ff 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", ] +[[package]] +name = "ark-bn254" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea691771ebbb28aea556c044e2e5c5227398d840cee0c34d4d20fa8eb2689e8c" +dependencies = [ + "ark-ec 0.3.0", + "ark-ff 0.3.0", + "ark-std 0.3.0", +] + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + [[package]] name = "ark-bn254" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" dependencies = [ - "ark-ec", + "ark-ec 0.5.0", "ark-ff 0.5.0", "ark-r1cs-std", "ark-std 0.5.0", ] +[[package]] +name = "ark-ec" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea978406c4b1ca13c2db2373b05cc55429c3575b8b21f1b9ee859aa5b03dd42" +dependencies = [ + "ark-ff 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ec" version = "0.5.0" @@ -467,12 +535,12 @@ checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" dependencies = [ "ahash", "ark-ff 0.5.0", - "ark-poly", + "ark-poly 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "itertools 0.13.0", "num-bigint 0.4.6", "num-integer", @@ -565,7 +633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -603,7 +671,20 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", ] [[package]] @@ -618,7 +699,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -627,7 +708,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" dependencies = [ - "ark-ec", + "ark-ec 0.5.0", "ark-ff 0.5.0", "ark-relations", "ark-std 0.5.0", @@ -666,6 +747,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ + "ark-serialize-derive 0.4.2", "ark-std 0.4.0", "digest 0.10.7", "num-bigint 0.4.6", @@ -677,13 +759,24 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ - "ark-serialize-derive", + "ark-serialize-derive 0.5.0", "ark-std 0.5.0", "arrayvec", "digest 0.10.7", "num-bigint 0.4.6", ] +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-serialize-derive" version = "0.5.0" @@ -692,7 +785,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -754,7 +847,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -790,20 +883,20 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.6.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a18fd934af6ae7ca52410d4548b98eb895aab0f1ea417d168d85db1434a141" +checksum = "455e9fb7743c6f6267eb2830ccc08686fbb3d13c9a689369562fd4d4ef9ea462" dependencies = [ "aws-credential-types", "aws-runtime", @@ -866,9 +959,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.7" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4063282c69991e57faab9e5cb21ae557e59f5b0fb285c196335243df8dc25c" +checksum = "4f6c68419d8ba16d9a7463671593c54f81ba58cab466e9b759418da606dcc2e2" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -891,9 +984,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.90.0" +version = "1.93.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5934beb9403c562bd129a1de1bd51ab67209c05ddf3a4a8c86714120181c860f" +checksum = "16b9734dc8145b417a3c22eae8769a2879851690982dba718bdc52bd28ad04ce" dependencies = [ "aws-credential-types", "aws-runtime", @@ -925,9 +1018,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.71.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a4fd09d6e863655d99cd2260f271c6d1030dc6bfad68e19e126d2e4c8ceb18" +checksum = "b2ac1674cba7872061a29baaf02209fefe499ff034dfd91bd4cc59e4d7741489" dependencies = [ "aws-credential-types", "aws-runtime", @@ -947,9 +1040,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.72.0" +version = "1.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3224ab02ebb3074467a33d57caf6fcb487ca36f3697fdd381b0428dc72380696" +checksum = "3a6a22f077f5fd3e3c0270d4e1a110346cddf6769e9433eb9e6daceb4ca3b149" dependencies = [ "aws-credential-types", "aws-runtime", @@ -969,9 +1062,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.72.0" +version = "1.75.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6933f189ed1255e78175fbd73fb200c0aae7240d220ed3346f567b0ddca3083" +checksum = "e3258fa707f2f585ee3049d9550954b959002abd59176975150a01d5cf38ae3f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -992,9 +1085,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3734aecf9ff79aa401a6ca099d076535ab465ff76b46440cf567c8e70b65dc13" +checksum = "ddfb9021f581b71870a17eac25b52335b82211cdc092e02b6876b2bcefa61666" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -1051,9 +1144,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.8" +version = "0.60.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c45d3dddac16c5c59d553ece225a88870cf81b7b813c9cc17b78cf4685eac7a" +checksum = "338a3642c399c0a5d157648426110e199ca7fd1c689cc395676b81aa563700c4" dependencies = [ "aws-smithy-types", "bytes", @@ -1083,13 +1176,14 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e44697a9bded898dcd0b1cb997430d949b87f4f8940d91023ae9062bf218250" +checksum = "7f491388e741b7ca73b24130ff464c1478acc34d5b331b7dd0a2ee4643595a15" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", + "h2 0.3.26", "h2 0.4.10", "http 0.2.12", "http 1.3.1", @@ -1097,11 +1191,11 @@ dependencies = [ "hyper 0.14.32", "hyper 1.6.0", "hyper-rustls 0.24.2", - "hyper-rustls 0.27.6", + "hyper-rustls 0.27.7", "hyper-util", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -1111,9 +1205,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.61.3" +version = "0.61.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" dependencies = [ "aws-smithy-types", ] @@ -1163,9 +1257,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e5d9e3a80a18afa109391fb5ad09c3daf887b516c6fd805a157c6ea7994a57" +checksum = "bd8531b6d8882fd8f48f82a9754e682e29dd44cff27154af51fa3eb730f59efb" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1180,9 +1274,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40076bd09fadbc12d5e026ae080d0930defa606856186e31d83ccc6a255eeaf3" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" dependencies = [ "base64-simd", "bytes", @@ -1206,9 +1300,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.9" +version = "0.60.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" dependencies = [ "xmlparser", ] @@ -1285,9 +1379,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bincode" @@ -1317,7 +1411,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.101", + "syn 2.0.103", "which", ] @@ -1372,7 +1466,7 @@ checksum = "42b6b4cb608b8282dc3b53d0f4c9ab404655d562674c682db7e6c0458cc83c23" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1481,9 +1575,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -1491,11 +1585,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bn-rs" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34e20109dce74b02019885a01edc8ca485380a297ed8d6eb9e63e657774074b" +dependencies = [ + "getrandom 0.2.16", + "js-sys", + "primitive-types", + "rustc-hex", + "thiserror 1.0.69", + "uint", + "wasm-bindgen", +] + [[package]] name = "bon" -version = "3.6.3" +version = "3.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced38439e7a86a4761f7f7d5ded5ff009135939ecb464a24452eaa4c1696af7d" +checksum = "f61138465baf186c63e8d9b6b613b508cd832cba4ce93cf37ce5f096f91ac1a6" dependencies = [ "bon-macros", "rustversion", @@ -1503,9 +1612,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.6.3" +version = "3.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce61d2d3844c6b8d31b2353d9f66cf5e632b3e9549583fe3cac2f4f6136725e" +checksum = "40d1dad34aa19bf02295382f08d9bc40651585bd497266831d40ee6296fb49ca" dependencies = [ "darling", "ident_case", @@ -1513,7 +1622,30 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.103", ] [[package]] @@ -1534,9 +1666,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byte-slice-cast" @@ -1546,9 +1678,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -1621,15 +1753,17 @@ dependencies = [ [[package]] name = "cargo-openvm" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "aws-config", "aws-sdk-s3", - "bitcode", "clap", "eyre", "hex", + "include_dir", + "itertools 0.14.0", "openvm-build", + "openvm-circuit", "openvm-native-recursion", "openvm-sdk", "openvm-stark-backend", @@ -1637,10 +1771,11 @@ dependencies = [ "openvm-transpiler", "serde", "serde_json", - "target-lexicon", + "target-lexicon 0.12.16", "tempfile", "tokio", - "toml 0.8.22", + "toml 0.8.23", + "toml_edit 0.22.27", "tracing", "vergen", ] @@ -1676,9 +1811,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.25" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -1696,9 +1831,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" @@ -1763,9 +1904,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -1773,9 +1914,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -1785,21 +1926,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmake" @@ -1812,9 +1953,18 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] [[package]] name = "const-default" @@ -1931,11 +2081,10 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc-fast" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fcb2be5386ffb77e30bf10820934cb89a628bcb976e7cc632dcd88c059ebea" +checksum = "6bf62af4cc77d8fe1c22dde4e721d87f2f54056139d8c412e1366b740305f56f" dependencies = [ - "cc", "crc", "digest 0.10.7", "libc", @@ -2111,7 +2260,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2122,7 +2271,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2142,6 +2291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -2174,7 +2324,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2185,18 +2335,18 @@ checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "derive-where" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2209,7 +2359,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2238,7 +2388,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "unicode-xid", ] @@ -2250,10 +2400,41 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "unicode-xid", ] +[[package]] +name = "diesel" +version = "2.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a917a9209950404d5be011c81d081a2692a822f73c3d6af586f0cab5ff50f614" +dependencies = [ + "diesel_derives", +] + +[[package]] +name = "diesel_derives" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52841e97814f407b895d836fa0012091dff79c6268f39ad8155d384c21ae0d26" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn 2.0.103", +] + [[package]] name = "digest" version = "0.9.0" @@ -2325,7 +2506,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2334,12 +2515,32 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dsl_auto_type" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" +dependencies = [ + "darling", + "either", + "heck", + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "ecdsa" version = "0.14.8" @@ -2362,6 +2563,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve 0.13.8", "rfc6979 0.4.0", + "serdect", "signature 2.2.0", "spki 0.7.3", ] @@ -2375,7 +2577,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2417,15 +2619,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct 0.2.0", + "base64ct", "crypto-bigint 0.5.5", "digest 0.10.7", "ff 0.13.1", "generic-array", "group 0.13.0", + "hkdf", + "pem-rfc7468", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", + "serde_json", + "serdect", "subtle", + "tap", "zeroize", ] @@ -2482,7 +2690,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2494,7 +2702,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2505,7 +2713,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2593,6 +2801,17 @@ dependencies = [ "uint", ] +[[package]] +name = "ethereum_ssz" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3627f83d8b87b432a5fad9934b4565260722a141a2c40f371f8080adec9425" +dependencies = [ + "ethereum-types", + "itertools 0.10.5", + "smallvec", +] + [[package]] name = "ethers-core" version = "2.0.14" @@ -2606,7 +2825,7 @@ dependencies = [ "elliptic-curve 0.13.8", "ethabi", "generic-array", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "num_enum", "open-fastrlp", "rand 0.8.5", @@ -2671,6 +2890,17 @@ dependencies = [ "yansi 0.5.1", ] +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "eyre" version = "0.6.12" @@ -2681,6 +2911,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "2.3.0" @@ -2757,7 +2993,7 @@ dependencies = [ "atomic", "pear", "serde", - "toml 0.8.22", + "toml 0.8.23", "uncased", "version_check", ] @@ -2782,9 +3018,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -2887,6 +3123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -2895,6 +3132,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -2909,7 +3157,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2933,6 +3181,7 @@ dependencies = [ "futures-core", "futures-io", "futures-macro", + "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -2964,8 +3213,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2982,14 +3233,14 @@ dependencies = [ [[package]] name = "getset" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3013,9 +3264,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b46b9ca4690308844c644e7c634d68792467260e051c8543e0c7871662b3ba7" +checksum = "50a99dbe56b72736564cfa4b85bf9a33079f16ae8b74983ab06af3b1a3696b11" [[package]] name = "glob" @@ -3270,6 +3521,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -3282,9 +3542,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -3292,6 +3552,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.4", +] + [[package]] name = "heck" version = "0.5.0" @@ -3300,9 +3569,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -3328,6 +3597,21 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hex-literal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -3476,14 +3760,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", "hyper 1.6.0", "hyper-util", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -3493,9 +3777,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "bytes", "futures-channel", @@ -3684,14 +3968,33 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] -name = "indenter" -version = "0.3.3" +name = "include_dir" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" @@ -3711,7 +4014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -3838,6 +4141,40 @@ dependencies = [ "subtle", ] +[[package]] +name = "k256" +version = "0.13.4" +dependencies = [ + "derive_more 1.0.0", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "eyre", + "ff 0.13.1", + "hex-literal 0.4.1", + "num-bigint 0.4.6", + "once_cell", + "openvm", + "openvm-algebra-circuit", + "openvm-algebra-guest", + "openvm-algebra-moduli-macros", + "openvm-algebra-transpiler", + "openvm-circuit", + "openvm-ecc-circuit", + "openvm-ecc-guest", + "openvm-ecc-sw-macros", + "openvm-ecc-transpiler", + "openvm-rv32im-circuit", + "openvm-rv32im-transpiler", + "openvm-sha256-circuit", + "openvm-sha256-transpiler", + "openvm-stark-backend", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "serde", + "signature 2.2.0", +] + [[package]] name = "k256" version = "0.13.4" @@ -3917,9 +4254,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libgit2-sys" @@ -3940,7 +4277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -3951,9 +4288,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmimalloc-sys" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" dependencies = [ "cc", "libc", @@ -4079,7 +4416,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -4090,7 +4427,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4124,9 +4461,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" @@ -4137,6 +4474,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "memuse" version = "0.2.2" @@ -4191,9 +4537,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" dependencies = [ "libmimalloc-sys", ] @@ -4212,9 +4558,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -4226,7 +4572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -4347,6 +4693,33 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-modular" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a5fe11d4135c3bcdf3a95b18b194afa9608a5f6ff034f5d857bc9a27fb0119" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-prime" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e238432a7881ec7164503ccc516c014bf009be7984cde1ba56837862543bdec3" +dependencies = [ + "bitvec", + "either", + "lru", + "num-bigint 0.4.6", + "num-integer", + "num-modular", + "num-traits", + "rand 0.8.5", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -4396,7 +4769,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4440,6 +4813,10 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -4492,10 +4869,11 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openvm" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "bytemuck", "chrono", + "getrandom 0.3.3", "num-bigint 0.4.6", "openvm-custom-insn", "openvm-platform", @@ -4505,10 +4883,11 @@ dependencies = [ [[package]] name = "openvm-algebra-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "derive_more 1.0.0", + "eyre", "halo2curves-axiom", "itertools 0.14.0", "num-bigint 0.4.6", @@ -4535,37 +4914,42 @@ dependencies = [ [[package]] name = "openvm-algebra-complex-macros" -version = "0.1.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-macros-common", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "openvm-algebra-guest" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "halo2curves-axiom", "num-bigint 0.4.6", + "once_cell", "openvm-algebra-complex-macros", "openvm-algebra-moduli-macros", + "openvm-custom-insn", + "openvm-rv32im-guest", "serde-big-array", "strum_macros", ] [[package]] name = "openvm-algebra-moduli-macros" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ + "num-bigint 0.4.6", + "num-prime", "openvm-macros-common", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "openvm-algebra-tests" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "eyre", "num-bigint 0.4.6", @@ -4582,7 +4966,7 @@ dependencies = [ [[package]] name = "openvm-algebra-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-algebra-guest", "openvm-instructions", @@ -4595,7 +4979,7 @@ dependencies = [ [[package]] name = "openvm-benchmarks-execute" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "cargo-openvm", "clap", @@ -4617,13 +5001,13 @@ dependencies = [ [[package]] name = "openvm-benchmarks-prove" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "clap", "derive-new 0.6.0", "derive_more 1.0.0", "eyre", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.4.6", "openvm-algebra-circuit", "openvm-algebra-transpiler", @@ -4653,7 +5037,7 @@ dependencies = [ [[package]] name = "openvm-benchmarks-utils" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "cargo_metadata", "clap", @@ -4667,7 +5051,7 @@ dependencies = [ [[package]] name = "openvm-bigint-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "derive_more 1.0.0", @@ -4688,35 +5072,15 @@ dependencies = [ [[package]] name = "openvm-bigint-guest" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ - "num-bigint 0.4.6", - "num-traits", - "openvm", "openvm-platform", - "serde", - "serde-big-array", "strum_macros", ] -[[package]] -name = "openvm-bigint-integration-tests" -version = "1.2.0" -dependencies = [ - "eyre", - "openvm-bigint-circuit", - "openvm-bigint-transpiler", - "openvm-circuit", - "openvm-instructions", - "openvm-rv32im-transpiler", - "openvm-stark-sdk", - "openvm-toolchain-tests", - "openvm-transpiler", -] - [[package]] name = "openvm-bigint-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-bigint-guest", "openvm-instructions", @@ -4730,7 +5094,7 @@ dependencies = [ [[package]] name = "openvm-build" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "cargo_metadata", "eyre", @@ -4741,7 +5105,7 @@ dependencies = [ [[package]] name = "openvm-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "backtrace", "cfg-if", @@ -4778,16 +5142,16 @@ dependencies = [ [[package]] name = "openvm-circuit-derive" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "itertools 0.14.0", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "openvm-circuit-primitives" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "itertools 0.14.0", @@ -4804,16 +5168,16 @@ dependencies = [ [[package]] name = "openvm-circuit-primitives-derive" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "itertools 0.14.0", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "openvm-continuations" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derivative", "openvm-circuit", @@ -4831,28 +5195,27 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "openvm-ecc-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "derive_more 1.0.0", "eyre", + "hex-literal 0.4.1", "lazy_static", "num-bigint 0.4.6", "num-integer", "num-traits", "once_cell", "openvm-algebra-circuit", - "openvm-algebra-guest", "openvm-circuit", "openvm-circuit-derive", "openvm-circuit-primitives", "openvm-circuit-primitives-derive", - "openvm-ecc-guest", "openvm-ecc-transpiler", "openvm-instructions", "openvm-mod-circuit-builder", @@ -4860,7 +5223,6 @@ dependencies = [ "openvm-rv32im-circuit", "openvm-stark-backend", "openvm-stark-sdk", - "rand 0.8.5", "serde", "serde_with", "strum", @@ -4868,40 +5230,34 @@ dependencies = [ [[package]] name = "openvm-ecc-guest" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "ecdsa 0.16.9", "elliptic-curve 0.13.8", "group 0.13.0", "halo2curves-axiom", - "hex-literal", - "k256", - "lazy_static", - "num-bigint 0.4.6", "once_cell", "openvm", "openvm-algebra-guest", - "openvm-algebra-moduli-macros", "openvm-custom-insn", "openvm-ecc-sw-macros", "openvm-rv32im-guest", - "p256 0.13.2", "serde", "strum_macros", ] [[package]] name = "openvm-ecc-integration-tests" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "eyre", - "hex-literal", + "halo2curves-axiom", + "hex-literal 0.4.1", "num-bigint 0.4.6", "openvm-algebra-circuit", "openvm-algebra-transpiler", "openvm-circuit", "openvm-ecc-circuit", - "openvm-ecc-guest", "openvm-ecc-transpiler", "openvm-keccak256-transpiler", "openvm-rv32im-transpiler", @@ -4913,16 +5269,16 @@ dependencies = [ [[package]] name = "openvm-ecc-sw-macros" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-macros-common", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "openvm-ecc-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-ecc-guest", "openvm-instructions", @@ -4933,17 +5289,44 @@ dependencies = [ "strum", ] +[[package]] +name = "openvm-ff-derive" +version = "1.2.1-rc.0" +dependencies = [ + "addchain", + "eyre", + "num-bigint 0.3.3", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "openvm-algebra-circuit", + "openvm-algebra-transpiler", + "openvm-circuit", + "openvm-instructions", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "openvm-instructions" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "backtrace", + "bitcode", + "criterion", "derive-new 0.6.0", "itertools 0.14.0", "num-bigint 0.4.6", "num-traits", "openvm-instructions-derive", "openvm-stark-backend", + "p3-baby-bear", + "rand 0.8.5", "serde", "strum", "strum_macros", @@ -4951,18 +5334,35 @@ dependencies = [ [[package]] name = "openvm-instructions-derive" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-instructions", "quote", "strum", "strum_macros", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "openvm-keccak256" +version = "1.2.1-rc.0" +dependencies = [ + "eyre", + "openvm-circuit", + "openvm-instructions", + "openvm-keccak256-circuit", + "openvm-keccak256-guest", + "openvm-keccak256-transpiler", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "tiny-keccak", ] [[package]] name = "openvm-keccak256-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "derive_more 1.0.0", @@ -4989,30 +5389,14 @@ dependencies = [ [[package]] name = "openvm-keccak256-guest" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-platform", - "tiny-keccak", -] - -[[package]] -name = "openvm-keccak256-integration-tests" -version = "1.2.0" -dependencies = [ - "eyre", - "openvm-circuit", - "openvm-instructions", - "openvm-keccak256-circuit", - "openvm-keccak256-transpiler", - "openvm-rv32im-transpiler", - "openvm-stark-sdk", - "openvm-toolchain-tests", - "openvm-transpiler", ] [[package]] name = "openvm-keccak256-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-instructions", "openvm-instructions-derive", @@ -5025,14 +5409,14 @@ dependencies = [ [[package]] name = "openvm-macros-common" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "openvm-mod-circuit-builder" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "halo2curves-axiom", "itertools 0.14.0", @@ -5052,7 +5436,7 @@ dependencies = [ [[package]] name = "openvm-native-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "derive_more 1.0.0", @@ -5079,7 +5463,7 @@ dependencies = [ [[package]] name = "openvm-native-compiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "backtrace", "itertools 0.14.0", @@ -5105,15 +5489,15 @@ dependencies = [ [[package]] name = "openvm-native-compiler-derive" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "openvm-native-recursion" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "bitcode", "cfg-if", @@ -5143,16 +5527,53 @@ dependencies = [ [[package]] name = "openvm-native-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-instructions", "openvm-transpiler", "p3-field", ] +[[package]] +name = "openvm-pairing" +version = "1.2.1-rc.0" +dependencies = [ + "eyre", + "group 0.13.0", + "halo2curves-axiom", + "hex-literal 0.4.1", + "itertools 0.14.0", + "num-bigint 0.4.6", + "num-traits", + "openvm", + "openvm-algebra-circuit", + "openvm-algebra-complex-macros", + "openvm-algebra-guest", + "openvm-algebra-moduli-macros", + "openvm-algebra-transpiler", + "openvm-circuit", + "openvm-custom-insn", + "openvm-ecc-circuit", + "openvm-ecc-guest", + "openvm-ecc-sw-macros", + "openvm-ecc-transpiler", + "openvm-instructions", + "openvm-pairing-circuit", + "openvm-pairing-guest", + "openvm-pairing-transpiler", + "openvm-platform", + "openvm-rv32im-guest", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "rand 0.8.5", + "serde", +] + [[package]] name = "openvm-pairing-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "derive_more 1.0.0", @@ -5183,58 +5604,28 @@ dependencies = [ [[package]] name = "openvm-pairing-guest" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ - "group 0.13.0", "halo2curves-axiom", - "hex-literal", + "hex-literal 0.4.1", "itertools 0.14.0", "lazy_static", "num-bigint 0.4.6", "num-traits", "openvm", - "openvm-algebra-complex-macros", "openvm-algebra-guest", "openvm-algebra-moduli-macros", "openvm-custom-insn", "openvm-ecc-guest", - "openvm-ecc-sw-macros", - "openvm-platform", - "openvm-rv32im-guest", "rand 0.8.5", "serde", "strum_macros", "subtle", ] -[[package]] -name = "openvm-pairing-integration-tests" -version = "1.2.0" -dependencies = [ - "eyre", - "num-bigint 0.4.6", - "num-traits", - "openvm", - "openvm-algebra-circuit", - "openvm-algebra-transpiler", - "openvm-circuit", - "openvm-ecc-circuit", - "openvm-ecc-guest", - "openvm-ecc-transpiler", - "openvm-instructions", - "openvm-pairing-circuit", - "openvm-pairing-guest", - "openvm-pairing-transpiler", - "openvm-rv32im-transpiler", - "openvm-stark-sdk", - "openvm-toolchain-tests", - "openvm-transpiler", - "rand 0.8.5", -] - [[package]] name = "openvm-pairing-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-instructions", "openvm-instructions-derive", @@ -5247,11 +5638,10 @@ dependencies = [ [[package]] name = "openvm-platform" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "critical-section", "embedded-alloc", - "getrandom 0.2.16", "libm", "openvm-custom-insn", "openvm-rv32im-guest", @@ -5259,7 +5649,7 @@ dependencies = [ [[package]] name = "openvm-poseidon2-air" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derivative", "lazy_static", @@ -5275,7 +5665,7 @@ dependencies = [ [[package]] name = "openvm-prof" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "clap", "eyre", @@ -5288,7 +5678,7 @@ dependencies = [ [[package]] name = "openvm-rv32-adapters" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "itertools 0.14.0", @@ -5308,7 +5698,7 @@ dependencies = [ [[package]] name = "openvm-rv32im-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "derive_more 1.0.0", @@ -5332,21 +5722,23 @@ dependencies = [ [[package]] name = "openvm-rv32im-guest" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-custom-insn", + "p3-field", "strum_macros", ] [[package]] name = "openvm-rv32im-integration-tests" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "eyre", "openvm", "openvm-circuit", "openvm-instructions", "openvm-rv32im-circuit", + "openvm-rv32im-guest", "openvm-rv32im-transpiler", "openvm-stark-sdk", "openvm-toolchain-tests", @@ -5357,7 +5749,7 @@ dependencies = [ [[package]] name = "openvm-rv32im-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-instructions", "openvm-instructions-derive", @@ -5372,7 +5764,7 @@ dependencies = [ [[package]] name = "openvm-sdk" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "alloy-sol-types", "async-trait", @@ -5387,6 +5779,7 @@ dependencies = [ "hex", "itertools 0.14.0", "metrics", + "num-bigint 0.4.6", "openvm", "openvm-algebra-circuit", "openvm-algebra-transpiler", @@ -5402,6 +5795,7 @@ dependencies = [ "openvm-native-circuit", "openvm-native-compiler", "openvm-native-recursion", + "openvm-native-transpiler", "openvm-pairing-circuit", "openvm-pairing-transpiler", "openvm-rv32im-circuit", @@ -5412,6 +5806,7 @@ dependencies = [ "openvm-stark-sdk", "openvm-transpiler", "p3-fri", + "rrs-lib", "serde", "serde_json", "serde_with", @@ -5422,9 +5817,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "openvm-sha2" +version = "1.2.1-rc.0" +dependencies = [ + "eyre", + "openvm-circuit", + "openvm-instructions", + "openvm-rv32im-transpiler", + "openvm-sha256-circuit", + "openvm-sha256-guest", + "openvm-sha256-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "sha2 0.10.9", +] + [[package]] name = "openvm-sha256-air" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-circuit", "openvm-circuit-primitives", @@ -5436,7 +5848,7 @@ dependencies = [ [[package]] name = "openvm-sha256-circuit" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive-new 0.6.0", "derive_more 1.0.0", @@ -5458,30 +5870,14 @@ dependencies = [ [[package]] name = "openvm-sha256-guest" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-platform", - "sha2 0.10.9", -] - -[[package]] -name = "openvm-sha256-integration-tests" -version = "1.2.0" -dependencies = [ - "eyre", - "openvm-circuit", - "openvm-instructions", - "openvm-rv32im-transpiler", - "openvm-sha256-circuit", - "openvm-sha256-transpiler", - "openvm-stark-sdk", - "openvm-toolchain-tests", - "openvm-transpiler", ] [[package]] name = "openvm-sha256-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "openvm-instructions", "openvm-instructions-derive", @@ -5495,7 +5891,7 @@ dependencies = [ [[package]] name = "openvm-stark-backend" version = "1.1.1" -source = "git+https://github.com/powdr-labs/stark-backend.git?rev=fe1c5a8#fe1c5a80772290203650cbdbc89ef63d5c61b1dc" +source = "git+https://github.com/powdr-labs/stark-backend.git?rev=fd3c685#fd3c6854035e72601fda783dd2998bd21522996e" dependencies = [ "bitcode", "cfg-if", @@ -5523,7 +5919,7 @@ dependencies = [ [[package]] name = "openvm-stark-sdk" version = "1.1.1" -source = "git+https://github.com/powdr-labs/stark-backend.git?rev=fe1c5a8#fe1c5a80772290203650cbdbc89ef63d5c61b1dc" +source = "git+https://github.com/powdr-labs/stark-backend.git?rev=fd3c685#fd3c6854035e72601fda783dd2998bd21522996e" dependencies = [ "derivative", "derive_more 0.99.20", @@ -5549,7 +5945,7 @@ dependencies = [ "serde", "serde_json", "static_assertions", - "toml 0.8.22", + "toml 0.8.23", "tracing", "tracing-forest", "tracing-subscriber 0.3.19", @@ -5558,7 +5954,7 @@ dependencies = [ [[package]] name = "openvm-toolchain-tests" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "derive_more 1.0.0", "eyre", @@ -5568,7 +5964,7 @@ dependencies = [ "openvm-bigint-circuit", "openvm-build", "openvm-circuit", - "openvm-ecc-guest", + "openvm-ecc-circuit", "openvm-instructions", "openvm-platform", "openvm-rv32im-circuit", @@ -5583,7 +5979,7 @@ dependencies = [ [[package]] name = "openvm-transpiler" -version = "1.2.0" +version = "1.2.1-rc.0" dependencies = [ "elf", "eyre", @@ -5595,6 +5991,20 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "openvm-verify-stark" +version = "1.2.1-rc.0" +dependencies = [ + "eyre", + "openvm-circuit", + "openvm-native-compiler", + "openvm-native-recursion", + "openvm-rv32im-guest", + "openvm-sdk", + "openvm-stark-sdk", + "openvm-verify-stark", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -5633,6 +6043,38 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "p256" +version = "0.13.2" +dependencies = [ + "derive_more 1.0.0", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "eyre", + "ff 0.13.1", + "hex-literal 0.4.1", + "num-bigint 0.4.6", + "openvm", + "openvm-algebra-circuit", + "openvm-algebra-guest", + "openvm-algebra-moduli-macros", + "openvm-algebra-transpiler", + "openvm-circuit", + "openvm-ecc-circuit", + "openvm-ecc-guest", + "openvm-ecc-sw-macros", + "openvm-ecc-transpiler", + "openvm-rv32im-circuit", + "openvm-rv32im-transpiler", + "openvm-sha256-circuit", + "openvm-sha256-transpiler", + "openvm-stark-backend", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "serde", +] + [[package]] name = "p256" version = "0.13.2" @@ -6029,9 +6471,15 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.4" @@ -6140,7 +6588,16 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] @@ -6151,9 +6608,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -6200,7 +6657,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6280,23 +6737,66 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "poseidon-primitives" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4aaeda7a092e21165cc5f0cbc738e72a46f31c03c3cbd87b71ceae9d2d93bc" +dependencies = [ + "bitvec", + "ff 0.13.1", + "lazy_static", + "log", + "rand 0.8.5", + "rand_xorshift 0.3.0", + "thiserror 1.0.69", +] + +[[package]] +name = "postgres" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e6dfbdd780d3aa3597b6eb430db76bb315fa9bad7fae595bb8def808b8470" +dependencies = [ + "bytes", + "fallible-iterator", + "futures-util", + "log", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" +dependencies = [ + "base64 0.22.1", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand 0.9.1", + "sha2 0.10.9", + "stringprep", +] [[package]] -name = "poseidon-primitives" -version = "0.2.0" +name = "postgres-types" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4aaeda7a092e21165cc5f0cbc738e72a46f31c03c3cbd87b71ceae9d2d93bc" +checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" dependencies = [ - "bitvec", - "ff 0.13.1", - "lazy_static", - "log", - "rand 0.8.5", - "rand_xorshift", - "thiserror 1.0.69", + "bytes", + "fallible-iterator", + "postgres-protocol", ] [[package]] @@ -6331,12 +6831,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6368,7 +6868,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.26", + "toml_edit 0.22.27", ] [[package]] @@ -6390,7 +6890,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6410,42 +6910,76 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "version_check", "yansi 1.0.1", ] [[package]] name = "proptest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set 0.8.0", "bit-vec 0.8.0", "bitflags 2.9.1", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_xorshift", + "rand 0.9.1", + "rand_chacha 0.9.0", + "rand_xorshift 0.4.0", "regex-syntax 0.8.5", "rusty-fork", "tempfile", "unarray", ] +[[package]] +name = "pyo3" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +dependencies = [ + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", +] + +[[package]] +name = "pyo3-build-config" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +dependencies = [ + "once_cell", + "target-lexicon 0.13.2", +] + +[[package]] +name = "pyo3-ffi" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +dependencies = [ + "libc", + "pyo3-build-config", +] + [[package]] name = "quanta" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -6456,6 +6990,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "quote" version = "1.0.40" @@ -6467,9 +7010,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -6558,6 +7101,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + [[package]] name = "raw-cpuid" version = "11.5.0" @@ -6589,9 +7141,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags 2.9.1", ] @@ -6607,6 +7159,26 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "regex" version = "1.11.1" @@ -6700,9 +7272,9 @@ dependencies = [ [[package]] name = "revm" -version = "22.0.1" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5378e95ffe5c8377002dafeb6f7d370a55517cef7d6d6c16fc552253af3b123" +checksum = "01d277408ff8d6f747665ad9e52150ab4caf8d5eaf0d787614cf84633c8337b4" dependencies = [ "revm-bytecode", "revm-context", @@ -6713,84 +7285,86 @@ dependencies = [ "revm-inspector", "revm-interpreter", "revm-precompile", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "revm-state", ] [[package]] name = "revm-bytecode" -version = "3.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63e138d520c5c5bc25ecc82506e9e4e6e85a811809fc5251c594378dccabfc6" +checksum = "942fe4724cf552fd28db6b0a2ca5b79e884d40dd8288a4027ed1e9090e0c6f49" dependencies = [ "bitvec", + "once_cell", "phf", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "serde", ] [[package]] name = "revm-context" -version = "3.0.1" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9765628dfea4f3686aa8f2a72471c52801e6b38b601939ac16965f49bac66580" +checksum = "b01aad49e1233f94cebda48a4e5cef022f7c7ed29b4edf0d202b081af23435ef" dependencies = [ "cfg-if", "derive-where", "revm-bytecode", "revm-context-interface", "revm-database-interface", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "revm-state", "serde", ] [[package]] name = "revm-context-interface" -version = "3.0.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d74335aa1f14222cc4d3be1f62a029cc7dc03819cc8d080ff17b7e1d76375f" +checksum = "1b844f48a411e62c7dde0f757bf5cce49c85b86d6fc1d3b2722c07f2bec4c3ce" dependencies = [ "alloy-eip2930", "alloy-eip7702", "auto_impl", + "either", "revm-database-interface", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "revm-state", "serde", ] [[package]] name = "revm-database" -version = "3.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5c80c5a2fd605f2119ee32a63fb3be941fb6a81ced8cdb3397abca28317224" +checksum = "ad3fbe34f6bb00a9c3155723b3718b9cb9f17066ba38f9eb101b678cd3626775" dependencies = [ "alloy-eips", "revm-bytecode", "revm-database-interface", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "revm-state", "serde", ] [[package]] name = "revm-database-interface" -version = "3.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0e4dfbc734b1ea67b5e8f8b3c7dc4283e2210d978cdaf6c7a45e97be5ea53b3" +checksum = "7b8acd36784a6d95d5b9e1b7be3ce014f1e759abb59df1fa08396b30f71adc2a" dependencies = [ "auto_impl", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "revm-state", "serde", ] [[package]] name = "revm-handler" -version = "3.0.1" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8676379521c7bf179c31b685c5126ce7800eab5844122aef3231b97026d41a10" +checksum = "481e8c3290ff4fa1c066592fdfeb2b172edfd14d12e6cade6f6f5588cad9359a" dependencies = [ "auto_impl", "revm-bytecode", @@ -6799,23 +7373,23 @@ dependencies = [ "revm-database-interface", "revm-interpreter", "revm-precompile", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "revm-state", "serde", ] [[package]] name = "revm-inspector" -version = "3.0.1" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfed4ecf999a3f6ae776ae2d160478c5dca986a8c2d02168e04066b1e34c789e" +checksum = "fdc1167ef8937d8867888e63581d8ece729a72073d322119ef4627d813d99ecb" dependencies = [ "auto_impl", "revm-context", "revm-database-interface", "revm-handler", "revm-interpreter", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "revm-state", "serde", "serde_json", @@ -6823,36 +7397,36 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "18.0.0" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb20260342003cfb791536e678ef5bbea1bfd1f8178b170e8885ff821985473" +checksum = "b5ee65e57375c6639b0f50555e92a4f1b2434349dd32f52e2176f5c711171697" dependencies = [ "revm-bytecode", "revm-context-interface", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "serde", ] [[package]] name = "revm-precompile" -version = "19.0.0" +version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418e95eba68c9806c74f3e36cd5d2259170b61e90ac608b17ff8c435038ddace" +checksum = "0f9311e735123d8d53a02af2aa81877bba185be7c141be7f931bb3d2f3af449c" dependencies = [ "ark-bls12-381", - "ark-bn254", - "ark-ec", + "ark-bn254 0.5.0", + "ark-ec 0.5.0", "ark-ff 0.5.0", "ark-serialize 0.5.0", "aurora-engine-modexp", "blst", "c-kzg", "cfg-if", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "libsecp256k1", "once_cell", - "p256 0.13.2", - "revm-primitives 18.0.0", + "p256 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "revm-primitives 19.2.0", "ripemd", "secp256k1", "sha2 0.10.9", @@ -6876,24 +7450,24 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "18.0.0" +version = "19.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc2283ff87358ec7501956c5dd8724a6c2be959c619c4861395ae5e0054575f" +checksum = "1c1588093530ec4442461163be49c433c07a3235d1ca6f6799fef338dacc50d3" dependencies = [ - "alloy-primitives 1.1.2", - "enumn", + "alloy-primitives 1.2.0", + "num_enum", "serde", ] [[package]] name = "revm-state" -version = "3.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dd121f6e66d75ab111fb51b4712f129511569bc3e41e6067ae760861418bd8" +checksum = "0040c61c30319254b34507383ba33d85f92949933adf6525a2cede05d165e1fa" dependencies = [ "bitflags 2.9.1", "revm-bytecode", - "revm-primitives 18.0.0", + "revm-primitives 19.2.0", "serde", ] @@ -6985,6 +7559,64 @@ dependencies = [ "paste", ] +[[package]] +name = "ruint" +version = "1.14.0" +dependencies = [ + "alloy-rlp", + "approx", + "arbitrary", + "ark-bn254 0.3.0", + "ark-bn254 0.4.0", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bincode", + "bn-rs", + "borsh", + "bytemuck", + "bytes", + "criterion", + "der 0.7.10", + "diesel", + "ethereum_ssz", + "eyre", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "hex", + "hex-literal 1.0.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "openvm-bigint-circuit", + "openvm-bigint-guest", + "openvm-bigint-transpiler", + "openvm-circuit", + "openvm-instructions", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "parity-scale-codec", + "postgres", + "postgres-types", + "primitive-types", + "proptest", + "pyo3", + "quickcheck", + "rand 0.8.5", + "rand 0.9.1", + "rlp", + "ruint 1.14.0", + "ruint-macro 1.2.1", + "serde", + "serde_json", + "sqlx-core", + "subtle", + "thiserror 2.0.12", + "valuable", + "zeroize", +] + [[package]] name = "ruint" version = "1.15.0" @@ -7006,12 +7638,19 @@ dependencies = [ "rand 0.8.5", "rand 0.9.1", "rlp", - "ruint-macro", + "ruint-macro 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "valuable", "zeroize", ] +[[package]] +name = "ruint-macro" +version = "1.2.1" +dependencies = [ + "ruint 1.14.0", +] + [[package]] name = "ruint-macro" version = "1.2.1" @@ -7020,9 +7659,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -7100,9 +7739,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "aws-lc-rs", "once_cell", @@ -7230,7 +7869,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7242,6 +7881,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -7282,6 +7933,7 @@ dependencies = [ "der 0.7.10", "generic-array", "pkcs8 0.10.2", + "serdect", "subtle", "zeroize", ] @@ -7404,7 +8056,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7432,9 +8084,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -7453,15 +8105,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars", "serde", "serde_derive", "serde_json", @@ -7471,14 +8124,24 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct 0.2.0", + "serde", ] [[package]] @@ -7594,24 +8257,21 @@ checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "snark-verifier" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d798d8ce8e29b8820ecc1028ac44cc4fc0f0296728af6fe6a0c4db05782c0a4" +checksum = "c9203c416ff9de0762667270b21573ba5e6edaeda08743b3ca37dc8a5e0a4480" dependencies = [ "halo2-base", "halo2-ecc", @@ -7624,16 +8284,16 @@ dependencies = [ "pairing 0.23.0", "rand 0.8.5", "revm", - "ruint", + "ruint 1.15.0", "serde", "sha3", ] [[package]] name = "snark-verifier-sdk" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338d065044702bf751e87cf353daac63e2fc4c53a3e323cbcd98c603ee6e66c" +checksum = "290ae6e750d9d5fdf05393bbcae6bf7a63e3408eab023abf7d466156a234ac85" dependencies = [ "bincode", "ethereum-types", @@ -7702,6 +8362,34 @@ dependencies = [ "der 0.7.10", ] +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.4", + "hashlink", + "indexmap 2.9.0", + "log", + "memchr", + "once_cell", + "percent-encoding", + "smallvec", + "thiserror 2.0.12", + "tracing", + "url", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -7732,6 +8420,17 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -7753,7 +8452,7 @@ source = "git+https://github.com/gzanitti/struct-reflection-rs.git#e9743fbb4aace dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7775,7 +8474,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7843,9 +8542,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -7861,7 +8560,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7878,7 +8577,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7914,6 +8613,12 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + [[package]] name = "tempfile" version = "3.20.0" @@ -7956,7 +8661,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7967,7 +8672,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "test-case-core", ] @@ -7990,7 +8695,7 @@ checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8019,7 +8724,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8030,17 +8735,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -8134,6 +8838,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.45.1" @@ -8159,7 +8878,33 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "tokio-postgres" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.9.1", + "socket2", + "tokio", + "tokio-util", + "whoami", ] [[package]] @@ -8178,7 +8923,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.27", + "rustls 0.23.28", "tokio", ] @@ -8210,21 +8955,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.26", + "toml_edit 0.22.27", ] [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] @@ -8244,23 +8989,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", "toml_write", - "winnow 0.7.10", + "winnow 0.7.11", ] [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" @@ -8290,6 +9035,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -8297,20 +9043,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -8422,12 +9168,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unicode-width" version = "0.1.14" @@ -8562,9 +9329,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -8575,6 +9342,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -8597,7 +9370,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-shared", ] @@ -8632,7 +9405,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8674,6 +9447,17 @@ dependencies = [ "rustix 0.38.44", ] +[[package]] +name = "whoami" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -8726,7 +9510,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8737,14 +9521,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" @@ -8824,9 +9608,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -8987,9 +9771,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -9066,28 +9850,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -9107,7 +9891,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] @@ -9128,7 +9912,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -9161,7 +9945,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0cf43631a4..bc3e2847ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,71 +1,74 @@ [workspace.package] -version = "1.2.0" +version = "1.2.1-rc.0" edition = "2021" rust-version = "1.82" authors = ["OpenVM Authors"] homepage = "https://openvm.dev" repository = "https://github.com/openvm-org/" -license = "MIT" +license = "MIT OR Apache-2.0" [workspace] members = [ - "benchmarks/utils", - "benchmarks/execute", - "benchmarks/prove", - "crates/prof", - "crates/sdk", - "crates/cli", - "crates/circuits/mod-builder", - "crates/circuits/poseidon2-air", - "crates/circuits/primitives", - "crates/circuits/primitives/derive", - "crates/toolchain/transpiler", - "crates/toolchain/openvm", - "crates/toolchain/build", - "crates/toolchain/instructions", - "crates/toolchain/instructions/derive", - "crates/toolchain/macros", - "crates/toolchain/platform", - "crates/toolchain/tests", - "crates/continuations", - "crates/vm", - "extensions/rv32im/circuit", - "extensions/rv32im/transpiler", - "extensions/rv32im/guest", - "extensions/rv32im/tests", - "extensions/rv32-adapters", - "extensions/native/circuit", - "extensions/native/compiler", - "extensions/native/compiler/derive", - "extensions/native/recursion", - "extensions/native/transpiler", - "extensions/algebra/circuit", - "extensions/algebra/transpiler", - "extensions/algebra/guest", - "extensions/algebra/moduli-macros", - "extensions/algebra/complex-macros", - "extensions/algebra/tests", - "extensions/bigint/circuit", - "extensions/bigint/transpiler", - "extensions/bigint/guest", - "extensions/bigint/tests", - "extensions/keccak256/circuit", - "extensions/keccak256/transpiler", - "extensions/keccak256/guest", - "extensions/keccak256/tests", - "extensions/sha256/circuit", - "extensions/sha256/transpiler", - "extensions/sha256/guest", - "extensions/sha256/tests", - "extensions/ecc/circuit", - "extensions/ecc/transpiler", - "extensions/ecc/guest", - "extensions/ecc/sw-macros", - "extensions/ecc/tests", - "extensions/pairing/circuit", - "extensions/pairing/transpiler", - "extensions/pairing/guest", - "extensions/pairing/tests", + "benchmarks/utils", + "benchmarks/execute", + "benchmarks/prove", + "crates/prof", + "crates/sdk", + "crates/cli", + "crates/circuits/mod-builder", + "crates/circuits/poseidon2-air", + "crates/circuits/primitives", + "crates/circuits/primitives/derive", + "crates/toolchain/transpiler", + "crates/toolchain/openvm", + "crates/toolchain/build", + "crates/toolchain/instructions", + "crates/toolchain/instructions/derive", + "crates/toolchain/macros", + "crates/toolchain/platform", + "crates/toolchain/tests", + "crates/continuations", + "crates/vm", + "extensions/rv32im/circuit", + "extensions/rv32im/transpiler", + "extensions/rv32im/guest", + "extensions/rv32im/tests", + "extensions/rv32-adapters", + "extensions/native/circuit", + "extensions/native/compiler", + "extensions/native/compiler/derive", + "extensions/native/recursion", + "extensions/native/transpiler", + "extensions/algebra/circuit", + "extensions/algebra/transpiler", + "extensions/algebra/guest", + "extensions/algebra/moduli-macros", + "extensions/algebra/complex-macros", + "extensions/algebra/tests", + "extensions/bigint/circuit", + "extensions/bigint/transpiler", + "extensions/bigint/guest", + "extensions/keccak256/circuit", + "extensions/keccak256/transpiler", + "extensions/keccak256/guest", + "extensions/sha256/circuit", + "extensions/sha256/transpiler", + "extensions/sha256/guest", + "extensions/ecc/circuit", + "extensions/ecc/transpiler", + "extensions/ecc/guest", + "extensions/ecc/sw-macros", + "extensions/ecc/tests", + "extensions/pairing/circuit", + "extensions/pairing/guest", + "guest-libs/ff_derive/", + "guest-libs/k256/", + "guest-libs/p256/", + "guest-libs/keccak256/", + "guest-libs/pairing/", + "guest-libs/ruint/", + "guest-libs/sha2/", + "guest-libs/verify_stark/", ] exclude = ["crates/sdk/example"] resolver = "2" @@ -107,8 +110,8 @@ lto = "thin" [workspace.dependencies] # Stark Backend -openvm-stark-backend = { git = "https://github.com/powdr-labs/stark-backend.git", rev = "fe1c5a8", default-features = false } -openvm-stark-sdk = { git = "https://github.com/powdr-labs/stark-backend.git", rev = "fe1c5a8", default-features = false } +openvm-stark-backend = { git = "https://github.com/powdr-labs/stark-backend.git", rev = "37c22d2", default-features = false } +openvm-stark-sdk = { git = "https://github.com/powdr-labs/stark-backend.git", rev = "37c22d2", default-features = false } # OpenVM openvm-sdk = { path = "crates/sdk", default-features = false } @@ -140,6 +143,7 @@ openvm-native-circuit = { path = "extensions/native/circuit", default-features = openvm-native-compiler = { path = "extensions/native/compiler", default-features = false } openvm-native-compiler-derive = { path = "extensions/native/compiler/derive", default-features = false } openvm-native-recursion = { path = "extensions/native/recursion", default-features = false } +openvm-native-transpiler = { path = "extensions/native/transpiler", default-features = false } openvm-keccak256-circuit = { path = "extensions/keccak256/circuit", default-features = false } openvm-keccak256-transpiler = { path = "extensions/keccak256/transpiler", default-features = false } openvm-keccak256-guest = { path = "extensions/keccak256/guest", default-features = false } @@ -161,6 +165,7 @@ openvm-ecc-sw-macros = { path = "extensions/ecc/sw-macros", default-features = f openvm-pairing-circuit = { path = "extensions/pairing/circuit", default-features = false } openvm-pairing-transpiler = { path = "extensions/pairing/transpiler", default-features = false } openvm-pairing-guest = { path = "extensions/pairing/guest", default-features = false } +openvm-verify-stark = { path = "guest-libs/verify_stark", default-features = false } # Benchmarking openvm-benchmarks-utils = { path = "benchmarks/utils", default-features = false } @@ -233,13 +238,14 @@ once_cell = { version = "1.20", default-features = false } # cryptography, default-features = false for no_std tiny-keccak = { version = "2.0.2", features = ["keccak"] } -k256 = { version = "0.13.3", default-features = false } -p256 = { version = "0.13.2", default-features = false } +k256 = { version = "0.13.4", default-features = false } elliptic-curve = { version = "0.13.8", default-features = false } -ecdsa = { version = "0.16.9", default-features = false } +ecdsa-core = { version = "0.16.9", package = "ecdsa", default-features = false } num-bigint = { version = "0.4.6", default-features = false } num-integer = { version = "0.1.46", default-features = false } num-traits = { version = "0.2.19", default-features = false } +ff = { version = "0.13.1", default-features = false } +sha2 = { version = "0.10", default-features = false } # For local development. Add to your `.cargo/config.toml` # [patch."https://github.com/Plonky3/Plonky3.git"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000000..353260ba82 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 The OpenVM Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE b/LICENSE-MIT similarity index 96% rename from LICENSE rename to LICENSE-MIT index 49fac70f94..7090881144 100644 --- a/LICENSE +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 The OpenVM Authors +Copyright (c) 2025 The OpenVM Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/benchmarks/execute/src/main.rs b/benchmarks/execute/src/main.rs index 80db3ec5a4..a05baeea44 100644 --- a/benchmarks/execute/src/main.rs +++ b/benchmarks/execute/src/main.rs @@ -1,4 +1,4 @@ -use cargo_openvm::{default::DEFAULT_APP_CONFIG_PATH, util::read_config_toml_or_default}; +use cargo_openvm::util::read_config_toml_or_default; use clap::{Parser, ValueEnum}; use eyre::Result; use openvm_benchmarks_utils::{get_elf_path, get_programs_dir, read_elf_file}; @@ -106,7 +106,7 @@ fn main() -> Result<()> { let elf_path = get_elf_path(&program_dir); let elf = read_elf_file(&elf_path)?; - let config_path = program_dir.join(DEFAULT_APP_CONFIG_PATH); + let config_path = program_dir.join("openvm.toml"); let vm_config = read_config_toml_or_default(&config_path)?.app_vm_config; let exe = VmExe::from_elf(elf, vm_config.transpiler())?; diff --git a/benchmarks/guest/ecrecover/Cargo.toml b/benchmarks/guest/ecrecover/Cargo.toml index ce1e632ac5..b9592028f7 100644 --- a/benchmarks/guest/ecrecover/Cargo.toml +++ b/benchmarks/guest/ecrecover/Cargo.toml @@ -5,23 +5,16 @@ version = "0.0.0" edition = "2021" [dependencies] -k256 = { version = "0.13.3", default-features = false, features = ["ecdsa"] } openvm = { path = "../../../crates/toolchain/openvm", features = ["std"] } -openvm-platform = { path = "../../../crates/toolchain/platform", default-features = false } openvm-algebra-guest = { path = "../../../extensions/algebra/guest", default-features = false } -openvm-ecc-guest = { path = "../../../extensions/ecc/guest", default-features = false, features = [ - "k256", -] } -openvm-keccak256-guest = { path = "../../../extensions/keccak256/guest", default-features = false } -# We do not patch revm-precompile so that the benchmark only depends on this repo. -revm-precompile = { version = "14.0.0", default-features = false } -alloy-primitives = { version = "0.8.10", default-features = false, features = [ +openvm-ecc-guest = { path = "../../../extensions/ecc/guest", default-features = false } +openvm-keccak256 = { path = "../../../guest-libs/keccak256/", default-features = false } +revm-precompile = { git = "https://github.com/bluealloy/revm.git", tag = "v75", default-features = false } +# IMPORTANT: must be same version as used by revm; revm does not re-export this feature so we enable it here +alloy-primitives = { version = "1.2.0", default-features = false, features = [ "native-keccak", -] } # revm does not re-export this feature so we enable it here -derive_more = { version = "1.0.0", default-features = false, features = [ - "from", - "display", ] } +k256 = { version = "0.13.3", default-features = false } [features] default = [] @@ -34,3 +27,6 @@ lto = "thin" # faster compile time inherits = "release" debug = 2 strip = false + +[patch.crates-io] +k256 = { path = "../../../guest-libs/k256/" } diff --git a/benchmarks/guest/ecrecover/openvm.toml b/benchmarks/guest/ecrecover/openvm.toml new file mode 100644 index 0000000000..c1261ee458 --- /dev/null +++ b/benchmarks/guest/ecrecover/openvm.toml @@ -0,0 +1,17 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +[app_vm_config.keccak] + +[app_vm_config.modular] +supported_moduli = [ + "115792089237316195423570985008687907853269984665640564039457584007908834671663", + "115792089237316195423570985008687907852837564279074904382605163141518161494337", +] + +[[app_vm_config.ecc.supported_curves]] +struct_name = "Secp256k1Point" +modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" +scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" +a = "0" +b = "7" diff --git a/benchmarks/guest/ecrecover/openvm_init.rs b/benchmarks/guest/ecrecover/openvm_init.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/benchmarks/guest/ecrecover/openvm_init.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/benchmarks/guest/ecrecover/src/main.rs b/benchmarks/guest/ecrecover/src/main.rs index fa29562fa6..ee77dfbb27 100644 --- a/benchmarks/guest/ecrecover/src/main.rs +++ b/benchmarks/guest/ecrecover/src/main.rs @@ -1,32 +1,15 @@ -use alloy_primitives::{keccak256, Bytes, B256, B512}; -use k256::{ - ecdsa::{Error, RecoveryId, Signature}, - Secp256k1, -}; +use alloy_primitives::Bytes; +#[allow(unused_imports)] // needed by init! macro +use k256::Secp256k1Point; use openvm::io::read_vec; -#[allow(unused_imports)] -use openvm_ecc_guest::{ - algebra::IntMod, ecdsa::VerifyingKey, k256::Secp256k1Point, weierstrass::WeierstrassPoint, -}; -#[allow(unused_imports, clippy::single_component_path_imports)] -use openvm_keccak256_guest; // export native keccak -use revm_precompile::{ - utilities::right_pad, Error as PrecompileError, PrecompileOutput, PrecompileResult, -}; +#[allow(unused_imports, clippy::single_component_path_imports)] +use openvm_keccak256::keccak256; +use revm_precompile::secp256k1::ec_recover_run; -openvm_algebra_guest::moduli_macros::moduli_init! { - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" -} -openvm_ecc_guest::sw_macros::sw_init! { - Secp256k1Point, -} +openvm::init!(); pub fn main() { - setup_all_moduli(); - setup_all_curves(); - let expected_address = read_vec(); for _ in 0..5 { let input = read_vec(); @@ -34,52 +17,3 @@ pub fn main() { assert_eq!(recovered.bytes.as_ref(), expected_address); } } - -// OpenVM version of ecrecover precompile. -pub fn ecrecover(sig: &B512, mut recid: u8, msg: &B256) -> Result { - // parse signature - let mut sig = Signature::from_slice(sig.as_slice())?; - if let Some(sig_normalized) = sig.normalize_s() { - sig = sig_normalized; - recid ^= 1; - } - let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid"); - - // annoying: Signature::to_bytes copies from slice - let recovered_key = - VerifyingKey::::recover_from_prehash_noverify(&msg[..], &sig.to_bytes(), recid)?; - let public_key = recovered_key.as_affine(); - let mut encoded = [0u8; 64]; - encoded[..32].copy_from_slice(&public_key.x().to_be_bytes()); - encoded[32..].copy_from_slice(&public_key.y().to_be_bytes()); - // hash it - let mut hash = keccak256(encoded); - // truncate to 20 bytes - hash[..12].fill(0); - Ok(B256::from(hash)) -} - -// We replicate code from `revm-precompile` to avoid importing the patched version. -pub fn ec_recover_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { - const ECRECOVER_BASE: u64 = 3_000; - - if ECRECOVER_BASE > gas_limit { - return Err(PrecompileError::OutOfGas.into()); - } - - let input = right_pad::<128>(input); - - // `v` must be a 32-byte big-endian integer equal to 27 or 28. - if !(input[32..63].iter().all(|&b| b == 0) && matches!(input[63], 27 | 28)) { - return Ok(PrecompileOutput::new(ECRECOVER_BASE, Bytes::new())); - } - - let msg = <&B256>::try_from(&input[0..32]).unwrap(); - let recid = input[63] - 27; - let sig = <&B512>::try_from(&input[64..128]).unwrap(); - - let out = ecrecover(sig, recid, msg) - .map(|o| o.to_vec().into()) - .unwrap_or_default(); - Ok(PrecompileOutput::new(ECRECOVER_BASE, out)) -} diff --git a/benchmarks/guest/kitchen-sink/Cargo.toml b/benchmarks/guest/kitchen-sink/Cargo.toml index af853486cf..17742db7d0 100644 --- a/benchmarks/guest/kitchen-sink/Cargo.toml +++ b/benchmarks/guest/kitchen-sink/Cargo.toml @@ -17,9 +17,10 @@ openvm-pairing-guest = { path = "../../../extensions/pairing/guest", default-fea "bn254", "bls12_381", ] } -openvm-keccak256-guest = { path = "../../../extensions/keccak256/guest", default-features = false } -openvm-sha256-guest = { path = "../../../extensions/sha256/guest", default-features = false } -openvm-bigint-guest = { path = "../../../extensions/bigint/guest", default-features = false } +openvm-pairing = { path = "../../../guest-libs/pairing/", features = ["bn254", "bls12_381"] } +openvm-keccak256 = { path = "../../../guest-libs/keccak256/", default-features = false } +openvm-sha2 = { path = "../../../guest-libs/sha2/", default-features = false } +openvm-ruint = { path = "../../../guest-libs/ruint/", default-features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } serde = "1.0" diff --git a/benchmarks/guest/kitchen-sink/openvm_init.rs b/benchmarks/guest/kitchen-sink/openvm_init.rs new file mode 100644 index 0000000000..c4a80b3602 --- /dev/null +++ b/benchmarks/guest/kitchen-sink/openvm_init.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "1000000000000000003", "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337", "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369", "21888242871839275222246405745257275088696311157297823662689037894645226208583", "21888242871839275222246405745257275088548364400416034343698204186575808495617", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", "52435875175126190479447740508185965837690552500527637822603658699938581184513", "2305843009213693951", "7" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 5 }, Bls12_381Fp2 { mod_idx = 7 } } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point, P256Point, Bn254G1Affine, Bls12_381G1Affine } diff --git a/benchmarks/guest/kitchen-sink/src/main.rs b/benchmarks/guest/kitchen-sink/src/main.rs index 6aa679eb3f..eaf8ef4423 100644 --- a/benchmarks/guest/kitchen-sink/src/main.rs +++ b/benchmarks/guest/kitchen-sink/src/main.rs @@ -1,19 +1,25 @@ use std::hint::black_box; use openvm_algebra_guest::IntMod; -use openvm_bigint_guest::I256; -use openvm_keccak256_guest::keccak256; -use openvm_sha256_guest::sha256; +use openvm_keccak256::keccak256; +use openvm_ruint::aliases::U256; +use openvm_sha2::sha256; #[allow(unused_imports)] use { openvm_ecc_guest::{ - k256::Secp256k1Point, p256::P256Point, weierstrass::WeierstrassPoint, AffinePoint, + k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}, + p256::{P256Coord, P256Point, P256Scalar}, + weierstrass::WeierstrassPoint, + CyclicGroup, }, - openvm_pairing_guest::{ - bls12_381::{Bls12_381G1Affine, G2Affine as Bls12_381G2Affine}, - bn254::{Bn254, Bn254G1Affine, Fp, Fp2, G2Affine as Bn254G2Affine}, - pairing::PairingCheck, + openvm_pairing::{ + bls12_381::{ + Bls12_381Fp, Bls12_381Fp2, Bls12_381G1Affine, Bls12_381Scalar, + G2Affine as Bls12_381G2Affine, + }, + bn254::{Bn254, Bn254Fp, Bn254Fp2, Bn254G1Affine, Bn254Scalar, G2Affine as Bn254G2Affine}, }, + openvm_pairing_guest::pairing::PairingCheck, }; // Note: these will all currently be represented as bytes32 even though they could be smaller @@ -23,35 +29,64 @@ openvm_algebra_guest::moduli_macros::moduli_declare! { Mersenne61 { modulus = "0x1fffffffffffffff" }, } -openvm_algebra_guest::moduli_macros::moduli_init! { - "1000000000000000003", // Mod1e18 - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", // secp256k1 Coordinate field - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141", // secp256k1 Scalar field - "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff", // secp256r1=p256 Coordinate field - "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", // secp256r1=p256 Scalar field - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", // Bn254Fp Coordinate field - "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", // Bn254 Scalar - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", // BLS12-381 Coordinate field - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", // BLS12-381 Scalar field - "0x1fffffffffffffff", - "7", +openvm::init!(); + +fn materialize_modular_chip() { + // ensure the compiler doesn't optimize out the operations + // add/sub chip + black_box(T::ZERO + T::ZERO); + // mul/div chip + black_box(T::ZERO * T::ZERO); + // is_equal chip + black_box(T::ZERO.assert_reduced()); } -openvm_ecc_guest::sw_macros::sw_init! { - Secp256k1Point, P256Point, - Bn254G1Affine, Bls12_381G1Affine +// making this a macro since there's no complex extension trait +macro_rules! materialize_complex_chip { + ($complex_type:ty, $modular_type:ty) => { + // ensure the compiler doesn't optimize out the operations + let zero = <$complex_type>::new( + <$modular_type as IntMod>::ZERO, + <$modular_type as IntMod>::ZERO, + ); + // add/sub chip + black_box(&zero + &zero); + // mul/div chip + black_box(&zero * &zero); + }; } -openvm_algebra_guest::complex_macros::complex_init! { - Bn254Fp2 { mod_idx = 5 }, - Bls12_381Fp2 { mod_idx = 7 }, +fn materialize_ecc_chip() { + // add chip + // it is important that neither operand is identity, otherwise the chip will not be materialized + black_box(T::GENERATOR + T::GENERATOR); + // double chip + // it is important that the operand is not identity, otherwise the chip will not be materialized + black_box(T::GENERATOR.double()); } pub fn main() { - // Setup will materialize every chip - setup_all_moduli(); - setup_all_complex_extensions(); - setup_all_curves(); + // Since we don't explicitly call setup functions anymore, we must ensure every declared modulus + // and curve chip is materialized. + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + materialize_modular_chip::(); + + materialize_complex_chip!(Bn254Fp2, Bn254Fp); + materialize_complex_chip!(Bls12_381Fp2, Bls12_381Fp); + + materialize_ecc_chip::(); + materialize_ecc_chip::(); + materialize_ecc_chip::(); + materialize_ecc_chip::(); let [one, six] = [1, 6].map(Seven::from_u32); assert_eq!(one + six, Seven::ZERO); @@ -81,9 +116,9 @@ pub fn main() { let digest2 = sha256(&hash); hash.extend_from_slice(&digest2); - // SAFETY: internally I256 is represented as [u8; 32] - let i1 = I256::from_le_bytes(digest1); - let i2 = I256::from_le_bytes(digest2); + // SAFETY: internally U256 is represented as [u8; 32] + let i1 = U256::from_le_bytes(digest1); + let i2 = U256::from_le_bytes(digest2); black_box(&i1 + &i2); black_box(&i1 - &i2); @@ -93,7 +128,7 @@ pub fn main() { black_box(i1 <= i2); black_box(&i1 & &i2); black_box(&i1 ^ &i2); - black_box(&i1 << &i2); - black_box(&i1 >> &i2); + black_box(i1 << &i2); + black_box(i1 >> &i2); } } diff --git a/benchmarks/guest/pairing/Cargo.toml b/benchmarks/guest/pairing/Cargo.toml index 47e0889148..dfd73f5eb6 100644 --- a/benchmarks/guest/pairing/Cargo.toml +++ b/benchmarks/guest/pairing/Cargo.toml @@ -8,6 +8,9 @@ edition = "2021" openvm = { path = "../../../crates/toolchain/openvm", features = ["std"] } openvm-algebra-guest = { path = "../../../extensions/algebra/guest", default-features = false } openvm-ecc-guest = { path = "../../../extensions/ecc/guest", default-features = false } +openvm-pairing = { path = "../../../guest-libs/pairing/", default-features = false, features = [ + "bn254", +] } openvm-pairing-guest = { path = "../../../extensions/pairing/guest", default-features = false, features = [ "bn254", ] } diff --git a/benchmarks/guest/pairing/openvm.toml b/benchmarks/guest/pairing/openvm.toml index 4b2dc738b3..dbc0d0ebf9 100644 --- a/benchmarks/guest/pairing/openvm.toml +++ b/benchmarks/guest/pairing/openvm.toml @@ -2,14 +2,14 @@ [app_vm_config.rv32m] [app_vm_config.io] [app_vm_config.modular] -supported_modulus = [ +supported_moduli = [ # bn254 (alt bn128) "21888242871839275222246405745257275088696311157297823662689037894645226208583", # coordinate field "21888242871839275222246405745257275088548364400416034343698204186575808495617", # scalar field ] [app_vm_config.fp2] -supported_modulus = [ +supported_moduli = [ # bn254 (alt bn128) "21888242871839275222246405745257275088696311157297823662689037894645226208583", ] diff --git a/benchmarks/guest/pairing/openvm_init.rs b/benchmarks/guest/pairing/openvm_init.rs new file mode 100644 index 0000000000..5baf894946 --- /dev/null +++ b/benchmarks/guest/pairing/openvm_init.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583", "21888242871839275222246405745257275088548364400416034343698204186575808495617" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { Bn254G1Affine } diff --git a/benchmarks/guest/pairing/src/main.rs b/benchmarks/guest/pairing/src/main.rs index 807c5d2866..819c1e2691 100644 --- a/benchmarks/guest/pairing/src/main.rs +++ b/benchmarks/guest/pairing/src/main.rs @@ -1,32 +1,16 @@ use openvm_algebra_guest::IntMod; use openvm_ecc_guest::AffinePoint; #[allow(unused_imports)] -use openvm_pairing_guest::{ - bn254::{Bn254, Bn254G1Affine, Fp, Fp2}, - pairing::PairingCheck, +use { + openvm_pairing::bn254::{Bn254, Bn254G1Affine, Fp, Fp2}, + openvm_pairing_guest::pairing::PairingCheck, }; -openvm_algebra_guest::moduli_macros::moduli_init! { - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", // Bn254Fp Coordinate field - "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", // Bn254 Scalar -} - -openvm_ecc_guest::sw_macros::sw_init! { - Bn254G1Affine -} - -openvm_algebra_guest::complex_macros::complex_init! { - Bn254Fp2 { mod_idx = 0 }, -} +openvm::init!(); const PAIR_ELEMENT_LEN: usize = 32 * (2 + 4); // 1 G1Affine (2 Fp), 1 G2Affine (4 Fp) pub fn main() { - setup_all_moduli(); - setup_all_complex_extensions(); - // Pairing doesn't need G1Affine intrinsics, but we trigger it anyways to test the chips - setup_all_curves(); - // copied from https://github.com/bluealloy/revm/blob/9e39df5dbc5fdc98779c644629b28b8bee75794a/crates/precompile/src/bn128.rs#L395 let input = hex::decode( "\ diff --git a/benchmarks/guest/regex/Cargo.toml b/benchmarks/guest/regex/Cargo.toml index 5ab7b1f6cd..40831a592d 100644 --- a/benchmarks/guest/regex/Cargo.toml +++ b/benchmarks/guest/regex/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] openvm = { path = "../../../crates/toolchain/openvm", features = ["std"] } -openvm-keccak256-guest = { path = "../../../extensions/keccak256/guest" } +openvm-keccak256 = { path = "../../../guest-libs/keccak256/" } regex = { version = "1.11.1", default-features = false } [features] diff --git a/benchmarks/guest/regex/src/main.rs b/benchmarks/guest/regex/src/main.rs index 70cd37c985..b247dcfddf 100644 --- a/benchmarks/guest/regex/src/main.rs +++ b/benchmarks/guest/regex/src/main.rs @@ -11,7 +11,7 @@ pub fn main() { let caps = re.captures(data).expect("No match found."); let email = caps.name("email").expect("No email found."); - let email_hash = openvm_keccak256_guest::keccak256(email.as_str().as_bytes()); + let email_hash = openvm_keccak256::keccak256(email.as_str().as_bytes()); openvm::io::reveal_bytes32(email_hash); } diff --git a/benchmarks/prove/src/bin/base64_json.rs b/benchmarks/prove/src/bin/base64_json.rs index 3c6f6e6d14..ed366e51ca 100644 --- a/benchmarks/prove/src/bin/base64_json.rs +++ b/benchmarks/prove/src/bin/base64_json.rs @@ -14,7 +14,8 @@ use openvm_transpiler::{transpiler::Transpiler, FromElf}; fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("base64_json")?; + let config = Keccak256Rv32Config::default(); + let elf = args.build_bench_program("base64_json", &config, None)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -28,11 +29,6 @@ fn main() -> Result<()> { let data = include_str!("../../../guest/base64_json/json_payload_encoded.txt"); let fe_bytes = data.to_owned().into_bytes(); - args.bench_from_exe( - "base64_json", - Keccak256Rv32Config::default(), - exe, - StdIn::from_bytes(&fe_bytes), - ) + args.bench_from_exe("base64_json", config, exe, StdIn::from_bytes(&fe_bytes)) }) } diff --git a/benchmarks/prove/src/bin/bincode.rs b/benchmarks/prove/src/bin/bincode.rs index c1add70344..3cc419c1e1 100644 --- a/benchmarks/prove/src/bin/bincode.rs +++ b/benchmarks/prove/src/bin/bincode.rs @@ -13,7 +13,8 @@ use openvm_transpiler::{transpiler::Transpiler, FromElf}; fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("bincode")?; + let config = Rv32ImConfig::default(); + let elf = args.build_bench_program("bincode", &config, None)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -24,6 +25,6 @@ fn main() -> Result<()> { run_with_metric_collection("OUTPUT_PATH", || -> Result<()> { let file_data = include_bytes!("../../../guest/bincode/minecraft_savedata.bin"); let stdin = StdIn::from_bytes(file_data); - args.bench_from_exe("bincode", Rv32ImConfig::default(), exe, stdin) + args.bench_from_exe("bincode", config, exe, stdin) }) } diff --git a/benchmarks/prove/src/bin/ecrecover.rs b/benchmarks/prove/src/bin/ecrecover.rs index 2a2f146f4c..23fe2c82af 100644 --- a/benchmarks/prove/src/bin/ecrecover.rs +++ b/benchmarks/prove/src/bin/ecrecover.rs @@ -8,7 +8,7 @@ use openvm_algebra_circuit::{ use openvm_algebra_transpiler::ModularTranspilerExtension; use openvm_benchmarks_prove::util::BenchmarkCli; use openvm_circuit::{ - arch::{instructions::exe::VmExe, SystemConfig}, + arch::{instructions::exe::VmExe, InitFileGenerator, SystemConfig}, derive::VmConfig, }; use openvm_ecc_circuit::{ @@ -66,6 +66,16 @@ pub struct Rv32ImEcRecoverConfig { pub weierstrass: WeierstrassExtension, } +impl InitFileGenerator for Rv32ImEcRecoverConfig { + fn generate_init_file_contents(&self) -> Option { + Some(format!( + "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n", + self.modular.generate_moduli_init(), + self.weierstrass.generate_sw_init() + )) + } +} + impl Rv32ImEcRecoverConfig { pub fn for_curves(curves: Vec) -> Self { let primes: Vec = curves @@ -87,7 +97,9 @@ impl Rv32ImEcRecoverConfig { fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("ecrecover")?; + let config = Rv32ImEcRecoverConfig::for_curves(vec![SECP256K1_CONFIG.clone()]); + + let elf = args.build_bench_program("ecrecover", &config, None)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -123,11 +135,6 @@ fn main() -> Result<()> { .map(|s| make_input(&signing_key, s.as_bytes())) .collect::>(), ); - args.bench_from_exe( - "ecrecover_program", - Rv32ImEcRecoverConfig::for_curves(vec![SECP256K1_CONFIG.clone()]), - exe, - input_stream.into(), - ) + args.bench_from_exe("ecrecover_program", config, exe, input_stream.into()) }) } diff --git a/benchmarks/prove/src/bin/fib_e2e.rs b/benchmarks/prove/src/bin/fib_e2e.rs index 6e1cfa7a35..41611d0970 100644 --- a/benchmarks/prove/src/bin/fib_e2e.rs +++ b/benchmarks/prove/src/bin/fib_e2e.rs @@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc}; use clap::Parser; use eyre::Result; use openvm_benchmarks_prove::util::BenchmarkCli; -use openvm_circuit::arch::instructions::{exe::VmExe, program::DEFAULT_MAX_NUM_PUBLIC_VALUES}; +use openvm_circuit::arch::{instructions::exe::VmExe, DEFAULT_MAX_NUM_PUBLIC_VALUES}; use openvm_native_recursion::halo2::utils::{CacheHalo2ParamsReader, DEFAULT_PARAMS_DIR}; use openvm_rv32im_circuit::Rv32ImConfig; use openvm_rv32im_transpiler::{ @@ -30,6 +30,8 @@ async fn main() -> Result<()> { NUM_PUBLIC_VALUES, max_segment_length, )); + let elf = args.build_bench_program("fibonacci", &app_config.app_vm_config, None)?; + let agg_config = args.agg_config(); let sdk = Sdk::new(); @@ -44,7 +46,6 @@ async fn main() -> Result<()> { &halo2_params_reader, &DefaultStaticVerifierPvHandler, )?; - let elf = args.build_bench_program("fibonacci")?; let exe = VmExe::from_elf( elf, Transpiler::default() diff --git a/benchmarks/prove/src/bin/fibonacci.rs b/benchmarks/prove/src/bin/fibonacci.rs index 3e2875fc35..1c886d8130 100644 --- a/benchmarks/prove/src/bin/fibonacci.rs +++ b/benchmarks/prove/src/bin/fibonacci.rs @@ -13,7 +13,8 @@ use openvm_transpiler::{transpiler::Transpiler, FromElf}; fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("fibonacci")?; + let config = Rv32ImConfig::default(); + let elf = args.build_bench_program("fibonacci", &config, None)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -26,6 +27,6 @@ fn main() -> Result<()> { let n = 100_000u64; let mut stdin = StdIn::default(); stdin.write(&n); - args.bench_from_exe("fibonacci_program", Rv32ImConfig::default(), exe, stdin) + args.bench_from_exe("fibonacci_program", config, exe, stdin) }) } diff --git a/benchmarks/prove/src/bin/kitchen_sink.rs b/benchmarks/prove/src/bin/kitchen_sink.rs index c4b7791330..3102c9e3fe 100644 --- a/benchmarks/prove/src/bin/kitchen_sink.rs +++ b/benchmarks/prove/src/bin/kitchen_sink.rs @@ -9,6 +9,9 @@ use openvm_circuit::arch::{instructions::exe::VmExe, SystemConfig}; use openvm_ecc_circuit::{WeierstrassExtension, P256_CONFIG, SECP256K1_CONFIG}; use openvm_native_recursion::halo2::utils::{CacheHalo2ParamsReader, DEFAULT_PARAMS_DIR}; use openvm_pairing_circuit::{PairingCurve, PairingExtension}; +use openvm_pairing_guest::{ + bls12_381::BLS12_381_COMPLEX_STRUCT_NAME, bn254::BN254_COMPLEX_STRUCT_NAME, +}; use openvm_sdk::{ commit::commit_app_exe, config::SdkVmConfig, prover::EvmHalo2Prover, DefaultStaticVerifierPvHandler, Sdk, StdIn, @@ -21,7 +24,6 @@ use openvm_transpiler::FromElf; fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("kitchen-sink")?; let bn_config = PairingCurve::Bn254.curve_config(); let bls_config = PairingCurve::Bls12_381.curve_config(); let vm_config = SdkVmConfig::builder() @@ -46,8 +48,14 @@ fn main() -> Result<()> { BigUint::from(7u32), ])) .fp2(Fp2Extension::new(vec![ - bn_config.modulus.clone(), - bls_config.modulus.clone(), + ( + BN254_COMPLEX_STRUCT_NAME.to_string(), + bn_config.modulus.clone(), + ), + ( + BLS12_381_COMPLEX_STRUCT_NAME.to_string(), + bls_config.modulus.clone(), + ), ])) .ecc(WeierstrassExtension::new(vec![ SECP256K1_CONFIG.clone(), @@ -60,6 +68,7 @@ fn main() -> Result<()> { PairingCurve::Bls12_381, ])) .build(); + let elf = args.build_bench_program("kitchen-sink", &vm_config, None)?; let exe = VmExe::from_elf(elf, vm_config.transpiler())?; let sdk = Sdk::new(); diff --git a/benchmarks/prove/src/bin/pairing.rs b/benchmarks/prove/src/bin/pairing.rs index 6f200172a4..1db6d1b491 100644 --- a/benchmarks/prove/src/bin/pairing.rs +++ b/benchmarks/prove/src/bin/pairing.rs @@ -5,14 +5,13 @@ use openvm_benchmarks_prove::util::BenchmarkCli; use openvm_circuit::arch::SystemConfig; use openvm_ecc_circuit::WeierstrassExtension; use openvm_pairing_circuit::{PairingCurve, PairingExtension}; -use openvm_pairing_guest::bn254::{BN254_MODULUS, BN254_ORDER}; +use openvm_pairing_guest::bn254::{BN254_COMPLEX_STRUCT_NAME, BN254_MODULUS, BN254_ORDER}; use openvm_sdk::{config::SdkVmConfig, Sdk, StdIn}; use openvm_stark_sdk::bench::run_with_metric_collection; fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("pairing")?; let vm_config = SdkVmConfig::builder() .system(SystemConfig::default().with_continuations().into()) .rv32i(Default::default()) @@ -23,12 +22,16 @@ fn main() -> Result<()> { BN254_MODULUS.clone(), BN254_ORDER.clone(), ])) - .fp2(Fp2Extension::new(vec![BN254_MODULUS.clone()])) + .fp2(Fp2Extension::new(vec![( + BN254_COMPLEX_STRUCT_NAME.to_string(), + BN254_MODULUS.clone(), + )])) .ecc(WeierstrassExtension::new(vec![ PairingCurve::Bn254.curve_config() ])) .pairing(PairingExtension::new(vec![PairingCurve::Bn254])) .build(); + let elf = args.build_bench_program("pairing", &vm_config, None)?; let sdk = Sdk::new(); let exe = sdk.transpile(elf, vm_config.transpiler()).unwrap(); diff --git a/benchmarks/prove/src/bin/regex.rs b/benchmarks/prove/src/bin/regex.rs index 6efe8dd5b3..d1de43dad5 100644 --- a/benchmarks/prove/src/bin/regex.rs +++ b/benchmarks/prove/src/bin/regex.rs @@ -14,7 +14,8 @@ use openvm_transpiler::{transpiler::Transpiler, FromElf}; fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("regex")?; + let config = Keccak256Rv32Config::default(); + let elf = args.build_bench_program("regex", &config, None)?; let exe = VmExe::from_elf( elf.clone(), Transpiler::::default() @@ -27,11 +28,6 @@ fn main() -> Result<()> { let data = include_str!("../../../guest/regex/regex_email.txt"); let fe_bytes = data.to_owned().into_bytes(); - args.bench_from_exe( - "regex_program", - Keccak256Rv32Config::default(), - exe, - StdIn::from_bytes(&fe_bytes), - ) + args.bench_from_exe("regex_program", config, exe, StdIn::from_bytes(&fe_bytes)) }) } diff --git a/benchmarks/prove/src/bin/revm_transfer.rs b/benchmarks/prove/src/bin/revm_transfer.rs index 03027aae83..1df994dc78 100644 --- a/benchmarks/prove/src/bin/revm_transfer.rs +++ b/benchmarks/prove/src/bin/revm_transfer.rs @@ -13,7 +13,8 @@ use openvm_transpiler::{transpiler::Transpiler, FromElf}; fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("revm_transfer")?; + let config = Keccak256Rv32Config::default(); + let elf = args.build_bench_program("revm_transfer", &config, None)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -23,11 +24,6 @@ fn main() -> Result<()> { .with_extension(Keccak256TranspilerExtension), )?; run_with_metric_collection("OUTPUT_PATH", || -> Result<()> { - args.bench_from_exe( - "revm_100_transfers", - Keccak256Rv32Config::default(), - exe, - StdIn::default(), - ) + args.bench_from_exe("revm_100_transfers", config, exe, StdIn::default()) }) } diff --git a/benchmarks/prove/src/bin/rkyv.rs b/benchmarks/prove/src/bin/rkyv.rs index 8a43bd5679..7bdf6ed920 100644 --- a/benchmarks/prove/src/bin/rkyv.rs +++ b/benchmarks/prove/src/bin/rkyv.rs @@ -13,7 +13,8 @@ use openvm_transpiler::{transpiler::Transpiler, FromElf}; fn main() -> Result<()> { let args = BenchmarkCli::parse(); - let elf = args.build_bench_program("rkyv")?; + let config = Rv32ImConfig::default(); + let elf = args.build_bench_program("rkyv", &config, None)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -25,6 +26,6 @@ fn main() -> Result<()> { run_with_metric_collection("OUTPUT_PATH", || -> Result<()> { let file_data = include_bytes!("../../../guest/rkyv/minecraft_savedata.bin"); let stdin = StdIn::from_bytes(file_data); - args.bench_from_exe("rkyv", Rv32ImConfig::default(), exe, stdin) + args.bench_from_exe("rkyv", config, exe, stdin) }) } diff --git a/benchmarks/prove/src/bin/verify_fibair.rs b/benchmarks/prove/src/bin/verify_fibair.rs index d2b75f7e71..1d8d6072da 100644 --- a/benchmarks/prove/src/bin/verify_fibair.rs +++ b/benchmarks/prove/src/bin/verify_fibair.rs @@ -1,7 +1,7 @@ use clap::Parser; use eyre::Result; use openvm_benchmarks_prove::util::BenchmarkCli; -use openvm_circuit::arch::instructions::program::DEFAULT_MAX_NUM_PUBLIC_VALUES; +use openvm_circuit::arch::DEFAULT_MAX_NUM_PUBLIC_VALUES; use openvm_native_circuit::NativeConfig; use openvm_native_compiler::conversion::CompilerOptions; use openvm_native_recursion::testing_utils::inner::build_verification_program; diff --git a/benchmarks/prove/src/util.rs b/benchmarks/prove/src/util.rs index ae742cd591..b3c17ead85 100644 --- a/benchmarks/prove/src/util.rs +++ b/benchmarks/prove/src/util.rs @@ -136,7 +136,15 @@ impl BenchmarkCli { } } - pub fn build_bench_program(&self, program_name: &str) -> Result { + pub fn build_bench_program( + &self, + program_name: &str, + vm_config: &VC, + init_file_name: Option<&str>, + ) -> Result + where + VC: VmConfig, + { let profile = if self.profiling { "profiling" } else { @@ -144,6 +152,7 @@ impl BenchmarkCli { } .to_string(); let manifest_dir = get_programs_dir().join(program_name); + vm_config.write_to_init_file(&manifest_dir, init_file_name)?; build_elf(&manifest_dir, profile) } diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 104ee4bd74..0bbe1daf92 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -12,6 +12,7 @@ - [Overview](./writing-apps/overview.md) - [Writing a Program](./writing-apps/write-program.md) - [Compiling](./writing-apps/build.md) +- [Running a Program](./writing-apps/run.md) - [Generating Proofs](./writing-apps/prove.md) - [Verifying Proofs](./writing-apps/verify.md) - [Solidity SDK](./writing-apps/solidity.md) diff --git a/book/src/custom-extensions/algebra.md b/book/src/custom-extensions/algebra.md index d80efbb1d9..9afd901441 100644 --- a/book/src/custom-extensions/algebra.md +++ b/book/src/custom-extensions/algebra.md @@ -17,6 +17,9 @@ The functional part is provided by the `openvm-algebra-guest` crate, which is a - `Field` trait: Provides constants `ZERO` and `ONE` and methods for basic arithmetic operations within a field. +- `Sqrt` trait: + Implements square root in a field using hinting. + ## Modular arithmetic To [leverage](./overview.md) compile-time known moduli for performance, you declare, initialize, and then set up the arithmetic structures: @@ -30,26 +33,28 @@ moduli_declare! { } ``` -This creates `Bls12_381Fp` and `Bn254Fp` structs, each implementing the `IntMod` trait. The modulus parameter must be a string literal in decimal or hexadecimal format. +This creates `Bls12_381Fp` and `Bn254Fp` structs, each implementing the `IntMod` trait. +Since both moduli are prime, both structs also implement the `Field` and `Sqrt` traits. +The modulus parameter must be a string literal in decimal or hexadecimal format. -2. **Init**: Use the `moduli_init!` macro exactly once in the final binary: +2. **Init**: Use the `init!` macro exactly once in the final binary: ```rust +init!(); +/* This expands to moduli_init! { "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", "21888242871839275222246405745257275088696311157297823662689037894645226208583" } +*/ ``` This step enumerates the declared moduli (e.g., `0` for the first one, `1` for the second one) and sets up internal linkage so the compiler can generate the appropriate RISC-V instructions associated with each modulus. -3. **Setup**: At runtime, before performing arithmetic, a setup instruction must be sent to ensure security and correctness. For the \\(i\\)-th modulus, you call `setup_()` (e.g., `setup_0()` or `setup_1()`). Alternatively, `setup_all_moduli()` can be used to handle all declared moduli. - **Summary**: - `moduli_declare!`: Declares modular arithmetic structures and can be done multiple times. -- `moduli_init!`: Called once in the final binary to assign and lock in the moduli. -- `setup_()`/`setup_all_moduli()`: Ensures at runtime that the correct modulus is in use, providing a security check and finalizing the environment for safe arithmetic operations. +- `init!`: Called once in the final binary to assign and lock in the moduli. ## Complex field extension @@ -65,49 +70,37 @@ complex_declare! { This creates a `Bn254Fp2` struct, representing a complex extension field. The `mod_type` must implement `IntMod`. -2. **Init**: Called once, after `moduli_init!`, to enumerate these extensions and generate corresponding instructions: - -```rust -complex_init! { - Bn254Fp2 { mod_idx = 0 }, -} -``` - -Note that you need to use the same type name in `complex_declare!` and `complex_init!`. For example, the following code will **fail** to compile: +2. **Init**: After calling `complex_declare!`, the `init!` macro will now expand to the appropriate call to `complex_init!`. ```rust -// moduli related macros... - -complex_declare! { - Bn254Fp2 { mod_type = Bn254Fp }, +init!(); +/* This expands to: +moduli_init! { + "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + "21888242871839275222246405745257275088696311157297823662689037894645226208583" } - -pub type Fp2 = Bn254Fp2; - complex_init! { - Fp2 { mod_idx = 0 }, + Bn254Fp2 { mod_idx = 0 }, } +*/ ``` -Here, `mod_idx` refers to the index of the underlying modulus as initialized by `moduli_init!` - -3. **Setup**: Similar to moduli, call `setup_complex_()` or `setup_all_complex_extensions()` at runtime to secure the environment. - ### Config parameters For the guest program to build successfully, all used moduli must be declared in the `.toml` config file in the following format: ```toml [app_vm_config.modular] -supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] +supported_moduli = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] [app_vm_config.fp2] -supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] +supported_moduli = [["Bn254Fp2", "115792089237316195423570985008687907853269984665640564039457584007908834671663"]] ``` -The `supported_modulus` parameter is a list of moduli that the guest program will use. They must be provided in decimal format in the `.toml` file. +The `supported_moduli` parameter is a list of moduli that the guest program will use. They must be provided in decimal format in the `.toml` file. The order of moduli in `[app_vm_config.modular]` must match the order in the `moduli_init!` macro. Similarly, the order of moduli in `[app_vm_config.fp2]` must match the order in the `complex_init!` macro. +Also, each modulus in `[app_vm_config.fp2]` must be paired with the name of the corresponding struct in `complex_declare!`. ### Example program @@ -135,8 +128,8 @@ Here is the full `openvm.toml` to accompany the above example: [app_vm_config.rv32m] [app_vm_config.io] [app_vm_config.modular] -supported_modulus = ["998244353","1000000007"] +supported_moduli = ["998244353","1000000007"] [app_vm_config.fp2] -supported_modulus = ["998244353","1000000007"] +supported_moduli = [["Complex1", "998244353"], ["Complex2", "1000000007"]] ``` diff --git a/book/src/custom-extensions/ecc.md b/book/src/custom-extensions/ecc.md index 331430d6ad..819b1c0845 100644 --- a/book/src/custom-extensions/ecc.md +++ b/book/src/custom-extensions/ecc.md @@ -43,21 +43,21 @@ sw_declare! { Each declared curve must specify the `mod_type` (implementing `IntMod`) and a constant `b` for the Weierstrass curve equation \\(y^2 = x^3 + ax + b\\). `a` is optional and defaults to 0 for short Weierstrass curves. This creates `Bls12_381G1Affine` and `P256Affine` structs which implement the `Group` and `WeierstrassPoint` traits. The underlying memory layout of the structs uses the memory layout of the `Bls12_381Fp` and `P256Coord` structs, respectively. -2. **Init**: Called once, it enumerates these curves and allows the compiler to produce optimized instructions: +2. **Init**: Called once, the `init!` macro produces a call to `sw_init!` that enumerates these curves and allows the compiler to produce optimized instructions: ```rust +init!(); +/* This expands to sw_init! { Bls12_381G1Affine, P256Affine, } +*/ ``` -3. **Setup**: Similar to the moduli and complex extensions, runtime setup instructions ensure that the correct curve parameters are being used, guaranteeing secure operation. - **Summary**: - `sw_declare!`: Declares elliptic curve structures. -- `sw_init!`: Initializes them once, linking them to the underlying moduli. -- `setup_sw_()`/`setup_all_curves()`: Secures runtime correctness. +- `init!`: Initializes them once, linking them to the underlying moduli. To use elliptic curve operations on a struct defined with `sw_declare!`, it is expected that the struct for the curve's coordinate field was defined using `moduli_declare!`. In particular, the coordinate field needs to be initialized and set up as described in the [algebra extension](./algebra.md) chapter. @@ -86,7 +86,7 @@ One can define their own ECC structs but we will use the Secp256k1 struct from ` {{ #include ../../../examples/ecc/src/main.rs:init }} ``` -We `moduli_init!` both the coordinate and scalar field because they were declared in the `k256` module, although we will not be using the scalar field below. +`moduli_init!` is called for both the coordinate and scalar field because they were declared in the `k256` module, although we will not be using the scalar field below. With the above we can start doing elliptic curve operations like adding points: @@ -100,15 +100,18 @@ For the guest program to build successfully, all used moduli and curves must be ```toml [app_vm_config.modular] -supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337"] +supported_moduli = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337"] [[app_vm_config.ecc.supported_curves]] +struct_name = "Secp256k1Point" modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" a = "0" b = "7" ``` -The `supported_modulus` parameter is a list of moduli that the guest program will use. As mentioned in the [algebra extension](./algebra.md) chapter, the order of moduli in `[app_vm_config.modular]` must match the order in the `moduli_init!` macro. +The `supported_moduli` parameter is a list of moduli that the guest program will use. As mentioned in the [algebra extension](./algebra.md) chapter, the order of moduli in `[app_vm_config.modular]` must match the order in the `moduli_init!` macro. The `ecc.supported_curves` parameter is a list of supported curves that the guest program will use. They must be provided in decimal format in the `.toml` file. For multiple curves create multiple `[[app_vm_config.ecc.supported_curves]]` sections. The order of curves in `[[app_vm_config.ecc.supported_curves]]` must match the order in the `sw_init!` macro. +Also, the `struct_name` field must be the name of the elliptic curve struct created by `sw_declare!`. +In this example, the `Secp256k1Point` struct is created in `openvm_ecc_guest::k256`. diff --git a/book/src/custom-extensions/overview.md b/book/src/custom-extensions/overview.md index 8c1a8b4002..70a52126b7 100644 --- a/book/src/custom-extensions/overview.md +++ b/book/src/custom-extensions/overview.md @@ -48,10 +48,10 @@ range_tuple_checker_sizes = [256, 8192] range_tuple_checker_sizes = [256, 8192] [app_vm_config.modular] -supported_modulus = ["", "", ...] +supported_moduli = ["", "", ...] [app_vm_config.fp2] -supported_modulus = ["", "", ...] +supported_moduli = ["", "", ...] [app_vm_config.pairing] supported_curves = ["Bls12_381", "Bn254"] diff --git a/book/src/custom-extensions/pairing.md b/book/src/custom-extensions/pairing.md index f2ed43970c..f72688af91 100644 --- a/book/src/custom-extensions/pairing.md +++ b/book/src/custom-extensions/pairing.md @@ -36,14 +36,6 @@ Additionally, we'll need to initialize our moduli and `Fp2` struct via the follo {{ #include ../../../examples/pairing/src/main.rs:init }} ``` -And we'll run the required setup functions at the top of the guest program's `main()` function: - -```rust,no_run,noplayground -{{ #include ../../../examples/pairing/src/main.rs:setup }} -``` - -There are two moduli defined internally in the `Bls12_381` feature. The `moduli_init!` macro thus requires both of them to be initialized. However, we do not need the scalar field of BLS12-381 (which is at index 1), and thus we only initialize the modulus from index 0, thus we only use `setup_0()` (as opposed to `setup_all_moduli()`, which will save us some columns when generating the trace). - ## Input values The inputs to the pairing check are `AffinePoint`s in \\(\mathbb{F}\_p\\) and \\(\mathbb{F}\_{p^2}\\). They can be constructed via the `AffinePoint::new` function, with the inner `Fp` and `Fp2` values constructed via various `from_...` functions. @@ -95,13 +87,13 @@ For the guest program to build successfully, we'll need to create an `openvm.tom supported_curves = ["Bls12_381"] [app_vm_config.modular] -supported_modulus = [ +supported_moduli = [ "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", ] [app_vm_config.fp2] -supported_modulus = [ - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", +supported_moduli = [ + ["Bls12_381Fp2", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787"], ] ``` diff --git a/book/src/getting-started/quickstart.md b/book/src/getting-started/quickstart.md index ae2d4de132..f0d5ca1f4b 100644 --- a/book/src/getting-started/quickstart.md +++ b/book/src/getting-started/quickstart.md @@ -7,10 +7,10 @@ In this section we will build and run a fibonacci program. First, create a new Rust project. ```bash -cargo init fibonacci +cargo openvm init fibonacci ``` -In `Cargo.toml`, add the following dependency: +This will generate an OpenVM-specific starter package. Notice `Cargo.toml` has the following dependency: ```toml [dependencies] @@ -49,7 +49,7 @@ To build the program, run: cargo openvm build ``` -This will output an OpenVM executable file to `./openvm/app.vmexe`. +This will output an OpenVM executable file to `./target/openvm/release/fibonacci.vmexe`. ## Keygen @@ -59,7 +59,7 @@ Before generating any proofs, we will also need to generate the proving and veri cargo openvm keygen ``` -This will output a serialized proving key to `./openvm/app.pk` and a verification key to `./openvm/app.vk`. +This will output a serialized proving key to `./target/openvm/app.pk` and a verification key to `./target/openvm/app.vk`. ## Proof Generation @@ -72,7 +72,7 @@ OPENVM_FAST_TEST=1 cargo openvm prove app --input "0x010A00000000000000" The `--input` field is passed to the program which receives it via the `io::read` function. In our `main.rs` we called `read()` to get `n: u64`. The input here is `n = 10u64` _in little endian_. Note that this value must be padded to exactly 8 bytes (64 bits) and is prefixed with `0x01` to indicate that the input is composed of raw bytes. -The serialized proof will be output to `./openvm/app.proof`. +The serialized proof will be output to `./fibonacci.app.proof`. The `OPENVM_FAST_TEST` environment variable is used to enable fast proving for testing purposes. To run with proof with secure parameters, remove the environmental variable. diff --git a/book/src/writing-apps/build.md b/book/src/writing-apps/build.md index cb65db10c9..4f0d207598 100644 --- a/book/src/writing-apps/build.md +++ b/book/src/writing-apps/build.md @@ -9,135 +9,142 @@ The command `cargo openvm build` compiles the program on host to an executable f It first compiles the program normally on your _host_ platform with RISC-V and then transpiles it to a different target. See here for some explanation of [cross-compilation](https://rust-lang.github.io/rustup/cross-compilation.html). Right now we use `riscv32im-risc0-zkvm-elf` target which is available in the [Rust toolchain](https://doc.rust-lang.org/rustc/platform-support/riscv32im-risc0-zkvm-elf.html), but we will contribute an OpenVM target to Rust in the future. -## Build flags +## Build Flags -The following flags are available for the `cargo openvm build` command: +The following flags are available for the `cargo openvm build` command. You can run `cargo openvm build --help` for this list within the command line. -- `--manifest-dir ` +Generally, outputs will always be built to the **target directory**, which will either be determined by the manifest path or explicitly set using the `--target-dir` option. By default Cargo sets this to be `/target/`. - **Description**: Specifies the directory containing the `Cargo.toml` file for the guest code. +OpenVM-specific artifacts will be placed in `${target_dir}/openvm/`, but if `--output-dir` is specified they will be copied to `${output-dir}/` as well. - **Default**: The current directory (`.`). +### OpenVM Options - **Usage Example**: If your `Cargo.toml` is located in `my_project/`, you can run: +- `--no-transpile` - ```bash - cargo openvm build --manifest-dir my_project - ``` + **Description**: Skips transpilation into an OpenVM-compatible `.vmexe` executable when set. - This ensures the build command is executed in that directory. +- `--config ` -- `--target-dir ` + **Description**: Path to the OpenVM config `.toml` file that specifies the VM extensions. By default will search the manifest directory for `openvm.toml`. If no file is found, OpenVM will use a default configuration. Currently the CLI only supports known extensions listed in the [Using Existing Extensions](../custom-extensions/overview.md) section. To use other extensions, use the [SDK](../advanced-usage/sdk.md). - **Description**: Specifies the directory where the guest binary will be built. If not specified, the default target directory is used. +- `--output_dir ` - **Default**: The `target` directory in the package root directory. + **Description**: Output directory for OpenVM artifacts to be copied to. Keys will be placed in `${output-dir}/`, while all other artifacts will be in `${output-dir}/${profile}`. - **Usage Example**: To build the guest binary in the `my_target` directory: +- `--init-file-name ` - ```bash - cargo openvm build --target-dir my_target - ``` + **Description**: Name of the generated initialization file, which will be written into the manifest directory. -- `--features ` + **Default**: `openvm_init.rs` - **Description**: Passes a list of feature flags to the Cargo build process. These flags enable or disable conditional compilation features defined in your `Cargo.toml`. +### Package Selection - **Usage Example**: To enable the `my_feature` feature: +As with `cargo build`, default package selection depends on the working directory. If the working directory is a subdirectory of a specific package, then only that package will be built. Else, all packages in the workspace will be built by default. - ```bash - cargo openvm build --features my_feature - ``` +- `--package ` -- `--bin ` + **Description**: Builds only the specified packages. This flag may be specified multiple times or as a comma-separated list. - **Description**: Restricts the build to the binary target with the given name, similar to `cargo build --bin `. If your project has multiple target types (binaries, libraries, examples, etc.), using `--bin ` narrows down the build to the binary target with the given name. +- `--workspace` - **Usage Example**: + **Description**: Builds all members of the workspace (alias `--all`). - ```bash - cargo openvm build --bin my_bin - ``` +- `--exclude ` -- `--example ` + **Description**: Excludes the specified packages. Must be used in conjunction with `--workspace`. This flag may be specified multiple times or as a comma-separated list. - **Description**: Restricts the build to the example target with the given name, similar to `cargo build --example `. Projects often include code samples or demos under the examples directory, and this flag focuses on compiling a specific example. +### Target Selection - **Usage Example**: +By default all package libraries and binaries will be built. To build samples or demos under the `examples` directory, use either the `--example` or `--examples` option. - ```bash - cargo openvm build --example my_example - ``` +- `--lib` -- `--no-transpile` + **Description**: Builds the package's library. - **Description**: After building the guest code, doesn't transpile the target ELF into an OpenVM-compatible executable (by default it does). +- `--bin ` - **Usage Example**: + **Description**: Builds the specified binary. This flag may be specified multiple times or as a comma-separated list. - ```bash - cargo openvm build --no-transpile - ``` +- `--bins` -- `--config ` + **Description**: Builds all binary targets. + +- `--example ` + + **Description**: Builds the specified example. This flag may be specified multiple times or as a comma-separated list. + +- `--examples` + + **Description**: Builds all example targets. + +- `--all-targets` + + **Description**: Builds all package targets. Equivalent to specifying `--lib` `--bins` `--examples`. + +### Feature Selection + +The following options enable or disable conditional compilation features defined in your `Cargo.toml`. + +- `-F`, `--features ` + + **Description**: Space or comma separated list of features to activate. Features of workspace members may be enabled with `package-name/feature-name` syntax. This flag may also be specified multiple times. + +- `--all-features` + + **Description**: Activates all available features of all selected packages. + +- `--no-default-features` + + **Description**: Do not activate the `default` feature of the selected packages. + +### Compilation Options + +- `--profile ` - **Description**: Specifies the path to a .toml configuration file that defines which VM extensions to use. + **Description**: Builds with the given profile. Common profiles are `dev` (faster builds, less optimization) and `release` (slower builds, more optimization). For more information on profiles, see [Cargo's reference page](https://doc.rust-lang.org/cargo/reference/profiles.html). - **Default**: `./openvm.toml` if `--config` flag is not provided. + **Default**: `release` - **Usage Example**: +### Output Options - ```bash - cargo openvm build --config path/to/openvm.toml - ``` +- `--target_dir ` - This allows you to customize the extensions. Currently the CLI only supports known extensions listed in the [Using Existing Extensions](../custom-extensions/overview.md) section. To use other extensions, use the [SDK](../advanced-usage/sdk.md). + **Description**: Directory for all generated artifacts and intermediate files. Defaults to directory `target/` at the root of the workspace. -- `--exe-output ` +### Display Options - **Description**: Sets the output path for the transpiled program. +- `-v`, `--verbose` - **Default**: `./openvm/app.vmexe` if `--exe-output` flag is not provided. + **Description**: Use verbose output. - **Usage Example**: To specify a custom output filename: +- `-q`, `--quiet` - ```bash - cargo openvm build --exe-output ./output/custom_name.vmexe - ``` + **Description**: Do not print Cargo log messages. -- `--profile ` +- `--color ` - **Description**: Determines the build profile used by Cargo. Common profiles are dev (faster builds, less optimization) and release (slower builds, more optimization). + **Description**: Controls when colored output is used. - **Default**: release + **Default**: `always` - **Usage Example**: +### Manifest Options - ```bash - cargo openvm build --profile dev - ``` +- `--manifest-path ` -- `--help` + **Description**: Path to the guest code Cargo.toml file. By default, `build` searches for the file in the current or any parent directory. The `build` command will be executed in that directory. - **Description**: Prints a help message describing the available options and their usage. +- `--ignore-rust-version` - **Usage Example**: + **Description**: Ignores rust-version specification in packages. - ```bash - cargo openvm build --help - ``` +- `--locked` -## Running a Program + **Description**: Asserts the same dependencies and versions are used as when the existing Cargo.lock file was originally generated. -After building and transpiling a program, you can execute it using the `run` command. The `run` command has the following arguments: +- `--offline` -```bash -cargo openvm run - --exe - --config - --input -``` + **Description**: Prevents Cargo from accessing the network for any reason. -If `--exe` and/or `--config` are not provided, the command will search for these files in `./openvm/app.vmexe` and `./openvm.toml` respectively. If `./openvm.toml` is not present, a default configuration will be used. +- `--frozen` -If your program doesn't require inputs, you can (and should) omit the `--input` flag. + **Description**: Equivalent to specifying both `--locked` and `--offline`. diff --git a/book/src/writing-apps/overview.md b/book/src/writing-apps/overview.md index 6666813193..9603ddef75 100644 --- a/book/src/writing-apps/overview.md +++ b/book/src/writing-apps/overview.md @@ -22,7 +22,7 @@ cargo openvm run --input Note if your program doesn't require inputs, you can omit the `--input` flag. -For more information on both commands, see the [build](./build.md) docs. +For more information see the [build](./build.md) and [run](./run.md) docs. ### Inputs diff --git a/book/src/writing-apps/prove.md b/book/src/writing-apps/prove.md index 88d7a40548..1a4b3cc1e6 100644 --- a/book/src/writing-apps/prove.md +++ b/book/src/writing-apps/prove.md @@ -4,43 +4,59 @@ Generating a proof using the CLI is simple - first generate a key, then generate ```bash cargo openvm keygen -cargo openvm prove [app | evm] +cargo openvm prove [app | stark | evm] ``` ## Key Generation -The `keygen` CLI command has the following optional arguments: +The `keygen` command generates both an application proving and verification key. ```bash cargo openvm keygen --config - --output - --vk_output ``` -If `--config` is not provided, the command will search for `./openvm.toml` and use that as the application configuration if present. If it is not present, a default configuration will be used. +Similarly to `build`, `run`, and `prove`, options `--manifest-path`, `--target-dir`, and `--output-dir` are provided. -If `--output` and/or `--vk_output` are not provided, the keys will be written to default locations `./openvm/app.pk` and/or `./openvm/app.vk` respectively. +If `--config` is not specified, the command will search for `openvm.toml` in the manifest directory. If the file isn't found, a default configuration will be used. + +The proving and verification key will be written to `${target_dir}/openvm/` (and `--output-dir` if specified). ## Proof Generation -The `prove` CLI command has the following optional arguments: +The `prove` CLI command, at its core, uses the options below. `prove` gets access to all of the options that `run` has (see [Running a Program](../writing-apps/run.md) for more information). ```bash -cargo openvm prove [app | evm] - --app_pk +cargo openvm prove [app | stark | evm] + --app-pk --exe --input - --output + --proof ``` +If `--app-pk` is not provided, the command will search for a proving key at `${target_dir}/openvm/app.pk`. + +If `--exe` is not provided, the command will call `build` before generating a proof. + If your program doesn't require inputs, you can (and should) omit the `--input` flag. -If `--app_pk` and/or `--exe` are not provided, the command will search for these files in `./openvm/app.pk` and `./openvm/app.vmexe` respectively. Similarly, if `--output` is not provided then the command will write the proof to `./openvm/[app | evm].proof` by default. +If `--proof` is not provided then the command will write the proof to `./${bin_name}.[app | stark | evm].proof` by default, where `bin_name` is the file stem of the executable run. -The `app` subcommand is used to generate an application-level proof, while the `evm` command generates an end-to-end EVM proof. +The `app` subcommand generates an application-level proof, the `stark` command generates an aggregated root-level proof, while the `evm` command generates an end-to-end EVM proof. For more information on aggregation, see [this specification](https://github.com/openvm-org/openvm/blob/bf8df90b13f4e80bb76dbb71f255a12154c84838/docs/specs/continuations.md). > ⚠️ **WARNING** > In order to run the `evm` subcommand, you must have previously called the costly `cargo openvm setup`, which requires very large amounts of computation and memory (~200 GB). See [EVM Proof Format](./verify.md#evm-proof-json-format) for details on the output format for `cargo openvm prove evm`. + +## Commit Hashes + +To see the commit hash for an executable, you may run: + +```bash +cargo openvm commit + --app-pk + --exe +``` + +The `commit` command has all the auxiliary options that `prove` does, and outputs Bn254 commits for both your executable and VM. Commits are written to `${target_dir}/openvm/` (and `--output-dir` if specified). diff --git a/book/src/writing-apps/run.md b/book/src/writing-apps/run.md new file mode 100644 index 0000000000..66a8dbf439 --- /dev/null +++ b/book/src/writing-apps/run.md @@ -0,0 +1,75 @@ +# Running a Program + +After building and transpiling a program, you can execute it using the `run` command. For example, you can call: + +```bash +cargo openvm run + --exe + --config + --input +``` + +If `--exe` is not provided, OpenVM will call `build` prior to attempting to run the executable. Note that only one executable may be run, so if your project contains multiple targets you will have to specify which one to run using the `--bin` or `--example` flag. + +If your program doesn't require inputs, you can (and should) omit the `--input` flag. + +## Run Flags + +Many of the options for `cargo openvm run` will be passed to `cargo openvm build` if `--exe` is not specified. For more information on `build` (or `run`'s **Feature Selection**, **Compilation**, **Output**, **Display**, and/or **Manifest** options) see [Compiling](./writing-apps/build.md). + +### OpenVM Options + +- `--exe ` + + **Description**: Path to the OpenVM executable, if specified `build` will be skipped. + +- `--config ` + + **Description**: Path to the OpenVM config `.toml` file that specifies the VM extensions. By default will search the manifest directory for `openvm.toml`. If no file is found, OpenVM will use a default configuration. Currently the CLI only supports known extensions listed in the [Using Existing Extensions](../custom-extensions/overview.md) section. To use other extensions, use the [SDK](../advanced-usage/sdk.md). + +- `--output_dir ` + + **Description**: Output directory for OpenVM artifacts to be copied to. Keys will be placed in `${output-dir}/`, while all other artifacts will be in `${output-dir}/${profile}`. + +- `--input ` + + **Description**: Input to the OpenVM program, or a hex string. + +- `--init-file-name ` + + **Description**: Name of the generated initialization file, which will be written into the manifest directory. + + **Default**: `openvm_init.rs` + +### Package Selection + +- `--package ` + + **Description**: The package to run, by default the package in the current workspace. + +### Target Selection + +Only one target may be built and run. + +- `--bin ` + + **Description**: Runs the specified binary. + +- `--example ` + + **Description**: Runs the specified example. + +## Examples + +### Running a Specific Binary + +```bash +cargo openvm run --bin bin_name +``` + +### Skipping Build Using `--exe` + +```bash +cargo openvm build --output-dir ./my_output_dir +cargo openvm run --exe ./my_output_dir/bin_name.vmexe +``` diff --git a/book/src/writing-apps/verify.md b/book/src/writing-apps/verify.md index 06e44b7389..8a45f31ebd 100644 --- a/book/src/writing-apps/verify.md +++ b/book/src/writing-apps/verify.md @@ -10,9 +10,9 @@ cargo openvm verify app --proof ``` -If you omit `--app_vk` and/or `--proof`, the command will search for those files at `./openvm/app.vk` and `./openvm/app.proof` respectively. +Options `--manifest-path`, `--target-dir` are also available to `verify`. If you omit `--app_vk` the command will search for the verifying key at `${target_dir}/openvm/app.vk`. -Once again, if you omitted `--output` and `--vk_output` in the `keygen` and `prove` commands, you can omit `--app_vk` and `--proof` in the `verify` command. +If you omit `--proof`, the command will search the working directory for files with the `.app.proof` extension. Note that for this default case a single proof is expected to be found, and `verify` will fail otherwise. ## EVM Level @@ -95,11 +95,11 @@ cargo openvm prove evm --input cargo openvm verify evm --proof ``` -If `proof` is omitted, the `verify` command will search for the proof at `./openvm/evm.proof`. +If `proof` is omitted, the `verify` command will search for a file with extension `.evm.proof` in the working directory. ### EVM Proof: JSON Format -The EVM proof is written to `evm.proof` as a JSON of the following format: +The EVM proof is written as a JSON of the following format: ```json { diff --git a/book/src/writing-apps/write-program.md b/book/src/writing-apps/write-program.md index 8f2fb7d9d9..24f00fe10a 100644 --- a/book/src/writing-apps/write-program.md +++ b/book/src/writing-apps/write-program.md @@ -2,16 +2,17 @@ ## Writing a guest program -See the example [fibonacci program](https://github.com/openvm-org/openvm-example-fibonacci). +To initialize an OpenVM guest program package, you can use the following CLI command: -The guest program should be a `no_std` Rust crate. As long as it is `no_std`, you can import any other -`no_std` crates and write Rust as you normally would. Import the `openvm` library crate to use `openvm` intrinsic functions (for example `openvm::io::*`). +```bash +cargo openvm init +``` -More examples of guest programs can be found in the [benchmarks/guest](https://github.com/openvm-org/openvm/tree/main/benchmarks/guest) directory. +For a guest program example, see this [fibonacci program](https://github.com/openvm-org/openvm-example-fibonacci). More examples can be found in the [benchmarks/guest](https://github.com/openvm-org/openvm/tree/main/benchmarks/guest) directory. ## Handling I/O -The program can take input from stdin, with some functions provided by `openvm::io`. +The program can take input from stdin, with some functions provided by `openvm::io`. Make sure to import the `openvm` library crate to use `openvm` intrinsic functions. `openvm::io::read` takes from stdin and deserializes it into a generic type `T`, so one should specify the type when calling it: @@ -122,3 +123,11 @@ This tells Rust to use the custom `main` handler when the environment is `no_std ## Building and running See the [overview](./overview.md) on how to build and run the program. + +## Using crates that depend on `getrandom` + +OpenVM is compatible with [getrandom](https://crates.io/crates/getrandom) `v0.3`. The `cargo openvm` CLI will always compile with the [custom](https://docs.rs/getrandom/latest/getrandom/#opt-in-backends) `getrandom` backend. + +By default the `openvm` crate has a default feature `"getrandom-unsupported"` which exports a `__getrandom_v03_custom` function that always returns `Err(Error::UNSUPPORTED)`. This is enabled by default to allow compilation of guest programs that pull in dependencies which require `getrandom` but where the executed code does not actually use `getrandom` functions. + +To override the default behavior and provide a custom implementation, turn off the `"getrandom-unsupported"` feature in the `openvm` crate and supply your own `__getrandom_v03_custom` function as specified in the [getrandom docs](https://docs.rs/getrandom/latest/getrandom/#custom-backend). diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a5d96e07a4..f105c588a3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -23,6 +23,7 @@ openvm-native-recursion = { workspace = true } openvm-sdk = { workspace = true } openvm-stark-sdk.workspace = true openvm-stark-backend.workspace = true +openvm-circuit = { workspace = true } aws-sdk-s3 = "1.78" aws-config = "1.5" @@ -36,7 +37,9 @@ hex = "0.4.3" target-lexicon = "0.12.15" tempfile = "3.10.1" toml = { workspace = true } -bitcode.workspace = true +itertools.workspace = true +toml_edit = "0.22" +include_dir = "0.7" [features] default = ["parallel", "jemalloc", "evm-verify", "bench-metrics"] diff --git a/crates/cli/src/bin/cargo-openvm.rs b/crates/cli/src/bin/cargo-openvm.rs index d3002fdfdf..36d5942bab 100644 --- a/crates/cli/src/bin/cargo-openvm.rs +++ b/crates/cli/src/bin/cargo-openvm.rs @@ -19,13 +19,16 @@ pub struct VmCli { } #[derive(Subcommand)] +#[allow(clippy::large_enum_variant)] pub enum VmCliCommands { Build(BuildCmd), + Commit(CommitCmd), Keygen(KeygenCmd), + Init(InitCmd), Prove(ProveCmd), Run(RunCmd), #[cfg(feature = "evm-verify")] - Setup(EvmProvingSetupCmd), + Setup(SetupCmd), Verify(VerifyCmd), } @@ -36,9 +39,11 @@ async fn main() -> Result<()> { setup_tracing_with_log_level(Level::WARN); match command { VmCliCommands::Build(cmd) => cmd.run(), - VmCliCommands::Run(cmd) => cmd.run(), + VmCliCommands::Commit(cmd) => cmd.run(), VmCliCommands::Keygen(cmd) => cmd.run(), + VmCliCommands::Init(cmd) => cmd.run(), VmCliCommands::Prove(cmd) => cmd.run(), + VmCliCommands::Run(cmd) => cmd.run(), #[cfg(feature = "evm-verify")] VmCliCommands::Setup(cmd) => cmd.run().await, VmCliCommands::Verify(cmd) => cmd.run(), diff --git a/crates/cli/src/commands/build.rs b/crates/cli/src/commands/build.rs index 80fc9c0733..92ecc9fc0d 100644 --- a/crates/cli/src/commands/build.rs +++ b/crates/cli/src/commands/build.rs @@ -1,28 +1,21 @@ use std::{ env::var, - fs::{create_dir_all, read, write}, + fs::{copy, create_dir_all, read}, path::PathBuf, - sync::Arc, }; use clap::Parser; use eyre::Result; +use itertools::izip; use openvm_build::{ - build_guest_package, find_unique_executable, get_package, GuestOptions, TargetFilter, -}; -use openvm_sdk::{ - commit::{commit_app_exe, committed_exe_as_bn254}, - fs::write_exe_to_file, - Sdk, + build_generic, get_package, get_workspace_packages, get_workspace_root, GuestOptions, }; +use openvm_circuit::arch::{InitFileGenerator, OPENVM_DEFAULT_INIT_FILE_NAME}; +use openvm_sdk::{fs::write_exe_to_file, Sdk}; use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE}; -use crate::{ - default::{ - DEFAULT_APP_CONFIG_PATH, DEFAULT_APP_EXE_PATH, DEFAULT_COMMITTED_APP_EXE_PATH, - DEFAULT_EXE_COMMIT_PATH, DEFAULT_MANIFEST_DIR, - }, - util::read_config_toml_or_default, +use crate::util::{ + get_manifest_path_and_dir, get_target_dir, get_target_output_dir, read_config_toml_or_default, }; #[derive(Parser)] @@ -30,11 +23,14 @@ use crate::{ pub struct BuildCmd { #[clap(flatten)] build_args: BuildArgs, + + #[clap(flatten)] + cargo_args: BuildCargoArgs, } impl BuildCmd { pub fn run(&self) -> Result<()> { - build(&self.build_args)?; + build(&self.build_args, &self.cargo_args)?; Ok(()) } } @@ -43,105 +39,326 @@ impl BuildCmd { pub struct BuildArgs { #[arg( long, - help = "Path to the directory containing the Cargo.toml file for the guest code (relative to the current directory)", - default_value = DEFAULT_MANIFEST_DIR + help = "Skips transpilation into exe when set", + help_heading = "OpenVM Options" )] - pub manifest_dir: PathBuf, + pub no_transpile: bool, - #[arg(long, help = "Path to the target directory")] - pub target_dir: Option, + #[arg( + long, + help = "Path to the OpenVM config .toml file that specifies the VM extensions, by default will search for the file at ${manifest_dir}/openvm.toml", + help_heading = "OpenVM Options" + )] + pub config: Option, - #[arg(long, value_delimiter = ',', help = "Feature flags passed to cargo")] - pub features: Vec, + #[arg( + long, + help = "Output directory that OpenVM proving artifacts will be copied to", + help_heading = "OpenVM Options" + )] + pub output_dir: Option, - #[clap(flatten, help = "Filter the target to build")] - pub bin_type_filter: BinTypeFilter, + #[arg( + long, + default_value = OPENVM_DEFAULT_INIT_FILE_NAME, + help = "Name of the init file", + help_heading = "OpenVM Options" + )] + pub init_file_name: String, +} +impl Default for BuildArgs { + fn default() -> Self { + Self { + no_transpile: false, + config: None, + output_dir: None, + init_file_name: OPENVM_DEFAULT_INIT_FILE_NAME.to_string(), + } + } +} + +#[derive(Clone, Parser)] +pub struct BuildCargoArgs { #[arg( long, - default_value = "false", - help = "Skips transpilation into exe when set" + short = 'p', + value_name = "PACKAGES", + help = "Build only specified packages", + help_heading = "Package Selection" )] - pub no_transpile: bool, + pub package: Vec, + + #[arg( + long, + alias = "all", + help = "Build all members of the workspace", + help_heading = "Package Selection" + )] + pub workspace: bool, + + #[arg( + long, + value_name = "PACKAGES", + help = "Exclude specified packages", + help_heading = "Package Selection" + )] + pub exclude: Vec, + + #[arg( + long, + help = "Build the package library", + help_heading = "Target Selection" + )] + pub lib: bool, + + #[arg( + long, + value_name = "BIN", + help = "Build the specified binary", + help_heading = "Target Selection" + )] + pub bin: Vec, + + #[arg( + long, + help = "Build all binary targets", + help_heading = "Target Selection" + )] + pub bins: bool, + + #[arg( + long, + value_name = "EXAMPLE", + help = "Build the specified example", + help_heading = "Target Selection" + )] + pub example: Vec, #[arg( long, - default_value = DEFAULT_APP_CONFIG_PATH, - help = "Path to the SDK config .toml file that specifies the transpiler extensions" + help = "Build all example targets", + help_heading = "Target Selection" )] - pub config: PathBuf, + pub examples: bool, #[arg( long, - default_value = DEFAULT_APP_EXE_PATH, - help = "Output path for the transpiled program" + help = "Build all package targets", + help_heading = "Target Selection" )] - pub exe_output: PathBuf, + pub all_targets: bool, #[arg( long, - default_value = DEFAULT_COMMITTED_APP_EXE_PATH, - help = "Output path for the committed program" + short = 'F', + value_name = "FEATURES", + value_delimiter = ',', + help = "Space/comma separated list of features to activate", + help_heading = "Feature Selection" )] - pub committed_exe_output: PathBuf, + pub features: Vec, #[arg( long, - default_value = DEFAULT_EXE_COMMIT_PATH, - help = "Output path for the exe commit (bn254 commit of committed program)" + help = "Activate all available features of all selected packages", + help_heading = "Feature Selection" )] - pub exe_commit_output: PathBuf, + pub all_features: bool, - #[arg(long, default_value = "release", help = "Build profile")] + #[arg( + long, + help = "Do not activate the `default` feature of the selected packages", + help_heading = "Feature Selection" + )] + pub no_default_features: bool, + + #[arg( + long, + value_name = "NAME", + default_value = "release", + help = "Build with the given profile", + help_heading = "Compilation Options" + )] pub profile: String, - #[arg(long, default_value = "false", help = "use --offline in cargo build")] + #[arg( + long, + value_name = "DIR", + help = "Directory for all generated artifacts and intermediate files", + help_heading = "Output Options" + )] + pub target_dir: Option, + + #[arg( + long, + short = 'v', + help = "Use verbose output", + help_heading = "Display Options" + )] + pub verbose: bool, + + #[arg( + long, + short = 'q', + help = "Do not print cargo log messages", + help_heading = "Display Options" + )] + pub quiet: bool, + + #[arg( + long, + value_name = "WHEN", + default_value = "always", + help = "Control when colored output is used", + help_heading = "Display Options" + )] + pub color: String, + + #[arg( + long, + value_name = "PATH", + help = "Path to the Cargo.toml file, by default searches for the file in the current or any parent directory", + help_heading = "Manifest Options" + )] + pub manifest_path: Option, + + #[arg( + long, + help = "Ignore rust-version specification in packages", + help_heading = "Manifest Options" + )] + pub ignore_rust_version: bool, + + #[arg( + long, + help = "Asserts same dependencies and versions are used as when the existing Cargo.lock file was originally generated", + help_heading = "Manifest Options" + )] + pub locked: bool, + + #[arg( + long, + help = "Prevents Cargo from accessing the network for any reason", + help_heading = "Manifest Options" + )] pub offline: bool, -} -#[derive(Clone, Default, clap::Args)] -#[group(required = false, multiple = false)] -pub struct BinTypeFilter { - #[arg(long, help = "Specifies the bin target to build")] - pub bin: Option, + #[arg( + long, + help = "Equivalent to specifying both --locked and --offline", + help_heading = "Manifest Options" + )] + pub frozen: bool, +} - #[arg(long, help = "Specifies the example target to build")] - pub example: Option, +impl Default for BuildCargoArgs { + fn default() -> Self { + Self { + package: vec![], + workspace: false, + exclude: vec![], + lib: false, + bin: vec![], + bins: false, + example: vec![], + examples: false, + all_targets: false, + features: vec![], + all_features: false, + no_default_features: false, + profile: "release".to_string(), + target_dir: None, + verbose: false, + quiet: false, + color: "always".to_string(), + manifest_path: None, + ignore_rust_version: false, + locked: false, + offline: false, + frozen: false, + } + } } -// Returns the path to the ELF file if it is unique. -pub(crate) fn build(build_args: &BuildArgs) -> Result> { +// Returns either a) the default transpilation output directory or b) the ELF output +// directory if no_transpile is set to true. +pub fn build(build_args: &BuildArgs, cargo_args: &BuildCargoArgs) -> Result { println!("[openvm] Building the package..."); - let target_filter = if let Some(bin) = &build_args.bin_type_filter.bin { - Some(TargetFilter { - name: bin.clone(), - kind: "bin".to_string(), - }) - } else { - build_args - .bin_type_filter - .example - .as_ref() - .map(|example| TargetFilter { - name: example.clone(), - kind: "example".to_string(), - }) - }; + + // Find manifest_path, manifest_dir, and target_dir + let (manifest_path, manifest_dir) = get_manifest_path_and_dir(&cargo_args.manifest_path)?; + let target_dir = get_target_dir(&cargo_args.target_dir, &manifest_path); + + // Set guest options using build arguments; use found manifest directory for consistency let mut guest_options = GuestOptions::default() - .with_features(build_args.features.clone()) - .with_profile(build_args.profile.clone()) + .with_features(cargo_args.features.clone()) + .with_profile(cargo_args.profile.clone()) .with_rustc_flags(var("RUSTFLAGS").unwrap_or_default().split_whitespace()); - guest_options.target_dir = build_args.target_dir.clone(); - if build_args.offline { - guest_options.options = vec!["--offline".to_string()]; + + guest_options.target_dir = Some(target_dir.clone()); + guest_options + .options + .push(format!("--color={}", cargo_args.color)); + guest_options.options.push("--manifest-path".to_string()); + guest_options + .options + .push(manifest_path.to_string_lossy().to_string()); + + for pkg in &cargo_args.package { + guest_options.options.push("--package".to_string()); + guest_options.options.push(pkg.clone()); + } + for pkg in &cargo_args.exclude { + guest_options.options.push("--exclude".to_string()); + guest_options.options.push(pkg.clone()); } + for target in &cargo_args.bin { + guest_options.options.push("--bin".to_string()); + guest_options.options.push(target.clone()); + } + for example in &cargo_args.example { + guest_options.options.push("--example".to_string()); + guest_options.options.push(example.clone()); + } + + let all_bins = cargo_args.bins || cargo_args.all_targets; + let all_examples = cargo_args.examples || cargo_args.all_targets; - let pkg = get_package(&build_args.manifest_dir); - // We support builds of libraries with 0 or >1 executables. - let elf_path = match build_guest_package(&pkg, &guest_options, None, &target_filter) { - Ok(target_dir) => { - find_unique_executable(&build_args.manifest_dir, &target_dir, &target_filter) + let boolean_flags = [ + ("--workspace", cargo_args.workspace), + ("--lib", cargo_args.lib || cargo_args.all_targets), + ("--bins", all_bins), + ("--examples", all_examples), + ("--all-features", cargo_args.all_features), + ("--no-default-features", cargo_args.no_default_features), + ("--verbose", cargo_args.verbose), + ("--quiet", cargo_args.quiet), + ("--ignore-rust-version", cargo_args.ignore_rust_version), + ("--locked", cargo_args.locked), + ("--offline", cargo_args.offline), + ("--frozen", cargo_args.frozen), + ]; + for (flag, enabled) in boolean_flags { + if enabled { + guest_options.options.push(flag.to_string()); } + } + + // Write to init file + let app_config = read_config_toml_or_default( + build_args + .config + .to_owned() + .unwrap_or_else(|| manifest_dir.join("openvm.toml")), + )?; + app_config + .app_vm_config + .write_to_init_file(&manifest_dir, Some(&build_args.init_file_name))?; + + // Build (allowing passed options to decide what gets built) + let elf_target_dir = match build_generic(&guest_options) { + Ok(raw_target_dir) => raw_target_dir, Err(None) => { return Err(eyre::eyre!("Failed to build guest")); } @@ -149,88 +366,97 @@ pub(crate) fn build(build_args: &BuildArgs) -> Result> { return Err(eyre::eyre!("Failed to build guest: code = {}", code)); } }; + println!("[openvm] Successfully built the packages"); - if !build_args.no_transpile { - let elf_path = elf_path?; - println!("[openvm] Transpiling the package..."); - let output_path = &build_args.exe_output; - let app_config = read_config_toml_or_default(&build_args.config)?; - let transpiler = app_config.app_vm_config.transpiler(); - - let data = read(elf_path.clone())?; - let elf = Elf::decode(&data, MEM_SIZE as u32)?; - let exe = Sdk::new().transpile(elf, transpiler)?; - let committed_exe = commit_app_exe(app_config.app_fri_params.fri_params, exe.clone()); - write_exe_to_file(exe, output_path)?; - - if let Some(parent) = build_args.exe_commit_output.parent() { - create_dir_all(parent)?; - } - write( - &build_args.exe_commit_output, - committed_exe_as_bn254(&committed_exe).value.to_bytes(), - )?; - if let Some(parent) = build_args.committed_exe_output.parent() { - create_dir_all(parent)?; + // If transpilation is skipped, return the raw target directory + if build_args.no_transpile { + if build_args.output_dir.is_some() { + println!("[openvm] WARNING: Output directory set but transpilation skipped"); } - let committed_exe = match Arc::try_unwrap(committed_exe) { - Ok(exe) => exe, - Err(_) => return Err(eyre::eyre!("Failed to unwrap committed_exe Arc")), - }; - write( - &build_args.committed_exe_output, - bitcode::serialize(&committed_exe)?, - )?; - - println!( - "[openvm] Successfully transpiled to {}", - output_path.display() - ); - Ok(Some(elf_path)) - } else if let Ok(elf_path) = elf_path { - println!( - "[openvm] Successfully built the package: {}", - elf_path.display() - ); - Ok(Some(elf_path)) - } else { - println!("[openvm] Successfully built the package"); - Ok(None) + return Ok(elf_target_dir); } -} -#[cfg(test)] -mod tests { - use std::path::PathBuf; + // Get all built packages + let workspace_root = get_workspace_root(&manifest_path); + let packages = if cargo_args.workspace || manifest_dir == workspace_root { + get_workspace_packages(manifest_dir) + .into_iter() + .filter(|pkg| { + (cargo_args.package.is_empty() || cargo_args.package.contains(&pkg.name)) + && !cargo_args.exclude.contains(&pkg.name) + }) + .collect() + } else { + vec![get_package(manifest_dir)] + }; - use eyre::Result; - use openvm_build::RUSTC_TARGET; + // Find elf paths of all targets for all built packages + let elf_targets = packages + .iter() + .flat_map(|pkg| pkg.targets.iter()) + .filter(|target| { + // We only build bin and example targets (note they are mutually exclusive + // types). If no target selection flags are set, then all bin targets are + // built by default. + if target.is_example() { + all_examples || cargo_args.example.contains(&target.name) + } else if target.is_bin() { + all_bins + || cargo_args.bin.contains(&target.name) + || (!cargo_args.examples + && !cargo_args.lib + && cargo_args.bin.is_empty() + && cargo_args.example.is_empty()) + } else { + false + } + }) + .collect::>(); + let elf_paths = elf_targets + .iter() + .map(|target| { + if target.is_example() { + elf_target_dir.join("examples") + } else { + elf_target_dir.clone() + } + .join(&target.name) + }) + .collect::>(); - use super::*; + // Transpile, storing in ${target_dir}/openvm/${profile} by default + let target_output_dir = get_target_output_dir(&target_dir, &cargo_args.profile); - #[test] - fn test_build_with_profile() -> Result<()> { - let temp_dir = tempfile::tempdir()?; - let target_dir = temp_dir.path(); - let build_args = BuildArgs { - manifest_dir: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("example"), - features: vec![], - bin_type_filter: Default::default(), - no_transpile: true, - config: PathBuf::from(DEFAULT_APP_CONFIG_PATH), - exe_output: PathBuf::from(DEFAULT_APP_EXE_PATH), - committed_exe_output: PathBuf::from(DEFAULT_COMMITTED_APP_EXE_PATH), - exe_commit_output: PathBuf::from(DEFAULT_EXE_COMMIT_PATH), - profile: "dev".to_string(), - target_dir: Some(target_dir.to_path_buf()), - offline: false, + println!("[openvm] Transpiling the package..."); + for (elf_path, target) in izip!(&elf_paths, &elf_targets) { + let transpiler = app_config.app_vm_config.transpiler(); + let data = read(elf_path.clone())?; + let elf = Elf::decode(&data, MEM_SIZE as u32)?; + let exe = Sdk::new().transpile(elf, transpiler)?; + + let target_name = if target.is_example() { + &format!("examples/{}", target.name) + } else { + &target.name }; - build(&build_args)?; - assert!( - target_dir.join(RUSTC_TARGET).join("debug").exists(), - "did not build with dev profile" - ); - temp_dir.close()?; - Ok(()) + let file_name = format!("{}.vmexe", target_name); + let file_path = target_output_dir.join(&file_name); + + write_exe_to_file(exe, &file_path)?; + if let Some(output_dir) = &build_args.output_dir { + create_dir_all(output_dir)?; + copy(file_path, output_dir.join(file_name))?; + } } + + let final_output_dir = if let Some(output_dir) = &build_args.output_dir { + output_dir + } else { + &target_output_dir + }; + println!( + "[openvm] Successfully transpiled to {}", + final_output_dir.display() + ); + Ok(final_output_dir.clone()) } diff --git a/crates/cli/src/commands/commit.rs b/crates/cli/src/commands/commit.rs new file mode 100644 index 0000000000..4c7e307300 --- /dev/null +++ b/crates/cli/src/commands/commit.rs @@ -0,0 +1,103 @@ +use std::{ + fs::{copy, create_dir_all}, + path::PathBuf, +}; + +use clap::Parser; +use eyre::Result; +use openvm_circuit::arch::OPENVM_DEFAULT_INIT_FILE_NAME; +use openvm_sdk::{commit::AppExecutionCommit, fs::write_to_file_json, Sdk}; + +use super::{RunArgs, RunCargoArgs}; +use crate::{ + commands::{load_app_pk, load_or_build_and_commit_exe}, + util::{get_manifest_path_and_dir, get_target_dir, get_target_output_dir}, +}; + +#[derive(Parser)] +#[command( + name = "commit", + about = "View the Bn254 commit of an OpenVM executable" +)] +pub struct CommitCmd { + #[arg( + long, + action, + help = "Path to app proving key, by default will be ${target_dir}/openvm/app.pk", + help_heading = "OpenVM Options" + )] + pub app_pk: Option, + + #[arg( + long, + action, + help = "Path to OpenVM executable, if specified build will be skipped", + help_heading = "OpenVM Options" + )] + pub exe: Option, + + #[arg( + long, + help = "Path to the OpenVM config .toml file that specifies the VM extensions, by default will search for the file at ${manifest_dir}/openvm.toml", + help_heading = "OpenVM Options" + )] + pub config: Option, + + #[arg( + long, + help = "Output directory that OpenVM proving artifacts will be copied to", + help_heading = "OpenVM Options" + )] + pub output_dir: Option, + + #[arg( + long, + default_value = OPENVM_DEFAULT_INIT_FILE_NAME, + help = "Name of the init file", + help_heading = "OpenVM Options" + )] + pub init_file_name: String, + + #[command(flatten)] + cargo_args: RunCargoArgs, +} + +impl CommitCmd { + pub fn run(&self) -> Result<()> { + let sdk = Sdk::new(); + let app_pk = load_app_pk(&self.app_pk, &self.cargo_args)?; + + let run_args = RunArgs { + exe: self.exe.clone(), + config: self.config.clone(), + output_dir: self.output_dir.clone(), + init_file_name: self.init_file_name.clone(), + input: None, + }; + let (committed_exe, target_name) = + load_or_build_and_commit_exe(&sdk, &run_args, &self.cargo_args, &app_pk)?; + + let commits = AppExecutionCommit::compute( + &app_pk.app_vm_pk.vm_config, + &committed_exe, + &app_pk.leaf_committed_exe, + ); + println!("exe commit: {:?}", commits.app_exe_commit.to_bn254()); + println!("vm commit: {:?}", commits.app_vm_commit.to_bn254()); + + let (manifest_path, _) = get_manifest_path_and_dir(&self.cargo_args.manifest_path)?; + let target_dir = get_target_dir(&self.cargo_args.target_dir, &manifest_path); + let target_output_dir = get_target_output_dir(&target_dir, &self.cargo_args.profile); + + let commit_name = format!("{}.commit.json", &target_name); + let commit_path = target_output_dir.join(&commit_name); + + write_to_file_json(&commit_path, commits)?; + if let Some(output_dir) = &self.output_dir { + create_dir_all(output_dir)?; + copy(commit_path, output_dir.join(commit_name))?; + } + + Ok(()) + } +} diff --git a/crates/cli/src/commands/init.rs b/crates/cli/src/commands/init.rs new file mode 100644 index 0000000000..4d81a50af9 --- /dev/null +++ b/crates/cli/src/commands/init.rs @@ -0,0 +1,167 @@ +use std::{ + fs::{read_to_string, write}, + path::{Path, PathBuf}, + process::Command, +}; + +use clap::Parser; +use eyre::Result; +use include_dir::{include_dir, Dir}; +use toml_edit::{DocumentMut, Item, Value}; + +static TEMPLATES: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates"); + +#[derive(Parser)] +#[command( + name = "init", + about = "Create an OpenVM package in an existing directory" +)] +pub struct InitCmd { + #[arg( + index = 1, + help = "Path to create the package in", + help_heading = "Arguments" + )] + pub path: Option, + + #[arg( + long, + help = "Create a package with a binary target (src/main.rs)", + help_heading = "Init Options" + )] + pub bin: bool, + + #[arg( + long, + help = "Create a package with a library target (src/lib.rs)", + help_heading = "Init Options" + )] + pub lib: bool, + + #[arg( + long, + default_value = "2021", + help = "Specify the Rust edition to use (2015, 2018, 2021, or 2024)", + help_heading = "Init Options" + )] + pub edition: String, + + #[arg( + long, + help = "Set the package name, default is the directory name", + help_heading = "Init Options" + )] + pub name: Option, + + #[arg( + long, + default_value = "git", + help = "Initialize a new VCS repository for the given version control system (git, hg, pijul, fossil, or none)", + help_heading = "Init Options" + )] + pub vcs: String, + + #[arg( + long, + short = 'v', + help = "Use verbose output", + help_heading = "Display Options" + )] + pub verbose: bool, + + #[arg( + long, + short = 'q', + help = "Do not print cargo log messages", + help_heading = "Display Options" + )] + pub quiet: bool, + + #[arg( + long, + value_name = "WHEN", + default_value = "always", + help = "Control when colored output is used", + help_heading = "Display Options" + )] + pub color: String, +} + +impl InitCmd { + pub fn run(&self) -> Result<()> { + let mut args = vec!["init"]; + args.extend_from_slice(&["--edition", &self.edition]); + args.extend_from_slice(&["--vcs", &self.vcs]); + args.extend_from_slice(&["--color", &self.color]); + if let Some(name) = &self.name { + args.extend_from_slice(&["--name", name]); + } + + let boolean_flags = [ + ("--bin", self.bin), + ("--lib", self.lib), + ("--verbose", self.verbose), + ("--quiet", self.quiet), + ]; + for (flag, enabled) in boolean_flags { + if enabled { + args.push(flag); + } + } + + let path = self + .path + .clone() + .unwrap_or(PathBuf::from(".").canonicalize()?); + args.push(path.to_str().unwrap()); + + let mut cmd = Command::new("cargo"); + cmd.args(&args); + + let status = cmd.status()?; + if !status.success() { + return Err(eyre::eyre!("cargo init failed with status: {}", status)); + } + + // Add openvm dependency to Cargo.toml, then write template main.rs or lib.rs + if self.lib { + add_openvm_dependency(&path, &[])?; + write_template_file("lib.rs", &path.join("src"))?; + } else { + add_openvm_dependency(&path, &["std"])?; + write_template_file("main.rs", &path.join("src"))?; + } + + // Write template openvm.toml + write_template_file("openvm.toml", &path)?; + + Ok(()) + } +} + +fn add_openvm_dependency(path: &Path, features: &[&str]) -> Result<()> { + let cargo_toml_path = path.join("Cargo.toml"); + let cargo_toml_content = read_to_string(&cargo_toml_path)?; + let mut doc = cargo_toml_content.parse::()?; + let mut openvm_table = toml_edit::InlineTable::new(); + let mut openvm_features = toml_edit::Array::new(); + for feature in features { + openvm_features.push(Value::from(feature.to_string())); + } + openvm_table.insert( + "git", + Value::from("https://github.com/openvm-org/openvm.git"), + ); + openvm_table.insert("features", Value::Array(openvm_features)); + doc["dependencies"]["openvm"] = Item::Value(toml_edit::Value::InlineTable(openvm_table)); + write(cargo_toml_path, doc.to_string())?; + Ok(()) +} + +fn write_template_file(file_name: &str, dest_dir: &Path) -> Result<()> { + let file = TEMPLATES + .get_file(file_name) + .ok_or_else(|| eyre::eyre!("Template not found: {}", file_name))?; + write(dest_dir.join(file_name), file.contents())?; + Ok(()) +} diff --git a/crates/cli/src/commands/keygen.rs b/crates/cli/src/commands/keygen.rs index 687e2f92a1..a021a183e1 100644 --- a/crates/cli/src/commands/keygen.rs +++ b/crates/cli/src/commands/keygen.rs @@ -1,4 +1,7 @@ -use std::path::PathBuf; +use std::{ + fs::{copy, create_dir_all}, + path::{Path, PathBuf}, +}; use clap::Parser; use eyre::Result; @@ -8,39 +11,99 @@ use openvm_sdk::{ }; use crate::{ - default::{DEFAULT_APP_CONFIG_PATH, DEFAULT_APP_PK_PATH, DEFAULT_APP_VK_PATH}, - util::read_config_toml_or_default, + default::{DEFAULT_APP_PK_NAME, DEFAULT_APP_VK_NAME}, + util::{ + get_app_pk_path, get_app_vk_path, get_manifest_path_and_dir, get_target_dir, + read_config_toml_or_default, + }, }; #[derive(Parser)] #[command(name = "keygen", about = "Generate an application proving key")] pub struct KeygenCmd { - #[arg(long, action, help = "Path to app config TOML file", default_value = DEFAULT_APP_CONFIG_PATH)] - config: PathBuf, + #[arg( + long, + help = "Path to the OpenVM config .toml file that specifies the VM extensions, by default will search for the file at ${manifest_dir}/openvm.toml", + help_heading = "OpenVM Options" + )] + config: Option, #[arg( long, - action, - help = "Path to output app proving key file", - default_value = DEFAULT_APP_PK_PATH + help = "Output directory that OpenVM proving artifacts will be copied to", + help_heading = "OpenVM Options" )] - output: PathBuf, + output_dir: Option, + #[command(flatten)] + cargo_args: KeygenCargoArgs, +} + +#[derive(Parser)] +pub struct KeygenCargoArgs { #[arg( long, - action, - help = "Path to output app verifying key file", - default_value = DEFAULT_APP_VK_PATH + value_name = "DIR", + help = "Directory for all Cargo-generated artifacts and intermediate files", + help_heading = "Cargo Options" )] - vk_output: PathBuf, + pub(crate) target_dir: Option, + + #[arg( + long, + value_name = "PATH", + help = "Path to the Cargo.toml file, by default searches for the file in the current or any parent directory", + help_heading = "Cargo Options" + )] + pub(crate) manifest_path: Option, } impl KeygenCmd { pub fn run(&self) -> Result<()> { - let app_config = read_config_toml_or_default(&self.config)?; - let app_pk = Sdk::new().app_keygen(app_config)?; - write_app_vk_to_file(app_pk.get_app_vk(), &self.vk_output)?; - write_app_pk_to_file(app_pk, &self.output)?; + let (manifest_path, manifest_dir) = + get_manifest_path_and_dir(&self.cargo_args.manifest_path)?; + let target_dir = get_target_dir(&self.cargo_args.target_dir, &manifest_path); + let app_pk_path = get_app_pk_path(&target_dir); + let app_vk_path = get_app_vk_path(&target_dir); + + keygen( + self.config + .to_owned() + .unwrap_or_else(|| manifest_dir.join("openvm.toml")), + &app_pk_path, + &app_vk_path, + self.output_dir.as_ref(), + )?; + println!( + "Successfully generated app pk and vk in {}", + if let Some(output_dir) = self.output_dir.as_ref() { + output_dir.display() + } else { + app_pk_path.parent().unwrap().display() + } + ); Ok(()) } } + +pub(crate) fn keygen( + config: impl AsRef, + app_pk_path: impl AsRef, + app_vk_path: impl AsRef, + output_dir: Option>, +) -> Result<()> { + let app_config = read_config_toml_or_default(config)?; + let app_pk = Sdk::new().app_keygen(app_config)?; + let app_vk = app_pk.get_app_vk(); + write_app_vk_to_file(app_vk, &app_vk_path)?; + write_app_pk_to_file(app_pk, &app_pk_path)?; + + if let Some(output_dir) = output_dir { + let output_dir = output_dir.as_ref(); + create_dir_all(output_dir)?; + copy(&app_pk_path, output_dir.join(DEFAULT_APP_PK_NAME))?; + copy(&app_vk_path, output_dir.join(DEFAULT_APP_VK_NAME))?; + } + + Ok(()) +} diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs index d34a45fd29..374ea2f29c 100644 --- a/crates/cli/src/commands/mod.rs +++ b/crates/cli/src/commands/mod.rs @@ -1,9 +1,15 @@ mod build; pub use build::*; +mod commit; +pub use commit::*; + mod keygen; pub use keygen::*; +mod init; +pub use init::*; + mod prove; pub use prove::*; diff --git a/crates/cli/src/commands/prove.rs b/crates/cli/src/commands/prove.rs index 2dbc07add8..55c10901b9 100644 --- a/crates/cli/src/commands/prove.rs +++ b/crates/cli/src/commands/prove.rs @@ -2,23 +2,29 @@ use std::{path::PathBuf, sync::Arc}; use clap::Parser; use eyre::Result; +#[cfg(feature = "evm-prove")] +use openvm_sdk::fs::write_evm_proof_to_file; use openvm_sdk::{ commit::AppExecutionCommit, - config::SdkVmConfig, - fs::{read_app_pk_from_file, read_exe_from_file, write_app_proof_to_file}, + config::{AggregationTreeConfig, SdkVmConfig}, + fs::{ + read_agg_stark_pk_from_file, read_app_pk_from_file, read_exe_from_file, + write_app_proof_to_file, write_to_file_json, + }, keygen::AppProvingKey, - NonRootCommittedExe, Sdk, StdIn, -}; -#[cfg(feature = "evm-prove")] -use openvm_sdk::{ - config::AggregationTreeConfig, - fs::{read_agg_pk_from_file, write_evm_proof_to_file}, + types::VmStarkProofBytes, + NonRootCommittedExe, Sdk, }; +use super::{RunArgs, RunCargoArgs}; use crate::{ - default::*, - input::{read_to_stdin, Input}, + commands::build, + default::default_agg_stark_pk_path, + input::read_to_stdin, + util::{get_app_pk_path, get_manifest_path_and_dir, get_single_target_name, get_target_dir}, }; +#[cfg(feature = "evm-prove")] +use crate::{default::default_params_dir, util::read_default_agg_pk}; #[derive(Parser)] #[command(name = "prove", about = "Generate a program proof")] @@ -30,31 +36,77 @@ pub struct ProveCmd { #[derive(Parser)] enum ProveSubCommand { App { - #[arg(long, action, help = "Path to app proving key", default_value = DEFAULT_APP_PK_PATH)] - app_pk: PathBuf, + #[arg( + long, + action, + help = "Path to app proof output, by default will be ./${bin_name}.app.proof", + help_heading = "Output" + )] + proof: Option, - #[arg(long, action, help = "Path to OpenVM executable", default_value = DEFAULT_APP_EXE_PATH)] - exe: PathBuf, + #[arg( + long, + action, + help = "Path to app proving key, by default will be ${target_dir}/openvm/app.pk", + help_heading = "OpenVM Options" + )] + app_pk: Option, - #[arg(long, value_parser, help = "Input to OpenVM program")] - input: Option, + #[command(flatten)] + run_args: RunArgs, - #[arg(long, action, help = "Path to output proof", default_value = DEFAULT_APP_PROOF_PATH)] - output: PathBuf, + #[command(flatten)] + cargo_args: RunCargoArgs, + }, + Stark { + #[arg( + long, + action, + help = "Path to STARK proof output, by default will be ./${bin_name}.stark.proof", + help_heading = "Output" + )] + proof: Option, + + #[arg( + long, + action, + help = "Path to app proving key, by default will be ${target_dir}/openvm/app.pk", + help_heading = "OpenVM Options" + )] + app_pk: Option, + + #[command(flatten)] + run_args: RunArgs, + + #[command(flatten)] + cargo_args: RunCargoArgs, + + #[command(flatten)] + agg_tree_config: AggregationTreeConfig, }, #[cfg(feature = "evm-prove")] Evm { - #[arg(long, action, help = "Path to app proving key", default_value = DEFAULT_APP_PK_PATH)] - app_pk: PathBuf, + #[arg( + long, + action, + help = "Path to EVM proof output, by default will be ./${bin_name}.evm.proof", + help_heading = "Output" + )] + proof: Option, - #[arg(long, action, help = "Path to OpenVM executable", default_value = DEFAULT_APP_EXE_PATH)] - exe: PathBuf, + #[arg( + long, + action, + help = "Path to app proving key, by default will be ${target_dir}/openvm/app.pk", + help_heading = "OpenVM Options" + )] + app_pk: Option, - #[arg(long, value_parser, help = "Input to OpenVM program")] - input: Option, + #[command(flatten)] + run_args: RunArgs, - #[arg(long, action, help = "Path to output proof", default_value = DEFAULT_EVM_PROOF_PATH)] - output: PathBuf, + #[command(flatten)] + cargo_args: RunCargoArgs, #[command(flatten)] agg_tree_config: AggregationTreeConfig, @@ -63,69 +115,153 @@ enum ProveSubCommand { impl ProveCmd { pub fn run(&self) -> Result<()> { - let sdk = Sdk::new(); match &self.command { ProveSubCommand::App { app_pk, - exe, - input, - output, + proof, + run_args, + cargo_args, } => { - let (app_pk, committed_exe, input) = - Self::prepare_execution(&sdk, app_pk, exe, input)?; - let app_proof = sdk.generate_app_proof(app_pk, committed_exe, input)?; - write_app_proof_to_file(app_proof, output)?; + let sdk = Sdk::new(); + let app_pk = load_app_pk(app_pk, cargo_args)?; + let (committed_exe, target_name) = + load_or_build_and_commit_exe(&sdk, run_args, cargo_args, &app_pk)?; + + let app_proof = + sdk.generate_app_proof(app_pk, committed_exe, read_to_stdin(&run_args.input)?)?; + + let proof_path = if let Some(proof) = proof { + proof + } else { + &PathBuf::from(format!("{}.app.proof", target_name)) + }; + write_app_proof_to_file(app_proof, proof_path)?; + } + ProveSubCommand::Stark { + app_pk, + proof, + run_args, + cargo_args, + agg_tree_config, + } => { + let sdk = Sdk::new().with_agg_tree_config(*agg_tree_config); + let app_pk = load_app_pk(app_pk, cargo_args)?; + let (committed_exe, target_name) = + load_or_build_and_commit_exe(&sdk, run_args, cargo_args, &app_pk)?; + + let commits = AppExecutionCommit::compute( + &app_pk.app_vm_pk.vm_config, + &committed_exe, + &app_pk.leaf_committed_exe, + ); + println!("exe commit: {:?}", commits.app_exe_commit.to_bn254()); + println!("vm commit: {:?}", commits.app_vm_commit.to_bn254()); + + let agg_stark_pk = read_agg_stark_pk_from_file(default_agg_stark_pk_path()).map_err(|e| { + eyre::eyre!("Failed to read aggregation proving key: {}\nPlease run 'cargo openvm setup' first", e) + })?; + let stark_proof = sdk.generate_e2e_stark_proof( + app_pk, + committed_exe, + agg_stark_pk, + read_to_stdin(&run_args.input)?, + )?; + + let stark_proof_bytes = VmStarkProofBytes::new(commits, stark_proof)?; + + let proof_path = if let Some(proof) = proof { + proof + } else { + &PathBuf::from(format!("{}.stark.proof", target_name)) + }; + write_to_file_json(proof_path, stark_proof_bytes)?; } #[cfg(feature = "evm-prove")] ProveSubCommand::Evm { app_pk, - exe, - input, - output, + proof, + run_args, + cargo_args, agg_tree_config, } => { use openvm_native_recursion::halo2::utils::CacheHalo2ParamsReader; - let mut sdk = sdk; - sdk.set_agg_tree_config(*agg_tree_config); - let params_reader = CacheHalo2ParamsReader::new(DEFAULT_PARAMS_DIR); - let (app_pk, committed_exe, input) = - Self::prepare_execution(&sdk, app_pk, exe, input)?; + let sdk = Sdk::new().with_agg_tree_config(*agg_tree_config); + let app_pk = load_app_pk(app_pk, cargo_args)?; + let (committed_exe, target_name) = + load_or_build_and_commit_exe(&sdk, run_args, cargo_args, &app_pk)?; + + let commits = AppExecutionCommit::compute( + &app_pk.app_vm_pk.vm_config, + &committed_exe, + &app_pk.leaf_committed_exe, + ); + println!("exe commit: {:?}", commits.app_exe_commit.to_bn254()); + println!("vm commit: {:?}", commits.app_vm_commit.to_bn254()); + println!("Generating EVM proof, this may take a lot of compute and memory..."); - let agg_pk = read_agg_pk_from_file(DEFAULT_AGG_PK_PATH).map_err(|e| { + let agg_pk = read_default_agg_pk().map_err(|e| { eyre::eyre!("Failed to read aggregation proving key: {}\nPlease run 'cargo openvm setup' first", e) })?; - let evm_proof = - sdk.generate_evm_proof(¶ms_reader, app_pk, committed_exe, agg_pk, input)?; - write_evm_proof_to_file(evm_proof, output)?; + let params_reader = CacheHalo2ParamsReader::new(default_params_dir()); + let evm_proof = sdk.generate_evm_proof( + ¶ms_reader, + app_pk, + committed_exe, + agg_pk, + read_to_stdin(&run_args.input)?, + )?; + + let proof_path = if let Some(proof) = proof { + proof + } else { + &PathBuf::from(format!("{}.evm.proof", target_name)) + }; + write_evm_proof_to_file(evm_proof, proof_path)?; } } Ok(()) } +} - fn prepare_execution( - sdk: &Sdk, - app_pk: &PathBuf, - exe: &PathBuf, - input: &Option, - ) -> Result<( - Arc>, - Arc, - StdIn, - )> { - let app_pk: Arc> = Arc::new(read_app_pk_from_file(app_pk)?); - let app_exe = read_exe_from_file(exe)?; - let committed_exe = sdk.commit_app_exe(app_pk.app_fri_params(), app_exe)?; - - let commits = AppExecutionCommit::compute( - &app_pk.app_vm_pk.vm_config, - &committed_exe, - &app_pk.leaf_committed_exe, - ); - println!("app_pk commit: {:?}", commits.app_config_commit_to_bn254()); - println!("exe commit: {:?}", commits.exe_commit_to_bn254()); - - let input = read_to_stdin(input)?; - Ok((app_pk, committed_exe, input)) - } +pub(crate) fn load_app_pk( + app_pk: &Option, + cargo_args: &RunCargoArgs, +) -> Result>> { + let (manifest_path, _) = get_manifest_path_and_dir(&cargo_args.manifest_path)?; + let target_dir = get_target_dir(&cargo_args.target_dir, &manifest_path); + + let app_pk_path = if let Some(app_pk) = app_pk { + app_pk.to_path_buf() + } else { + get_app_pk_path(&target_dir) + }; + + Ok(Arc::new(read_app_pk_from_file(app_pk_path)?)) +} + +// Returns (committed_exe, target_name) where target_name has no extension +pub(crate) fn load_or_build_and_commit_exe( + sdk: &Sdk, + run_args: &RunArgs, + cargo_args: &RunCargoArgs, + app_pk: &Arc>, +) -> Result<(Arc, String)> { + let exe_path = if let Some(exe) = &run_args.exe { + exe + } else { + // Build and get the executable name + let target_name = get_single_target_name(cargo_args)?; + let build_args = run_args.clone().into(); + let cargo_args = cargo_args.clone().into(); + let output_dir = build(&build_args, &cargo_args)?; + &output_dir.join(format!("{}.vmexe", target_name)) + }; + + let app_exe = read_exe_from_file(exe_path)?; + let committed_exe = sdk.commit_app_exe(app_pk.app_fri_params(), app_exe)?; + Ok(( + committed_exe, + exe_path.file_stem().unwrap().to_string_lossy().into_owned(), + )) } diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 0bdcd438de..133e7eebf5 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -2,33 +2,259 @@ use std::path::PathBuf; use clap::Parser; use eyre::Result; +use openvm_circuit::arch::OPENVM_DEFAULT_INIT_FILE_NAME; use openvm_sdk::{fs::read_exe_from_file, Sdk}; +use super::{build, BuildArgs, BuildCargoArgs}; use crate::{ - default::{DEFAULT_APP_CONFIG_PATH, DEFAULT_APP_EXE_PATH}, input::{read_to_stdin, Input}, - util::read_config_toml_or_default, + util::{get_manifest_path_and_dir, get_single_target_name, read_config_toml_or_default}, }; #[derive(Parser)] #[command(name = "run", about = "Run an OpenVM program")] pub struct RunCmd { - #[arg(long, action, help = "Path to OpenVM executable", default_value = DEFAULT_APP_EXE_PATH)] - exe: PathBuf, + #[clap(flatten)] + run_args: RunArgs, - #[arg(long, action, help = "Path to app config TOML file", default_value = DEFAULT_APP_CONFIG_PATH)] - config: PathBuf, + #[clap(flatten)] + cargo_args: RunCargoArgs, +} + +#[derive(Clone, Parser)] +pub struct RunArgs { + #[arg( + long, + action, + help = "Path to OpenVM executable, if specified build will be skipped", + help_heading = "OpenVM Options" + )] + pub exe: Option, + + #[arg( + long, + help = "Path to the OpenVM config .toml file that specifies the VM extensions, by default will search for the file at ${manifest_dir}/openvm.toml", + help_heading = "OpenVM Options" + )] + pub config: Option, + + #[arg( + long, + help = "Output directory that OpenVM proving artifacts will be copied to", + help_heading = "OpenVM Options" + )] + pub output_dir: Option, + + #[arg( + long, + value_parser, + help = "Input to OpenVM program", + help_heading = "OpenVM Options" + )] + pub input: Option, + + #[arg( + long, + default_value = OPENVM_DEFAULT_INIT_FILE_NAME, + help = "Name of the init file", + help_heading = "OpenVM Options" + )] + pub init_file_name: String, +} + +impl From for BuildArgs { + fn from(args: RunArgs) -> Self { + BuildArgs { + config: args.config, + output_dir: args.output_dir, + init_file_name: args.init_file_name, + ..Default::default() + } + } +} + +#[derive(Clone, Parser)] +pub struct RunCargoArgs { + #[arg( + long, + short = 'p', + value_name = "PACKAGES", + help = "The package to run; by default is the package in the current workspace", + help_heading = "Package Selection" + )] + pub package: Option, + + #[arg( + long, + value_name = "BIN", + help = "Run the specified binary", + help_heading = "Target Selection" + )] + pub bin: Vec, + + #[arg( + long, + value_name = "EXAMPLE", + help = "Run the specified example", + help_heading = "Target Selection" + )] + pub example: Vec, + + #[arg( + long, + short = 'F', + value_name = "FEATURES", + value_delimiter = ',', + help = "Space/comma separated list of features to activate", + help_heading = "Feature Selection" + )] + pub features: Vec, + + #[arg( + long, + help = "Activate all available features of all selected packages", + help_heading = "Feature Selection" + )] + pub all_features: bool, - #[arg(long, value_parser, help = "Input to OpenVM program")] - input: Option, + #[arg( + long, + help = "Do not activate the `default` feature of the selected packages", + help_heading = "Feature Selection" + )] + pub no_default_features: bool, + + #[arg( + long, + value_name = "NAME", + default_value = "release", + help = "Run with the given profile", + help_heading = "Compilation Options" + )] + pub profile: String, + + #[arg( + long, + value_name = "DIR", + help = "Directory for all generated artifacts and intermediate files", + help_heading = "Output Options" + )] + pub target_dir: Option, + + #[arg( + long, + short = 'v', + help = "Use verbose output", + help_heading = "Display Options" + )] + pub verbose: bool, + + #[arg( + long, + short = 'q', + help = "Do not print cargo log messages", + help_heading = "Display Options" + )] + pub quiet: bool, + + #[arg( + long, + value_name = "WHEN", + default_value = "always", + help = "Control when colored output is used", + help_heading = "Display Options" + )] + pub color: String, + + #[arg( + long, + value_name = "PATH", + help = "Path to the Cargo.toml file, by default searches for the file in the current or any parent directory", + help_heading = "Manifest Options" + )] + pub manifest_path: Option, + + #[arg( + long, + help = "Ignore rust-version specification in packages", + help_heading = "Manifest Options" + )] + pub ignore_rust_version: bool, + + #[arg( + long, + help = "Asserts same dependencies and versions are used as when the existing Cargo.lock file was originally generated", + help_heading = "Manifest Options" + )] + pub locked: bool, + + #[arg( + long, + help = "Prevents Cargo from accessing the network for any reason", + help_heading = "Manifest Options" + )] + pub offline: bool, + + #[arg( + long, + help = "Equivalent to specifying both --locked and --offline", + help_heading = "Manifest Options" + )] + pub frozen: bool, +} + +impl From for BuildCargoArgs { + fn from(args: RunCargoArgs) -> Self { + BuildCargoArgs { + package: args.package.into_iter().collect(), + bin: args.bin, + example: args.example, + features: args.features, + all_features: args.all_features, + no_default_features: args.no_default_features, + profile: args.profile, + target_dir: args.target_dir, + verbose: args.verbose, + quiet: args.quiet, + color: args.color, + manifest_path: args.manifest_path, + ignore_rust_version: args.ignore_rust_version, + locked: args.locked, + offline: args.offline, + frozen: args.frozen, + ..Default::default() + } + } } impl RunCmd { pub fn run(&self) -> Result<()> { - let exe = read_exe_from_file(&self.exe)?; - let app_config = read_config_toml_or_default(&self.config)?; + let exe_path = if let Some(exe) = &self.run_args.exe { + exe + } else { + // Build and get the executable name + let target_name = get_single_target_name(&self.cargo_args)?; + let build_args = self.run_args.clone().into(); + let cargo_args = self.cargo_args.clone().into(); + let output_dir = build(&build_args, &cargo_args)?; + &output_dir.join(format!("{}.vmexe", target_name)) + }; + + let (_, manifest_dir) = get_manifest_path_and_dir(&self.cargo_args.manifest_path)?; + let app_config = read_config_toml_or_default( + self.run_args + .config + .to_owned() + .unwrap_or_else(|| manifest_dir.join("openvm.toml")), + )?; + let exe = read_exe_from_file(exe_path)?; + let sdk = Sdk::new(); - let output = sdk.execute(exe, app_config.app_vm_config, read_to_stdin(&self.input)?)?; + let output = sdk.execute( + exe, + app_config.app_vm_config, + read_to_stdin(&self.run_args.input)?, + )?; println!("Execution output: {:?}", output); Ok(()) } diff --git a/crates/cli/src/commands/setup.rs b/crates/cli/src/commands/setup.rs index a62faaa0df..770be24d4c 100644 --- a/crates/cli/src/commands/setup.rs +++ b/crates/cli/src/commands/setup.rs @@ -9,62 +9,122 @@ use clap::Parser; use eyre::{eyre, Result}; use openvm_native_recursion::halo2::utils::CacheHalo2ParamsReader; use openvm_sdk::{ - config::AggConfig, + config::{AggConfig, AggStarkConfig}, fs::{ - write_agg_pk_to_file, write_evm_halo2_verifier_to_folder, EVM_HALO2_VERIFIER_BASE_NAME, - EVM_HALO2_VERIFIER_INTERFACE_NAME, EVM_HALO2_VERIFIER_PARENT_NAME, + write_agg_halo2_pk_to_file, write_agg_stark_pk_to_file, write_evm_halo2_verifier_to_folder, + EVM_HALO2_VERIFIER_BASE_NAME, EVM_HALO2_VERIFIER_INTERFACE_NAME, + EVM_HALO2_VERIFIER_PARENT_NAME, }, DefaultStaticVerifierPvHandler, Sdk, }; -use crate::default::{DEFAULT_AGG_PK_PATH, DEFAULT_EVM_HALO2_VERIFIER_PATH, DEFAULT_PARAMS_DIR}; +use crate::{ + default::{ + default_agg_halo2_pk_path, default_agg_stark_pk_path, default_asm_path, + default_evm_halo2_verifier_path, default_params_dir, + }, + util::read_default_agg_pk, +}; #[derive(Parser)] #[command( - name = "evm-proving-setup", + name = "setup", about = "Set up for generating EVM proofs. ATTENTION: this requires large amounts of computation and memory. " )] -pub struct EvmProvingSetupCmd {} +pub struct SetupCmd { + #[arg( + long, + default_value = "false", + help = "use --evm to also generate proving keys for EVM verifier" + )] + pub evm: bool, + #[arg( + long, + default_value = "false", + help = "force keygen even if the proving keys already exist" + )] + pub force_agg_keygen: bool, +} -impl EvmProvingSetupCmd { +impl SetupCmd { pub async fn run(&self) -> Result<()> { - if PathBuf::from(DEFAULT_AGG_PK_PATH).exists() - && PathBuf::from(DEFAULT_EVM_HALO2_VERIFIER_PATH) - .join(EVM_HALO2_VERIFIER_PARENT_NAME) - .exists() - && PathBuf::from(DEFAULT_EVM_HALO2_VERIFIER_PATH) - .join(EVM_HALO2_VERIFIER_BASE_NAME) - .exists() - && PathBuf::from(DEFAULT_EVM_HALO2_VERIFIER_PATH) - .join("interfaces") - .join(EVM_HALO2_VERIFIER_INTERFACE_NAME) - .exists() - { - println!("Aggregation proving key and verifier contract already exist"); - return Ok(()); - } else if !Self::check_solc_installed() { - return Err(eyre!( - "solc is not installed, please install solc to continue" - )); - } + let default_agg_stark_pk_path = default_agg_stark_pk_path(); + let default_params_dir = default_params_dir(); + let default_evm_halo2_verifier_path = default_evm_halo2_verifier_path(); + let default_asm_path = default_asm_path(); + if !self.evm { + if PathBuf::from(&default_agg_stark_pk_path).exists() { + println!("Aggregation stark proving key already exists"); + return Ok(()); + } + let agg_stark_config = AggStarkConfig::default(); + let sdk = Sdk::new(); + let agg_stark_pk = sdk.agg_stark_keygen(agg_stark_config)?; + + println!("Writing stark proving key to file..."); + write_agg_stark_pk_to_file(&agg_stark_pk, default_agg_stark_pk_path)?; - Self::download_params(10, 24).await?; - let params_reader = CacheHalo2ParamsReader::new(DEFAULT_PARAMS_DIR); - let agg_config = AggConfig::default(); - let sdk = Sdk::new(); + println!("Generating root verifier ASM..."); + let root_verifier_asm = sdk.generate_root_verifier_asm(&agg_stark_pk); - println!("Generating proving key..."); - let agg_pk = sdk.agg_keygen(agg_config, ¶ms_reader, &DefaultStaticVerifierPvHandler)?; + println!("Writing root verifier ASM to file..."); + write(&default_asm_path, root_verifier_asm)?; + } else { + let default_agg_halo2_pk_path = default_agg_halo2_pk_path(); + if PathBuf::from(&default_agg_stark_pk_path).exists() + && PathBuf::from(&default_agg_halo2_pk_path).exists() + && PathBuf::from(&default_evm_halo2_verifier_path) + .join(EVM_HALO2_VERIFIER_PARENT_NAME) + .exists() + && PathBuf::from(&default_evm_halo2_verifier_path) + .join(EVM_HALO2_VERIFIER_BASE_NAME) + .exists() + && PathBuf::from(&default_evm_halo2_verifier_path) + .join("interfaces") + .join(EVM_HALO2_VERIFIER_INTERFACE_NAME) + .exists() + { + println!("Aggregation proving key and verifier contract already exist"); + return Ok(()); + } else if !Self::check_solc_installed() { + return Err(eyre!( + "solc is not installed, please install solc to continue" + )); + } - println!("Generating verifier contract..."); - let verifier = sdk.generate_halo2_verifier_solidity(¶ms_reader, &agg_pk)?; + Self::download_params(10, 24).await?; + let params_reader = CacheHalo2ParamsReader::new(&default_params_dir); + let agg_config = AggConfig::default(); + let sdk = Sdk::new(); - println!("Writing proving key to file..."); - write_agg_pk_to_file(agg_pk, DEFAULT_AGG_PK_PATH)?; + let agg_pk = if !self.force_agg_keygen + && PathBuf::from(&default_agg_stark_pk_path).exists() + && PathBuf::from(&default_agg_halo2_pk_path).exists() + { + read_default_agg_pk()? + } else { + println!("Generating proving key..."); + sdk.agg_keygen(agg_config, ¶ms_reader, &DefaultStaticVerifierPvHandler)? + }; - println!("Writing verifier contract to file..."); - write_evm_halo2_verifier_to_folder(verifier, DEFAULT_EVM_HALO2_VERIFIER_PATH)?; + println!("Generating root verifier ASM..."); + let root_verifier_asm = sdk.generate_root_verifier_asm(&agg_pk.agg_stark_pk); + println!("Generating verifier contract..."); + let verifier = sdk.generate_halo2_verifier_solidity(¶ms_reader, &agg_pk)?; + + println!("Writing stark proving key to file..."); + write_agg_stark_pk_to_file(&agg_pk.agg_stark_pk, &default_agg_stark_pk_path)?; + + println!("Writing halo2 proving key to file..."); + write_agg_halo2_pk_to_file(&agg_pk.halo2_pk, &default_agg_halo2_pk_path)?; + + println!("Writing root verifier ASM to file..."); + write(&default_asm_path, root_verifier_asm)?; + + println!("Writing verifier contract to file..."); + write_evm_halo2_verifier_to_folder(verifier, &default_evm_halo2_verifier_path)?; + } Ok(()) } @@ -76,7 +136,9 @@ impl EvmProvingSetupCmd { } async fn download_params(min_k: u32, max_k: u32) -> Result<()> { - create_dir_all(DEFAULT_PARAMS_DIR)?; + let default_params_dir = default_params_dir(); + create_dir_all(&default_params_dir)?; + let config = defaults(BehaviorVersion::latest()) .region(Region::new("us-east-1")) .no_credentials() @@ -86,7 +148,7 @@ impl EvmProvingSetupCmd { for k in min_k..=max_k { let file_name = format!("kzg_bn254_{}.srs", k); - let local_file_path = PathBuf::from(DEFAULT_PARAMS_DIR).join(&file_name); + let local_file_path = PathBuf::from(&default_params_dir).join(&file_name); if !local_file_path.exists() { println!("Downloading {}", file_name); let key = format!("challenge_0085/{}", file_name); diff --git a/crates/cli/src/commands/verify.rs b/crates/cli/src/commands/verify.rs index 41a1f397cc..a4fb6f7fa1 100644 --- a/crates/cli/src/commands/verify.rs +++ b/crates/cli/src/commands/verify.rs @@ -1,13 +1,23 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use clap::Parser; use eyre::Result; use openvm_sdk::{ - fs::{read_app_proof_from_file, read_app_vk_from_file}, + fs::{ + read_agg_stark_pk_from_file, read_app_proof_from_file, read_app_vk_from_file, + read_from_file_json, + }, + types::VmStarkProofBytes, Sdk, }; -use crate::default::*; +use super::KeygenCargoArgs; +#[cfg(feature = "evm-verify")] +use crate::default::default_evm_halo2_verifier_path; +use crate::{ + default::default_agg_stark_pk_path, + util::{get_app_vk_path, get_files_with_ext, get_manifest_path_and_dir, get_target_dir}, +}; #[derive(Parser)] #[command(name = "verify", about = "Verify a proof")] @@ -19,16 +29,43 @@ pub struct VerifyCmd { #[derive(Parser)] enum VerifySubCommand { App { - #[arg(long, action, help = "Path to app verifying key", default_value = DEFAULT_APP_VK_PATH)] - app_vk: PathBuf, + #[arg( + long, + action, + help = "Path to app verifying key, by default will search for it in ${target_dir}/openvm/app.vk", + help_heading = "OpenVM Options" + )] + app_vk: Option, + + #[arg( + long, + action, + help = "Path to app proof, by default will search the working directory for a file with extension .app.proof", + help_heading = "OpenVM Options" + )] + proof: Option, - #[arg(long, action, help = "Path to app proof", default_value = DEFAULT_APP_PROOF_PATH)] - proof: PathBuf, + #[command(flatten)] + cargo_args: KeygenCargoArgs, + }, + Stark { + #[arg( + long, + action, + help = "Path to STARK proof, by default will search the working directory for a file with extension .stark.proof", + help_heading = "OpenVM Options" + )] + proof: Option, }, #[cfg(feature = "evm-verify")] Evm { - #[arg(long, action, help = "Path to EVM proof", default_value = DEFAULT_EVM_PROOF_PATH)] - proof: PathBuf, + #[arg( + long, + action, + help = "Path to EVM proof, by default will search the working directory for a file with extension .evm.proof", + help_heading = "OpenVM Options" + )] + proof: Option, }, } @@ -36,21 +73,93 @@ impl VerifyCmd { pub fn run(&self) -> Result<()> { let sdk = Sdk::new(); match &self.command { - VerifySubCommand::App { app_vk, proof } => { - let app_vk = read_app_vk_from_file(app_vk)?; - let app_proof = read_app_proof_from_file(proof)?; + VerifySubCommand::App { + app_vk, + proof, + cargo_args, + } => { + let app_vk_path = if let Some(app_vk) = app_vk { + app_vk.to_path_buf() + } else { + let (manifest_path, _) = get_manifest_path_and_dir(&cargo_args.manifest_path)?; + let target_dir = get_target_dir(&cargo_args.target_dir, &manifest_path); + get_app_vk_path(&target_dir) + }; + let app_vk = read_app_vk_from_file(app_vk_path)?; + + let proof_path = if let Some(proof) = proof { + proof.clone() + } else { + let files = get_files_with_ext(Path::new("."), "app.proof")?; + if files.len() > 1 { + return Err(eyre::eyre!("multiple .app.proof files found, please specify the path using option --proof")); + } else if files.is_empty() { + return Err(eyre::eyre!("no .app.proof file found, please specify the path using option --proof")); + } + files[0].clone() + }; + println!("Verifying application proof at {}", proof_path.display()); + let app_proof = read_app_proof_from_file(proof_path)?; sdk.verify_app_proof(&app_vk, &app_proof)?; } + VerifySubCommand::Stark { proof } => { + let agg_stark_pk = read_agg_stark_pk_from_file(default_agg_stark_pk_path()) + .map_err(|e| { + eyre::eyre!( + "Failed to read aggregation STARK proving key: {}\nPlease run 'cargo openvm setup' first", + e + ) + })?; + let proof_path = if let Some(proof) = proof { + proof.clone() + } else { + let files = get_files_with_ext(Path::new("."), "stark.proof")?; + if files.len() > 1 { + return Err(eyre::eyre!("multiple .stark.proof files found, please specify the path using option --proof")); + } else if files.is_empty() { + return Err(eyre::eyre!("no .stark.proof file found, please specify the path using option --proof")); + } + files[0].clone() + }; + println!("Verifying STARK proof at {}", proof_path.display()); + let stark_proof_bytes: VmStarkProofBytes = read_from_file_json(proof_path)?; + let expected_exe_commit = stark_proof_bytes.app_commit.app_exe_commit.to_bn254(); + let expected_vm_commit = stark_proof_bytes.app_commit.app_vm_commit.to_bn254(); + sdk.verify_e2e_stark_proof( + &agg_stark_pk, + &stark_proof_bytes.try_into()?, + &expected_exe_commit, + &expected_vm_commit, + )?; + } #[cfg(feature = "evm-verify")] VerifySubCommand::Evm { proof } => { use openvm_sdk::fs::{ read_evm_halo2_verifier_from_folder, read_evm_proof_from_file, }; - let evm_verifier = read_evm_halo2_verifier_from_folder(DEFAULT_EVM_HALO2_VERIFIER_PATH).map_err(|e| { - eyre::eyre!("Failed to read EVM verifier: {}\nPlease run 'cargo openvm evm-proving-setup' first", e) - })?; - let evm_proof = read_evm_proof_from_file(proof)?; + let evm_verifier = + read_evm_halo2_verifier_from_folder(default_evm_halo2_verifier_path()) + .map_err(|e| { + eyre::eyre!( + "Failed to read EVM verifier: {}\nPlease run 'cargo openvm setup' first", + e + ) + })?; + + let proof_path = if let Some(proof) = proof { + proof.clone() + } else { + let files = get_files_with_ext(Path::new("."), "evm.proof")?; + if files.len() > 1 { + return Err(eyre::eyre!("multiple .evm.proof files found, please specify the path using option --proof")); + } else if files.is_empty() { + return Err(eyre::eyre!("no .evm.proof file found, please specify the path using option --proof")); + } + files[0].clone() + }; + println!("Verifying EVM proof at {}", proof_path.display()); + let evm_proof = read_evm_proof_from_file(proof_path)?; sdk.verify_evm_halo2_proof(&evm_verifier, evm_proof)?; } } diff --git a/crates/cli/src/default.rs b/crates/cli/src/default.rs index 3d1e9156ed..d1972551b0 100644 --- a/crates/cli/src/default.rs +++ b/crates/cli/src/default.rs @@ -1,21 +1,32 @@ +use std::env; + use openvm_sdk::config::{AppConfig, SdkVmConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP}; use openvm_stark_sdk::config::FriParameters; pub const DEFAULT_MANIFEST_DIR: &str = "."; -pub const DEFAULT_AGG_PK_PATH: &str = concat!(env!("HOME"), "/.openvm/agg.pk"); -pub const DEFAULT_PARAMS_DIR: &str = concat!(env!("HOME"), "/.openvm/params/"); +pub const DEFAULT_APP_PK_NAME: &str = "app.pk"; +pub const DEFAULT_APP_VK_NAME: &str = "app.vk"; -pub const DEFAULT_EVM_HALO2_VERIFIER_PATH: &str = concat!(env!("HOME"), "/.openvm/halo2/"); +pub fn default_agg_stark_pk_path() -> String { + env::var("HOME").unwrap() + "/.openvm/agg_stark.pk" +} -pub const DEFAULT_APP_CONFIG_PATH: &str = "./openvm.toml"; -pub const DEFAULT_APP_EXE_PATH: &str = "./openvm/app.vmexe"; -pub const DEFAULT_EXE_COMMIT_PATH: &str = "./openvm/exe_commit.bytes"; -pub const DEFAULT_COMMITTED_APP_EXE_PATH: &str = "./openvm/committed_app_exe.bc"; -pub const DEFAULT_APP_PK_PATH: &str = "./openvm/app.pk"; -pub const DEFAULT_APP_VK_PATH: &str = "./openvm/app.vk"; -pub const DEFAULT_APP_PROOF_PATH: &str = "./openvm/app.proof"; -pub const DEFAULT_EVM_PROOF_PATH: &str = "./openvm/evm.proof"; +pub fn default_agg_halo2_pk_path() -> String { + env::var("HOME").unwrap() + "/.openvm/agg_halo2.pk" +} + +pub fn default_asm_path() -> String { + env::var("HOME").unwrap() + "/.openvm/root.asm" +} + +pub fn default_params_dir() -> String { + env::var("HOME").unwrap() + "/.openvm/params/" +} + +pub fn default_evm_halo2_verifier_path() -> String { + env::var("HOME").unwrap() + "/.openvm/halo2/" +} pub fn default_app_config() -> AppConfig { AppConfig { diff --git a/crates/cli/src/util.rs b/crates/cli/src/util.rs index 19938c64bb..5882d437c6 100644 --- a/crates/cli/src/util.rs +++ b/crates/cli/src/util.rs @@ -1,28 +1,163 @@ use std::{ - fs::read_to_string, + fs::{read_dir, read_to_string}, path::{Path, PathBuf}, }; use eyre::Result; -use openvm_sdk::config::{AppConfig, SdkVmConfig}; +use openvm_build::{get_in_scope_packages, get_workspace_packages}; +use openvm_sdk::{ + config::{AppConfig, SdkVmConfig}, + fs::{read_agg_halo2_pk_from_file, read_agg_stark_pk_from_file}, + keygen::AggProvingKey, +}; use serde::de::DeserializeOwned; -use crate::default::default_app_config; +use crate::{ + commands::RunCargoArgs, + default::{ + default_agg_halo2_pk_path, default_agg_stark_pk_path, default_app_config, + DEFAULT_APP_PK_NAME, DEFAULT_APP_VK_NAME, + }, +}; -pub(crate) fn read_to_struct_toml(path: &PathBuf) -> Result { - let toml = read_to_string(path.as_ref() as &Path)?; +pub(crate) fn read_to_struct_toml(path: impl AsRef) -> Result { + let toml = read_to_string(path)?; let ret = toml::from_str(&toml)?; Ok(ret) } -pub fn read_config_toml_or_default(config: &PathBuf) -> Result> { - if config.exists() { +pub fn read_config_toml_or_default(config: impl AsRef) -> Result> { + if config.as_ref().exists() { read_to_struct_toml(config) } else { println!( "{:?} not found, using default application configuration", - config + config.as_ref() ); Ok(default_app_config()) } } + +pub fn read_default_agg_pk() -> Result { + let agg_stark_pk = read_agg_stark_pk_from_file(default_agg_stark_pk_path())?; + let halo2_pk = read_agg_halo2_pk_from_file(default_agg_halo2_pk_path())?; + Ok(AggProvingKey { + agg_stark_pk, + halo2_pk, + }) +} + +pub fn find_manifest_dir(mut current_dir: PathBuf) -> Result { + current_dir = current_dir.canonicalize()?; + while !current_dir.join("Cargo.toml").exists() { + current_dir = current_dir + .parent() + .expect("Could not find Cargo.toml in current directory or any parent directory") + .to_path_buf(); + } + Ok(current_dir) +} + +pub fn get_manifest_path_and_dir(manifest_path: &Option) -> Result<(PathBuf, PathBuf)> { + let manifest_dir = if let Some(manifest_path) = &manifest_path { + if !manifest_path.ends_with("Cargo.toml") { + return Err(eyre::eyre!( + "manifest_path must be a path to a Cargo.toml file" + )); + } + manifest_path.parent().unwrap().canonicalize()? + } else { + find_manifest_dir(PathBuf::from("."))? + }; + let manifest_path = manifest_dir.join("Cargo.toml"); + Ok((manifest_path.clone(), manifest_dir)) +} + +pub fn get_target_dir(target_dir: &Option, manifest_path: &PathBuf) -> PathBuf { + target_dir + .clone() + .unwrap_or_else(|| openvm_build::get_target_dir(manifest_path)) +} + +pub fn get_target_output_dir(target_dir: &Path, profile: &str) -> PathBuf { + target_dir.join("openvm").join(profile).to_path_buf() +} + +pub fn get_app_pk_path(target_dir: &Path) -> PathBuf { + target_dir.join("openvm").join(DEFAULT_APP_PK_NAME) +} + +pub fn get_app_vk_path(target_dir: &Path) -> PathBuf { + target_dir.join("openvm").join(DEFAULT_APP_VK_NAME) +} + +// Given the arguments to a run command, this function isolates the executable to +// run. If a specific binary or example is specified it will return that, else it +// will search the workspace/package for binary targets. If there is a single +// binary that will be returned, else an error will be raised. +pub fn get_single_target_name(cargo_args: &RunCargoArgs) -> Result { + let num_targets = cargo_args.bin.len() + cargo_args.example.len(); + let single_target_name = if num_targets > 1 { + return Err(eyre::eyre!( + "`cargo openvm run` can run at most one executable, but multiple were specified" + )); + } else if num_targets == 0 { + let (_, manifest_dir) = get_manifest_path_and_dir(&cargo_args.manifest_path)?; + + let packages = if cargo_args.package.is_some() { + get_workspace_packages(&manifest_dir) + } else { + get_in_scope_packages(&manifest_dir) + } + .into_iter() + .filter(|pkg| { + if let Some(package) = &cargo_args.package { + pkg.name == *package + } else { + true + } + }) + .collect::>(); + + let binaries = packages + .iter() + .flat_map(|pkg| pkg.targets.iter()) + .filter(|t| t.is_bin()) + .collect::>(); + + if binaries.len() > 1 { + return Err(eyre::eyre!( + "Could not determine which binary to run. Use the --bin flag to specify.\n\ + Available targets: {:?}", + binaries.iter().map(|t| t.name.clone()).collect::>() + )); + } else if binaries.is_empty() { + return Err(eyre::eyre!( + "No binaries found. If you would like to run an example, use the --example flag.", + )); + } else { + binaries[0].name.clone() + } + } else if cargo_args.bin.is_empty() { + format!("examples/{}", cargo_args.example[0]) + } else { + cargo_args.bin[0].clone() + }; + Ok(single_target_name) +} + +pub fn get_files_with_ext(dir: &Path, extension: &str) -> Result> { + let dir = dir.canonicalize()?; + let mut files = Vec::new(); + for entry in read_dir(dir)? { + let path = entry?.path(); + if path.is_file() + && path + .to_str() + .is_some_and(|path_str| path_str.ends_with(extension)) + { + files.push(path); + } + } + Ok(files) +} diff --git a/crates/cli/templates/lib.rs b/crates/cli/templates/lib.rs new file mode 100644 index 0000000000..d9dd4ea096 --- /dev/null +++ b/crates/cli/templates/lib.rs @@ -0,0 +1,13 @@ +// src/lib.rs +#![no_std] + +pub fn fibonacci(n: u64) -> u64 { + let mut a: u64 = 0; + let mut b: u64 = 1; + for _ in 0..n { + let c: u64 = a.wrapping_add(b); + a = b; + b = c; + } + a +} diff --git a/crates/cli/templates/main.rs b/crates/cli/templates/main.rs new file mode 100644 index 0000000000..87f05e5502 --- /dev/null +++ b/crates/cli/templates/main.rs @@ -0,0 +1,15 @@ +// src/main.rs +use openvm::io::{read, reveal_u32}; + +fn main() { + let n: u64 = read(); + let mut a: u64 = 0; + let mut b: u64 = 1; + for _ in 0..n { + let c: u64 = a.wrapping_add(b); + a = b; + b = c; + } + reveal_u32(a as u32, 0); + reveal_u32((a >> 32) as u32, 1); +} diff --git a/crates/cli/templates/openvm.toml b/crates/cli/templates/openvm.toml new file mode 100644 index 0000000000..19a1e670e5 --- /dev/null +++ b/crates/cli/templates/openvm.toml @@ -0,0 +1,3 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] diff --git a/crates/cli/tests/app_e2e.rs b/crates/cli/tests/app_e2e.rs index a2af9cb08f..0b526979d7 100644 --- a/crates/cli/tests/app_e2e.rs +++ b/crates/cli/tests/app_e2e.rs @@ -7,22 +7,20 @@ use tempfile::tempdir; fn test_cli_app_e2e() -> Result<()> { let temp_dir = tempdir()?; run_cmd("cargo", &["install", "--path", ".", "--force"])?; - let temp_exe = temp_dir.path().join("example.vmexe"); - let temp_pk = temp_dir.path().join("example.pk"); - let temp_vk = temp_dir.path().join("example.vk"); - let temp_proof = temp_dir.path().join("example.apppf"); + let exe_path = "tests/programs/fibonacci/target/openvm/release/openvm-cli-example-test.vmexe"; + let temp_pk = temp_dir.path().join("app.pk"); + let temp_vk = temp_dir.path().join("app.vk"); + let temp_proof = temp_dir.path().join("fibonacci.app.proof"); run_cmd( "cargo", &[ "openvm", "build", - "--manifest-dir", - "example", + "--manifest-path", + "tests/programs/fibonacci/Cargo.toml", "--config", - "example/openvm.toml", - "--exe-output", - temp_exe.to_str().unwrap(), + "tests/programs/fibonacci/openvm.toml", ], )?; @@ -32,11 +30,9 @@ fn test_cli_app_e2e() -> Result<()> { "openvm", "keygen", "--config", - "example/openvm.toml", - "--output", - temp_pk.to_str().unwrap(), - "--vk-output", - temp_vk.to_str().unwrap(), + "tests/programs/fibonacci/openvm.toml", + "--output-dir", + temp_dir.path().to_str().unwrap(), ], )?; @@ -46,9 +42,9 @@ fn test_cli_app_e2e() -> Result<()> { "openvm", "run", "--exe", - temp_exe.to_str().unwrap(), + exe_path, "--config", - "example/openvm.toml", + "tests/programs/fibonacci/openvm.toml", ], )?; @@ -61,8 +57,8 @@ fn test_cli_app_e2e() -> Result<()> { "--app-pk", temp_pk.to_str().unwrap(), "--exe", - temp_exe.to_str().unwrap(), - "--output", + exe_path, + "--proof", temp_proof.to_str().unwrap(), ], )?; @@ -84,13 +80,73 @@ fn test_cli_app_e2e() -> Result<()> { } #[test] -fn test_cli_app_e2e_default_paths() -> Result<()> { +fn test_cli_app_e2e_simplified() -> Result<()> { run_cmd("cargo", &["install", "--path", ".", "--force"])?; - run_cmd("cargo", &["openvm", "build", "--manifest-dir", "example"])?; - run_cmd("cargo", &["openvm", "keygen"])?; - run_cmd("cargo", &["openvm", "run"])?; - run_cmd("cargo", &["openvm", "prove", "app"])?; - run_cmd("cargo", &["openvm", "verify", "app"])?; + run_cmd( + "cargo", + &[ + "openvm", + "keygen", + "--manifest-path", + "tests/programs/multi/Cargo.toml", + ], + )?; + run_cmd( + "cargo", + &[ + "openvm", + "prove", + "app", + "--manifest-path", + "tests/programs/multi/Cargo.toml", + "--example", + "fibonacci", + ], + )?; + run_cmd( + "cargo", + &[ + "openvm", + "verify", + "app", + "--manifest-path", + "tests/programs/multi/Cargo.toml", + ], + )?; + Ok(()) +} + +#[test] +fn test_cli_init_build() -> Result<()> { + let temp_dir = tempdir()?; + let temp_path = temp_dir.path(); + let config_path = temp_path.join("openvm.toml"); + let manifest_path = temp_path.join("Cargo.toml"); + run_cmd("cargo", &["install", "--path", ".", "--force"])?; + + run_cmd( + "cargo", + &[ + "openvm", + "init", + temp_path.to_str().unwrap(), + "--name", + "cli-package", + ], + )?; + + run_cmd( + "cargo", + &[ + "openvm", + "build", + "--config", + config_path.to_str().unwrap(), + "--manifest-path", + manifest_path.to_str().unwrap(), + ], + )?; + Ok(()) } diff --git a/crates/cli/tests/build.rs b/crates/cli/tests/build.rs new file mode 100644 index 0000000000..084db50367 --- /dev/null +++ b/crates/cli/tests/build.rs @@ -0,0 +1,190 @@ +use std::path::PathBuf; + +use cargo_openvm::commands::{build, BuildArgs, BuildCargoArgs}; +use eyre::Result; +use openvm_build::RUSTC_TARGET; + +fn default_build_test_args(example: &str) -> BuildArgs { + BuildArgs { + no_transpile: true, + config: Some( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("programs") + .join(example) + .join("openvm.toml"), + ), + ..Default::default() + } +} + +fn default_cargo_test_args(example: &str) -> BuildCargoArgs { + BuildCargoArgs { + manifest_path: Some( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("programs") + .join(example) + .join("Cargo.toml"), + ), + ..Default::default() + } +} + +#[test] +fn test_build_with_profile() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let target_dir = temp_dir.path(); + + let build_args = default_build_test_args("fibonacci"); + let mut cargo_args = default_cargo_test_args("fibonacci"); + cargo_args.target_dir = Some(target_dir.to_path_buf()); + cargo_args.profile = "dev".to_string(); + + build(&build_args, &cargo_args)?; + assert!( + target_dir.join(RUSTC_TARGET).join("debug").exists(), + "did not build with dev profile" + ); + temp_dir.close()?; + Ok(()) +} + +#[test] +fn test_multi_target_build() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let target_dir = temp_dir.path(); + + let build_args = default_build_test_args("multi"); + let mut cargo_args = default_cargo_test_args("multi"); + cargo_args.target_dir = Some(target_dir.to_path_buf()); + + // Build lib + cargo_args.lib = true; + let elf_dir = build(&build_args, &cargo_args)?; + assert!(!elf_dir.join("calculator").exists()); + assert!(!elf_dir.join("string-utils").exists()); + assert!(!elf_dir.join("examples/fibonacci").exists()); + assert!(!elf_dir.join("examples/palindrome").exists()); + std::fs::remove_dir_all(&elf_dir)?; + std::fs::create_dir_all(&elf_dir)?; + + // Build bins + cargo_args.lib = false; + let elf_dir = build(&build_args, &cargo_args)?; + assert!(elf_dir.join("calculator").exists()); + assert!(elf_dir.join("string-utils").exists()); + assert!(!elf_dir.join("examples/fibonacci").exists()); + assert!(!elf_dir.join("examples/palindrome").exists()); + std::fs::remove_dir_all(&elf_dir)?; + std::fs::create_dir_all(&elf_dir)?; + + // Build examples + cargo_args.examples = true; + let elf_dir = build(&build_args, &cargo_args)?; + assert!(!elf_dir.join("calculator").exists()); + assert!(!elf_dir.join("string-utils").exists()); + assert!(elf_dir.join("examples/fibonacci").exists()); + assert!(elf_dir.join("examples/palindrome").exists()); + std::fs::remove_dir_all(&elf_dir)?; + std::fs::create_dir_all(&elf_dir)?; + + // Build examples and a single bin + cargo_args.bin = vec!["calculator".to_string()]; + let elf_dir = build(&build_args, &cargo_args)?; + assert!(elf_dir.join("calculator").exists()); + assert!(!elf_dir.join("string-utils").exists()); + assert!(elf_dir.join("examples/fibonacci").exists()); + assert!(elf_dir.join("examples/palindrome").exists()); + std::fs::remove_dir_all(&elf_dir)?; + std::fs::create_dir_all(&elf_dir)?; + + // Build all targets + cargo_args.bin = vec![]; + cargo_args.examples = false; + cargo_args.all_targets = true; + let elf_dir = build(&build_args, &cargo_args)?; + assert!(elf_dir.join("calculator").exists()); + assert!(elf_dir.join("string-utils").exists()); + assert!(elf_dir.join("examples/fibonacci").exists()); + assert!(elf_dir.join("examples/palindrome").exists()); + + temp_dir.close()?; + Ok(()) +} + +#[test] +fn test_multi_target_transpile_default() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let target_dir = temp_dir.path(); + + let mut build_args = default_build_test_args("multi"); + let mut cargo_args = default_cargo_test_args("multi"); + build_args.no_transpile = false; + cargo_args.target_dir = Some(target_dir.to_path_buf()); + cargo_args.all_targets = true; + + build(&build_args, &cargo_args)?; + + // Check for openvm directory + let openvm_dir = target_dir.join("openvm"); + assert!(openvm_dir.exists(),); + + // Check for release directory + let release_dir = openvm_dir.join("release"); + assert!(release_dir.exists()); + + // Check for expected vmexe files + let calculator_exe = release_dir.join("calculator.vmexe"); + let string_utils_exe = release_dir.join("string-utils.vmexe"); + assert!(calculator_exe.exists()); + assert!(string_utils_exe.exists()); + + // Check for example directory + let examples_dir = release_dir.join("examples"); + assert!(examples_dir.exists()); + + // Check for expected example files + let fibonacci_exe = examples_dir.join("fibonacci.vmexe"); + let palindrome_exe = examples_dir.join("palindrome.vmexe"); + assert!(fibonacci_exe.exists()); + assert!(palindrome_exe.exists()); + + Ok(()) +} + +#[test] +fn test_output_dir_copy() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + let target_dir = temp_dir.path(); + + let temp_dir_2 = tempfile::tempdir()?; + let output_dir = temp_dir_2.path(); + + let mut build_args = default_build_test_args("fibonacci"); + let mut cargo_args = default_cargo_test_args("fibonacci"); + build_args.output_dir = Some(output_dir.to_path_buf()); + build_args.no_transpile = false; + cargo_args.target_dir = Some(target_dir.to_path_buf()); + + build(&build_args, &cargo_args)?; + + // Check for executable in target_dir + let default_target = target_dir + .join("openvm") + .join("release") + .join("openvm-cli-example-test.vmexe"); + assert!(default_target.exists()); + + // Check for executable in output_dir + let copied_target = output_dir.join("openvm-cli-example-test.vmexe"); + assert!(copied_target.exists()); + + // Check that the executable is the same + assert_eq!( + std::fs::read(default_target)?, + std::fs::read(copied_target)? + ); + + Ok(()) +} diff --git a/crates/cli/example/Cargo.toml b/crates/cli/tests/programs/fibonacci/Cargo.toml similarity index 67% rename from crates/cli/example/Cargo.toml rename to crates/cli/tests/programs/fibonacci/Cargo.toml index 140901f1d0..9cbe05c99b 100644 --- a/crates/cli/example/Cargo.toml +++ b/crates/cli/tests/programs/fibonacci/Cargo.toml @@ -5,4 +5,4 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { path = "../../toolchain/openvm" } +openvm = { path = "../../../../toolchain/openvm" } diff --git a/crates/cli/example/openvm.toml b/crates/cli/tests/programs/fibonacci/openvm.toml similarity index 100% rename from crates/cli/example/openvm.toml rename to crates/cli/tests/programs/fibonacci/openvm.toml diff --git a/crates/cli/example/src/main.rs b/crates/cli/tests/programs/fibonacci/src/main.rs similarity index 100% rename from crates/cli/example/src/main.rs rename to crates/cli/tests/programs/fibonacci/src/main.rs diff --git a/crates/cli/tests/programs/multi/Cargo.toml b/crates/cli/tests/programs/multi/Cargo.toml new file mode 100644 index 0000000000..c420c46bc2 --- /dev/null +++ b/crates/cli/tests/programs/multi/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +members = [ + "calculator", + "string-utils" +] + +[workspace.package] +version = "0.0.0" +edition = "2021" diff --git a/crates/cli/tests/programs/multi/calculator/Cargo.toml b/crates/cli/tests/programs/multi/calculator/Cargo.toml new file mode 100644 index 0000000000..9362608c61 --- /dev/null +++ b/crates/cli/tests/programs/multi/calculator/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "openvm-cli-example-calculator" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../../toolchain/openvm" } + +[lib] +name = "calculator" +path = "src/lib.rs" + +[[bin]] +name = "calculator" +path = "src/main.rs" + +[[example]] +name = "fibonacci" +path = "examples/fibonacci.rs" diff --git a/crates/cli/tests/programs/multi/calculator/examples/fibonacci.rs b/crates/cli/tests/programs/multi/calculator/examples/fibonacci.rs new file mode 100644 index 0000000000..5009081f3b --- /dev/null +++ b/crates/cli/tests/programs/multi/calculator/examples/fibonacci.rs @@ -0,0 +1,18 @@ +#![cfg_attr(target_os = "zkvm", no_main)] +#![cfg_attr(target_os = "zkvm", no_std)] + +openvm::entry!(main); + +pub fn main() { + let n = core::hint::black_box(10); + let mut a: u32 = 0; + let mut b: u32 = 1; + for _ in 1..n { + let sum = a + b; + a = b; + b = sum; + } + if a == 0 { + panic!(); + } +} diff --git a/crates/cli/tests/programs/multi/calculator/src/lib.rs b/crates/cli/tests/programs/multi/calculator/src/lib.rs new file mode 100644 index 0000000000..e6ceb3f555 --- /dev/null +++ b/crates/cli/tests/programs/multi/calculator/src/lib.rs @@ -0,0 +1,33 @@ +#![cfg_attr(target_os = "zkvm", no_std)] + +/// Checks if a number is prime. +pub fn is_prime(n: u32) -> bool { + if n <= 1 { + return false; + } + if n <= 3 { + return true; + } + if n % 2 == 0 || n % 3 == 0 { + return false; + } + let mut i = 5; + while i * i <= n { + if n % i == 0 || n % (i + 2) == 0 { + return false; + } + i += 6; + } + true +} + +/// Counts the number of prime numbers up to a given limit. +pub fn count_primes(limit: u32) -> u32 { + let mut count = 0; + for i in 2..limit { + if is_prime(i) { + count += 1; + } + } + count +} diff --git a/crates/cli/tests/programs/multi/calculator/src/main.rs b/crates/cli/tests/programs/multi/calculator/src/main.rs new file mode 100644 index 0000000000..42438497c2 --- /dev/null +++ b/crates/cli/tests/programs/multi/calculator/src/main.rs @@ -0,0 +1,13 @@ +#![cfg_attr(target_os = "zkvm", no_main)] +#![cfg_attr(target_os = "zkvm", no_std)] + +use calculator::count_primes; +openvm::entry!(main); + +pub fn main() { + let n = core::hint::black_box(100); + let count = count_primes(n); + if count == 0 { + panic!(); + } +} diff --git a/crates/cli/tests/programs/multi/openvm.toml b/crates/cli/tests/programs/multi/openvm.toml new file mode 100644 index 0000000000..8ac6a25d95 --- /dev/null +++ b/crates/cli/tests/programs/multi/openvm.toml @@ -0,0 +1,3 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +range_tuple_checker_sizes = [256, 2048] diff --git a/crates/cli/tests/programs/multi/string-utils/Cargo.toml b/crates/cli/tests/programs/multi/string-utils/Cargo.toml new file mode 100644 index 0000000000..f2f0561d70 --- /dev/null +++ b/crates/cli/tests/programs/multi/string-utils/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "openvm-cli-example-string-utils" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../../toolchain/openvm" } + +[[bin]] +name = "string-utils" +path = "src/main.rs" + +[[example]] +name = "palindrome" +path = "examples/palindrome.rs" diff --git a/crates/cli/tests/programs/multi/string-utils/examples/palindrome.rs b/crates/cli/tests/programs/multi/string-utils/examples/palindrome.rs new file mode 100644 index 0000000000..030d7c76d9 --- /dev/null +++ b/crates/cli/tests/programs/multi/string-utils/examples/palindrome.rs @@ -0,0 +1,25 @@ +#![cfg_attr(target_os = "zkvm", no_main)] +#![cfg_attr(target_os = "zkvm", no_std)] + +extern crate alloc; +use alloc::vec::Vec; + +openvm::entry!(main); + +fn is_palindrome(s: &str) -> bool { + let chars: Vec = s.chars().collect(); + let len = chars.len(); + for i in 0..len / 2 { + if chars[i] != chars[len - 1 - i] { + return false; + } + } + true +} + +pub fn main() { + let text = core::hint::black_box("racecar"); + if !is_palindrome(text) { + panic!(); + } +} diff --git a/crates/cli/tests/programs/multi/string-utils/src/main.rs b/crates/cli/tests/programs/multi/string-utils/src/main.rs new file mode 100644 index 0000000000..fa903446c0 --- /dev/null +++ b/crates/cli/tests/programs/multi/string-utils/src/main.rs @@ -0,0 +1,21 @@ +#![cfg_attr(target_os = "zkvm", no_main)] +#![cfg_attr(target_os = "zkvm", no_std)] + +openvm::entry!(main); + +pub fn main() { + let text = core::hint::black_box("hello world"); + let mut counts = [0u32; 26]; + + for c in text.chars() { + if c.is_ascii_alphabetic() { + let idx = (c.to_ascii_lowercase() as u8 - b'a') as usize; + counts[idx] += 1; + } + } + + let total: u32 = counts.iter().sum(); + if total == 0 { + panic!(); + } +} diff --git a/crates/continuations/src/verifier/internal/mod.rs b/crates/continuations/src/verifier/internal/mod.rs index 8775e9e89c..eb444d0291 100644 --- a/crates/continuations/src/verifier/internal/mod.rs +++ b/crates/continuations/src/verifier/internal/mod.rs @@ -21,7 +21,7 @@ use crate::{ }; pub mod types; -mod vars; +pub mod vars; /// Config to generate internal VM verifier program. pub struct InternalVmVerifierConfig { diff --git a/crates/continuations/src/verifier/internal/types.rs b/crates/continuations/src/verifier/internal/types.rs index 9512518843..14560053c0 100644 --- a/crates/continuations/src/verifier/internal/types.rs +++ b/crates/continuations/src/verifier/internal/types.rs @@ -30,6 +30,36 @@ pub struct InternalVmVerifierInput { } assert_impl_all!(InternalVmVerifierInput: Serialize, DeserializeOwned); +/// A proof which can prove OpenVM program execution. +#[derive(Deserialize, Serialize, Derivative)] +#[serde(bound = "")] +#[derivative(Clone(bound = "Com: Clone"))] +pub struct VmStarkProof { + pub proof: Proof, + pub user_public_values: Vec>, +} +assert_impl_all!(VmStarkProof: Serialize, DeserializeOwned); + +/// Aggregated state of all segments +#[derive(Debug, Clone, Copy, AlignedBorrow)] +#[repr(C)] +pub struct InternalVmVerifierPvs { + pub vm_verifier_pvs: VmVerifierPvs, + pub extra_pvs: InternalVmVerifierExtraPvs, +} + +/// Extra PVs for internal VM verifier except VmVerifierPvs. +#[derive(Debug, Clone, Copy, AlignedBorrow)] +#[repr(C)] +pub struct InternalVmVerifierExtraPvs { + /// The commitment of the leaf verifier program. + pub leaf_verifier_commit: [T; DIGEST_SIZE], + /// For recursion verification, a program need its own commitment, but its own commitment + /// cannot be hardcoded inside the program itself. So the commitment has to be read from + /// external and be committed. + pub internal_program_commit: [T; DIGEST_SIZE], +} + impl InternalVmVerifierInput { pub fn chunk_leaf_or_internal_proofs( self_program_commit: [Val; DIGEST_SIZE], @@ -45,13 +75,6 @@ impl InternalVmVerifierInput { .collect() } } -/// Aggregated state of all segments -#[derive(Debug, Clone, Copy, AlignedBorrow)] -#[repr(C)] -pub struct InternalVmVerifierPvs { - pub vm_verifier_pvs: VmVerifierPvs, - pub extra_pvs: InternalVmVerifierExtraPvs, -} impl InternalVmVerifierPvs> { pub fn uninit>(builder: &mut Builder) -> Self { @@ -70,18 +93,6 @@ impl InternalVmVerifierPvs> { } } -/// Extra PVs for internal VM verifier except VmVerifierPvs. -#[derive(Debug, Clone, Copy, AlignedBorrow)] -#[repr(C)] -pub struct InternalVmVerifierExtraPvs { - /// The commitment of the leaf verifier program. - pub leaf_verifier_commit: [T; DIGEST_SIZE], - /// For recursion verification, a program need its own commitment, but its own commitment - /// cannot be hardcoded inside the program itself. So the commitment has to be read from - /// external and be committed. - pub internal_program_commit: [T; DIGEST_SIZE], -} - impl InternalVmVerifierExtraPvs> { pub fn uninit>(builder: &mut Builder) -> Self { Self { diff --git a/crates/continuations/src/verifier/internal/vars.rs b/crates/continuations/src/verifier/internal/vars.rs index e700d92248..4fa00c004b 100644 --- a/crates/continuations/src/verifier/internal/vars.rs +++ b/crates/continuations/src/verifier/internal/vars.rs @@ -5,8 +5,11 @@ use openvm_native_recursion::{hints::Hintable, vars::StarkProofVariable}; use openvm_stark_sdk::openvm_stark_backend::proof::Proof; use crate::{ - verifier::{internal::types::InternalVmVerifierInput, utils::write_field_slice}, - C, SC, + verifier::{ + internal::types::{InternalVmVerifierInput, VmStarkProof}, + utils::write_field_slice, + }, + C, F, SC, }; #[derive(DslVariable, Clone)] @@ -16,6 +19,12 @@ pub struct InternalVmVerifierInputVariable { pub proofs: Array>, } +#[derive(DslVariable, Clone)] +pub struct E2eStarkProofVariable { + pub proof: StarkProofVariable, + pub user_public_values: Array>, +} + impl Hintable for InternalVmVerifierInput { type HintVariable = InternalVmVerifierInputVariable; @@ -34,3 +43,22 @@ impl Hintable for InternalVmVerifierInput { stream } } + +impl Hintable for VmStarkProof { + type HintVariable = E2eStarkProofVariable; + + fn read(builder: &mut Builder) -> Self::HintVariable { + let proof = Proof::::read(builder); + let user_public_values = Vec::::read(builder); + Self::HintVariable { + proof, + user_public_values, + } + } + + fn write(&self) -> Vec::N>> { + let mut stream = self.proof.write(); + stream.extend(self.user_public_values.write()); + stream + } +} diff --git a/crates/continuations/src/verifier/root/mod.rs b/crates/continuations/src/verifier/root/mod.rs index 666f2ca969..b6f2816b1e 100644 --- a/crates/continuations/src/verifier/root/mod.rs +++ b/crates/continuations/src/verifier/root/mod.rs @@ -1,11 +1,12 @@ use std::array; use openvm_circuit::arch::instructions::program::Program; -use openvm_native_compiler::{conversion::CompilerOptions, prelude::*}; +use openvm_native_compiler::{asm::HEAP_START_ADDRESS, conversion::CompilerOptions, prelude::*}; use openvm_native_recursion::{ fri::TwoAdicFriPcsVariable, hints::Hintable, types::new_from_inner_multi_vk, utils::const_fri_config, }; +use openvm_stark_backend::proof::Proof; use openvm_stark_sdk::{ config::FriParameters, openvm_stark_backend::{keygen::types::MultiStarkVerifyingKey, p3_field::FieldAlgebra}, @@ -30,7 +31,7 @@ mod vars; pub struct RootVmVerifierConfig { pub leaf_fri_params: FriParameters, pub internal_fri_params: FriParameters, - pub num_public_values: usize, + pub num_user_public_values: usize, pub internal_vm_verifier_commit: [F; DIGEST_SIZE], pub compiler_options: CompilerOptions, } @@ -40,73 +41,163 @@ impl RootVmVerifierConfig { leaf_vm_vk: &MultiStarkVerifyingKey, internal_vm_vk: &MultiStarkVerifyingKey, ) -> Program { - let leaf_advice = new_from_inner_multi_vk(leaf_vm_vk); - let internal_advice = new_from_inner_multi_vk(internal_vm_vk); let mut builder = Builder::::default(); - { - builder.cycle_tracker_start("ReadProofsFromInput"); - let RootVmVerifierInputVariable { + builder.cycle_tracker_start("ReadProofsFromInput"); + let root_verifier_input = RootVmVerifierInput::::read(&mut builder); + builder.cycle_tracker_end("ReadProofsFromInput"); + let pvs = self.verifier_impl( + &mut builder, + leaf_vm_vk, + internal_vm_vk, + root_verifier_input, + ); + pvs.flatten() + .into_iter() + .for_each(|v| builder.commit_public_value(v)); + builder.halt(); + builder.compile_isa_with_options(self.compiler_options) + } + + /// Build instructions which can be called as a kernel function in RISC-V guest programs. + /// Inputs for generated instructions: + /// - expected `app_exe_commit`, `app_vm_commit` and user public values should be stored from + /// `HEAP_START_ADDRESS` in the native address space . + /// + /// These instructions take a proof from the input stream and verify the proof. Then these + /// instructions check if the public values are consistent with the expected public values + /// from RISC-V guest programs. + pub fn build_kernel_asm( + &self, + leaf_vm_vk: &MultiStarkVerifyingKey, + internal_vm_vk: &MultiStarkVerifyingKey, + ) -> Program { + let mut builder = Builder::::default(); + + const BYTE_PER_WORD: usize = 4; + let num_public_values = self.num_user_public_values + DIGEST_SIZE * 2; + let num_bytes = num_public_values * BYTE_PER_WORD; + // Move heap pointer in order to keep input arguments from address space 2. + let heap_addr: Var = builder.eval(F::from_canonical_u32( + HEAP_START_ADDRESS as u32 + num_bytes as u32, + )); + builder.store_heap_ptr(Ptr { address: heap_addr }); + let expected_pvs: Vec> = (0..num_public_values) + .map(|i| { + let fs: [Felt<_>; BYTE_PER_WORD] = array::from_fn(|j| { + let ptr = Ptr { + address: builder.eval(F::from_canonical_u32( + HEAP_START_ADDRESS as u32 + (i * 4) as u32, + )), + }; + let idx = MemIndex { + index: RVar::from(j), + offset: 0, + size: 1, + }; + let f = Felt::uninit(&mut builder); + f.load(ptr, idx, &mut builder); + f + }); + builder.eval( + fs[0] + + fs[1] * F::from_canonical_u32(1 << 8) + + fs[2] * F::from_canonical_u32(1 << 16) + + fs[3] * F::from_canonical_u32(1 << 24), + ) + }) + .collect(); + let expected_pvs = RootVmVerifierPvs::>::from_flatten(expected_pvs); + let user_pvs = builder.array(self.num_user_public_values); + for (i, &pv) in expected_pvs.public_values.iter().enumerate() { + builder.set(&user_pvs, i, pv); + } + + builder.cycle_tracker_start("ReadFromStdin"); + let proof = Proof::::read(&mut builder); + builder.cycle_tracker_end("ReadFromStdin"); + let proofs = builder.array(1); + builder.set(&proofs, 0, proof); + let pvs = self.verifier_impl( + &mut builder, + leaf_vm_vk, + internal_vm_vk, + RootVmVerifierInputVariable { proofs, - public_values, - } = RootVmVerifierInput::::read(&mut builder); - builder.cycle_tracker_end("ReadProofsFromInput"); - builder.cycle_tracker_start("InitializePcsConst"); - let leaf_pcs = TwoAdicFriPcsVariable { - config: const_fri_config(&mut builder, &self.leaf_fri_params), - }; - let internal_pcs = TwoAdicFriPcsVariable { - config: const_fri_config(&mut builder, &self.internal_fri_params), - }; - builder.cycle_tracker_end("InitializePcsConst"); - builder.cycle_tracker_start("VerifyProofs"); - let internal_program_commit = - array::from_fn(|i| builder.eval(self.internal_vm_verifier_commit[i])); - let non_leaf_verifier = NonLeafVerifierVariables { - internal_program_commit, - leaf_pcs, - leaf_advice, - internal_pcs, - internal_advice, - }; - let (merged_pvs, expected_leaf_commit) = - non_leaf_verifier.verify_internal_or_leaf_verifier_proofs(&mut builder, &proofs); - builder.cycle_tracker_end("VerifyProofs"); + public_values: user_pvs, + }, + ); + builder.assert_eq::<[Felt<_>; DIGEST_SIZE]>(pvs.exe_commit, expected_pvs.exe_commit); + builder.assert_eq::<[Felt<_>; DIGEST_SIZE]>( + pvs.leaf_verifier_commit, + expected_pvs.leaf_verifier_commit, + ); + + builder.compile_isa_with_options(self.compiler_options) + } + + fn verifier_impl( + &self, + builder: &mut Builder, + leaf_vm_vk: &MultiStarkVerifyingKey, + internal_vm_vk: &MultiStarkVerifyingKey, + root_verifier_input: RootVmVerifierInputVariable, + ) -> RootVmVerifierPvs> { + let leaf_advice = new_from_inner_multi_vk(leaf_vm_vk); + let internal_advice = new_from_inner_multi_vk(internal_vm_vk); + let RootVmVerifierInputVariable { + proofs, + public_values, + } = root_verifier_input; - // App Program should terminate - builder.assert_felt_eq(merged_pvs.connector.is_terminate, F::ONE); - // App Program should exit successfully - builder.assert_felt_eq(merged_pvs.connector.exit_code, F::ZERO); + builder.cycle_tracker_start("InitializePcsConst"); + let leaf_pcs = TwoAdicFriPcsVariable { + config: const_fri_config(builder, &self.leaf_fri_params), + }; + let internal_pcs = TwoAdicFriPcsVariable { + config: const_fri_config(builder, &self.internal_fri_params), + }; + builder.cycle_tracker_end("InitializePcsConst"); + builder.cycle_tracker_start("VerifyProofs"); + let internal_program_commit = + array::from_fn(|i| builder.eval(self.internal_vm_verifier_commit[i])); + let non_leaf_verifier = NonLeafVerifierVariables { + internal_program_commit, + leaf_pcs, + leaf_advice, + internal_pcs, + internal_advice, + }; + let (merged_pvs, expected_leaf_commit) = + non_leaf_verifier.verify_internal_or_leaf_verifier_proofs(builder, &proofs); + builder.cycle_tracker_end("VerifyProofs"); - builder.cycle_tracker_start("ExtractPublicValues"); - builder.assert_usize_eq(public_values.len(), RVar::from(self.num_public_values)); - let public_values_vec: Vec> = (0..self.num_public_values) - .map(|i| builder.get(&public_values, i)) - .collect(); - let hasher = VariableP2Hasher::new(&mut builder); - let pv_commit = hasher.merkle_root(&mut builder, &public_values_vec); - builder.assert_eq::<[_; DIGEST_SIZE]>(merged_pvs.public_values_commit, pv_commit); - builder.cycle_tracker_end("ExtractPublicValues"); + // App Program should terminate + builder.assert_felt_eq(merged_pvs.connector.is_terminate, F::ONE); + // App Program should exit successfully + builder.assert_felt_eq(merged_pvs.connector.exit_code, F::ZERO); - let pvs = RootVmVerifierPvs { - exe_commit: compute_exe_commit( - &mut builder, - &hasher, - merged_pvs.app_commit, - merged_pvs.memory.initial_root, - merged_pvs.connector.initial_pc, - ), - leaf_verifier_commit: expected_leaf_commit, - public_values: public_values_vec, - }; - pvs.flatten() - .into_iter() - .for_each(|v| builder.commit_public_value(v)); + builder.cycle_tracker_start("ExtractPublicValues"); + builder.assert_usize_eq(public_values.len(), RVar::from(self.num_user_public_values)); + let public_values_vec: Vec> = (0..self.num_user_public_values) + .map(|i| builder.get(&public_values, i)) + .collect(); + let hasher = VariableP2Hasher::new(builder); + let pv_commit = hasher.merkle_root(builder, &public_values_vec); + builder.assert_eq::<[_; DIGEST_SIZE]>(merged_pvs.public_values_commit, pv_commit); + builder.cycle_tracker_end("ExtractPublicValues"); - builder.halt(); + RootVmVerifierPvs { + exe_commit: compute_exe_commit( + builder, + &hasher, + merged_pvs.app_commit, + merged_pvs.memory.initial_root, + merged_pvs.connector.initial_pc, + ), + leaf_verifier_commit: expected_leaf_commit, + public_values: public_values_vec, } - - builder.compile_isa_with_options(self.compiler_options) } } diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 4d69470a55..c2dfaa650a 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -25,6 +25,7 @@ openvm-pairing-transpiler = { workspace = true } openvm-native-circuit = { workspace = true } openvm-native-compiler = { workspace = true } openvm-native-recursion = { workspace = true, features = ["static-verifier"] } +openvm-native-transpiler = { workspace = true } openvm-rv32im-circuit = { workspace = true } openvm-rv32im-transpiler = { workspace = true } openvm-transpiler = { workspace = true } @@ -55,6 +56,8 @@ snark-verifier-sdk.workspace = true tempfile.workspace = true hex.workspace = true forge-fmt = { workspace = true, optional = true } +rrs-lib = { workspace = true } +num-bigint = { workspace = true } [features] default = ["parallel", "jemalloc", "evm-verify"] diff --git a/crates/sdk/examples/sdk_app.rs b/crates/sdk/examples/sdk_app.rs index fb78a01a3a..31ba0ab264 100644 --- a/crates/sdk/examples/sdk_app.rs +++ b/crates/sdk/examples/sdk_app.rs @@ -46,7 +46,7 @@ fn main() -> Result<(), Box> { /// use std::path::PathBuf; /// /// let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); - /// path.push("guest"); + /// path.push("guest/fib"); /// let target_path = path.to_str().unwrap(); /// ``` // ANCHOR: build @@ -56,7 +56,13 @@ fn main() -> Result<(), Box> { // 2a. Build the ELF with guest options and a target filter. let guest_opts = GuestOptions::default(); let target_path = "your_path_project_root"; - let elf = sdk.build(guest_opts, target_path, &Default::default())?; + let elf = sdk.build( + guest_opts, + &vm_config, + target_path, + &Default::default(), + None, + )?; // ANCHOR_END: build // ANCHOR: transpilation diff --git a/crates/sdk/examples/sdk_evm.rs b/crates/sdk/examples/sdk_evm.rs index 7126870550..8833542b73 100644 --- a/crates/sdk/examples/sdk_evm.rs +++ b/crates/sdk/examples/sdk_evm.rs @@ -46,7 +46,7 @@ fn main() -> Result<(), Box> { /// use std::path::PathBuf; /// /// let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); - /// path.push("guest"); + /// path.push("guest/fib"); /// let target_path = path.to_str().unwrap(); /// ``` // ANCHOR: build @@ -56,7 +56,13 @@ fn main() -> Result<(), Box> { // 2a. Build the ELF with guest options and a target filter. let guest_opts = GuestOptions::default(); let target_path = "your_path_project_root"; - let elf = sdk.build(guest_opts, target_path, &Default::default())?; + let elf = sdk.build( + guest_opts, + &vm_config, + target_path, + &Default::default(), + None, + )?; // ANCHOR_END: build // ANCHOR: transpilation diff --git a/crates/sdk/guest/Cargo.toml b/crates/sdk/guest/fib/Cargo.toml similarity index 68% rename from crates/sdk/guest/Cargo.toml rename to crates/sdk/guest/fib/Cargo.toml index 1ca1917784..8bb44b44b6 100644 --- a/crates/sdk/guest/Cargo.toml +++ b/crates/sdk/guest/fib/Cargo.toml @@ -5,4 +5,4 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { path = "../../toolchain/openvm" } +openvm = { path = "../../../toolchain/openvm" } diff --git a/crates/sdk/guest/src/main.rs b/crates/sdk/guest/fib/src/main.rs similarity index 100% rename from crates/sdk/guest/src/main.rs rename to crates/sdk/guest/fib/src/main.rs diff --git a/crates/sdk/src/codec.rs b/crates/sdk/src/codec.rs index c66f254625..9d0ab48a93 100644 --- a/crates/sdk/src/codec.rs +++ b/crates/sdk/src/codec.rs @@ -3,7 +3,9 @@ use std::io::{self, Cursor, Read, Result, Write}; use openvm_circuit::{ arch::ContinuationVmProof, system::memory::tree::public_values::UserPublicValuesProof, }; -use openvm_continuations::verifier::root::types::RootVmVerifierInput; +use openvm_continuations::verifier::{ + internal::types::VmStarkProof, root::types::RootVmVerifierInput, +}; use openvm_native_compiler::ir::DIGEST_SIZE; use openvm_native_recursion::hints::{InnerBatchOpening, InnerFriProof, InnerQueryProof}; use openvm_stark_backend::{ @@ -16,7 +18,7 @@ use openvm_stark_backend::{ }; use p3_fri::CommitPhaseProofStep; -use super::{F, SC}; // BabyBearPoseidon2Config +use super::{F, SC}; type Challenge = BinomialExtensionField; @@ -59,6 +61,13 @@ impl Encode for ContinuationVmProof { } } +impl Encode for VmStarkProof { + fn encode(&self, writer: &mut W) -> Result<()> { + self.proof.encode(writer)?; + encode_slice(&self.user_public_values, writer) + } +} + impl Encode for UserPublicValuesProof { fn encode(&self, writer: &mut W) -> Result<()> { encode_slice(&self.proof, writer)?; @@ -289,7 +298,7 @@ impl Encode for [F; DIGEST_SIZE] { } /// Encodes length of slice and then each element -fn encode_slice(slice: &[T], writer: &mut W) -> Result<()> { +pub(crate) fn encode_slice(slice: &[T], writer: &mut W) -> Result<()> { slice.len().encode(writer)?; for elt in slice { elt.encode(writer)?; @@ -323,6 +332,17 @@ impl Decode for ContinuationVmProof { } } +impl Decode for VmStarkProof { + fn decode(reader: &mut R) -> Result { + let proof = Proof::decode(reader)?; + let user_public_values = decode_vec(reader)?; + Ok(Self { + proof, + user_public_values, + }) + } +} + impl Decode for UserPublicValuesProof { fn decode(reader: &mut R) -> Result { let proof = decode_vec(reader)?; @@ -590,7 +610,7 @@ impl Decode for [F; DIGEST_SIZE] { } /// Decodes a vector of elements -fn decode_vec(reader: &mut R) -> Result> { +pub(crate) fn decode_vec(reader: &mut R) -> Result> { let len = usize::decode(reader)?; let mut vec = Vec::with_capacity(len); diff --git a/crates/sdk/src/commit.rs b/crates/sdk/src/commit.rs index 7045d96443..53207d463b 100644 --- a/crates/sdk/src/commit.rs +++ b/crates/sdk/src/commit.rs @@ -1,11 +1,11 @@ -use std::sync::Arc; +use std::{array::from_fn, sync::Arc}; +use num_bigint::BigUint; use openvm_circuit::{ arch::{instructions::exe::VmExe, VmConfig}, system::program::trace::VmCommittedExe, }; -use openvm_continuations::verifier::leaf::LeafVmVerifierConfig; -use openvm_native_compiler::{conversion::CompilerOptions, ir::DIGEST_SIZE}; +use openvm_native_compiler::ir::DIGEST_SIZE; use openvm_stark_backend::{config::StarkGenericConfig, p3_field::PrimeField32}; use openvm_stark_sdk::{ config::{baby_bear_poseidon2::BabyBearPoseidon2Engine, FriParameters}, @@ -14,15 +14,53 @@ use openvm_stark_sdk::{ p3_baby_bear::BabyBear, p3_bn254_fr::Bn254Fr, }; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; -use crate::{keygen::AppProvingKey, NonRootCommittedExe, F, SC}; +use crate::{types::BN254_BYTES, NonRootCommittedExe, F, SC}; + +/// Wrapper for an array of big-endian bytes, representing an unsigned big integer. Each commit can +/// be converted to a Bn254Fr using the trivial identification as natural numbers or into a `u32` +/// digest by decomposing the big integer base-`F::MODULUS`. +#[serde_as] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct CommitBytes(#[serde_as(as = "serde_with::hex::Hex")] [u8; BN254_BYTES]); + +impl CommitBytes { + pub fn new(bytes: [u8; BN254_BYTES]) -> Self { + Self(bytes) + } + + pub fn as_slice(&self) -> &[u8; BN254_BYTES] { + &self.0 + } + + pub fn to_bn254(&self) -> Bn254Fr { + bytes_to_bn254(&self.0) + } + + pub fn to_u32_digest(&self) -> [u32; DIGEST_SIZE] { + bytes_to_u32_digest(&self.0) + } + + pub fn from_bn254(bn254: Bn254Fr) -> Self { + Self(bn254_to_bytes(bn254)) + } + + pub fn from_u32_digest(digest: &[u32; DIGEST_SIZE]) -> Self { + Self(u32_digest_to_bytes(digest)) + } + + pub fn reverse(&mut self) { + self.0.reverse(); + } +} /// `AppExecutionCommit` has all the commitments users should check against the final proof. -pub struct AppExecutionCommit { - /// Commitment of the leaf VM verifier program which commits the VmConfig of App VM. - /// Internal verifier will verify `leaf_vm_verifier_commit`. - pub leaf_vm_verifier_commit: [T; DIGEST_SIZE], - /// Commitment of the executable. It's computed as +#[serde_as] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct AppExecutionCommit { + /// Commitment of the executable. In base-F::MODULUS, it's computed as /// compress( /// compress( /// hash(app_program_commit), @@ -31,10 +69,14 @@ pub struct AppExecutionCommit { /// hash(right_pad(pc_start, 0)) /// ) /// `right_pad` example, if pc_start = 123, right_pad(pc_start, 0) = \[123,0,0,0,0,0,0,0\] - pub exe_commit: [T; DIGEST_SIZE], + pub app_exe_commit: CommitBytes, + + /// Commitment of the leaf VM verifier program which commits the VmConfig of App VM. + /// Internal verifier will verify `leaf_vm_verifier_commit`. + pub app_vm_commit: CommitBytes, } -impl AppExecutionCommit { +impl AppExecutionCommit { /// Users should use this function to compute `AppExecutionCommit` and check it against the /// final proof. pub fn compute>( @@ -42,28 +84,28 @@ impl AppExecutionCommit { app_exe: &NonRootCommittedExe, leaf_vm_verifier_exe: &NonRootCommittedExe, ) -> Self { - assert!( - app_exe.exe.program.max_num_public_values <= app_vm_config.system().num_public_values - ); - let exe_commit = app_exe + let exe_commit: [F; DIGEST_SIZE] = app_exe .compute_exe_commit(&app_vm_config.system().memory_config) .into(); - let leaf_vm_verifier_commit: [F; DIGEST_SIZE] = - leaf_vm_verifier_exe.committed_program.commitment.into(); + let vm_commit: [F; DIGEST_SIZE] = leaf_vm_verifier_exe.committed_program.commitment.into(); + Self::from_field_commit(exe_commit, vm_commit) + } + pub fn from_field_commit(exe_commit: [F; DIGEST_SIZE], vm_commit: [F; DIGEST_SIZE]) -> Self { Self { - leaf_vm_verifier_commit, - exe_commit, + app_exe_commit: CommitBytes::from_u32_digest(&exe_commit.map(|x| x.as_canonical_u32())), + app_vm_commit: CommitBytes::from_u32_digest(&vm_commit.map(|x| x.as_canonical_u32())), } } +} - pub fn app_config_commit_to_bn254(&self) -> Bn254Fr { - babybear_digest_to_bn254(&self.leaf_vm_verifier_commit) - } - - pub fn exe_commit_to_bn254(&self) -> Bn254Fr { - babybear_digest_to_bn254(&self.exe_commit) - } +pub fn commit_app_exe( + app_fri_params: FriParameters, + app_exe: impl Into>, +) -> Arc { + let exe: VmExe<_> = app_exe.into(); + let app_engine = BabyBearPoseidon2Engine::new(app_fri_params); + Arc::new(VmCommittedExe::::commit(exe, app_engine.config.pcs())) } pub(crate) fn babybear_digest_to_bn254(digest: &[F; DIGEST_SIZE]) -> Bn254Fr { @@ -77,34 +119,42 @@ pub(crate) fn babybear_digest_to_bn254(digest: &[F; DIGEST_SIZE]) -> Bn254Fr { ret } -pub fn generate_leaf_committed_exe>( - leaf_fri_params: FriParameters, - compiler_options: CompilerOptions, - app_pk: &AppProvingKey, -) -> Arc { - let app_vm_vk = app_pk.app_vm_pk.vm_pk.get_vk(); - let leaf_engine = BabyBearPoseidon2Engine::new(leaf_fri_params); - let leaf_program = LeafVmVerifierConfig { - app_fri_params: app_pk.app_vm_pk.fri_params, - app_system_config: app_pk.app_vm_pk.vm_config.system().clone(), - compiler_options, +fn bytes_to_bn254(bytes: &[u8; BN254_BYTES]) -> Bn254Fr { + let order = Bn254Fr::from_canonical_u32(1 << 8); + let mut ret = Bn254Fr::ZERO; + let mut base = Bn254Fr::ONE; + for byte in bytes.iter().rev() { + ret += base * Bn254Fr::from_canonical_u8(*byte); + base *= order; } - .build_program(&app_vm_vk); - Arc::new(VmCommittedExe::commit( - leaf_program.into(), - leaf_engine.config.pcs(), - )) + ret } -pub fn commit_app_exe( - app_fri_params: FriParameters, - app_exe: impl Into>, -) -> Arc { - let exe: VmExe<_> = app_exe.into(); - let app_engine = BabyBearPoseidon2Engine::new(app_fri_params); - Arc::new(VmCommittedExe::::commit(exe, app_engine.config.pcs())) +fn bn254_to_bytes(bn254: Bn254Fr) -> [u8; BN254_BYTES] { + let mut ret = bn254.value.to_bytes(); + ret.reverse(); + ret +} + +fn bytes_to_u32_digest(bytes: &[u8; BN254_BYTES]) -> [u32; DIGEST_SIZE] { + let mut bigint = BigUint::ZERO; + for byte in bytes.iter() { + bigint <<= 8; + bigint += BigUint::from(*byte); + } + let order = BabyBear::ORDER_U32; + from_fn(|_| { + let bigint_digit = bigint.clone() % order; + let digit = if bigint_digit == BigUint::ZERO { + 0u32 + } else { + bigint_digit.to_u32_digits()[0] + }; + bigint /= order; + digit + }) } -pub fn committed_exe_as_bn254(committed_exe: &NonRootCommittedExe) -> Bn254Fr { - babybear_digest_to_bn254(&committed_exe.get_program_commit().into()) +fn u32_digest_to_bytes(digest: &[u32; DIGEST_SIZE]) -> [u8; BN254_BYTES] { + bn254_to_bytes(babybear_digest_to_bn254(&digest.map(F::from_canonical_u32))) } diff --git a/crates/sdk/src/config/global.rs b/crates/sdk/src/config/global.rs index 532c9b8d1c..faf8182246 100644 --- a/crates/sdk/src/config/global.rs +++ b/crates/sdk/src/config/global.rs @@ -9,7 +9,8 @@ use openvm_bigint_circuit::{Int256, Int256Executor, Int256Periphery}; use openvm_bigint_transpiler::Int256TranspilerExtension; use openvm_circuit::{ arch::{ - SystemConfig, SystemExecutor, SystemPeriphery, VmChipComplex, VmConfig, VmInventoryError, + InitFileGenerator, SystemConfig, SystemExecutor, SystemPeriphery, VmChipComplex, VmConfig, + VmInventoryError, }, circuit_derive::{Chip, ChipUsageGetter}, derive::{AnyEnum, InstructionExecutor}, @@ -24,6 +25,7 @@ use openvm_native_circuit::{ CastFExtension, CastFExtensionExecutor, CastFExtensionPeriphery, Native, NativeExecutor, NativePeriphery, }; +use openvm_native_transpiler::LongFormTranspilerExtension; use openvm_pairing_circuit::{ PairingExtension, PairingExtensionExecutor, PairingExtensionPeriphery, }; @@ -138,6 +140,9 @@ impl SdkVmConfig { if self.sha256.is_some() { transpiler = transpiler.with_extension(Sha256TranspilerExtension); } + if self.native.is_some() { + transpiler = transpiler.with_extension(LongFormTranspilerExtension); + } if self.rv32m.is_some() { transpiler = transpiler.with_extension(Rv32MTranspilerExtension); } @@ -233,11 +238,49 @@ impl VmConfig for SdkVmConfig { } } +impl InitFileGenerator for SdkVmConfig { + fn generate_init_file_contents(&self) -> Option { + if self.modular.is_some() || self.fp2.is_some() || self.ecc.is_some() { + let mut contents = String::new(); + contents.push_str( + "// This file is automatically generated by cargo openvm. Do not rename or edit.\n", + ); + + if let Some(modular_config) = &self.modular { + contents.push_str(&modular_config.generate_moduli_init()); + contents.push('\n'); + } + + if let Some(fp2_config) = &self.fp2 { + assert!( + self.modular.is_some(), + "ModularExtension is required for Fp2Extension" + ); + let modular_config = self.modular.as_ref().unwrap(); + contents.push_str(&fp2_config.generate_complex_init(modular_config)); + contents.push('\n'); + } + + if let Some(ecc_config) = &self.ecc { + contents.push_str(&ecc_config.generate_sw_init()); + contents.push('\n'); + } + + Some(contents) + } else { + None + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SdkSystemConfig { pub config: SystemConfig, } +// Default implementation uses no init file +impl InitFileGenerator for SdkSystemConfig {} + impl Default for SdkSystemConfig { fn default() -> Self { Self { diff --git a/crates/sdk/src/config/mod.rs b/crates/sdk/src/config/mod.rs index 035e3709ef..3a231f180d 100644 --- a/crates/sdk/src/config/mod.rs +++ b/crates/sdk/src/config/mod.rs @@ -1,5 +1,5 @@ use clap::Args; -use openvm_circuit::arch::instructions::program::DEFAULT_MAX_NUM_PUBLIC_VALUES; +use openvm_circuit::arch::DEFAULT_MAX_NUM_PUBLIC_VALUES; use openvm_continuations::verifier::{ common::types::VmVerifierPvs, internal::types::InternalVmVerifierPvs, }; @@ -68,16 +68,31 @@ pub struct Halo2Config { #[derive(Clone, Copy, Debug, Serialize, Deserialize, Args)] pub struct AggregationTreeConfig { /// Each leaf verifier circuit will aggregate this many App VM proofs. - #[arg(long, default_value_t = DEFAULT_NUM_CHILDREN_LEAF)] + #[arg( + long, + default_value_t = DEFAULT_NUM_CHILDREN_LEAF, + help = "Number of children per leaf verifier circuit", + help_heading = "Aggregation Tree Options" + )] pub num_children_leaf: usize, /// Each internal verifier circuit will aggregate this many proofs, /// where each proof may be of either leaf or internal verifier (self) circuit. - #[arg(long, default_value_t = DEFAULT_NUM_CHILDREN_INTERNAL)] + #[arg( + long, + default_value_t = DEFAULT_NUM_CHILDREN_INTERNAL, + help = "Number of children per internal verifier circuit", + help_heading = "Aggregation Tree Options" + )] pub num_children_internal: usize, /// Safety threshold: how many times to do 1-to-1 aggregation of the "last" internal /// verifier proof before it is small enough for the root verifier circuit. /// Note: almost always no wrapping is needed. - #[arg(long, default_value_t = DEFAULT_MAX_INTERNAL_WRAPPER_LAYERS)] + #[arg( + long, + default_value_t = DEFAULT_MAX_INTERNAL_WRAPPER_LAYERS, + help = "Maximum number of internal wrapper layers", + help_heading = "Aggregation Tree Options" + )] pub max_internal_wrapper_layers: usize, // root currently always has 1 child for now } diff --git a/crates/sdk/src/fs.rs b/crates/sdk/src/fs.rs index e2b11075bd..9003e69d6d 100644 --- a/crates/sdk/src/fs.rs +++ b/crates/sdk/src/fs.rs @@ -3,7 +3,7 @@ use std::{ path::Path, }; -use eyre::Result; +use eyre::{Report, Result}; use openvm_circuit::arch::{instructions::exe::VmExe, ContinuationVmProof, VmConfig}; use openvm_continuations::verifier::root::types::RootVmVerifierInput; use openvm_native_recursion::halo2::wrapper::EvmVerifierByteCode; @@ -11,7 +11,7 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::{ codec::{Decode, Encode}, - keygen::{AggProvingKey, AppProvingKey, AppVerifyingKey}, + keygen::{AggStarkProvingKey, AppProvingKey, AppVerifyingKey, Halo2ProvingKey}, types::{EvmHalo2Verifier, EvmProof}, F, OPENVM_VERSION, SC, }; @@ -22,88 +22,97 @@ pub const EVM_HALO2_VERIFIER_BASE_NAME: &str = "OpenVmHalo2Verifier.sol"; pub const EVM_VERIFIER_ARTIFACT_FILENAME: &str = "verifier.bytecode.json"; pub fn read_exe_from_file>(path: P) -> Result> { - read_from_file_bitcode(path) + read_from_file_bitcode(&path) } pub fn write_exe_to_file>(exe: VmExe, path: P) -> Result<()> { - write_to_file_bitcode(path, exe) + write_to_file_bitcode(&path, exe) } pub fn read_app_pk_from_file, P: AsRef>( path: P, ) -> Result> { - read_from_file_bitcode(path) + read_from_file_bitcode(&path) } pub fn write_app_pk_to_file, P: AsRef>( app_pk: AppProvingKey, path: P, ) -> Result<()> { - write_to_file_bitcode(path, app_pk) + write_to_file_bitcode(&path, app_pk) } pub fn read_app_vk_from_file>(path: P) -> Result { - read_from_file_bitcode(path) + read_from_file_bitcode(&path) } pub fn write_app_vk_to_file>(app_vk: AppVerifyingKey, path: P) -> Result<()> { - write_to_file_bitcode(path, app_vk) + write_to_file_bitcode(&path, app_vk) } pub fn read_app_proof_from_file>(path: P) -> Result> { - decode_from_file(path) + decode_from_file(&path) } pub fn write_app_proof_to_file>( proof: ContinuationVmProof, path: P, ) -> Result<()> { - encode_to_file(path, proof) + encode_to_file(&path, proof) } pub fn read_root_verifier_input_from_file>( path: P, ) -> Result> { - decode_from_file(path) + decode_from_file(&path) } pub fn write_root_verifier_input_to_file>( input: RootVmVerifierInput, path: P, ) -> Result<()> { - encode_to_file(path, input) + encode_to_file(&path, input) } -pub fn read_agg_pk_from_file>(path: P) -> Result { - read_from_file_bitcode(path) +pub fn read_agg_stark_pk_from_file>(path: P) -> Result { + read_from_file_bitcode(&path) +} + +pub fn write_agg_stark_pk_to_file>(pk: &AggStarkProvingKey, path: P) -> Result<()> { + write_to_file_bitcode(&path, pk) +} + +pub fn read_agg_halo2_pk_from_file>(path: P) -> Result { + read_from_file_bitcode(&path) } -pub fn write_agg_pk_to_file>(agg_pk: AggProvingKey, path: P) -> Result<()> { - write_to_file_bitcode(path, agg_pk) +pub fn write_agg_halo2_pk_to_file>(pk: &Halo2ProvingKey, path: P) -> Result<()> { + write_to_file_bitcode(&path, pk) } pub fn read_evm_proof_from_file>(path: P) -> Result { - let proof: EvmProof = serde_json::from_reader(File::open(path)?)?; - Ok(proof) + read_from_file_json(&path) } pub fn write_evm_proof_to_file>(proof: EvmProof, path: P) -> Result<()> { - serde_json::to_writer(File::create(path)?, &proof)?; - Ok(()) + write_to_file_json(&path, proof) } pub fn read_evm_halo2_verifier_from_folder>(folder: P) -> Result { - let halo2_verifier_code_path = folder.as_ref().join(EVM_HALO2_VERIFIER_PARENT_NAME); - let openvm_verifier_code_path = folder.as_ref().join(EVM_HALO2_VERIFIER_BASE_NAME); - let interface_path = folder + let folder = folder .as_ref() + .join("src") + .join(format!("v{}", OPENVM_VERSION)); + let halo2_verifier_code_path = folder.join(EVM_HALO2_VERIFIER_PARENT_NAME); + let openvm_verifier_code_path = folder.join(EVM_HALO2_VERIFIER_BASE_NAME); + let interface_path = folder .join("interfaces") .join(EVM_HALO2_VERIFIER_INTERFACE_NAME); let halo2_verifier_code = read_to_string(halo2_verifier_code_path)?; let openvm_verifier_code = read_to_string(openvm_verifier_code_path)?; let interface = read_to_string(interface_path)?; - let artifact_path = folder.as_ref().join(EVM_VERIFIER_ARTIFACT_FILENAME); + let artifact_path = folder.join(EVM_VERIFIER_ARTIFACT_FILENAME); let artifact: EvmVerifierByteCode = serde_json::from_reader(File::open(artifact_path)?)?; Ok(EvmHalo2Verifier { @@ -170,18 +179,39 @@ pub fn write_object_to_file>(path: P, data: T) -> R write_to_file_bitcode(path, data) } -pub(crate) fn read_from_file_bitcode>(path: P) -> Result { - let data = read(path)?; - let ret = bitcode::deserialize(&data)?; +pub fn read_from_file_bitcode>(path: P) -> Result { + let ret = read(&path) + .map_err(|e| read_error(&path, e.into())) + .and_then(|data| { + bitcode::deserialize(&data).map_err(|e: bitcode::Error| read_error(&path, e.into())) + })?; Ok(ret) } -pub(crate) fn write_to_file_bitcode>(path: P, data: T) -> Result<()> { - let bytes = bitcode::serialize(&data)?; +pub fn write_to_file_bitcode>(path: P, data: T) -> Result<()> { if let Some(parent) = path.as_ref().parent() { - create_dir_all(parent)?; + create_dir_all(parent).map_err(|e| write_error(&path, e.into()))?; } - write(path, bytes)?; + bitcode::serialize(&data) + .map_err(|e| write_error(&path, e.into())) + .and_then(|bytes| write(&path, bytes).map_err(|e| write_error(&path, e.into())))?; + Ok(()) +} + +pub fn read_from_file_json>(path: P) -> Result { + let ret: T = File::open(&path) + .and_then(|file| serde_json::from_reader(file).map_err(|e| e.into())) + .map_err(|e| read_error(&path, e.into()))?; + Ok(ret) +} + +pub fn write_to_file_json>(path: P, data: T) -> Result<()> { + if let Some(parent) = path.as_ref().parent() { + create_dir_all(parent).map_err(|e| write_error(&path, e.into()))?; + } + File::create(&path) + .and_then(|file| serde_json::to_writer_pretty(file, &data).map_err(|e| e.into())) + .map_err(|e| write_error(&path, e.into()))?; Ok(()) } @@ -212,3 +242,19 @@ pub fn encode_to_file>(path: P, data: T) -> Result<()> data.encode(writer)?; Ok(()) } + +fn read_error>(path: P, error: Report) -> Report { + eyre::eyre!( + "reading from {} failed with the following error:\n {}", + path.as_ref().display(), + error, + ) +} + +fn write_error>(path: P, error: Report) -> Report { + eyre::eyre!( + "writing to {} failed with the following error:\n {}", + path.as_ref().display(), + error, + ) +} diff --git a/crates/sdk/src/keygen/asm.rs b/crates/sdk/src/keygen/asm.rs new file mode 100644 index 0000000000..5c8779abbe --- /dev/null +++ b/crates/sdk/src/keygen/asm.rs @@ -0,0 +1,106 @@ +use openvm_circuit::arch::instructions::{ + instruction::{Instruction, NUM_OPERANDS}, + program::Program, + LocalOpcode, +}; +use openvm_continuations::F; +use openvm_native_compiler::{asm::A0, conversion::AS, NativeJalOpcode}; +use openvm_stark_backend::p3_field::{FieldAlgebra, PrimeField32}; +use rrs_lib::instruction_formats::IType; + +const OPCODE: u32 = 0x0b; +const FUNCT3: u32 = 0b111; +const LONG_FORM_INSTRUCTION_INDICATOR: u32 = (FUNCT3 << 12) + OPCODE; +const GAP_INDICATOR: u32 = (1 << 25) + (FUNCT3 << 12) + OPCODE; + +pub fn program_to_asm(mut program: Program) -> String { + let pc_diff = handle_pc_diff(&mut program); + let assembly_and_comments = convert_program_to_u32s_and_comments(&program, pc_diff); + let mut asm_output = String::new(); + for (u32s, comment) in &assembly_and_comments { + for (idx, x) in u32s.iter().enumerate() { + asm_output.push_str(&u32_to_directive(*x)); + if idx == 0 { + asm_output.push_str(" // "); + asm_output.push_str(comment); + } + asm_output.push('\n'); + } + } + asm_output +} + +fn u32_to_directive(x: u32) -> String { + let opcode = x & 0b1111111; + let dec_insn = IType::new(x); + format!( + ".insn i {}, {}, x{}, x{}, {}", + opcode, dec_insn.funct3, dec_insn.rd, dec_insn.rs1, dec_insn.imm + ) +} + +/// In order to use native instructions in kernel functions, native instructions need to be +/// converted to RISC-V machine code(long form instructions) first. Then Rust compiler compiles the +/// whole program into an ELF. Finally, the ELF is transpiled into an OpenVm Exe. +/// In the perspective of the native compiler and the transpiler, the PC step between 2 native +/// instructions is 4. However, in the ELF, each native instruction takes longer than 4 bytes, so +/// the instructions after the code blocks use the actual lengths of the native instructions to +/// compute PC offsets. To solve this problem, we need the gap indicator to pad the native code +/// block in order to align the PC of the following instructions. +/// More details about long form instructions and gap indicators can be found in +/// `docs/specs/transpiler.md`. +fn handle_pc_diff(program: &mut Program) -> usize { + const GAP_INDICATOR_WIDTH: usize = 2; + const LONG_FORM_NATIVE_INSTRUCTION_WIDTH: usize = 10; + const PC_STEP: usize = 4; + // For GAP_INDICATOR, whose width is 2. + let mut pc_diff = GAP_INDICATOR_WIDTH; + // For each native instruction + pc_diff += program.num_defined_instructions() * (LONG_FORM_NATIVE_INSTRUCTION_WIDTH - 1); + // For next jal + pc_diff += LONG_FORM_NATIVE_INSTRUCTION_WIDTH - 1; + let jal = Instruction:: { + opcode: NativeJalOpcode::JAL.global_opcode(), + a: F::from_canonical_usize(A0 as usize), // A0 + // +1 means the next instruction after the gap + b: F::from_canonical_usize(PC_STEP * (pc_diff + 1)), + c: F::from_canonical_usize(0), + d: F::from_canonical_u32(AS::Native as u32), + e: F::from_canonical_usize(0), + f: F::from_canonical_usize(0), + g: F::from_canonical_usize(0), + }; + program.push_instruction(jal); + pc_diff +} + +fn convert_program_to_u32s_and_comments( + program: &Program, + pc_diff: usize, +) -> Vec<(Vec, String)> { + program + .defined_instructions() + .iter() + .map(|ins| { + ( + vec![ + LONG_FORM_INSTRUCTION_INDICATOR, + NUM_OPERANDS as u32, + ins.opcode.as_usize() as u32, + ins.a.as_canonical_u32(), + ins.b.as_canonical_u32(), + ins.c.as_canonical_u32(), + ins.d.as_canonical_u32(), + ins.e.as_canonical_u32(), + ins.f.as_canonical_u32(), + ins.g.as_canonical_u32(), + ], + format!("{:?}", ins.opcode), + ) + }) + .chain(std::iter::once(( + vec![GAP_INDICATOR, pc_diff as u32], + "GAP_INDICATOR".to_string(), + ))) + .collect() +} diff --git a/crates/sdk/src/keygen/dummy.rs b/crates/sdk/src/keygen/dummy.rs index 44b66454b9..3fe2bcd300 100644 --- a/crates/sdk/src/keygen/dummy.rs +++ b/crates/sdk/src/keygen/dummy.rs @@ -208,14 +208,12 @@ fn dummy_app_committed_exe(fri_params: FriParameters) -> Arc Program { - let mut ret = Program::from_instructions(&[Instruction::from_isize( + Program::from_instructions(&[Instruction::from_isize( TERMINATE.global_opcode(), 0, 0, 0, 0, 0, - )]); - ret.max_num_public_values = 0; - ret + )]) } diff --git a/crates/sdk/src/keygen/mod.rs b/crates/sdk/src/keygen/mod.rs index bbd6ec269e..182926a282 100644 --- a/crates/sdk/src/keygen/mod.rs +++ b/crates/sdk/src/keygen/mod.rs @@ -46,6 +46,7 @@ use crate::{ NonRootCommittedExe, RootSC, F, SC, }; +pub mod asm; pub(crate) mod dummy; pub mod perm; pub mod static_verifier; @@ -321,7 +322,7 @@ impl AggStarkProvingKey { let root_program = RootVmVerifierConfig { leaf_fri_params: config.leaf_fri_params, internal_fri_params: config.internal_fri_params, - num_public_values: config.max_num_user_public_values, + num_user_public_values: config.max_num_user_public_values, internal_vm_verifier_commit: internal_committed_exe.get_program_commit().into(), compiler_options: config.compiler_options, } @@ -369,7 +370,7 @@ impl AggStarkProvingKey { self.internal_committed_exe.get_program_commit().into() } - pub fn num_public_values(&self) -> usize { + pub fn num_user_public_values(&self) -> usize { self.root_verifier_pk .vm_pk .vm_config diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 193c161580..fe21d7cb3b 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,8 +1,8 @@ -use std::{fs::read, marker::PhantomData, path::Path, sync::Arc}; +use std::{borrow::Borrow, fs::read, marker::PhantomData, path::Path, sync::Arc}; #[cfg(feature = "evm-verify")] use alloy_sol_types::sol; -use commit::commit_app_exe; +use commit::{commit_app_exe, AppExecutionCommit}; use config::{AggregationTreeConfig, AppConfig}; use eyre::Result; use keygen::{AppProvingKey, AppVerifyingKey}; @@ -11,16 +11,22 @@ use openvm_build::{ }; use openvm_circuit::{ arch::{ - hasher::poseidon2::vm_poseidon2_hasher, instructions::exe::VmExe, verify_segments, - ContinuationVmProof, ExecutionError, VerifiedExecutionPayload, VmConfig, VmExecutor, - VmVerificationError, + hasher::{poseidon2::vm_poseidon2_hasher, Hasher}, + instructions::exe::VmExe, + verify_segments, ContinuationVmProof, ExecutionError, InitFileGenerator, + VerifiedExecutionPayload, VmConfig, VmExecutor, CONNECTOR_AIR_ID, PROGRAM_AIR_ID, + PROGRAM_CACHED_TRACE_INDEX, PUBLIC_VALUES_AIR_ID, }, system::{ memory::{tree::public_values::extract_public_values, CHUNK}, - program::trace::VmCommittedExe, + program::trace::{compute_exe_commit, VmCommittedExe}, }, }; -use openvm_continuations::verifier::root::types::RootVmVerifierInput; +use openvm_continuations::verifier::{ + common::types::VmVerifierPvs, + internal::types::{InternalVmVerifierPvs, VmStarkProof}, + root::{types::RootVmVerifierInput, RootVmVerifierConfig}, +}; pub use openvm_continuations::{ static_verifier::{DefaultStaticVerifierPvHandler, StaticVerifierPvHandler}, RootSC, C, F, SC, @@ -30,7 +36,8 @@ use openvm_stark_backend::proof::Proof; use openvm_stark_sdk::{ config::{baby_bear_poseidon2::BabyBearPoseidon2Engine, FriParameters}, engine::StarkFriEngine, - openvm_stark_backend::{verifier::VerificationError, Chip}, + openvm_stark_backend::Chip, + p3_bn254_fr::Bn254Fr, }; use openvm_transpiler::{ elf::Elf, @@ -42,7 +49,7 @@ use openvm_transpiler::{ use snark_verifier_sdk::{evm::gen_evm_verifier_sol_code, halo2::aggregation::AggregationCircuit}; use crate::{ - config::AggConfig, + config::{AggConfig, SdkVmConfig}, keygen::{AggProvingKey, AggStarkProvingKey}, prover::{AppProver, StarkProver}, }; @@ -58,6 +65,8 @@ pub mod prover; mod stdin; pub use stdin::*; +use crate::{config::AggStarkConfig, keygen::asm::program_to_asm}; + pub mod fs; pub mod types; @@ -114,20 +123,24 @@ impl> GenericSdk { Self::default() } - pub fn agg_tree_config(&self) -> &AggregationTreeConfig { - &self.agg_tree_config + pub fn with_agg_tree_config(mut self, agg_tree_config: AggregationTreeConfig) -> Self { + self.agg_tree_config = agg_tree_config; + self } - pub fn set_agg_tree_config(&mut self, agg_tree_config: AggregationTreeConfig) { - self.agg_tree_config = agg_tree_config; + pub fn agg_tree_config(&self) -> &AggregationTreeConfig { + &self.agg_tree_config } pub fn build>( &self, guest_opts: GuestOptions, + vm_config: &SdkVmConfig, pkg_dir: P, target_filter: &Option, + init_file_name: Option<&str>, // If None, we use "openvm-init.rs" ) -> Result { + vm_config.write_to_init_file(pkg_dir.as_ref(), init_file_name)?; let pkg = get_package(pkg_dir.as_ref()); let target_dir = match build_guest_package(&pkg, &guest_opts, None, target_filter) { Ok(target_dir) => target_dir, @@ -219,7 +232,7 @@ impl> GenericSdk { &self, app_vk: &AppVerifyingKey, proof: &ContinuationVmProof, - ) -> Result { + ) -> Result { let engine = E::new(app_vk.fri_params); let VerifiedExecutionPayload { exe_commit, @@ -241,9 +254,10 @@ impl> GenericSdk { &self, app_vk: &AppVerifyingKey, proof: &Proof, - ) -> Result<(), VerificationError> { + ) -> Result<()> { let e = E::new(app_vk.fri_params); - e.verify(&app_vk.app_vm_vk, proof) + e.verify(&app_vk.app_vm_vk, proof)?; + Ok(()) } pub fn agg_keygen( @@ -256,6 +270,29 @@ impl> GenericSdk { Ok(agg_pk) } + pub fn agg_stark_keygen(&self, config: AggStarkConfig) -> Result { + let agg_pk = AggStarkProvingKey::keygen(config); + Ok(agg_pk) + } + + pub fn generate_root_verifier_asm(&self, agg_stark_pk: &AggStarkProvingKey) -> String { + let kernel_asm = RootVmVerifierConfig { + leaf_fri_params: agg_stark_pk.leaf_vm_pk.fri_params, + internal_fri_params: agg_stark_pk.internal_vm_pk.fri_params, + num_user_public_values: agg_stark_pk.num_user_public_values(), + internal_vm_verifier_commit: agg_stark_pk + .internal_committed_exe + .get_program_commit() + .into(), + compiler_options: Default::default(), + } + .build_kernel_asm( + &agg_stark_pk.leaf_vm_pk.vm_pk.get_vk(), + &agg_stark_pk.internal_vm_pk.vm_pk.get_vk(), + ); + program_to_asm(kernel_asm) + } + pub fn generate_root_verifier_input>( &self, app_pk: Arc>, @@ -273,6 +310,123 @@ impl> GenericSdk { Ok(proof) } + pub fn generate_e2e_stark_proof>( + &self, + app_pk: Arc>, + app_exe: Arc, + agg_stark_pk: AggStarkProvingKey, + inputs: StdIn, + ) -> Result> + where + VC::Executor: Chip, + VC::Periphery: Chip, + { + let stark_prover = + StarkProver::::new(app_pk, app_exe, agg_stark_pk, self.agg_tree_config); + let proof = stark_prover.generate_e2e_stark_proof(inputs); + Ok(proof) + } + + pub fn verify_e2e_stark_proof( + &self, + agg_stark_pk: &AggStarkProvingKey, + proof: &VmStarkProof, + expected_exe_commit: &Bn254Fr, + expected_vm_commit: &Bn254Fr, + ) -> Result { + if proof.proof.per_air.len() < 3 { + return Err(eyre::eyre!( + "Invalid number of AIRs: expected at least 3, got {}", + proof.proof.per_air.len() + )); + } else if proof.proof.per_air[0].air_id != PROGRAM_AIR_ID { + return Err(eyre::eyre!("Missing program AIR")); + } else if proof.proof.per_air[1].air_id != CONNECTOR_AIR_ID { + return Err(eyre::eyre!("Missing connector AIR")); + } else if proof.proof.per_air[2].air_id != PUBLIC_VALUES_AIR_ID { + return Err(eyre::eyre!("Missing public values AIR")); + } + let public_values_air_proof_data = &proof.proof.per_air[2]; + + let program_commit = + proof.proof.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref(); + let internal_commit: &[_; CHUNK] = &agg_stark_pk + .internal_committed_exe + .get_program_commit() + .into(); + + let (vm_pk, vm_commit) = if program_commit == internal_commit { + let internal_pvs: &InternalVmVerifierPvs<_> = public_values_air_proof_data + .public_values + .as_slice() + .borrow(); + if internal_commit != &internal_pvs.extra_pvs.internal_program_commit { + return Err(eyre::eyre!( + "Invalid internal program commit: expected {:?}, got {:?}", + internal_commit, + internal_pvs.extra_pvs.internal_program_commit + )); + } + ( + &agg_stark_pk.internal_vm_pk, + internal_pvs.extra_pvs.leaf_verifier_commit, + ) + } else { + (&agg_stark_pk.leaf_vm_pk, *program_commit) + }; + let e = E::new(vm_pk.fri_params); + e.verify(&vm_pk.vm_pk.get_vk(), &proof.proof)?; + + let pvs: &VmVerifierPvs<_> = + public_values_air_proof_data.public_values[..VmVerifierPvs::::width()].borrow(); + + if let Some(exit_code) = pvs.connector.exit_code() { + if exit_code != 0 { + return Err(eyre::eyre!( + "Invalid exit code: expected 0, got {}", + exit_code + )); + } + } else { + return Err(eyre::eyre!("Program did not terminate")); + } + + let hasher = vm_poseidon2_hasher(); + let public_values_root = hasher.merkle_root(&proof.user_public_values); + if public_values_root != pvs.public_values_commit { + return Err(eyre::eyre!( + "Invalid public values root: expected {:?}, got {:?}", + pvs.public_values_commit, + public_values_root + )); + } + + let exe_commit = compute_exe_commit( + &hasher, + &pvs.app_commit, + &pvs.memory.initial_root, + pvs.connector.initial_pc, + ); + let app_commit = AppExecutionCommit::from_field_commit(exe_commit, vm_commit); + let exe_commit_bn254 = app_commit.app_exe_commit.to_bn254(); + let vm_commit_bn254 = app_commit.app_vm_commit.to_bn254(); + + if exe_commit_bn254 != *expected_exe_commit { + return Err(eyre::eyre!( + "Invalid app exe commit: expected {:?}, got {:?}", + expected_exe_commit, + exe_commit_bn254 + )); + } else if vm_commit_bn254 != *expected_vm_commit { + return Err(eyre::eyre!( + "Invalid app vm commit: expected {:?}, got {:?}", + expected_vm_commit, + vm_commit_bn254 + )); + } + Ok(app_commit) + } + #[cfg(feature = "evm-prove")] pub fn generate_evm_proof>( &self, diff --git a/crates/sdk/src/prover/agg.rs b/crates/sdk/src/prover/agg.rs index 42f0d85d58..d3c5fd29c1 100644 --- a/crates/sdk/src/prover/agg.rs +++ b/crates/sdk/src/prover/agg.rs @@ -2,10 +2,12 @@ use std::sync::Arc; use openvm_circuit::arch::ContinuationVmProof; use openvm_continuations::verifier::{ - internal::types::InternalVmVerifierInput, leaf::types::LeafVmVerifierInput, + internal::types::{InternalVmVerifierInput, VmStarkProof}, + leaf::types::LeafVmVerifierInput, root::types::RootVmVerifierInput, }; use openvm_native_circuit::NativeConfig; +use openvm_native_compiler::ir::DIGEST_SIZE; use openvm_native_recursion::hints::Hintable; use openvm_stark_sdk::{engine::StarkFriEngine, openvm_stark_backend::proof::Proof}; use tracing::info_span; @@ -77,56 +79,36 @@ impl> AggStarkProver { self } - /// Generate a proof to aggregate app proofs. - pub fn generate_agg_proof(&self, app_proofs: ContinuationVmProof) -> Proof { + /// Generate the root proof for outer recursion. + pub fn generate_root_proof(&self, app_proofs: ContinuationVmProof) -> Proof { let root_verifier_input = self.generate_root_verifier_input(app_proofs); self.generate_root_proof_impl(root_verifier_input) } + pub fn generate_leaf_proofs(&self, app_proofs: &ContinuationVmProof) -> Vec> { + self.leaf_controller + .generate_proof(&self.leaf_prover, app_proofs) + } + pub fn generate_root_verifier_input( &self, app_proofs: ContinuationVmProof, ) -> RootVmVerifierInput { - let leaf_proofs = self - .leaf_controller - .generate_proof(&self.leaf_prover, &app_proofs); + let leaf_proofs = self.generate_leaf_proofs(&app_proofs); let public_values = app_proofs.user_public_values.public_values; - let internal_proof = self.generate_internal_proof_impl(leaf_proofs, &public_values); - RootVmVerifierInput { - proofs: vec![internal_proof], - public_values, - } + let e2e_stark_proof = self.aggregate_leaf_proofs(leaf_proofs, public_values); + self.wrap_e2e_stark_proof(e2e_stark_proof) } - fn generate_internal_proof_impl( + pub fn aggregate_leaf_proofs( &self, leaf_proofs: Vec>, - public_values: &[F], - ) -> Proof { + public_values: Vec, + ) -> VmStarkProof { let mut internal_node_idx = -1; let mut internal_node_height = 0; let mut proofs = leaf_proofs; - let mut wrapper_layers = 0; - loop { - if proofs.len() == 1 { - let actual_air_heights = - self.root_prover - .execute_for_air_heights(RootVmVerifierInput { - proofs: vec![proofs[0].clone()], - public_values: public_values.to_vec(), - }); - // Root verifier can handle the internal proof. We can stop here. - if heights_le( - &actual_air_heights, - &self.root_prover.root_verifier_pk.air_heights, - ) { - break; - } - if wrapper_layers >= self.max_internal_wrapper_layers { - panic!("The heights of the root verifier still exceed the required heights after {} wrapper layers", self.max_internal_wrapper_layers); - } - wrapper_layers += 1; - } + while proofs.len() > 1 { let internal_inputs = InternalVmVerifierInput::chunk_leaf_or_internal_proofs( self.internal_prover .committed_exe @@ -158,7 +140,29 @@ impl> AggStarkProver { }); internal_node_height += 1; } - proofs.pop().unwrap() + VmStarkProof { + proof: proofs.pop().unwrap(), + user_public_values: public_values, + } + } + + /// Wrap the e2e stark proof until its heights meet the requirements of the root verifier. + pub fn wrap_e2e_stark_proof( + &self, + e2e_stark_proof: VmStarkProof, + ) -> RootVmVerifierInput { + let internal_commit = self + .internal_prover + .committed_exe + .get_program_commit() + .into(); + wrap_e2e_stark_proof( + &self.internal_prover, + &self.root_prover, + internal_commit, + self.max_internal_wrapper_layers, + e2e_stark_proof, + ) } fn generate_root_proof_impl(&self, root_input: RootVmVerifierInput) -> Proof { @@ -204,6 +208,58 @@ impl LeafProvingController { } } +/// Wrap the e2e stark proof until its heights meet the requirements of the root verifier. +pub fn wrap_e2e_stark_proof>( + internal_prover: &VmLocalProver, + root_prover: &RootVerifierLocalProver, + internal_commit: [F; DIGEST_SIZE], + max_internal_wrapper_layers: usize, + e2e_stark_proof: VmStarkProof, +) -> RootVmVerifierInput { + let VmStarkProof { + mut proof, + user_public_values, + } = e2e_stark_proof; + let mut wrapper_layers = 0; + loop { + let actual_air_heights = root_prover.execute_for_air_heights(RootVmVerifierInput { + proofs: vec![proof.clone()], + public_values: user_public_values.clone(), + }); + // Root verifier can handle the internal proof. We can stop here. + if heights_le( + &actual_air_heights, + &root_prover.root_verifier_pk.air_heights, + ) { + break; + } + if wrapper_layers >= max_internal_wrapper_layers { + panic!("The heights of the root verifier still exceed the required heights after {} wrapper layers", max_internal_wrapper_layers); + } + wrapper_layers += 1; + let input = InternalVmVerifierInput { + self_program_commit: internal_commit, + proofs: vec![proof.clone()], + }; + proof = info_span!( + "wrapper_layer", + group = format!("internal_wrapper.{wrapper_layers}") + ) + .in_scope(|| { + #[cfg(feature = "bench-metrics")] + { + metrics::counter!("fri.log_blowup") + .absolute(internal_prover.fri_params().log_blowup as u64); + } + SingleSegmentVmProver::prove(internal_prover, input.write()) + }); + } + RootVmVerifierInput { + proofs: vec![proof], + public_values: user_public_values, + } +} + fn heights_le(a: &[usize], b: &[usize]) -> bool { assert_eq!(a.len(), b.len()); a.iter().zip(b.iter()).all(|(a, b)| a <= b) diff --git a/crates/sdk/src/prover/stark.rs b/crates/sdk/src/prover/stark.rs index c95bdc0655..fdec583f0f 100644 --- a/crates/sdk/src/prover/stark.rs +++ b/crates/sdk/src/prover/stark.rs @@ -1,7 +1,9 @@ use std::sync::Arc; use openvm_circuit::arch::VmConfig; -use openvm_continuations::verifier::root::types::RootVmVerifierInput; +use openvm_continuations::verifier::{ + internal::types::VmStarkProof, root::types::RootVmVerifierInput, +}; use openvm_stark_backend::{proof::Proof, Chip}; use openvm_stark_sdk::engine::StarkFriEngine; @@ -32,7 +34,7 @@ impl> StarkProver { ); assert_eq!( app_pk.app_vm_pk.vm_config.system().num_public_values, - agg_stark_pk.num_public_values(), + agg_stark_pk.num_user_public_values(), "App VM is incompatible with Agg VM because of the number of public values" ); @@ -56,7 +58,7 @@ impl> StarkProver { VC::Periphery: Chip, { let app_proof = self.app_prover.generate_app_proof(input); - self.agg_prover.generate_agg_proof(app_proof) + self.agg_prover.generate_root_proof(app_proof) } pub fn generate_root_verifier_input(&self, input: StdIn) -> RootVmVerifierInput @@ -68,4 +70,16 @@ impl> StarkProver { let app_proof = self.app_prover.generate_app_proof(input); self.agg_prover.generate_root_verifier_input(app_proof) } + + pub fn generate_e2e_stark_proof(&self, input: StdIn) -> VmStarkProof + where + VC: VmConfig, + VC::Executor: Chip, + VC::Periphery: Chip, + { + let app_proof = self.app_prover.generate_app_proof(input); + let leaf_proofs = self.agg_prover.generate_leaf_proofs(&app_proof); + self.agg_prover + .aggregate_leaf_proofs(leaf_proofs, app_proof.user_public_values.public_values) + } } diff --git a/crates/sdk/src/stdin.rs b/crates/sdk/src/stdin.rs index eaa7bebeef..9101e8d4de 100644 --- a/crates/sdk/src/stdin.rs +++ b/crates/sdk/src/stdin.rs @@ -1,4 +1,7 @@ -use std::collections::VecDeque; +use std::{ + collections::{HashMap, VecDeque}, + sync::Arc, +}; use openvm_circuit::arch::Streams; use openvm_stark_backend::p3_field::FieldAlgebra; @@ -9,6 +12,7 @@ use crate::F; #[derive(Clone, Default, Serialize, Deserialize)] pub struct StdIn { pub buffer: VecDeque>, + pub kv_store: HashMap, Vec>, } impl StdIn { @@ -36,6 +40,9 @@ impl StdIn { pub fn write_field(&mut self, data: &[F]) { self.buffer.push_back(data.to_vec()); } + pub fn add_key_value(&mut self, key: Vec, value: Vec) { + self.kv_store.insert(key, value); + } } impl From for Streams { @@ -44,7 +51,9 @@ impl From for Streams { while let Some(input) = std_in.read() { data.push(input); } - Streams::new(data) + let mut ret = Streams::new(data); + ret.kv_store = Arc::new(std_in.kv_store); + ret } } diff --git a/crates/sdk/src/types.rs b/crates/sdk/src/types.rs index f305505bef..76a894a5a3 100644 --- a/crates/sdk/src/types.rs +++ b/crates/sdk/src/types.rs @@ -1,13 +1,24 @@ -use std::iter::{once, repeat}; +use std::{ + io::Cursor, + iter::{once, repeat}, +}; +use eyre::Result; use itertools::Itertools; +use openvm_continuations::{verifier::internal::types::VmStarkProof, SC}; use openvm_native_recursion::halo2::{wrapper::EvmVerifierByteCode, Fr, RawEvmProof}; +use openvm_stark_backend::proof::Proof; use serde::{Deserialize, Serialize}; use serde_with::serde_as; use thiserror::Error; +use crate::{ + codec::{decode_vec, encode_slice, Decode, Encode}, + commit::{AppExecutionCommit, CommitBytes}, +}; + /// Number of bytes in a Bn254Fr. -const BN254_BYTES: usize = 32; +pub(crate) const BN254_BYTES: usize = 32; /// Number of Bn254Fr in `accumulator` field. pub const NUM_BN254_ACCUMULATOR: usize = 12; /// Number of Bn254Fr in `proof` field for a circuit with only 1 advice column. @@ -36,12 +47,9 @@ pub struct ProofData { #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EvmProof { - #[serde_as(as = "serde_with::hex::Hex")] - /// 1 Bn254Fr public value for app exe commit in big-endian bytes. - pub app_exe_commit: [u8; BN254_BYTES], - #[serde_as(as = "serde_with::hex::Hex")] - /// 1 Bn254Fr public value for app vm commit in big-endian bytes. - pub app_vm_commit: [u8; BN254_BYTES], + #[serde(flatten)] + /// Bn254Fr public value app commits. + pub app_commit: AppExecutionCommit, #[serde_as(as = "serde_with::hex::Hex")] /// User public values packed into bytes. pub user_public_values: Vec, @@ -71,8 +79,7 @@ impl EvmProof { let EvmProof { user_public_values, - app_exe_commit, - app_vm_commit, + app_commit, proof_data, } = self; @@ -84,8 +91,8 @@ impl EvmProof { IOpenVmHalo2Verifier::verifyCall { publicValues: user_public_values.into(), proofData: proof_data.into(), - appExeCommit: app_exe_commit.into(), - appVmCommit: app_vm_commit.into(), + appExeCommit: app_commit.app_exe_commit.as_slice().into(), + appVmCommit: app_commit.app_vm_commit.as_slice().into(), } .abi_encode() } @@ -130,10 +137,13 @@ impl TryFrom for EvmProof { acc }, ); + let app_commit = AppExecutionCommit { + app_exe_commit: CommitBytes::new(app_exe_commit), + app_vm_commit: CommitBytes::new(app_vm_commit), + }; Ok(Self { - app_exe_commit, - app_vm_commit, + app_commit, user_public_values, proof_data: ProofData { accumulator: evm_accumulator, @@ -147,14 +157,13 @@ impl TryFrom for RawEvmProof { type Error = EvmProofConversionError; fn try_from(evm_openvm_proof: EvmProof) -> Result { let EvmProof { - mut app_exe_commit, - mut app_vm_commit, + mut app_commit, user_public_values, proof_data, } = evm_openvm_proof; - app_exe_commit.reverse(); - app_vm_commit.reverse(); + app_commit.app_exe_commit.reverse(); + app_commit.app_vm_commit.reverse(); let ProofData { accumulator, proof } = proof_data; @@ -183,8 +192,8 @@ impl TryFrom for RawEvmProof { let mut ret = Vec::new(); for chunk in &reversed_accumulator .iter() - .chain(&app_exe_commit) - .chain(&app_vm_commit) + .chain(app_commit.app_exe_commit.as_slice()) + .chain(app_commit.app_vm_commit.as_slice()) .chain(&user_public_values) .chunks(BN254_BYTES) { @@ -196,3 +205,43 @@ impl TryFrom for RawEvmProof { Ok(RawEvmProof { instances, proof }) } } + +#[serde_as] +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct VmStarkProofBytes { + #[serde(flatten)] + pub app_commit: AppExecutionCommit, + #[serde_as(as = "serde_with::hex::Hex")] + pub user_public_values: Vec, + #[serde_as(as = "serde_with::hex::Hex")] + pub proof: Vec, +} + +impl VmStarkProofBytes { + pub fn new(app_commit: AppExecutionCommit, proof: VmStarkProof) -> Result { + let mut user_public_values = Vec::new(); + encode_slice(&proof.user_public_values, &mut user_public_values)?; + Ok(Self { + app_commit, + user_public_values, + proof: proof.proof.encode_to_vec()?, + }) + } +} + +impl TryFrom for VmStarkProof { + type Error = std::io::Error; + fn try_from(proof: VmStarkProofBytes) -> Result { + let VmStarkProofBytes { + proof, + user_public_values, + .. + } = proof; + let mut reader = Cursor::new(user_public_values); + let user_public_values = decode_vec(&mut reader)?; + Ok(Self { + user_public_values, + proof: Proof::decode_from_bytes(&proof)?, + }) + } +} diff --git a/crates/sdk/tests/integration_test.rs b/crates/sdk/tests/integration_test.rs index 1968f1c1da..2e3421897b 100644 --- a/crates/sdk/tests/integration_test.rs +++ b/crates/sdk/tests/integration_test.rs @@ -115,9 +115,7 @@ fn app_committed_exe_for_test(app_log_blowup: usize) -> Arc> builder.assign(&b, c); }); builder.halt(); - let mut program = builder.compile_isa(); - program.max_num_public_values = NUM_PUB_VALUES; - program + builder.compile_isa() }; Sdk::new() .commit_app_exe( @@ -343,10 +341,6 @@ fn test_static_verifier_custom_pv_handler() { let app_pk = sdk.app_keygen(app_config.clone()).unwrap(); let app_committed_exe = app_committed_exe_for_test(app_log_blowup); println!("app_config: {:?}", app_config.app_vm_config); - println!( - "app_committed_exe max_num_public_values: {:?}", - app_committed_exe.exe.program.max_num_public_values - ); let params_reader = CacheHalo2ParamsReader::new_with_default_params_dir(); // Generate PK using custom PV handler @@ -356,8 +350,8 @@ fn test_static_verifier_custom_pv_handler() { &app_committed_exe, &app_pk.leaf_committed_exe, ); - let exe_commit = commits.exe_commit_to_bn254(); - let leaf_verifier_commit = commits.app_config_commit_to_bn254(); + let exe_commit = commits.app_exe_commit.to_bn254(); + let leaf_verifier_commit = commits.app_vm_commit.to_bn254(); let pv_handler = CustomPvHandler { exe_commit, @@ -399,7 +393,7 @@ fn test_static_verifier_custom_pv_handler() { #[test] fn test_e2e_proof_generation_and_verification_with_pvs() { let mut pkg_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); - pkg_dir.push("guest"); + pkg_dir.push("guest/fib"); let vm_config = SdkVmConfig::builder() .system(SdkSystemConfig { @@ -416,7 +410,13 @@ fn test_e2e_proof_generation_and_verification_with_pvs() { let sdk = Sdk::new(); let elf = sdk - .build(Default::default(), pkg_dir, &Default::default()) + .build( + Default::default(), + &vm_config, + pkg_dir, + &Default::default(), + None, + ) .unwrap(); let exe = sdk.transpile(elf, vm_config.transpiler()).unwrap(); @@ -469,12 +469,38 @@ fn test_sdk_guest_build_and_transpile() { // .with_options(vec!["--release"]); ; let mut pkg_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); - pkg_dir.push("guest"); + pkg_dir.push("guest/fib"); + + let vm_config = SdkVmConfig::builder() + .system(SdkSystemConfig { + config: SystemConfig::default() + .with_max_segment_len(200) + .with_continuations() + .with_public_values(NUM_PUB_VALUES), + }) + .rv32i(Default::default()) + .rv32m(Default::default()) + .io(Default::default()) + .native(Default::default()) + .build(); + let one = sdk - .build(guest_opts.clone(), &pkg_dir, &Default::default()) + .build( + guest_opts.clone(), + &vm_config, + &pkg_dir, + &Default::default(), + None, + ) .unwrap(); let two = sdk - .build(guest_opts.clone(), &pkg_dir, &Default::default()) + .build( + guest_opts.clone(), + &vm_config, + &pkg_dir, + &Default::default(), + None, + ) .unwrap(); assert_eq!(one.instructions, two.instructions); assert_eq!(one.instructions, two.instructions); @@ -490,8 +516,7 @@ fn test_inner_proof_codec_roundtrip() -> eyre::Result<()> { // generate a proof let sdk = Sdk::new(); let mut pkg_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); - pkg_dir.push("guest"); - let elf = sdk.build(Default::default(), pkg_dir, &Default::default())?; + pkg_dir.push("guest/fib"); let vm_config = SdkVmConfig::builder() .system(SdkSystemConfig { @@ -505,6 +530,13 @@ fn test_inner_proof_codec_roundtrip() -> eyre::Result<()> { .io(Default::default()) .native(Default::default()) .build(); + let elf = sdk.build( + Default::default(), + &vm_config, + pkg_dir, + &Default::default(), + None, + )?; assert!(vm_config.system.config.continuation_enabled); let exe = sdk.transpile(elf, vm_config.transpiler())?; let fri_params = FriParameters::standard_fast(); diff --git a/crates/toolchain/build/src/lib.rs b/crates/toolchain/build/src/lib.rs index b6cffb3164..749a29d346 100644 --- a/crates/toolchain/build/src/lib.rs +++ b/crates/toolchain/build/src/lib.rs @@ -12,7 +12,7 @@ use std::{ process::{Command, Stdio}, }; -use cargo_metadata::{MetadataCommand, Package}; +use cargo_metadata::{Metadata, MetadataCommand, Package}; use openvm_platform::memory; pub use self::config::GuestOptions; @@ -30,25 +30,14 @@ const ALLOWED_CARGO_ENVS: &[&str] = &["CARGO_HOME"]; /// Returns the given cargo Package from the metadata in the Cargo.toml manifest /// within the provided `manifest_dir`. pub fn get_package(manifest_dir: impl AsRef) -> Package { - let manifest_path = fs::canonicalize(manifest_dir.as_ref().join("Cargo.toml")).unwrap(); - let manifest_meta = MetadataCommand::new() - .manifest_path(&manifest_path) - .no_deps() - .exec() - .unwrap_or_else(|e| { - panic!( - "cargo metadata command failed for manifest path: {}: {e:?}", - manifest_path.display() - ) - }); - let mut matching: Vec = manifest_meta - .packages - .into_iter() - .filter(|pkg| { - let std_path: &Path = pkg.manifest_path.as_ref(); - std_path == manifest_path - }) - .collect(); + let manifest_path = manifest_dir + .as_ref() + .join("Cargo.toml") + .canonicalize() + .unwrap(); + let manifest_meta = get_metadata(&manifest_path); + let matching = find_matching_packages(&manifest_meta, &manifest_path); + if matching.is_empty() { eprintln!( "ERROR: No package found in {}", @@ -63,7 +52,76 @@ pub fn get_package(manifest_dir: impl AsRef) -> Package { ); std::process::exit(-1); } - matching.pop().unwrap() + matching.into_iter().next().unwrap() +} + +/// Returns all packages from the Cargo.toml manifest at the given `manifest_dir`. +pub fn get_workspace_packages(manifest_dir: impl AsRef) -> Vec { + let manifest_path = manifest_dir + .as_ref() + .join("Cargo.toml") + .canonicalize() + .unwrap(); + let manifest_meta = get_metadata(&manifest_path); + get_workspace_member_packages(manifest_meta) +} + +/// Returns a single package if the manifest path matches exactly, otherwise returns all +/// workspace packages. +pub fn get_in_scope_packages(manifest_dir: impl AsRef) -> Vec { + let manifest_path = manifest_dir + .as_ref() + .join("Cargo.toml") + .canonicalize() + .unwrap(); + let manifest_meta = get_metadata(&manifest_path); + + // Check if any package has this exact manifest path + let matching = find_matching_packages(&manifest_meta, &manifest_path); + + // If we found a package with this exact manifest path, return it + if !matching.is_empty() { + return matching; + } + + // Otherwise return all workspace members + get_workspace_member_packages(manifest_meta) +} + +/// Helper function to get cargo metadata for a manifest path +fn get_metadata(manifest_path: &Path) -> Metadata { + MetadataCommand::new() + .manifest_path(manifest_path) + .no_deps() + .exec() + .unwrap_or_else(|e| { + panic!( + "cargo metadata command failed for manifest path: {}: {e:?}", + manifest_path.display() + ) + }) +} + +/// Helper function to get workspace members +fn get_workspace_member_packages(manifest_meta: Metadata) -> Vec { + manifest_meta + .packages + .into_iter() + .filter(|pkg| manifest_meta.workspace_members.contains(&pkg.id)) + .collect() +} + +/// Helper function to find packages matching a manifest path +fn find_matching_packages(manifest_meta: &Metadata, manifest_path: &Path) -> Vec { + manifest_meta + .packages + .iter() + .filter(|pkg| { + let std_path: &Path = pkg.manifest_path.as_ref(); + std_path == manifest_path + }) + .cloned() + .collect() } /// Determines and returns the build target directory from the Cargo manifest at @@ -78,6 +136,18 @@ pub fn get_target_dir(manifest_path: impl AsRef) -> PathBuf { .into() } +/// Returns the workspace root directory from the Cargo manifest at +/// the given `manifest_path`. +pub fn get_workspace_root(manifest_path: impl AsRef) -> PathBuf { + MetadataCommand::new() + .manifest_path(manifest_path.as_ref()) + .no_deps() + .exec() + .expect("cargo metadata command failed") + .workspace_root + .into() +} + /// Returns the target executable directory given `target_dir` and `profile`. pub fn get_dir_with_profile( target_dir: impl AsRef, @@ -233,6 +303,9 @@ pub(crate) fn encode_rust_flags(rustc_flags: &[&str]) -> String { "link-arg=--fatal-warnings", "-C", "panic=abort", + // https://docs.rs/getrandom/0.3.2/getrandom/index.html#opt-in-backends + "--cfg", + "getrandom_backend=\"custom\"", ], ] .concat() @@ -269,7 +342,41 @@ pub fn build_guest_package( runtime_lib: Option<&str>, target_filter: &Option, ) -> Result> { - if is_skip_build() { + let mut new_opts = guest_opts.clone(); + + if new_opts.target_dir.is_none() { + new_opts.target_dir = Some(get_target_dir(&pkg.manifest_path)); + } + + new_opts.options.extend(vec![ + "--manifest-path".into(), + pkg.manifest_path.to_string(), + ]); + + if let Some(runtime_lib) = runtime_lib { + new_opts.rustc_flags.extend(vec![ + String::from("-C"), + format!("link_arg={}", runtime_lib), + ]); + } + + let mut example = false; + if let Some(target_filter) = target_filter { + new_opts.options.extend(vec![ + format!("--{}", target_filter.kind), + target_filter.name.clone(), + ]); + example = target_filter.kind == "example"; + } + + let res = build_generic(&new_opts); + res.map(|path| if example { path.join("examples") } else { path }) +} + +/// Generic wrapper call to cargo build +pub fn build_generic(guest_opts: &GuestOptions) -> Result> { + if is_skip_build() || guest_opts.target_dir.is_none() { + eprintln!("Skipping build"); return Err(None); } @@ -280,45 +387,16 @@ pub fn build_guest_package( return Err(Some(code)); } - let target_dir = guest_opts - .target_dir - .clone() - .unwrap_or_else(|| get_target_dir(pkg.manifest_path.clone())); - - fs::create_dir_all(&target_dir).unwrap(); - - let runtime_rust_flags = runtime_lib - .map(|lib| vec![String::from("-C"), format!("link_arg={}", lib)]) - .unwrap_or_default(); - let rust_flags: Vec<_> = [ - runtime_rust_flags - .iter() - .map(|s| s.as_str()) - .collect::>(), - guest_opts.rustc_flags.iter().map(|s| s.as_str()).collect(), - ] - .concat(); + let target_dir = guest_opts.target_dir.as_ref().unwrap(); + fs::create_dir_all(target_dir).unwrap(); + let rust_flags: Vec<_> = guest_opts.rustc_flags.iter().map(|s| s.as_str()).collect(); let mut cmd = cargo_command("build", &rust_flags); - let features_str = guest_opts.features.join(","); - if !features_str.is_empty() { - cmd.args(["--features", &features_str]); - } - - cmd.args([ - "--manifest-path", - pkg.manifest_path.as_str(), - "--target-dir", - target_dir.to_str().unwrap(), - ]); - - if let Some(target_filter) = target_filter { - cmd.args([ - format!("--{}", target_filter.kind).as_str(), - target_filter.name.as_str(), - ]); + if !guest_opts.features.is_empty() { + cmd.args(["--features", guest_opts.features.join(",").as_str()]); } + cmd.args(["--target-dir", target_dir.to_str().unwrap()]); let profile = if let Some(profile) = &guest_opts.profile { profile @@ -346,24 +424,17 @@ pub fn build_guest_package( .expect("cargo build failed"); let stderr = child.stderr.take().unwrap(); - tty_println(&format!("{}: Starting build for {RUSTC_TARGET}", pkg.name)); + tty_println(&format!("openvm build: Starting build for {RUSTC_TARGET}")); for line in BufReader::new(stderr).lines() { - tty_println(&format!("{}: {}", pkg.name, line.unwrap())); + tty_println(&format!("openvm build: {}", line.unwrap())); } let res = child.wait().expect("Guest 'cargo build' failed"); if !res.success() { Err(res.code()) } else { - Ok(get_dir_with_profile( - &target_dir, - profile, - target_filter - .as_ref() - .map(|t| t.kind == "example") - .unwrap_or(false), - )) + Ok(get_dir_with_profile(target_dir, profile, false)) } } diff --git a/crates/toolchain/instructions/Cargo.toml b/crates/toolchain/instructions/Cargo.toml index 5bf558da8d..99286c0854 100644 --- a/crates/toolchain/instructions/Cargo.toml +++ b/crates/toolchain/instructions/Cargo.toml @@ -21,3 +21,11 @@ num-bigint.workspace = true num-traits.workspace = true [dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } +p3-baby-bear.workspace = true +bitcode.workspace = true +rand.workspace = true + +[[bench]] +name = "program_serde" +harness = false \ No newline at end of file diff --git a/crates/toolchain/instructions/benches/program_serde.rs b/crates/toolchain/instructions/benches/program_serde.rs new file mode 100644 index 0000000000..240d184ab4 --- /dev/null +++ b/crates/toolchain/instructions/benches/program_serde.rs @@ -0,0 +1,39 @@ +use std::hint::black_box; + +use criterion::{criterion_group, criterion_main, Criterion}; +use openvm_instructions::{instruction::Instruction, program::Program, VmOpcode}; +use p3_baby_bear::BabyBear; +use rand::prelude::*; + +type F = BabyBear; + +fn random_instruction(rng: &mut impl Rng) -> Instruction { + Instruction::new( + VmOpcode::from_usize(rng.gen()), + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + ) +} + +fn program_serde_bench(c: &mut Criterion) { + let mut rng = StdRng::from_seed([42; 32]); + let instructions: Vec<_> = (0..100_000).map(|_| random_instruction(&mut rng)).collect(); + let program: Program = Program::from_instructions(&instructions); + c.bench_function("bitcode serialize Program with 100000 instructions", |b| { + b.iter(|| bitcode::serialize(black_box(&program))) + }); + let bytes = bitcode::serialize(&program).unwrap(); + println!("Result length in bytes: {}", bytes.len()); + c.bench_function( + "bitcode deserialize Program with 100000 instructions", + |b| b.iter(|| bitcode::deserialize::<'_, Program>(black_box(&bytes))), + ); +} + +criterion_group!(benches, program_serde_bench); +criterion_main!(benches); diff --git a/crates/toolchain/instructions/src/program.rs b/crates/toolchain/instructions/src/program.rs index 08bbc51806..010b70514d 100644 --- a/crates/toolchain/instructions/src/program.rs +++ b/crates/toolchain/instructions/src/program.rs @@ -2,7 +2,7 @@ use std::{fmt, fmt::Display}; use itertools::Itertools; use openvm_stark_backend::p3_field::Field; -use serde::{Deserialize, Serialize}; +use serde::{de::Deserializer, Deserialize, Serialize, Serializer}; use crate::instruction::{DebugInfo, Instruction}; @@ -10,31 +10,30 @@ pub const PC_BITS: usize = 30; /// We use default PC step of 4 whenever possible for consistency with RISC-V, where 4 comes /// from the fact that each standard RISC-V instruction is 32-bits = 4 bytes. pub const DEFAULT_PC_STEP: u32 = 4; -pub const DEFAULT_MAX_NUM_PUBLIC_VALUES: usize = 32; pub const MAX_ALLOWED_PC: u32 = (1 << PC_BITS) - 1; #[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(bound(serialize = "F: Serialize", deserialize = "F: Deserialize<'de>"))] pub struct Program { /// A map from program counter to instruction. /// Sometimes the instructions are enumerated as 0, 4, 8, etc. /// Maybe at some point we will replace this with a struct that would have a `Vec` under the /// hood and divide the incoming `pc` by whatever given. + #[serde( + serialize_with = "serialize_instructions_and_debug_infos", + deserialize_with = "deserialize_instructions_and_debug_infos" + )] pub instructions_and_debug_infos: Vec, Option)>>, pub step: u32, pub pc_base: u32, - /// The upper bound of the number of public values the program would publish. - /// Currently, this won't result any constraint. But users should always be aware of the limit - /// of public values when they write programs. - pub max_num_public_values: usize, } impl Program { - pub fn new_empty(step: u32, pc_base: u32, max_num_public_values: usize) -> Self { + pub fn new_empty(step: u32, pc_base: u32) -> Self { Self { instructions_and_debug_infos: vec![], step, pc_base, - max_num_public_values, } } @@ -42,7 +41,6 @@ impl Program { instructions: &[Instruction], step: u32, pc_base: u32, - max_num_public_values: usize, ) -> Self { Self { instructions_and_debug_infos: instructions @@ -51,7 +49,6 @@ impl Program { .collect(), step, pc_base, - max_num_public_values, } } @@ -59,7 +56,6 @@ impl Program { instructions: &[Option>], step: u32, pc_base: u32, - max_num_public_values: usize, ) -> Self { Self { instructions_and_debug_infos: instructions @@ -68,7 +64,6 @@ impl Program { .collect(), step, pc_base, - max_num_public_values, } } @@ -86,7 +81,6 @@ impl Program { .collect(), step: DEFAULT_PC_STEP, pc_base: 0, - max_num_public_values: DEFAULT_MAX_NUM_PUBLIC_VALUES, } } @@ -102,12 +96,7 @@ impl Program { } pub fn from_instructions(instructions: &[Instruction]) -> Self { - Self::new_without_debug_infos( - instructions, - DEFAULT_PC_STEP, - 0, - DEFAULT_MAX_NUM_PUBLIC_VALUES, - ) + Self::new_without_debug_infos(instructions, DEFAULT_PC_STEP, 0) } pub fn len(&self) -> usize { @@ -224,3 +213,81 @@ pub fn display_program_with_pc(program: &Program) { ); } } + +// `debug_info` is based on the symbol table of the binary. Usually serializing `debug_info` is not +// meaningful because the program is executed by another binary. So here we only serialize +// instructions. +fn serialize_instructions_and_debug_infos( + data: &[Option<(Instruction, Option)>], + serializer: S, +) -> Result { + let mut ins_data = Vec::with_capacity(data.len()); + let total_len = data.len() as u32; + for (i, o) in data.iter().enumerate() { + if let Some(o) = o { + ins_data.push((&o.0, i as u32)); + } + } + (ins_data, total_len).serialize(serializer) +} + +#[allow(clippy::type_complexity)] +fn deserialize_instructions_and_debug_infos<'de, F: Deserialize<'de>, D: Deserializer<'de>>( + deserializer: D, +) -> Result, Option)>>, D::Error> { + let (inst_data, total_len): (Vec<(Instruction, u32)>, u32) = + Deserialize::deserialize(deserializer)?; + let mut ret: Vec, Option)>> = Vec::new(); + ret.resize_with(total_len as usize, || None); + for (inst, i) in inst_data { + ret[i as usize] = Some((inst, None)); + } + Ok(ret) +} + +#[cfg(test)] +mod tests { + use itertools::izip; + use p3_baby_bear::BabyBear; + + use super::*; + use crate::VmOpcode; + + type F = BabyBear; + + #[test] + fn test_program_serde() { + let mut program = Program::::new_empty(4, 0); + program.instructions_and_debug_infos.push(Some(( + Instruction::from_isize(VmOpcode::from_usize(113), 1, 2, 3, 4, 5), + None, + ))); + program.instructions_and_debug_infos.push(None); + program.instructions_and_debug_infos.push(None); + program.instructions_and_debug_infos.push(Some(( + Instruction::from_isize(VmOpcode::from_usize(145), 10, 20, 30, 40, 50), + None, + ))); + program.instructions_and_debug_infos.push(Some(( + Instruction::from_isize(VmOpcode::from_usize(145), 10, 20, 30, 40, 50), + None, + ))); + program.instructions_and_debug_infos.push(None); + let bytes = bitcode::serialize(&program).unwrap(); + let de_program: Program = bitcode::deserialize(&bytes).unwrap(); + for (expected_ins, ins) in izip!( + &program.instructions_and_debug_infos, + &de_program.instructions_and_debug_infos + ) { + match (expected_ins, ins) { + (Some(expected_ins), Some(ins)) => { + assert_eq!(expected_ins.0, ins.0); + } + (None, None) => {} + _ => { + panic!("Different instructions after serialization"); + } + } + } + } +} diff --git a/crates/toolchain/openvm/Cargo.toml b/crates/toolchain/openvm/Cargo.toml index 08a7996a3d..1692e30f79 100644 --- a/crates/toolchain/openvm/Cargo.toml +++ b/crates/toolchain/openvm/Cargo.toml @@ -9,15 +9,15 @@ repository.workspace = true license.workspace = true [dependencies] -openvm-platform = { workspace = true, features = [ - "rust-runtime", - "export-getrandom", -] } +openvm-platform = { workspace = true, features = ["rust-runtime"] } openvm-custom-insn = { workspace = true } openvm-rv32im-guest = { workspace = true } serde = { workspace = true, features = ["alloc"] } bytemuck = { workspace = true, features = ["extern_crate_alloc"] } +[target.'cfg(target_os = "zkvm")'.dependencies] +getrandom = { version = "0.3", optional = true } + [target.'cfg(not(target_os = "zkvm"))'.dependencies] num-bigint.workspace = true @@ -25,10 +25,9 @@ num-bigint.workspace = true chrono = { version = "0.4", default-features = false, features = ["serde"] } [features] -default = [] -# The zkVM exposes a getrandom implementation that panics by default. -# This (currently unimplemented) feature expose a getrandom implementation that uses host randomness. -getrandom = ["openvm-platform/getrandom"] +default = ["getrandom-unsupported"] +# Defines a custom getrandom backend that always errors. This feature should be enabled if you are sure getrandom is never used but it is pulled in as a compilation dependency. +getrandom-unsupported = ["dep:getrandom"] # The zkVM uses a bump-pointer heap allocator by default which does not free # memory. This will use a slower linked-list heap allocator to reclaim memory. heap-embedded-alloc = ["openvm-platform/heap-embedded-alloc"] diff --git a/crates/toolchain/openvm/src/getrandom.rs b/crates/toolchain/openvm/src/getrandom.rs new file mode 100644 index 0000000000..429c2c9b76 --- /dev/null +++ b/crates/toolchain/openvm/src/getrandom.rs @@ -0,0 +1,14 @@ +//! [getrandom] custom backend implementations. The implementations are feature-gated. The default +//! feature enables "getrandom-unsupported", which is a backend that always errors. This should be +//! used when `getrandom` is never called but pulled in as a dependency unavoidably. If no feature +//! is enabled, then no custom implementation is registered, and the user must supply their own as +//! described in the [getrandom] documentation. + +#[cfg(feature = "getrandom-unsupported")] +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + _dest: *mut u8, + _len: usize, +) -> Result<(), getrandom::Error> { + Err(getrandom::Error::UNSUPPORTED) +} diff --git a/crates/toolchain/openvm/src/io/mod.rs b/crates/toolchain/openvm/src/io/mod.rs index 0c3b80ccca..eb00a9d3cd 100644 --- a/crates/toolchain/openvm/src/io/mod.rs +++ b/crates/toolchain/openvm/src/io/mod.rs @@ -56,6 +56,16 @@ fn hint_store_word(ptr: *mut u32) { } } +/// Load hints by key and append into the input stream. +#[allow(unused_variables)] +#[inline(always)] +pub fn hint_load_by_key(key: &[u8]) { + #[cfg(target_os = "zkvm")] + openvm_rv32im_guest::hint_load_by_key(key.as_ptr(), key.len() as u32); + #[cfg(not(target_os = "zkvm"))] + panic!("hint_load_by_key cannot run on non-zkVM platforms"); +} + /// Read the next `len` bytes from the hint stream into a vector. pub(crate) fn read_vec_by_len(len: usize) -> Vec { let num_words = len.div_ceil(4); @@ -116,6 +126,16 @@ pub fn reveal_u32(x: u32, index: usize) { println!("reveal {} at byte location {}", x, index * 4); } +/// Store u32 `x` to the native address `native_addr` as 4 field element in byte. +#[allow(unused_variables)] +#[inline(always)] +pub fn store_u32_to_native(native_addr: u32, x: u32) { + #[cfg(target_os = "zkvm")] + openvm_rv32im_guest::store_to_native!(native_addr, x); + #[cfg(not(target_os = "zkvm"))] + panic!("store_to_native_u32 cannot run on non-zkVM platforms"); +} + /// A no-alloc writer to print to stdout on host machine for debugging purposes. pub struct Writer; diff --git a/crates/toolchain/openvm/src/lib.rs b/crates/toolchain/openvm/src/lib.rs index 134d10b559..d0507db84c 100644 --- a/crates/toolchain/openvm/src/lib.rs +++ b/crates/toolchain/openvm/src/lib.rs @@ -18,6 +18,8 @@ use openvm_platform::rust_rt; #[cfg(target_os = "zkvm")] pub use openvm_rv32im_guest::*; +#[cfg(target_os = "zkvm")] +mod getrandom; pub mod io; #[cfg(all(feature = "std", target_os = "zkvm"))] pub mod pal_abi; @@ -162,3 +164,14 @@ fn panic_impl(panic_info: &core::panic::PanicInfo) -> ! { openvm_platform::rust_rt::terminate::<1>(); unreachable!() } + +// Includes the openvm_init.rs file generated at build time +#[macro_export] +macro_rules! init { + () => { + include!(concat!(env!("CARGO_MANIFEST_DIR"), "/openvm_init.rs")); + }; + ($name:expr) => { + include!(concat!(env!("CARGO_MANIFEST_DIR"), concat!("/", $name))); + }; +} diff --git a/crates/toolchain/platform/Cargo.toml b/crates/toolchain/platform/Cargo.toml index 9f6bb6d47a..26d935dc41 100644 --- a/crates/toolchain/platform/Cargo.toml +++ b/crates/toolchain/platform/Cargo.toml @@ -18,29 +18,16 @@ critical-section = { version = "1.1.2", optional = true } embedded-alloc = { version = "0.6.0", features = [ "allocator_api", ], optional = true } -getrandom = { version = "0.2", features = ["custom"], optional = true } libm = { version = "0.2", optional = true } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] -features = [ - "rust-runtime", - "panic-handler", - "export-libm", - "export-getrandom", - "export-pal-abi", - "getrandom", - "unstable", -] +features = ["rust-runtime", "panic-handler", "export-libm", "export-pal-abi"] [features] default = [] entrypoint = [] -# exports a `getrandom` implementation that panics -export-getrandom = ["dep:getrandom"] export-libm = ["dep:libm"] -# Currently unimplemented: exports a `getrandom` implementation that uses sys_random -getrandom = ["export-getrandom"] heap-embedded-alloc = [ "dep:critical-section", "dep:embedded-alloc", @@ -50,4 +37,3 @@ panic-handler = [] # Build a rust runtime rust-runtime = ["export-libm"] std = [] -unstable = [] diff --git a/crates/toolchain/platform/src/getrandom.rs b/crates/toolchain/platform/src/getrandom.rs deleted file mode 100644 index 1edb4578ee..0000000000 --- a/crates/toolchain/platform/src/getrandom.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! We need to export a custom getrandom implementation just to get crates that import getrandom to -//! compile. -use getrandom::{register_custom_getrandom, Error}; - -/// This is a getrandom handler for the zkvm. It's intended to hook into a -/// getrandom crate or a dependent of the getrandom crate used by the guest code. -#[cfg(feature = "getrandom")] -pub fn zkvm_getrandom(dest: &mut [u8]) -> Result<(), Error> { - todo!() - // Randomness would come from the host -} - -#[cfg(not(feature = "getrandom"))] -pub fn zkvm_getrandom(dest: &mut [u8]) -> Result<(), Error> { - panic!("getrandom is not enabled in the current build"); -} - -register_custom_getrandom!(zkvm_getrandom); diff --git a/crates/toolchain/platform/src/lib.rs b/crates/toolchain/platform/src/lib.rs index 901666d530..1ace328a66 100644 --- a/crates/toolchain/platform/src/lib.rs +++ b/crates/toolchain/platform/src/lib.rs @@ -6,8 +6,6 @@ #[cfg(all(feature = "rust-runtime", target_os = "zkvm"))] pub use openvm_custom_insn::{custom_insn_i, custom_insn_r}; -#[cfg(all(feature = "export-getrandom", target_os = "zkvm"))] -mod getrandom; #[cfg(all(feature = "rust-runtime", target_os = "zkvm"))] pub mod heap; #[cfg(all(feature = "export-libm", target_os = "zkvm"))] diff --git a/crates/toolchain/tests/Cargo.toml b/crates/toolchain/tests/Cargo.toml index d31d388c32..9f3e3caa82 100644 --- a/crates/toolchain/tests/Cargo.toml +++ b/crates/toolchain/tests/Cargo.toml @@ -18,7 +18,7 @@ openvm-bigint-circuit.workspace = true openvm-rv32im-circuit.workspace = true openvm-rv32im-transpiler.workspace = true openvm-algebra-circuit.workspace = true -openvm-ecc-guest = { workspace = true, features = ["halo2curves", "k256"] } +openvm-ecc-circuit = { workspace = true } openvm-instructions = { workspace = true } openvm-platform = { workspace = true } diff --git a/crates/toolchain/tests/src/lib.rs b/crates/toolchain/tests/src/lib.rs index 6fdae2b4d0..7c9c01f62e 100644 --- a/crates/toolchain/tests/src/lib.rs +++ b/crates/toolchain/tests/src/lib.rs @@ -7,6 +7,7 @@ use eyre::{Context, Result}; use openvm_build::{ build_guest_package, get_dir_with_profile, get_package, GuestOptions, TargetFilter, }; +use openvm_circuit::arch::{InitFileGenerator, OPENVM_DEFAULT_INIT_FILE_BASENAME}; use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE}; use tempfile::tempdir; @@ -30,33 +31,63 @@ pub fn decode_elf(elf_path: impl AsRef) -> Result { Elf::decode(&data, MEM_SIZE as u32) } -pub fn build_example_program(example_name: &str) -> Result { - build_example_program_with_features::<&str>(example_name, []) +// Some tests need to manually override the init macro build script (e.g. to test invalid moduli), +// so we can use this struct to avoid generating an init file +pub struct NoInitFile; +impl InitFileGenerator for NoInitFile {} + +pub fn build_example_program( + example_name: &str, + init_config: &impl InitFileGenerator, +) -> Result { + build_example_program_with_features::<&str>(example_name, [], init_config) } pub fn build_example_program_with_features>( example_name: &str, - features: impl IntoIterator, + features: impl IntoIterator + Clone, + init_config: &impl InitFileGenerator, ) -> Result { let manifest_dir = get_programs_dir!(); - build_example_program_at_path_with_features(manifest_dir, example_name, features) + build_example_program_at_path_with_features(manifest_dir, example_name, features, init_config) } -pub fn build_example_program_at_path(manifest_dir: PathBuf, example_name: &str) -> Result { - build_example_program_at_path_with_features::<&str>(manifest_dir, example_name, []) +pub fn build_example_program_at_path( + manifest_dir: PathBuf, + example_name: &str, + init_config: &impl InitFileGenerator, +) -> Result { + build_example_program_at_path_with_features::<&str>(manifest_dir, example_name, [], init_config) } pub fn build_example_program_at_path_with_features>( manifest_dir: PathBuf, example_name: &str, - features: impl IntoIterator, + features: impl IntoIterator + Clone, + init_config: &impl InitFileGenerator, ) -> Result { - let pkg = get_package(manifest_dir); + let pkg = get_package(&manifest_dir); let target_dir = tempdir()?; // Build guest with default features let guest_opts = GuestOptions::default() - .with_features(features) + .with_features(features.clone()) .with_target_dir(target_dir.path()); + let features = features + .into_iter() + .map(|x| x.as_ref().to_string()) + .collect::>(); + let features_str = if !features.is_empty() { + format!("_{}", features.join("_")) + } else { + "".to_string() + }; + init_config.write_to_init_file( + &manifest_dir, + Some(&format!( + "{}_{}{}.rs", + OPENVM_DEFAULT_INIT_FILE_BASENAME, example_name, features_str + )), + )?; if let Err(Some(code)) = build_guest_package( &pkg, &guest_opts, diff --git a/crates/toolchain/tests/tests/transpiler_tests.rs b/crates/toolchain/tests/tests/transpiler_tests.rs index 82d74afcca..bf07eccc42 100644 --- a/crates/toolchain/tests/tests/transpiler_tests.rs +++ b/crates/toolchain/tests/tests/transpiler_tests.rs @@ -12,11 +12,11 @@ use openvm_algebra_circuit::{ use openvm_algebra_transpiler::{Fp2TranspilerExtension, ModularTranspilerExtension}; use openvm_bigint_circuit::{Int256, Int256Executor, Int256Periphery}; use openvm_circuit::{ - arch::{SystemConfig, VmExecutor}, + arch::{InitFileGenerator, SystemConfig, VmExecutor}, derive::VmConfig, utils::air_test, }; -use openvm_ecc_guest::k256::{SECP256K1_MODULUS, SECP256K1_ORDER}; +use openvm_ecc_circuit::{SECP256K1_MODULUS, SECP256K1_ORDER}; use openvm_instructions::exe::VmExe; use openvm_platform::memory::MEM_SIZE; use openvm_rv32im_circuit::{ @@ -104,7 +104,7 @@ pub struct Rv32ModularFp2Int256Config { } impl Rv32ModularFp2Int256Config { - pub fn new(modular_moduli: Vec, fp2_moduli: Vec) -> Self { + pub fn new(modular_moduli: Vec, fp2_moduli: Vec<(String, BigUint)>) -> Self { Self { system: SystemConfig::default().with_continuations(), base: Default::default(), @@ -117,11 +117,21 @@ impl Rv32ModularFp2Int256Config { } } +impl InitFileGenerator for Rv32ModularFp2Int256Config { + fn generate_init_file_contents(&self) -> Option { + Some(format!( + "{}\n{}\n", + self.modular.generate_moduli_init(), + self.fp2.generate_complex_init(&self.modular) + )) + } +} + #[test_case("tests/data/rv32im-intrin-from-as")] fn test_intrinsic_runtime(elf_path: &str) -> Result<()> { let config = Rv32ModularFp2Int256Config::new( vec![SECP256K1_MODULUS.clone(), SECP256K1_ORDER.clone()], - vec![SECP256K1_MODULUS.clone()], + vec![("Secp256k1Coord".to_string(), SECP256K1_MODULUS.clone())], ); let elf = get_elf(elf_path)?; let openvm_exe = VmExe::from_elf( diff --git a/crates/toolchain/transpiler/src/elf.rs b/crates/toolchain/transpiler/src/elf.rs index 5059d90454..ab355683d5 100644 --- a/crates/toolchain/transpiler/src/elf.rs +++ b/crates/toolchain/transpiler/src/elf.rs @@ -19,8 +19,6 @@ use openvm_instructions::exe::FnBound; use openvm_instructions::{exe::FnBounds, program::MAX_ALLOWED_PC}; use openvm_platform::WORD_SIZE; -pub const ELF_DEFAULT_MAX_NUM_PUBLIC_VALUES: usize = 32; - /// RISC-V 32IM ELF (Executable and Linkable Format) File. /// /// This file represents a binary in the ELF format, specifically the RISC-V 32IM architecture @@ -40,9 +38,6 @@ pub struct Elf { pub(crate) pc_base: u32, /// The initial memory image, useful for global constants. pub(crate) memory_image: BTreeMap, - /// The upper bound of the number of public values the program would publish. - /// TODO: read from project config. - pub(crate) max_num_public_values: usize, /// Debug info for spanning benchmark metrics by function. pub(crate) fn_bounds: FnBounds, } @@ -61,7 +56,6 @@ impl Elf { pc_start, pc_base, memory_image, - max_num_public_values: ELF_DEFAULT_MAX_NUM_PUBLIC_VALUES, fn_bounds, } } diff --git a/crates/toolchain/transpiler/src/lib.rs b/crates/toolchain/transpiler/src/lib.rs index bf88d25a8e..367b028393 100644 --- a/crates/toolchain/transpiler/src/lib.rs +++ b/crates/toolchain/transpiler/src/lib.rs @@ -33,7 +33,6 @@ impl FromElf for VmExe { &instructions, DEFAULT_PC_STEP, elf.pc_base, - elf.max_num_public_values, ); let init_memory = elf_memory_image_to_openvm_memory_image(elf.memory_image); diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index 30d92130a1..d82b5f7cf0 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -1,8 +1,7 @@ -use std::sync::Arc; +use std::{fs::File, io::Write, path::Path, sync::Arc}; use derive_new::new; use openvm_circuit::system::memory::MemoryTraceHeights; -use openvm_instructions::program::DEFAULT_MAX_NUM_PUBLIC_VALUES; use openvm_poseidon2_air::Poseidon2Config; use openvm_stark_backend::{p3_field::PrimeField32, ChipUsageGetter}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -17,6 +16,7 @@ use crate::system::memory::BOUNDARY_AIR_OFFSET; // sbox is decomposed to have this max degree for Poseidon2. We set to 3 so quotient_degree = 2 // allows log_blowup = 1 const DEFAULT_POSEIDON2_MAX_CONSTRAINT_DEGREE: usize = 3; +pub const DEFAULT_MAX_NUM_PUBLIC_VALUES: usize = 32; /// Width of Poseidon2 VM uses. pub const POSEIDON2_WIDTH: usize = 16; /// Returns a Poseidon2 config for the VM. @@ -24,7 +24,9 @@ pub fn vm_poseidon2_config() -> Poseidon2Config { Poseidon2Config::default() } -pub trait VmConfig: Clone + Serialize + DeserializeOwned { +pub trait VmConfig: + Clone + Serialize + DeserializeOwned + InitFileGenerator +{ type Executor: InstructionExecutor + AnyEnum + ChipUsageGetter; type Periphery: AnyEnum + ChipUsageGetter; @@ -37,6 +39,35 @@ pub trait VmConfig: Clone + Serialize + DeserializeOwned { ) -> Result, VmInventoryError>; } +pub const OPENVM_DEFAULT_INIT_FILE_BASENAME: &str = "openvm_init"; +pub const OPENVM_DEFAULT_INIT_FILE_NAME: &str = "openvm_init.rs"; + +/// Trait for generating a init.rs file that contains a call to moduli_init!, +/// complex_init!, sw_init! with the supported moduli and curves. +/// Should be implemented by all VM config structs. +pub trait InitFileGenerator { + // Default implementation is no init file. + fn generate_init_file_contents(&self) -> Option { + None + } + + // Do not override this method's default implementation. + // This method is called by cargo openvm and the SDK before building the guest package. + fn write_to_init_file( + &self, + manifest_dir: &Path, + init_file_name: Option<&str>, + ) -> eyre::Result<()> { + if let Some(contents) = self.generate_init_file_contents() { + let dest_path = Path::new(manifest_dir) + .join(init_file_name.unwrap_or(OPENVM_DEFAULT_INIT_FILE_NAME)); + let mut f = File::create(&dest_path)?; + write!(f, "{}", contents)?; + } + Ok(()) + } +} + #[derive(Debug, Serialize, Deserialize, Clone, new, Copy)] pub struct MemoryConfig { /// The maximum height of the address space. This means the trie has `as_height` layers for @@ -222,3 +253,6 @@ impl VmConfig for SystemConfig { Ok(complex) } } + +// Default implementation uses no init file +impl InitFileGenerator for SystemConfig {} diff --git a/crates/vm/src/arch/vm.rs b/crates/vm/src/arch/vm.rs index a826fb4137..c9d5cb2ffc 100644 --- a/crates/vm/src/arch/vm.rs +++ b/crates/vm/src/arch/vm.rs @@ -1,4 +1,10 @@ -use std::{borrow::Borrow, collections::VecDeque, marker::PhantomData, mem, sync::Arc}; +use std::{ + borrow::Borrow, + collections::{HashMap, VecDeque}, + marker::PhantomData, + mem, + sync::Arc, +}; use openvm_circuit::system::program::trace::compute_exe_commit; use openvm_instructions::exe::VmExe; @@ -49,11 +55,25 @@ pub enum GenerationError { /// VM memory state for continuations. pub type VmMemoryState = MemoryImage; -#[derive(Clone, Default, Debug)] +/// A trait for key-value store for `Streams`. +pub trait KvStore: Send + Sync { + fn get(&self, key: &[u8]) -> Option<&[u8]>; +} + +impl KvStore for HashMap, Vec> { + fn get(&self, key: &[u8]) -> Option<&[u8]> { + self.get(key).map(|v| v.as_slice()) + } +} + +#[derive(Clone)] pub struct Streams { pub input_stream: VecDeque>, pub hint_stream: VecDeque, pub hint_space: Vec>, + /// The key-value store for hints. Both key and value are byte arrays. Executors which + /// read `kv_store` need to encode the key and decode the value. + pub kv_store: Arc, } impl Streams { @@ -62,10 +82,17 @@ impl Streams { input_stream: input_stream.into(), hint_stream: VecDeque::default(), hint_space: Vec::default(), + kv_store: Arc::new(HashMap::new()), } } } +impl Default for Streams { + fn default() -> Self { + Self::new(VecDeque::default()) + } +} + impl From>> for Streams { fn from(value: VecDeque>) -> Self { Streams::new(value) diff --git a/crates/vm/src/system/connector/mod.rs b/crates/vm/src/system/connector/mod.rs index 85a92d1185..2223bd1b9c 100644 --- a/crates/vm/src/system/connector/mod.rs +++ b/crates/vm/src/system/connector/mod.rs @@ -58,6 +58,20 @@ pub struct VmConnectorPvs { pub is_terminate: F, } +impl VmConnectorPvs { + pub fn is_terminate(&self) -> bool { + self.is_terminate == F::from_bool(true) + } + + pub fn exit_code(&self) -> Option { + if self.is_terminate() && self.exit_code == F::ZERO { + Some(self.exit_code.as_canonical_u32()) + } else { + None + } + } +} + impl BaseAirWithPublicValues for VmConnectorAir { fn num_public_values(&self) -> usize { VmConnectorPvs::::width() diff --git a/crates/vm/src/system/program/tests/mod.rs b/crates/vm/src/system/program/tests/mod.rs index 8a2726e150..4a0293b348 100644 --- a/crates/vm/src/system/program/tests/mod.rs +++ b/crates/vm/src/system/program/tests/mod.rs @@ -2,7 +2,7 @@ use std::iter; use openvm_instructions::{ instruction::Instruction, - program::{Program, DEFAULT_MAX_NUM_PUBLIC_VALUES, DEFAULT_PC_STEP}, + program::{Program, DEFAULT_PC_STEP}, LocalOpcode, }; use openvm_native_compiler::{ @@ -265,12 +265,7 @@ fn test_program_with_undefined_instructions() { )), ]; - let program = Program::new_without_debug_infos_with_option( - &instructions, - DEFAULT_PC_STEP, - 0, - DEFAULT_MAX_NUM_PUBLIC_VALUES, - ); + let program = Program::new_without_debug_infos_with_option(&instructions, DEFAULT_PC_STEP, 0); interaction_test(program, vec![0, 2, 5]); } diff --git a/docs/crates/vm-extensions.md b/docs/crates/vm-extensions.md index 70ddef0277..490bac08c5 100644 --- a/docs/crates/vm-extensions.md +++ b/docs/crates/vm-extensions.md @@ -152,6 +152,14 @@ function. What this does in words: For each extension's inventory generation, the `VmInventoryBuilder` is provided with a view of all current chips already inside the running chip complex. This means the inventory generation process is sequential in the order the extensions are specified, and each extension has borrow access to all chips constructed by any extension before it. +## Build hooks +Some of our extensions need to generate some code at build-time depending on the VM config (for example, the Algebra extension needs to call `moduli_init!` with the appropriate moduli). +To accommodate this, we support build hooks in both `cargo openvm` and the SDK. +To make use of this functionality, implement the `InitFileGenerator` trait. +The `String` returned by the `generate_init_file_contents` must be valid Rust code. +It will be written to a `openvm_init.rs` file in the package's manifest directory, and then (unhygenically) included in the guest code in place of the `openvm::init!` macro. +You can specify a custom file name at build time (by a `cargo openvm` option or an SDK method argument), in which case you must also pass it to `openvm::init!` as an argument. + ## Examples The [`extensions/`](../../extensions/) folder contains extensions implementing all non-system functionality via custom extensions. For example, the `Rv32I`, `Rv32M`, and `Rv32Io` extensions implement `VmExtension` in [`openvm-rv32im-circuit`](../../extensions/rv32im/circuit/) and correspond to the RISC-V 32-bit base and multiplication instruction sets and an extension for IO, respectively. diff --git a/docs/specs/ISA.md b/docs/specs/ISA.md index 1bc3d0c4a0..2755243cdf 100644 --- a/docs/specs/ISA.md +++ b/docs/specs/ISA.md @@ -171,6 +171,9 @@ structures during runtime execution: - `hint_space`: a vector of vectors of field elements used to store hints during runtime execution via [phantom sub-instructions](#phantom-sub-instructions) such as `NativeHintLoad`. The outer `hint_space` vector is append-only, but each internal `hint_space[hint_id]` vector may be mutated, including deletions, by the host. +- `kv_store`: a read-only key-value store for hints. Executors(e.g. `Rv32HintLoadByKey`) can read data from `kv_store` + at runtime. `kv_store` is designed for general purposes so both key and value are byte arrays. Encoding of key/value + are decided by each executor. Users need to use the corresponding encoding when adding data to `kv_store`. These data structures are **not** part of the guest state, and their state depends on host behavior that cannot be determined by the guest. @@ -204,7 +207,7 @@ which must satisfy the following conditions: - The execution has full read/write access to the data memory, except address space `0` must be read-only. - User public outputs can be set at any index in `[0, num_public_values)`. If continuations are disabled, a public value cannot be overwritten with a different value once it is set. -- Input stream can only be popped from the front as a queue. Appends are not allowed. +- Input stream can only be popped from the front as a queue. - Full read/write access to the hint stream. - Hint spaces can be read from at any index. Hint spaces may be mutated only by append. - The program counter is set to a new `to_pc` at the end of the instruction execution. @@ -395,7 +398,7 @@ signed×unsigned multiplication respectively. DIV_RV32 and DIVU_RV32 perform signed and unsigned integer division of 32-bits by 32-bits. REM_RV32 and REMU_RV32 provide the remainder of the corresponding division operation. Integer division is defined by -`dividend = q * divisor + r` where `0 <= |r| < |divisor|` and either `sign(r) = sign(divisor)` or `r = 0`. +`dividend = q * divisor + r` where `0 <= |r| < |divisor|` and either `sign(r) = sign(dividend)` or `r = 0`. Below `x[n:m]` denotes the bits from `n` to `m` inclusive of `x`. @@ -426,12 +429,12 @@ with user input-output. The RV32IM extension defines the following phantom sub-instructions. -| Name | Discriminant | Operands | Description | -| -------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Rv32HintInput | 0x20 | `_` | Pops a vector `hint` of field elements from the input stream and resets the hint stream to equal the vector `[(hint.len() as u32).to_le_bytes()), hint].concat()`. | -| Rv32PrintStr | 0x21 | `a,b,_` | Peeks at `[r32{0}(a)..r32{0}(a) + r32{0}(b)]_2`, tries to convert to byte array and then UTF-8 string and prints to host stdout. Prints error message if conversion fails. Does not change any VM state. | -| Rv32HintRandom | 0x22 | `a,_,_` | Resets the hint stream to `4 * r32{0}(a)` random bytes. The source of randomness is the host operating system (`rand::rngs::OsRng`). Its result is not constrained in any way. | - +| Name | Discriminant | Operands | Description | +|-------------------| ------------ | -------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Rv32HintInput | 0x20 | `_` | Pops a vector `hint` of field elements from the input stream and resets the hint stream to equal the vector `[(hint.len() as u32).to_le_bytes()), hint].concat()`. | +| Rv32PrintStr | 0x21 | `a,b,_` | Peeks at `[r32{0}(a)..r32{0}(a) + r32{0}(b)]_2`, tries to convert to byte array and then UTF-8 string and prints to host stdout. Prints error message if conversion fails. Does not change any VM state. | +| Rv32HintRandom | 0x22 | `a,_,_` | Resets the hint stream to `4 * r32{0}(a)` random bytes. The source of randomness is the host operating system (`rand::rngs::OsRng`). Its result is not constrained in any way. | +| Rv32HintLoadByKey | 0x23 | `a,b,_` | Look up the value by key `[r32{0}{a}:r32{0}{b}]_2` and prepend the value into `input_stream`. The logical value is `Vec>`. The serialization of `Vec` follows the format `[length, ]`. Both length and content encoded as little-endian bytes. | ### Native Extension The native extension operates over native field elements and has instructions tailored for STARK proof recursion. It @@ -611,9 +614,7 @@ the same format that is congruent modulo `N` to the respective operation applied For each instruction, the operand `d` is fixed to be `1` and `e` is fixed to be `2`. Each instruction performs block accesses with block size `4` in address space `1` and block size `N::BLOCK_SIZE` in -address space `2`, where `N::NUM_LIMBS` is divisible by `N::BLOCK_SIZE`. Recall that `N::BLOCK_SIZE` must be a power of - -2. +address space `2`, where `N::NUM_LIMBS` is divisible by `N::BLOCK_SIZE`. Recall that `N::BLOCK_SIZE` must be a power of 2. | Name | Operands | Description | | ------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -635,6 +636,16 @@ format with each limb having `LIMB_BITS` bits. | ISEQMOD_RV32\ | `a,b,c,1,2` | `[a:4]_1 = [r32{0}(b): N::NUM_LIMBS]_2 == [r32{0}(c): N::NUM_LIMBS]_2 (mod N) ? 1 : 0`. Enforces that `[r32{0}(b): N::NUM_LIMBS]_2, [r32{0}(c): N::NUM_LIMBS]_2` are less than `N` and then sets the register value of `[a:4]_1` to `1` or `0` depending on whether the two big integers are equal. | | SETUP_ISEQMOD_RV32\ | `a,b,c,1,2` | `assert([r32{0}(b): N::NUM_LIMBS]_2 == N)` in the chip that handles modular equality. For the sake of implementation convenience it also writes something (can be anything) into register value of `[a:4]_1` | +#### Phantom Sub-Instructions + + +| Name | Discriminant | Operands | Description | +| -------------- | ------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| HintNonQr\ | 0x50 | `_,_,c_upper` | Use `c_upper` to determine the index of the modulus from the list of supported moduli. Reset the hint stream to equal a quadratic nonresidue modulo `N`. | +| HintSqrt\ | 0x51 | `a,_,c_upper` | Use `c_upper` to determine the index of the modulus from the list of supported moduli. Read from memory `x = [r32{0}(a): N::NUM_LIMBS]_2`. If `x` is a quadratic residue modulo `N`, reset the hint stream to `[1u8, 0u8, 0u8, 0u8]` followed by a square root of `x`. If `x` is not a quadratic residue, reset the hint stream to `[0u8; 4]` followed by a square root of `x * non_qr`, where `non_qr` is the quadratic nonresidue returned by `HintNonQr`. | + +# + #### Complex Extension Field A complex extension field `Fp2` is the quadratic extension of a prime field `Fp` with irreducible polynomial `X^2 + 1`. @@ -696,15 +707,6 @@ r32_ec_point(a) -> EcPoint { | EC_DOUBLE\ | `a,b,_,1,2` | Set `r32_ec_point(a) = 2 * r32_ec_point(b)`. This doubles the input point. Assumes that `r32_ec_point(b)` lies on the curve and is not the identity point. | | SETUP_EC_DOUBLE\ | `a,b,_,1,2` | `assert(r32_ec_point(b).x == C::MODULUS)` in the chip for EC DOUBLE. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): 2*C::COORD_SIZE]_2`. It is required for proper functionality that `assert(r32_ec_point(b).y != 0 mod C::MODULUS)` | -#### Phantom Sub-Instructions - -The elliptic curve extension defines the following phantom sub-instructions. - -| Name | Discriminant | Operands | Description | -| -------------- | ------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| HintDecompress | 0x40 | `a,b,c_upper` | Uses `c_upper = C::IDX` to determine the index of the curve `C`, from the list of enabled curves. Read from memory `x = [r32{0}(a): C::COORD_SIZE]_2` for an element in the coordinate field of `C`. Let `rec_id = [r32{0}(b)]_2` be a byte in memory for the recovery id, where the lowest bit is 1 if and only if the `y` coordinate of the corresponding point is odd. If there exists a unique `y` such that `(x, y)` is a point on `C` and `y` has the same parity as `rec_id`, then the sub-instruction resets the hint stream to `[1, 0, 0, 0]` followed by `y: [_; C::COORD_SIZE]`. Otherwise, it resets the hint stream to `[0, 0, 0, 0]` followed by `sqrt: [_; C::COORD_SIZE]` where `sqrt * sqrt == (x^3 + ax + b) * non_qr` (`non_qr` is a quadratic nonresidue of `C::Fp`). | -| HintNonQr | 0x41 | `_,_,c_upper` | Reset the hint stream to equal `non_qr: [_; C::COORD_SIZE]` where `non_qr` is a quadratic nonresidue of `C::Fp`. | - ### Pairing Extension The pairing extension supports opcodes tailored to accelerate pairing checks using the optimal Ate pairing over certain diff --git a/docs/specs/RISCV.md b/docs/specs/RISCV.md index 7948272c66..32d0cc63fa 100644 --- a/docs/specs/RISCV.md +++ b/docs/specs/RISCV.md @@ -72,6 +72,13 @@ the guest must take care to validate all data and account for behavior in cases | printstr | I | 0001011 | 011 | 0x1 | Tries to convert `[rd..rd + rs1]_2` to UTF-8 string and print to host stdout. Will print error message if conversion fails. | | hintrandom | I | 0001011 | 011 | 0x2 | Resets the hint stream to `4 * rd` random bytes from `rand::rngs::OsRng` on the host. | +| RISC-V Inst | FMT | opcode[6:0] | funct3 | funct7 | RISC-V description and notes | +|--------------|-----|-------------|---------|--------|------------------------------------------------------------------------------------------------------------------------------| +| nativestorew | R | 0001011 | 111 | 0x2 | Stores the 4-byte word `rs1` at address `rd` in native address space. The address `rd` must be aligned to a 4-byte boundary. | + +`nativestorew` connects RV32 address space and native address space. We put it in RV32 extension because its +implementation is here. But we use `funct3 = 111` because the native extension has an available slot. + ## Keccak Extension | RISC-V Inst | FMT | opcode[6:0] | funct3 | funct7 | RISC-V description and notes | @@ -117,6 +124,8 @@ These use the _custom-0_ opcode prefix and funct3 = 0b111. | lfii | R | 0001011 | 111 | 0 | Long Form Instruction Indicator. `rd = rs1 = rs2 = 0` | | gi | R | 0001011 | 111 | 1 | Gap Indicator. `rd = rs1 = rs2 = 0` | +`nativestorew` also uses `funct3 = 111`. It's listed in the RV32 extension. + ## Algebra Extension Modular arithmetic instructions depend on the modulus `N`. The ordered list of supported moduli should be saved in the `.openvm` section of the ELF file in the serialized format. This is achieved by the `moduli_declare!` macro; for example, the following code @@ -132,7 +141,9 @@ generates classes `Bls12381` and `Bn254` that represent the elements of the corr ### Field Arithmetic -For each created modular class, one must call a corresponding `setup_*` function once at the beginning of the program. For example, for the structs above this would be `setup_0()` and `setup_1()`. This function generates the `setup` intrinsics which are distinguished by the `rs2` operand that specifies the chip this instruction is passed to.. +For each created modular class, one must call a corresponding `setup_*` function before using the intrinsics. +For example, for the structs above this would be `setup_0()` and `setup_1()`. This function generates the `setup` intrinsics which are distinguished by the `rs2` operand that specifies the chip this instruction is passed to. +For developer convenience, in the Rust function bindings for these intrinsics, each modulus's `setup_*` function is automatically called on the first use of any of its intrinsics. We use `config.mod_idx(N)` to denote the index of `N` in this list. In the list below, `idx` denotes `config.mod_idx(N)`. @@ -146,6 +157,8 @@ We use `config.mod_idx(N)` to denote the index of `N` in this list. In the list | divmod\ | R | 0101011 | 000 | `idx*8+3` | `[rd: N::NUM_LIMBS]_2 = [rs1: N::NUM_LIMBS]_2 / [rs2: N::NUM_LIMBS]_2 (mod N)` (undefined when `gcd([rs2: N::NUM_LIMBS]_2, N) != 1`) | | iseqmod\ | R | 0101011 | 000 | `idx*8+4` | `rd = [rs1: N::NUM_LIMBS]_2 == [rs2: N::NUM_LIMBS]_2 (mod N) ? 1 : 0`. If `rd != x0`, enforces that `[rs1: N::NUM_LIMBS]_2` and `[rs2: N::NUM_LIMBS]_2` are both less than `N` and then sets `rd` equal to boolean comparison value. If `rd = x0`, this is a no-op. | | setup\ | R | 0101011 | 000 | `idx*8+5` | `assert([rs1: N::NUM_LIMBS]_2 == N)` in the chip defined by the register index of `rs2`. For the sake of implementation convenience it also writes an unconstrained value into `[rd: N::NUM_LIMBS]_2` if `ind(rs2) = 0,1` (for add_sub, mul_div) or it overwrites the register value of `rd` with an unconstrained value if `ind(rs2) = 2` (for iseq). If `ind(rs2) = 2`, then the instruction is **invalid** if `rd = x0`. | +| hint_non_qr\ | R | 0101011 | 000 | `idx*8+6` | Reset the hint stream to equal `non_qr` where `non_qr` is a quadratic nonresidue modulo `N`. The same `non_qr` is returned in each execution of this instruction. `rd`, `rs1`, and `rs2` should be `x0`. | +| hint_sqrt\ | R | 0101011 | 000 | `idx*8+7` | Read `x = [rs1: N::NUM_LIMBS]_2`. If `x` is a quadratic residue modulo `N` then reset the hint stream to `[1u0, 0u8, 0u8, 0u8]` concatenated with a square root of `x`. If `x` is not a quadratic residue, then reset the hint stream to `[0u8; 4]` concatenated with a square root of `x * non_qr` where `non_qr` is the quadratic nonresidue returned by `hint_non_qr`. `rd` and `rs2` should be `x0`. | Since `funct7` is 7-bits, up to 16 moduli can be supported simultaneously. We use `idx*8` to leave some room for future expansion. @@ -170,8 +183,6 @@ The elliptic curve extension supports arithmetic over short Weierstrass curves, | sw_add_ne\ | R | 0101011 | 001 | `idx*8` | `EcPoint([rd:2*C::COORD_SIZE]_2) = EcPoint([rs1:2*C::COORD_SIZE]_2) + EcPoint([rs2:2*C::COORD_SIZE]_2)`. Assumes that input affine points are not identity and do not have same x-coordinate. | | sw_double\ | R | 0101011 | 001 | `idx*8+1` | `EcPoint([rd:2*C::COORD_SIZE]_2) = 2 * EcPoint([rs1:2*C::COORD_SIZE]_2)`. Assumes that input affine point is not identity. `rs2` is unused and must be set to `x0`. | | setup\ | R | 0101011 | 001 | `idx*8+2` | `assert([rs1: C::COORD_SIZE]_2 == C::MODULUS)` in the chip defined by the register index of `rs2`. For the sake of implementation convenience it also writes an unconstrained value into `[rd: 2*C::COORD_SIZE]_2`. If `ind(rs2) != 0`, then this instruction is setup for `sw_add_ne`. Otherwise it is setup for `sw_double`. When `ind(rs2) != 0` (add_ne), it is required for proper functionality that `[rs2: C::COORD_SIZE]_2 != [rs1: C::COORD_SIZE]_2`; otherwise (double), it is required that `[rs1 + C::COORD_SIZE: C::COORD_SIZE]_2 != C::Fp::ZERO` | -| hint_decompress | R | 0101011 | 001 | `idx*8+3` | Read `x: C::Fp` from `[rs1: C::COORD_SIZE]_2` and `rec_id: u8` from `[rs2]_2`. If there exists a unique `y: C::Fp` such that `(x, y)` is a point on `C` and `y` has the same parity as `rec_id`, then reset the hint stream to `[1u8, 0u8, 0u8, 0u8]` concatenated with the limbs representing `y` in little-endian order; otherwise, reset it to `[0u8; 4]` concatenated the limbs representing `sqrt` where `sqrt * sqrt == (x^3 + ax + b) * non_qr` (`non_qr` is a quadratic nonresidue of `C::Fp`). `rd` should be `x0`. | -| hint_non_qr | R | 0101011 | 001 | `idx*8+4` | Reset the hint stream to equal `non_qr` where `non_qr` is a quadratic nonresidue of `C::Fp`. `rd`, `rs1`, and `rs2` should be `x0`. | Since `funct7` is 7-bits, up to 16 curves can be supported simultaneously. We use `idx*8` to leave some room for future expansion. diff --git a/docs/specs/isa-table.md b/docs/specs/isa-table.md index 75cf1bad2c..7b7f374065 100644 --- a/docs/specs/isa-table.md +++ b/docs/specs/isa-table.md @@ -31,55 +31,57 @@ In the tables below, we provide the mapping between the `LocalOpcode` and `Phant #### Instructions -| VM Extension | `LocalOpcode` | ISA Instruction | -| ------------- | ---------- | ------------- | -| RV32IM | `BaseAluOpcode::ADD` | ADD_RV32 | -| RV32IM | `BaseAluOpcode::SUB` | SUB_RV32 | -| RV32IM | `BaseAluOpcode::XOR` | XOR_RV32 | -| RV32IM | `BaseAluOpcode::OR` | OR_RV32 | -| RV32IM | `BaseAluOpcode::AND` | AND_RV32 | -| RV32IM | `ShiftOpcode::SLL` | SLL_RV32 | -| RV32IM | `ShiftOpcode::SRL` | SRL_RV32 | -| RV32IM | `ShiftOpcode::SRA` | SRA_RV32 | -| RV32IM | `LessThanOpcode::SLT` | SLT_RV32 | -| RV32IM | `LessThanOpcode::SLTU` | SLTU_RV32 | -| RV32IM | `Rv32LoadStoreOpcode::LOADB` | LOADB_RV32 | -| RV32IM | `Rv32LoadStoreOpcode::LOADH` | LOADH_RV32 | -| RV32IM | `Rv32LoadStoreOpcode::LOADW` | LOADW_RV32 | -| RV32IM | `Rv32LoadStoreOpcode::LOADBU` | LOADBU_RV32 | -| RV32IM | `Rv32LoadStoreOpcode::LOADHU` | LOADHU_RV32 | -| RV32IM | `Rv32LoadStoreOpcode::STOREB` | STOREB_RV32 | -| RV32IM | `Rv32LoadStoreOpcode::STOREH` | STOREH_RV32 | -| RV32IM | `Rv32LoadStoreOpcode::STOREW` | STOREW_RV32 | -| RV32IM | `BranchEqualOpcode::BEQ` | BEQ_RV32 | -| RV32IM | `BranchEqualOpcode::BNE` | BNE_RV32 | -| RV32IM | `BranchLessThanOpcode::BLT` | BLT_RV32 | -| RV32IM | `BranchLessThanOpcode::BGE` | BGE_RV32 | -| RV32IM | `BranchLessThanOpcode::BLTU` | BLTU_RV32 | -| RV32IM | `BranchLessThanOpcode::BGEU` | BGEU_RV32 | -| RV32IM | `Rv32JalLuiOpcode::JAL` | JAL_RV32 | -| RV32IM | `Rv32JalrOpcode::JALR` | JALR_RV32 | -| RV32IM | `Rv32JalLuiOpcode::LUI` | LUI_RV32 | -| RV32IM | `Rv32AuipcOpcode::AUIPC` | AUIPC_RV32 | -| RV32IM | `MulOpcode::MUL` | MUL_RV32 | -| RV32IM | `MulHOpcode::MULH` | MULH_RV32 | -| RV32IM | `MulHOpcode::MULHSU` | MULHSU_RV32 | -| RV32IM | `MulHOpcode::MULHU` | MULHU_RV32 | -| RV32IM | `DivRemOpcode::DIV` | DIV_RV32 | -| RV32IM | `DivRemOpcode::DIVU` | DIVU_RV32 | -| RV32IM | `DivRemOpcode::REM` | REM_RV32 | -| RV32IM | `DivRemOpcode::REMU` | REMU_RV32 | +| VM Extension | `LocalOpcode` | ISA Instruction | +| ------------- | ---------- |------------------| +| RV32IM | `BaseAluOpcode::ADD` | ADD_RV32 | +| RV32IM | `BaseAluOpcode::SUB` | SUB_RV32 | +| RV32IM | `BaseAluOpcode::XOR` | XOR_RV32 | +| RV32IM | `BaseAluOpcode::OR` | OR_RV32 | +| RV32IM | `BaseAluOpcode::AND` | AND_RV32 | +| RV32IM | `ShiftOpcode::SLL` | SLL_RV32 | +| RV32IM | `ShiftOpcode::SRL` | SRL_RV32 | +| RV32IM | `ShiftOpcode::SRA` | SRA_RV32 | +| RV32IM | `LessThanOpcode::SLT` | SLT_RV32 | +| RV32IM | `LessThanOpcode::SLTU` | SLTU_RV32 | +| RV32IM | `Rv32LoadStoreOpcode::LOADB` | LOADB_RV32 | +| RV32IM | `Rv32LoadStoreOpcode::LOADH` | LOADH_RV32 | +| RV32IM | `Rv32LoadStoreOpcode::LOADW` | LOADW_RV32 | +| RV32IM | `Rv32LoadStoreOpcode::LOADBU` | LOADBU_RV32 | +| RV32IM | `Rv32LoadStoreOpcode::LOADHU` | LOADHU_RV32 | +| RV32IM | `Rv32LoadStoreOpcode::STOREB` | STOREB_RV32 | +| RV32IM | `Rv32LoadStoreOpcode::STOREH` | STOREH_RV32 | +| RV32IM | `Rv32LoadStoreOpcode::STOREW` | STOREW_RV32 | +| RV32IM | `BranchEqualOpcode::BEQ` | BEQ_RV32 | +| RV32IM | `BranchEqualOpcode::BNE` | BNE_RV32 | +| RV32IM | `BranchLessThanOpcode::BLT` | BLT_RV32 | +| RV32IM | `BranchLessThanOpcode::BGE` | BGE_RV32 | +| RV32IM | `BranchLessThanOpcode::BLTU` | BLTU_RV32 | +| RV32IM | `BranchLessThanOpcode::BGEU` | BGEU_RV32 | +| RV32IM | `Rv32JalLuiOpcode::JAL` | JAL_RV32 | +| RV32IM | `Rv32JalrOpcode::JALR` | JALR_RV32 | +| RV32IM | `Rv32JalLuiOpcode::LUI` | LUI_RV32 | +| RV32IM | `Rv32AuipcOpcode::AUIPC` | AUIPC_RV32 | +| RV32IM | `MulOpcode::MUL` | MUL_RV32 | +| RV32IM | `MulHOpcode::MULH` | MULH_RV32 | +| RV32IM | `MulHOpcode::MULHSU` | MULHSU_RV32 | +| RV32IM | `MulHOpcode::MULHU` | MULHU_RV32 | +| RV32IM | `DivRemOpcode::DIV` | DIV_RV32 | +| RV32IM | `DivRemOpcode::DIVU` | DIVU_RV32 | +| RV32IM | `DivRemOpcode::REM` | REM_RV32 | +| RV32IM | `DivRemOpcode::REMU` | REMU_RV32 | | RV32IM | `Rv32HintStoreOpcode::HINT_STOREW` | HINT_STOREW_RV32 | | RV32IM | `Rv32HintStoreOpcode::HINT_BUFFER` | HINT_BUFFER_RV32 | -| RV32IM | Pseudo-instruction for `STOREW_RV32` | REVEAL_RV32 | +| RV32IM | Pseudo-instruction for `STOREW_RV32` | REVEAL_RV32 | +| RV32IM | Pseudo-instruction for `STOREW_RV32` | NATIVE_STOREW | | #### Phantom Sub-Instructions -| VM Extension | `PhantomDiscriminant` | ISA Phantom Sub-Instruction | -| ------------- | ---------- | ------------- | -| RV32IM | `Rv32Phantom::HintInput` | Rv32HintInput | -| RV32IM | `Rv32Phantom::PrintStr` | Rv32PrintStr | -| RV32IM | `Rv32Phantom::HintRandom` | Rv32HintRandom | +| VM Extension | `PhantomDiscriminant` | ISA Phantom Sub-Instruction | +| ------------- |-------------------------------| ------------- | +| RV32IM | `Rv32Phantom::HintInput` | Rv32HintInput | +| RV32IM | `Rv32Phantom::PrintStr` | Rv32PrintStr | +| RV32IM | `Rv32Phantom::HintRandom` | Rv32HintRandom | +| RV32IM | `Rv32Phantom::HintLoadByKey` | Rv32HintLoadByKey | ## Native Extension diff --git a/docs/specs/transpiler.md b/docs/specs/transpiler.md index a8b94ef2b0..fded65b6d8 100644 --- a/docs/specs/transpiler.md +++ b/docs/specs/transpiler.md @@ -186,6 +186,8 @@ Each VM extension's behavior is specified below. | divmod\ | DIVMOD_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` | | iseqmod\ | ISEQMOD_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` if `rd != x0`, otherwise PHANTOM `_, _, disc(Nop)` | | setup\ | SETUP_ADDSUBMOD_RV32\ `ind(rd), ind(rs1), x0, 1, 2` if `ind(rs2) = 0`, SETUP_MULDIVMOD_RV32\ `ind(rd), ind(rs1), x0, 1, 2` if `ind(rs2) = 1`, SETUP_ISEQMOD_RV32\ `ind(rd), ind(rs1), x0, 1, 2` if `ind(rs2) = 2` | +| hint_non_qr | PHANTOM `0, 0, phantom_c(curve_idx, HintNonQr)` | +| hint_sqrt | PHANTOM `ind(rs1), 0, phantom_c(curve_idx, HintSqrt)` | #### Complex Extension Field Arithmetic @@ -204,8 +206,6 @@ Each VM extension's behavior is specified below. | sw_add_ne\ | EC_ADD_NE_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` | | sw_double\ | EC_DOUBLE_RV32\ `ind(rd), ind(rs1), 0, 1, 2` | | setup\ | SETUP_EC_ADD_NE_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` if `ind(rs2) != 0`, SETUP_EC_DOUBLE_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` if `ind(rs2) = 0` | -| hint_decompress | PHANTOM `ind(rs1), ind(rs2), phantom_c(curve_idx, HintDecompress)` | -| hint_non_qr | PHANTOM `0, 0, phantom_c(curve_idx, HintNonQr)` | ### Pairing Extension diff --git a/examples/algebra/Cargo.toml b/examples/algebra/Cargo.toml index 58a7402e68..9d369431c8 100644 --- a/examples/algebra/Cargo.toml +++ b/examples/algebra/Cargo.toml @@ -10,9 +10,8 @@ members = [] openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ "std", ] } -openvm-platform = { git = "https://github.com/openvm-org/openvm.git" } openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } -openvm-algebra-complex-macros = { git = "https://github.com/openvm-org/openvm.git" } + serde = { version = "1.0.216" } num-bigint = { version = "0.4.6", features = ["serde"] } diff --git a/examples/algebra/openvm.toml b/examples/algebra/openvm.toml index fcdce806e4..f5b3678720 100644 --- a/examples/algebra/openvm.toml +++ b/examples/algebra/openvm.toml @@ -2,7 +2,7 @@ [app_vm_config.rv32m] [app_vm_config.io] [app_vm_config.modular] -supported_modulus = ["998244353","1000000007"] +supported_moduli = ["998244353","1000000007"] [app_vm_config.fp2] -supported_modulus = ["998244353","1000000007"] \ No newline at end of file +supported_moduli = [["Complex1", "998244353"], ["Complex2", "1000000007"]] \ No newline at end of file diff --git a/examples/algebra/openvm_init.rs b/examples/algebra/openvm_init.rs new file mode 100644 index 0000000000..1de98b97a1 --- /dev/null +++ b/examples/algebra/openvm_init.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "998244353", "1000000007" } +openvm_algebra_guest::complex_macros::complex_init! { Complex1 { mod_idx = 0 }, Complex2 { mod_idx = 1 } } diff --git a/examples/algebra/src/main.rs b/examples/algebra/src/main.rs index e386041f9b..e2265a2c97 100644 --- a/examples/algebra/src/main.rs +++ b/examples/algebra/src/main.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use openvm_algebra_guest::{moduli_macros::*, IntMod}; // This macro will create two structs, `Mod1` and `Mod2`, @@ -7,31 +9,30 @@ moduli_declare! { Mod2 { modulus = "1000000007" } } -// This macro will initialize the moduli. -// Now, `Mod1` is the "zeroth" modular struct, and `Mod2` is the "first" one. -moduli_init! { - "998244353", "1000000007" -} - // This macro will create two structs, `Complex1` and `Complex2`, // one for arithmetic in the field $\mathbb{F}_{998244353}[x]/(x^2 + 1)$, // and the other for arithmetic in the field $\mathbb{F}_{1000000007}[x]/(x^2 + 1)$. -openvm_algebra_complex_macros::complex_declare! { +openvm_algebra_guest::complex_macros::complex_declare! { Complex1 { mod_type = Mod1 }, Complex2 { mod_type = Mod2 }, } +openvm::init!(); +/* The init! macro will expand to the following (excluding comments): +// This macro will initialize the moduli. +// Now, `Mod1` is the "zeroth" modular struct, and `Mod2` is the "first" one. +moduli_init! { + "998244353", "1000000007" +} + // The order of these structs does not matter, // given that we specify the `mod_idx` parameters properly. openvm_algebra_complex_macros::complex_init! { Complex1 { mod_idx = 0 }, Complex2 { mod_idx = 1 }, } +*/ pub fn main() { - // Since we only use an arithmetic operation with `Mod1` and not `Mod2`, - // we only need to call `setup_0()` here. - setup_0(); - setup_all_complex_extensions(); let a = Complex1::new(Mod1::ZERO, Mod1::from_u32(0x3b8) * Mod1::from_u32(0x100000)); // a = -i in the corresponding field let b = Complex2::new(Mod2::ZERO, Mod2::from_u32(1000000006)); // b = -i in the corresponding field assert_eq!(a.clone() * &a * &a * &a * &a, a); // a^5 = a diff --git a/examples/ecc/Cargo.toml b/examples/ecc/Cargo.toml index 55dd320b19..3e0dcdbcfc 100644 --- a/examples/ecc/Cargo.toml +++ b/examples/ecc/Cargo.toml @@ -7,10 +7,20 @@ edition = "2021" members = [] [dependencies] -openvm = { path = "../../crates/toolchain/openvm", features = ["std"] } -openvm-algebra-guest = { path = "../../extensions/algebra/guest" } -openvm-ecc-guest = { path = "../../extensions/ecc/guest", features = ["k256"] } +openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ + "std", +] } +openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-k256 = { git = "https://github.com/openvm-org/openvm.git", package = "k256" } hex-literal = { version = "0.4.1", default-features = false } [features] default = [] + +# remove this if copying example outside of monorepo +[patch."https://github.com/openvm-org/openvm.git"] +openvm = { path = "../../crates/toolchain/openvm" } +openvm-algebra-guest = { path = "../../extensions/algebra/guest" } +openvm-ecc-guest = { path = "../../extensions/ecc/guest" } +k256 = { path = "../../guest-libs/k256" } diff --git a/examples/ecc/openvm.toml b/examples/ecc/openvm.toml index bd968d85a2..1dc6cf25f2 100644 --- a/examples/ecc/openvm.toml +++ b/examples/ecc/openvm.toml @@ -2,9 +2,10 @@ [app_vm_config.rv32m] [app_vm_config.io] [app_vm_config.modular] -supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337"] +supported_moduli = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337"] [[app_vm_config.ecc.supported_curves]] +struct_name = "Secp256k1Point" modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" a = "0" diff --git a/examples/ecc/openvm_init.rs b/examples/ecc/openvm_init.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/examples/ecc/openvm_init.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/examples/ecc/src/main.rs b/examples/ecc/src/main.rs index 2fbb4ba45b..0d706d5363 100644 --- a/examples/ecc/src/main.rs +++ b/examples/ecc/src/main.rs @@ -1,13 +1,13 @@ // ANCHOR: imports use hex_literal::hex; use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{ - k256::{Secp256k1Coord, Secp256k1Point}, - weierstrass::WeierstrassPoint, -}; +use openvm_ecc_guest::weierstrass::WeierstrassPoint; +use openvm_k256::{Secp256k1Coord, Secp256k1Point}; // ANCHOR_END: imports // ANCHOR: init +openvm::init!(); +/* The init! macro will expand to the following openvm_algebra_guest::moduli_macros::moduli_init! { "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" @@ -16,12 +16,11 @@ openvm_algebra_guest::moduli_macros::moduli_init! { openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point, } +*/ // ANCHOR_END: init // ANCHOR: main pub fn main() { - setup_all_moduli(); - setup_all_curves(); let x1 = Secp256k1Coord::from_u32(1); let y1 = Secp256k1Coord::from_le_bytes(&hex!( "EEA7767E580D75BC6FDD7F58D2A84C2614FB22586068DB63B346C6E60AF21842" @@ -34,6 +33,7 @@ pub fn main() { )); let p2 = Secp256k1Point::from_xy_nonidentity(x2, y2).unwrap(); + #[allow(clippy::op_ref)] let _p3 = &p1 + &p2; } // ANCHOR_END: main diff --git a/examples/i256/Cargo.toml b/examples/i256/Cargo.toml index 78c0fa9402..ad4e062a1e 100644 --- a/examples/i256/Cargo.toml +++ b/examples/i256/Cargo.toml @@ -10,7 +10,20 @@ members = [] openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ "std", ] } -openvm-bigint-guest = { git = "https://github.com/openvm-org/openvm.git" } +alloy-primitives = "1.1.2" [features] default = [] + +[patch.crates-io] +# alloy-primitives uses ruint: +# ruint = { git = "https://github.com/openvm-org/openvm.git" } +# Use above if outside of monorepo +ruint = { path = "../../guest-libs/ruint" } + +# remove this if copying example outside of monorepo +[patch."https://github.com/openvm-org/openvm.git"] +openvm = { path = "../../crates/toolchain/openvm" } + +[profile.release] +lto = "thin" diff --git a/examples/i256/src/main.rs b/examples/i256/src/main.rs index 0cd58e82e8..8f008f40a0 100644 --- a/examples/i256/src/main.rs +++ b/examples/i256/src/main.rs @@ -1,13 +1,15 @@ #![allow(clippy::needless_range_loop)] use core::array; -use openvm_bigint_guest::I256; +use alloy_primitives::I256; + +openvm::entry!(main); const N: usize = 16; type Matrix = [[I256; N]; N]; pub fn get_matrix(val: i32) -> Matrix { - array::from_fn(|_| array::from_fn(|_| I256::from_i32(val))) + array::from_fn(|_| array::from_fn(|_| I256::try_from(val).unwrap())) } pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { @@ -15,7 +17,7 @@ pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { for i in 0..N { for j in 0..N { for k in 0..N { - c[i][j] += &a[i][k] * &b[k][j]; + c[i][j] += a[i][k] * b[k][j]; } } } @@ -25,7 +27,7 @@ pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { pub fn get_identity_matrix() -> Matrix { let mut res = get_matrix(0); for i in 0..N { - res[i][i] = I256::from_i32(1); + res[i][i] = I256::try_from(1i32).unwrap(); } res } diff --git a/examples/keccak/Cargo.toml b/examples/keccak/Cargo.toml index 45f48acea0..74f15e6234 100644 --- a/examples/keccak/Cargo.toml +++ b/examples/keccak/Cargo.toml @@ -10,8 +10,13 @@ members = [] openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ "std", ] } -openvm-keccak256-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-keccak256 = { git = "https://github.com/openvm-org/openvm.git" } hex = { version = "0.4.3" } [features] default = [] + +# remove this if copying example outside of monorepo +[patch."https://github.com/openvm-org/openvm.git"] +openvm = { path = "../../crates/toolchain/openvm" } +openvm-keccak256 = { path = "../../guest-libs/keccak256" } diff --git a/examples/keccak/src/main.rs b/examples/keccak/src/main.rs index b17be3fd8f..7b98d36ed1 100644 --- a/examples/keccak/src/main.rs +++ b/examples/keccak/src/main.rs @@ -2,10 +2,12 @@ use core::hint::black_box; use hex::FromHex; -use openvm_keccak256_guest::keccak256; +use openvm_keccak256::keccak256; // ANCHOR_END: imports // ANCHOR: main +openvm::entry!(main); + pub fn main() { let test_vectors = [ ( diff --git a/examples/pairing/Cargo.toml b/examples/pairing/Cargo.toml index dd64cfea53..88f53d8f13 100644 --- a/examples/pairing/Cargo.toml +++ b/examples/pairing/Cargo.toml @@ -7,17 +7,24 @@ edition = "2021" members = [] [dependencies] + openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ "std", ] } openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } -openvm-algebra-moduli-macros = { git = "https://github.com/openvm-org/openvm.git" } -openvm-algebra-complex-macros = { git = "https://github.com/openvm-org/openvm.git" } openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git" } -openvm-pairing-guest = { git = "https://github.com/openvm-org/openvm.git", features = [ +openvm-pairing = { git = "https://github.com/openvm-org/openvm.git", features = [ "bls12_381", ] } + hex-literal = { version = "0.4.1", default-features = false } [features] default = [] + +# remove this if copying example outside of monorepo +[patch."https://github.com/openvm-org/openvm.git"] +openvm = { path = "../../crates/toolchain/openvm" } +openvm-algebra-guest = { path = "../../extensions/algebra/guest" } +openvm-ecc-guest = { path = "../../extensions/ecc/guest" } +openvm-pairing = { path = "../../guest-libs/pairing" } diff --git a/examples/pairing/openvm.toml b/examples/pairing/openvm.toml index 94337448fe..fb218d6836 100644 --- a/examples/pairing/openvm.toml +++ b/examples/pairing/openvm.toml @@ -5,11 +5,11 @@ supported_curves = ["Bls12_381"] [app_vm_config.modular] -supported_modulus = [ +supported_moduli = [ "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", ] [app_vm_config.fp2] -supported_modulus = [ - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", +supported_moduli = [ + ["Bls12_381Fp2", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787"], ] \ No newline at end of file diff --git a/examples/pairing/openvm_init.rs b/examples/pairing/openvm_init.rs new file mode 100644 index 0000000000..991d1237fc --- /dev/null +++ b/examples/pairing/openvm_init.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" } +openvm_algebra_guest::complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 } } diff --git a/examples/pairing/src/main.rs b/examples/pairing/src/main.rs index c1d19a1756..e317af7897 100644 --- a/examples/pairing/src/main.rs +++ b/examples/pairing/src/main.rs @@ -4,13 +4,15 @@ use hex_literal::hex; // ANCHOR: imports use openvm_algebra_guest::{field::FieldExtension, IntMod}; use openvm_ecc_guest::AffinePoint; -use openvm_pairing_guest::{ +use openvm_pairing::{ bls12_381::{Bls12_381, Fp, Fp2}, - pairing::PairingCheck, + PairingCheck, }; // ANCHOR_END: imports // ANCHOR: init +openvm::init!(); +/* The init! macro will expand to the following openvm_algebra_moduli_macros::moduli_init! { "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" @@ -19,15 +21,11 @@ openvm_algebra_moduli_macros::moduli_init! { openvm_algebra_complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 }, } +*/ // ANCHOR_END: init // ANCHOR: main pub fn main() { - // ANCHOR: setup - setup_0(); - setup_all_complex_extensions(); - // ANCHOR_END: setup - let p0 = AffinePoint::new( Fp::from_be_bytes(&hex!("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")), Fp::from_be_bytes(&hex!("08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1")) diff --git a/examples/sha256/Cargo.toml b/examples/sha256/Cargo.toml index dd749c1afd..0b5a44bc3e 100644 --- a/examples/sha256/Cargo.toml +++ b/examples/sha256/Cargo.toml @@ -10,8 +10,13 @@ members = [] openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ "std", ] } -openvm-sha256-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-sha2 = { git = "https://github.com/openvm-org/openvm.git" } hex = { version = "0.4.3" } [features] default = [] + +# remove this if copying example outside of monorepo +[patch."https://github.com/openvm-org/openvm.git"] +openvm = { path = "../../crates/toolchain/openvm" } +openvm-sha2 = { path = "../../guest-libs/sha2" } diff --git a/examples/sha256/src/main.rs b/examples/sha256/src/main.rs index 502a12366b..a6195390a4 100644 --- a/examples/sha256/src/main.rs +++ b/examples/sha256/src/main.rs @@ -2,7 +2,7 @@ use core::hint::black_box; use hex::FromHex; -use openvm_sha256_guest::sha256; +use openvm_sha2::sha256; // ANCHOR_END: imports // ANCHOR: main diff --git a/examples/u256/Cargo.toml b/examples/u256/Cargo.toml index 2ee553bbee..23e7fa8a7b 100644 --- a/examples/u256/Cargo.toml +++ b/examples/u256/Cargo.toml @@ -10,7 +10,12 @@ members = [] openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ "std", ] } -openvm-bigint-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-ruint = { git = "https://github.com/openvm-org/openvm.git", package = "ruint" } [features] default = [] + +# remove this if copying example outside of monorepo +[patch."https://github.com/openvm-org/openvm.git"] +openvm = { path = "../../crates/toolchain/openvm" } +ruint = { path = "../../guest-libs/ruint" } diff --git a/examples/u256/src/main.rs b/examples/u256/src/main.rs index 37e0e17ca9..75b80afd3d 100644 --- a/examples/u256/src/main.rs +++ b/examples/u256/src/main.rs @@ -1,13 +1,15 @@ #![allow(clippy::needless_range_loop)] use core::array; -use openvm_bigint_guest::U256; +use openvm_ruint::aliases::U256; + +openvm::entry!(main); const N: usize = 16; type Matrix = [[U256; N]; N]; pub fn get_matrix(val: u32) -> Matrix { - array::from_fn(|_| array::from_fn(|_| U256::from_u32(val))) + array::from_fn(|_| array::from_fn(|_| U256::from(val))) } pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { @@ -15,7 +17,7 @@ pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { for i in 0..N { for j in 0..N { for k in 0..N { - c[i][j] += &a[i][k] * &b[k][j]; + c[i][j] += a[i][k] * b[k][j]; } } } @@ -25,7 +27,7 @@ pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { pub fn get_identity_matrix() -> Matrix { let mut res = get_matrix(0); for i in 0..N { - res[i][i] = U256::from_u32(1); + res[i][i] = U256::from(1u32); } res } @@ -34,5 +36,7 @@ pub fn main() { let a: Matrix = get_identity_matrix(); let b: Matrix = get_matrix(28); let c: Matrix = mult(&a, &b); - assert_eq!(c, b); + if c != b { + panic!("Matrix multiplication failed"); + } } diff --git a/extensions/algebra/circuit/Cargo.toml b/extensions/algebra/circuit/Cargo.toml index d22905bffc..1d4eabfea5 100644 --- a/extensions/algebra/circuit/Cargo.toml +++ b/extensions/algebra/circuit/Cargo.toml @@ -31,6 +31,7 @@ serde.workspace = true serde_with = { workspace = true } serde-big-array = { workspace = true } struct-reflection = { workspace = true } +eyre = { workspace = true } [dev-dependencies] halo2curves-axiom = { workspace = true } diff --git a/extensions/algebra/circuit/src/config.rs b/extensions/algebra/circuit/src/config.rs index 3f2e83fef3..5b43163b77 100644 --- a/extensions/algebra/circuit/src/config.rs +++ b/extensions/algebra/circuit/src/config.rs @@ -1,5 +1,5 @@ use num_bigint::BigUint; -use openvm_circuit::arch::SystemConfig; +use openvm_circuit::arch::{InitFileGenerator, SystemConfig}; use openvm_circuit_derive::VmConfig; use openvm_rv32im_circuit::*; use openvm_stark_backend::p3_field::PrimeField32; @@ -21,6 +21,15 @@ pub struct Rv32ModularConfig { pub modular: ModularExtension, } +impl InitFileGenerator for Rv32ModularConfig { + fn generate_init_file_contents(&self) -> Option { + Some(format!( + "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n", + self.modular.generate_moduli_init() + )) + } +} + impl Rv32ModularConfig { pub fn new(moduli: Vec) -> Self { Self { @@ -50,14 +59,28 @@ pub struct Rv32ModularWithFp2Config { } impl Rv32ModularWithFp2Config { - pub fn new(moduli: Vec) -> Self { + pub fn new(moduli_with_names: Vec<(String, BigUint)>) -> Self { + let moduli = moduli_with_names + .iter() + .map(|(_, modulus)| modulus.clone()) + .collect(); Self { system: SystemConfig::default().with_continuations(), base: Default::default(), mul: Default::default(), io: Default::default(), - modular: ModularExtension::new(moduli.clone()), - fp2: Fp2Extension::new(moduli), + modular: ModularExtension::new(moduli), + fp2: Fp2Extension::new(moduli_with_names), } } } + +impl InitFileGenerator for Rv32ModularWithFp2Config { + fn generate_init_file_contents(&self) -> Option { + Some(format!( + "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n", + self.modular.generate_moduli_init(), + self.fp2.generate_complex_init(&self.modular) + )) + } +} diff --git a/extensions/algebra/circuit/src/fp2_extension.rs b/extensions/algebra/circuit/src/fp2_extension.rs index 940ec4c864..37968081bd 100644 --- a/extensions/algebra/circuit/src/fp2_extension.rs +++ b/extensions/algebra/circuit/src/fp2_extension.rs @@ -18,13 +18,45 @@ use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; use strum::EnumCount; -use crate::fp2_chip::{Fp2AddSubChip, Fp2MulDivChip}; +use crate::{ + fp2_chip::{Fp2AddSubChip, Fp2MulDivChip}, + ModularExtension, +}; #[serde_as] #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct Fp2Extension { - #[serde_as(as = "Vec")] - pub supported_modulus: Vec, + // (name, modulus) + // name must match the struct name defined by complex_declare + #[serde_as(as = "Vec<(_, DisplayFromStr)>")] + pub supported_moduli: Vec<(String, BigUint)>, +} + +impl Fp2Extension { + pub fn generate_complex_init(&self, modular_config: &ModularExtension) -> String { + fn get_index_of_modulus(modulus: &BigUint, modular_config: &ModularExtension) -> usize { + modular_config + .supported_moduli + .iter() + .position(|m| m == modulus) + .expect("Modulus used in Fp2Extension not found in ModularExtension") + } + + let supported_moduli = self + .supported_moduli + .iter() + .map(|(name, modulus)| { + format!( + "{} {{ mod_idx = {} }}", + name, + get_index_of_modulus(modulus, modular_config) + ) + }) + .collect::>() + .join(", "); + + format!("openvm_algebra_guest::complex_macros::complex_init! {{ {supported_moduli} }}") + } } #[derive(ChipUsageGetter, Chip, InstructionExecutor, AnyEnum, From)] @@ -76,7 +108,7 @@ impl VmExtension for Fp2Extension { let addsub_opcodes = (Fp2Opcode::ADD as usize)..=(Fp2Opcode::SETUP_ADDSUB as usize); let muldiv_opcodes = (Fp2Opcode::MUL as usize)..=(Fp2Opcode::SETUP_MULDIV as usize); - for (i, modulus) in self.supported_modulus.iter().enumerate() { + for (i, (_, modulus)) in self.supported_moduli.iter().enumerate() { // determine the number of bytes needed to represent a prime field element let bytes = modulus.bits().div_ceil(8); let start_offset = Fp2Opcode::CLASS_OFFSET + i * Fp2Opcode::COUNT; diff --git a/extensions/algebra/circuit/src/modular_extension.rs b/extensions/algebra/circuit/src/modular_extension.rs index 18a19becfa..b51696841b 100644 --- a/extensions/algebra/circuit/src/modular_extension.rs +++ b/extensions/algebra/circuit/src/modular_extension.rs @@ -1,6 +1,7 @@ use derive_more::derive::From; -use num_bigint::BigUint; -use openvm_algebra_transpiler::Rv32ModularArithmeticOpcode; +use num_bigint::{BigUint, RandBigInt}; +use num_traits::{FromPrimitive, One}; +use openvm_algebra_transpiler::{ModularPhantom, Rv32ModularArithmeticOpcode}; use openvm_circuit::{ self, arch::{SystemPort, VmExtension, VmInventory, VmInventoryBuilder, VmInventoryError}, @@ -11,10 +12,11 @@ use openvm_circuit_primitives::bitwise_op_lookup::{ BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip, }; use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter}; -use openvm_instructions::{LocalOpcode, VmOpcode}; +use openvm_instructions::{LocalOpcode, PhantomDiscriminant, VmOpcode}; use openvm_mod_circuit_builder::ExprBuilderConfig; use openvm_rv32_adapters::{Rv32IsEqualModAdapterChip, Rv32VecHeapAdapterChip}; use openvm_stark_backend::p3_field::PrimeField32; +use rand::{rngs::StdRng, SeedableRng}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; use strum::EnumCount; @@ -27,7 +29,21 @@ use crate::modular_chip::{ #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct ModularExtension { #[serde_as(as = "Vec")] - pub supported_modulus: Vec, + pub supported_moduli: Vec, +} + +impl ModularExtension { + // Generates a call to the moduli_init! macro with moduli in the correct order + pub fn generate_moduli_init(&self) -> String { + let supported_moduli = self + .supported_moduli + .iter() + .map(|modulus| format!("\"{}\"", modulus)) + .collect::>() + .join(", "); + + format!("openvm_algebra_guest::moduli_macros::moduli_init! {{ {supported_moduli} }}",) + } } #[derive(ChipUsageGetter, Chip, InstructionExecutor, AnyEnum, From)] @@ -85,7 +101,7 @@ impl VmExtension for ModularExtension { let iseq_opcodes = (Rv32ModularArithmeticOpcode::IS_EQ as usize) ..=(Rv32ModularArithmeticOpcode::SETUP_ISEQ as usize); - for (i, modulus) in self.supported_modulus.iter().enumerate() { + for (i, modulus) in self.supported_moduli.iter().enumerate() { // determine the number of bytes needed to represent a prime field element let bytes = modulus.bits().div_ceil(8); let start_offset = @@ -216,7 +232,255 @@ impl VmExtension for ModularExtension { panic!("Modulus too large"); } } + let non_qr_hint_sub_ex = phantom::NonQrHintSubEx::new(self.supported_moduli.clone()); + builder.add_phantom_sub_executor( + non_qr_hint_sub_ex.clone(), + PhantomDiscriminant(ModularPhantom::HintNonQr as u16), + )?; + + let sqrt_hint_sub_ex = phantom::SqrtHintSubEx::new(non_qr_hint_sub_ex); + builder.add_phantom_sub_executor( + sqrt_hint_sub_ex, + PhantomDiscriminant(ModularPhantom::HintSqrt as u16), + )?; Ok(inventory) } } + +pub(crate) mod phantom { + use std::{ + iter::{once, repeat}, + ops::Deref, + }; + + use eyre::bail; + use num_bigint::BigUint; + use openvm_circuit::{ + arch::{PhantomSubExecutor, Streams}, + system::memory::MemoryController, + }; + use openvm_instructions::{riscv::RV32_MEMORY_AS, PhantomDiscriminant}; + use openvm_rv32im_circuit::adapters::unsafe_read_rv32_register; + use openvm_stark_backend::p3_field::PrimeField32; + + use super::{find_non_qr, mod_sqrt}; + + #[derive(derive_new::new)] + pub struct SqrtHintSubEx(NonQrHintSubEx); + + impl Deref for SqrtHintSubEx { + type Target = NonQrHintSubEx; + + fn deref(&self) -> &NonQrHintSubEx { + &self.0 + } + } + + // Given x returns either a sqrt of x or a sqrt of x * non_qr, whichever exists. + // Note that non_qr is fixed for each modulus. + impl PhantomSubExecutor for SqrtHintSubEx { + fn phantom_execute( + &mut self, + memory: &MemoryController, + streams: &mut Streams, + _: PhantomDiscriminant, + a: F, + _: F, + c_upper: u16, + ) -> eyre::Result<()> { + let mod_idx = c_upper as usize; + if mod_idx >= self.supported_moduli.len() { + bail!( + "Modulus index {mod_idx} out of range: {} supported moduli", + self.supported_moduli.len() + ); + } + let modulus = &self.supported_moduli[mod_idx]; + let num_limbs: usize = if modulus.bits().div_ceil(8) <= 32 { + 32 + } else if modulus.bits().div_ceil(8) <= 48 { + 48 + } else { + bail!("Modulus too large") + }; + + let rs1 = unsafe_read_rv32_register(memory, a); + let mut x_limbs: Vec = Vec::with_capacity(num_limbs); + for i in 0..num_limbs { + let limb = memory.unsafe_read_cell( + F::from_canonical_u32(RV32_MEMORY_AS), + F::from_canonical_u32(rs1 + i as u32), + ); + x_limbs.push(limb.as_canonical_u32() as u8); + } + let x = BigUint::from_bytes_le(&x_limbs); + + let (success, sqrt) = match mod_sqrt(&x, modulus, &self.non_qrs[mod_idx]) { + Some(sqrt) => (true, sqrt), + None => { + let sqrt = mod_sqrt( + &(&x * &self.non_qrs[mod_idx]), + modulus, + &self.non_qrs[mod_idx], + ) + .expect("Either x or x * non_qr should be a square"); + (false, sqrt) + } + }; + + let hint_bytes = once(F::from_bool(success)) + .chain(repeat(F::ZERO)) + .take(4) + .chain( + sqrt.to_bytes_le() + .into_iter() + .map(F::from_canonical_u8) + .chain(repeat(F::ZERO)) + .take(num_limbs), + ) + .collect(); + streams.hint_stream = hint_bytes; + Ok(()) + } + } + + #[derive(Clone)] + pub struct NonQrHintSubEx { + pub supported_moduli: Vec, + pub non_qrs: Vec, + } + + impl NonQrHintSubEx { + pub fn new(supported_moduli: Vec) -> Self { + let non_qrs = supported_moduli.iter().map(find_non_qr).collect(); + Self { + supported_moduli, + non_qrs, + } + } + } + + impl PhantomSubExecutor for NonQrHintSubEx { + fn phantom_execute( + &mut self, + _: &MemoryController, + streams: &mut Streams, + _: PhantomDiscriminant, + _: F, + _: F, + c_upper: u16, + ) -> eyre::Result<()> { + let mod_idx = c_upper as usize; + if mod_idx >= self.supported_moduli.len() { + bail!( + "Modulus index {mod_idx} out of range: {} supported moduli", + self.supported_moduli.len() + ); + } + let modulus = &self.supported_moduli[mod_idx]; + + let num_limbs: usize = if modulus.bits().div_ceil(8) <= 32 { + 32 + } else if modulus.bits().div_ceil(8) <= 48 { + 48 + } else { + bail!("Modulus too large") + }; + + let hint_bytes = self.non_qrs[mod_idx] + .to_bytes_le() + .into_iter() + .map(F::from_canonical_u8) + .chain(repeat(F::ZERO)) + .take(num_limbs) + .collect(); + streams.hint_stream = hint_bytes; + Ok(()) + } + } +} + +/// Find the square root of `x` modulo `modulus` with `non_qr` a +/// quadratic nonresidue of the field. +pub fn mod_sqrt(x: &BigUint, modulus: &BigUint, non_qr: &BigUint) -> Option { + if modulus % 4u32 == BigUint::from_u8(3).unwrap() { + // x^(1/2) = x^((p+1)/4) when p = 3 mod 4 + let exponent = (modulus + BigUint::one()) >> 2; + let ret = x.modpow(&exponent, modulus); + if &ret * &ret % modulus == x % modulus { + Some(ret) + } else { + None + } + } else { + // Tonelli-Shanks algorithm + // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm#The_algorithm + let mut q = modulus - BigUint::one(); + let mut s = 0; + while &q % 2u32 == BigUint::ZERO { + s += 1; + q /= 2u32; + } + let z = non_qr; + let mut m = s; + let mut c = z.modpow(&q, modulus); + let mut t = x.modpow(&q, modulus); + let mut r = x.modpow(&((q + BigUint::one()) >> 1), modulus); + loop { + if t == BigUint::ZERO { + return Some(BigUint::ZERO); + } + if t == BigUint::one() { + return Some(r); + } + let mut i = 0; + let mut tmp = t.clone(); + while tmp != BigUint::one() && i < m { + tmp = &tmp * &tmp % modulus; + i += 1; + } + if i == m { + // self is not a quadratic residue + return None; + } + for _ in 0..m - i - 1 { + c = &c * &c % modulus; + } + let b = c; + m = i; + c = &b * &b % modulus; + t = ((t * &b % modulus) * &b) % modulus; + r = (r * b) % modulus; + } + } +} + +// Returns a non-quadratic residue in the field +pub fn find_non_qr(modulus: &BigUint) -> BigUint { + if modulus % 4u32 == BigUint::from(3u8) { + // p = 3 mod 4 then -1 is a quadratic residue + modulus - BigUint::one() + } else if modulus % 8u32 == BigUint::from(5u8) { + // p = 5 mod 8 then 2 is a non-quadratic residue + // since 2^((p-1)/2) = (-1)^((p^2-1)/8) + BigUint::from_u8(2u8).unwrap() + } else { + let mut rng = StdRng::from_entropy(); + let mut non_qr = rng.gen_biguint_range( + &BigUint::from_u8(2).unwrap(), + &(modulus - BigUint::from_u8(1).unwrap()), + ); + // To check if non_qr is a quadratic nonresidue, we compute non_qr^((p-1)/2) + // If the result is p-1, then non_qr is a quadratic nonresidue + // Otherwise, non_qr is a quadratic residue + let exponent = (modulus - BigUint::one()) >> 1; + while non_qr.modpow(&exponent, modulus) != modulus - BigUint::one() { + non_qr = rng.gen_biguint_range( + &BigUint::from_u8(2).unwrap(), + &(modulus - BigUint::from_u8(1).unwrap()), + ); + } + non_qr + } +} diff --git a/extensions/algebra/complex-macros/Cargo.toml b/extensions/algebra/complex-macros/Cargo.toml index 72bf84810b..a6cf8a2fe4 100644 --- a/extensions/algebra/complex-macros/Cargo.toml +++ b/extensions/algebra/complex-macros/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "openvm-algebra-complex-macros" -version = "0.1.0" -edition = "2021" +description = "OpenVM algebra macros for complex ring extensions" +version.workspace = true +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] syn = { version = "2.0", features = ["full"] } diff --git a/extensions/algebra/complex-macros/README.md b/extensions/algebra/complex-macros/README.md index 35e4dfa69a..8536b9c371 100644 --- a/extensions/algebra/complex-macros/README.md +++ b/extensions/algebra/complex-macros/README.md @@ -10,21 +10,23 @@ The workflow of this macro is very similar to the [`openvm-algebra-moduli-macros openvm_algebra_moduli_macros::moduli_declare! { Secp256k1Coord { modulus = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" } } -openvm_algebra_moduli_macros::moduli_init!( - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" -); openvm_algebra_complex_macros::complex_declare! { Complex { mod_type = Secp256k1Coord } } +openvm::init!(); +/* The init! macro will expand to: +openvm_algebra_moduli_macros::moduli_init!( + "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" +); + openvm_algebra_complex_macros::complex_init! { Complex { mod_idx = 0 }, } +*/ pub fn main() { - setup_all_moduli(); - setup_all_complex_extensions(); // ... } ``` @@ -64,24 +66,23 @@ extern "C" { 2. Again, `complex_init!` macro implements these extern functions and defines the setup functions for the complex arithmetic struct. ```rust +#[allow(non_snake_case)] #[cfg(target_os = "zkvm")] mod openvm_intrinsics_ffi_complex { - fn complex_add_extern_func_Complex(rd: usize, rs1: usize, rs2: usize) { + #[no_mangle] + extern "C" fn complex_add_extern_func_Complex(rd: usize, rs1: usize, rs2: usize) { // send the instructions for the corresponding complex chip // If this struct was `init`ed k-th, these operations will be sent to the k-th complex chip } - // implement the other functions -} -pub fn setup_complex_0() { - // send the setup instructions -} -pub fn setup_all_complex_extensions() { - setup_complex_0(); - // call all other setup_complex_* for all the items in the moduli_init! macro + // .. implement the other functions + #[no_mangle] + extern "C" fn complex_setup_extern_func_Complex() { + // send the setup instructions + } } ``` -3. Obviously, `mod_idx` in the `complex_init!` must match the position of the corresponding modulus in the `moduli_init!` macro. The order of the items in `complex_init!` affects what `setup_complex_*` function will correspond to what complex class. Also, it **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of numbers in `Fp2Extension::supported_modulus`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). However, it again imposes the restriction that we only can invoke `complex_init!` once. Again analogous to the moduli setups, we must call `setup_complex_*` for each used complex extension before doing anything with entities of that class (or one can call `setup_all_complex_extensions` to setup all of them, if all are used). +3. Obviously, `mod_idx` in the `complex_init!` must match the position of the corresponding modulus in the `moduli_init!` macro. The order of the items in `complex_init!` affects what `setup_complex_*` function will correspond to what complex class. Also, it **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of numbers in `Fp2Extension::supported_moduli`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). However, it again imposes the restriction that we only can invoke `complex_init!` once. Again analogous to the moduli setups, the rust bindings will automatically call `complex_setup_extern_func_*` on each complex extension on first use of its intrinsics. 4. Note that, due to the nature of function names, the name of the struct used in `complex_init!` must be the same as in `complex_declare!`. To illustrate, the following code will **fail** to compile: @@ -100,3 +101,7 @@ complex_init! { ``` The reason is that, for example, the function `complex_add_extern_func_Bn254Fp2` remains unimplemented, but we implement `complex_add_extern_func_Fp2` instead. + +5. `cargo openvm build` will automatically generate a call to `complex_init!` based on `openvm.toml`. +Note that `openvm.toml` must list the supported moduli as pairs `(name, modulus)` where `name` is the name of the struct created by `complex_declare!` as a string (in the example at the top of this document, its `"Complex"`). +The SDK also supports this feature. diff --git a/extensions/algebra/complex-macros/src/lib.rs b/extensions/algebra/complex-macros/src/lib.rs index d829abeeed..ba25ee7279 100644 --- a/extensions/algebra/complex-macros/src/lib.rs +++ b/extensions/algebra/complex-macros/src/lib.rs @@ -58,6 +58,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { create_extern_func!(complex_sub_extern_func); create_extern_func!(complex_mul_extern_func); create_extern_func!(complex_div_extern_func); + create_extern_func!(complex_setup_extern_func); let result = TokenStream::from(quote::quote_spanned! { span.into() => extern "C" { @@ -65,6 +66,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { fn #complex_sub_extern_func(rd: usize, rs1: usize, rs2: usize); fn #complex_mul_extern_func(rd: usize, rs1: usize, rs2: usize); fn #complex_div_extern_func(rd: usize, rs1: usize, rs2: usize); + fn #complex_setup_extern_func(); } @@ -110,6 +112,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #complex_add_extern_func( self as *mut Self as usize, @@ -130,6 +133,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #complex_sub_extern_func( self as *mut Self as usize, @@ -154,6 +158,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #complex_mul_extern_func( self as *mut Self as usize, @@ -179,6 +184,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #complex_div_extern_func( self as *mut Self as usize, @@ -199,6 +205,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); let mut uninit: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); unsafe { #complex_add_extern_func( @@ -222,6 +229,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); let mut uninit: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); unsafe { #complex_sub_extern_func( @@ -249,6 +257,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #complex_mul_extern_func( dst_ptr as usize, @@ -270,6 +279,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); let mut uninit: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); unsafe { #complex_div_extern_func( @@ -281,6 +291,15 @@ pub fn complex_declare(input: TokenStream) -> TokenStream { unsafe { uninit.assume_init() } } } + + // Helper function to call the setup instruction on first use + fn set_up_once() { + static is_setup: ::openvm_algebra_guest::once_cell::race::OnceBool = ::openvm_algebra_guest::once_cell::race::OnceBool::new(); + is_setup.get_or_init(|| { + unsafe { #complex_setup_extern_func(); } + true + }); + } } impl openvm_algebra_guest::field::ComplexConjugate for #struct_name { @@ -527,8 +546,6 @@ pub fn complex_init(input: TokenStream) -> TokenStream { let MacroArgs { items } = parse_macro_input!(input as MacroArgs); let mut externs = Vec::new(); - let mut setups = Vec::new(); - let mut setup_all_complex_extensions = Vec::new(); let span = proc_macro::Span::call_site(); @@ -587,22 +604,22 @@ pub fn complex_init(input: TokenStream) -> TokenStream { }); } - let setup_function = - syn::Ident::new(&format!("setup_complex_{}", complex_idx), span.into()); + let setup_extern_func = syn::Ident::new( + &format!("complex_setup_extern_func_{}", struct_name), + span.into(), + ); - setup_all_complex_extensions.push(quote::quote_spanned! { span.into() => - #setup_function(); - }); - setups.push(quote::quote_spanned! { span.into() => - #[allow(non_snake_case)] - pub fn #setup_function() { + externs.push(quote::quote_spanned! { span.into() => + #[no_mangle] + extern "C" fn #setup_extern_func() { #[cfg(target_os = "zkvm")] { - let two_modulus_bytes = &openvm_intrinsics_meta_do_not_type_this_by_yourself::two_modular_limbs_list[openvm_intrinsics_meta_do_not_type_this_by_yourself::limb_list_borders[#mod_idx]..openvm_intrinsics_meta_do_not_type_this_by_yourself::limb_list_borders[#mod_idx + 1]]; + use super::openvm_intrinsics_meta_do_not_type_this_by_yourself::{two_modular_limbs_list, limb_list_borders}; + let two_modulus_bytes = &two_modular_limbs_list[limb_list_borders[#mod_idx]..limb_list_borders[#mod_idx + 1]]; // We are going to use the numeric representation of the `rs2` register to distinguish the chip to setup. // The transpiler will transform this instruction, based on whether `rs2` is `x0` or `x1`, into a `SETUP_ADDSUB` or `SETUP_MULDIV` instruction. - let mut uninit: core::mem::MaybeUninit<[u8; openvm_intrinsics_meta_do_not_type_this_by_yourself::limb_list_borders[#mod_idx + 1] - openvm_intrinsics_meta_do_not_type_this_by_yourself::limb_list_borders[#mod_idx]]> = core::mem::MaybeUninit::uninit(); + let mut uninit: core::mem::MaybeUninit<[u8; limb_list_borders[#mod_idx + 1] - limb_list_borders[#mod_idx]]> = core::mem::MaybeUninit::uninit(); openvm::platform::custom_insn_r!( opcode = ::openvm_algebra_guest::OPCODE, funct3 = ::openvm_algebra_guest::COMPLEX_EXT_FIELD_FUNCT3, @@ -629,14 +646,11 @@ pub fn complex_init(input: TokenStream) -> TokenStream { } TokenStream::from(quote::quote_spanned! { span.into() => + #[allow(non_snake_case)] #[cfg(target_os = "zkvm")] mod openvm_intrinsics_ffi_complex { #(#externs)* } - #(#setups)* - pub fn setup_all_complex_extensions() { - #(#setup_all_complex_extensions)* - } }) } diff --git a/extensions/algebra/guest/Cargo.toml b/extensions/algebra/guest/Cargo.toml index d57c9117e3..89548bbb60 100644 --- a/extensions/algebra/guest/Cargo.toml +++ b/extensions/algebra/guest/Cargo.toml @@ -10,8 +10,11 @@ repository.workspace = true [dependencies] openvm-algebra-moduli-macros = { workspace = true } openvm-algebra-complex-macros = { workspace = true } +openvm-rv32im-guest = { workspace = true } +openvm-custom-insn = { workspace = true } serde-big-array.workspace = true strum_macros.workspace = true +once_cell = { workspace = true, features = ["race", "alloc"] } [target.'cfg(not(target_os = "zkvm"))'.dependencies] num-bigint.workspace = true diff --git a/extensions/algebra/guest/src/halo2curves.rs b/extensions/algebra/guest/src/halo2curves.rs index a9a6513ac6..725bd889dc 100644 --- a/extensions/algebra/guest/src/halo2curves.rs +++ b/extensions/algebra/guest/src/halo2curves.rs @@ -1,55 +1,76 @@ -use core::ops::{Add, Mul, Sub}; - use halo2curves_axiom::ff; use crate::{field::Field, DivAssignUnsafe, DivUnsafe}; -impl<'a, F: ff::Field> DivUnsafe<&'a F> for F { - type Output = F; +macro_rules! field_impls { + ($($t:ty),*) => { + $( + impl DivUnsafe for $t { + type Output = $t; - fn div_unsafe(self, other: &'a F) -> Self::Output { - self * other.invert().unwrap() - } -} + fn div_unsafe(self, other: Self) -> Self::Output { + self * other.invert().unwrap() + } + } -impl<'a, F: ff::Field> DivUnsafe<&'a F> for &'a F { - type Output = F; + impl<'a> DivUnsafe<&'a $t> for $t { + type Output = $t; - fn div_unsafe(self, other: &'a F) -> Self::Output { - *self * other.invert().unwrap() - } -} + fn div_unsafe(self, other: &'a $t) -> Self::Output { + self * other.invert().unwrap() + } + } -impl DivAssignUnsafe for F { - fn div_assign_unsafe(&mut self, other: Self) { - *self *= other.invert().unwrap(); - } -} + impl<'a> DivUnsafe<&'a $t> for &'a $t { + type Output = $t; -impl<'a, F: ff::Field> DivAssignUnsafe<&'a F> for F { - fn div_assign_unsafe(&mut self, other: &'a F) { - *self *= other.invert().unwrap(); - } -} + fn div_unsafe(self, other: &'a $t) -> Self::Output { + *self * other.invert().unwrap() + } + } -impl Field for F -where - for<'a> &'a F: Add<&'a F, Output = F> + Sub<&'a F, Output = F> + Mul<&'a F, Output = F>, -{ - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; + impl DivAssignUnsafe for $t { + fn div_assign_unsafe(&mut self, other: Self) { + *self *= other.invert().unwrap(); + } + } - type SelfRef<'a> = &'a F; + impl<'a> DivAssignUnsafe<&'a $t> for $t { + fn div_assign_unsafe(&mut self, other: &'a $t) { + *self *= other.invert().unwrap(); + } + } - fn double_assign(&mut self) { - *self += *self; - } + impl Field for $t { + const ZERO: Self = <$t as ff::Field>::ZERO; + const ONE: Self = <$t as ff::Field>::ONE; - fn square_assign(&mut self) { - *self = self.square(); - } + type SelfRef<'a> = &'a Self; + + fn double_assign(&mut self) { + *self += *self; + } + + fn square_assign(&mut self) { + *self = self.square(); + } + } + + )* + }; } +field_impls!( + halo2curves_axiom::bls12_381::Fq, + halo2curves_axiom::bls12_381::Fq12, + halo2curves_axiom::bls12_381::Fq2 +); +field_impls!( + halo2curves_axiom::bn256::Fq, + halo2curves_axiom::bn256::Fq12, + halo2curves_axiom::bn256::Fq2 +); + mod bn254 { use alloc::vec::Vec; diff --git a/extensions/algebra/guest/src/lib.rs b/extensions/algebra/guest/src/lib.rs index 1d778f0013..a83e43b18a 100644 --- a/extensions/algebra/guest/src/lib.rs +++ b/extensions/algebra/guest/src/lib.rs @@ -17,6 +17,8 @@ pub enum ModArithBaseFunct7 { DivMod, IsEqMod, SetupMod, + HintNonQr, + HintSqrt, } impl ModArithBaseFunct7 { @@ -54,11 +56,13 @@ pub use field::Field; use num_bigint::BigUint; pub use openvm_algebra_complex_macros as complex_macros; pub use openvm_algebra_moduli_macros as moduli_macros; +#[cfg(target_os = "zkvm")] +pub use openvm_custom_insn; +#[cfg(target_os = "zkvm")] +pub use openvm_rv32im_guest; pub use serde_big_array::BigArray; use strum_macros::FromRepr; -/// Field traits -pub mod field; /// Implementation of this library's traits on halo2curves types. /// Used for testing and also VM runtime execution. /// These should **only** be importable on a host machine. @@ -67,7 +71,10 @@ mod halo2curves; /// Exponentiation by bytes mod exp_bytes; +/// Field traits +pub mod field; pub use exp_bytes::*; +pub use once_cell; /// Division operation that is undefined behavior when the denominator is not invertible. pub trait DivUnsafe: Sized { @@ -222,6 +229,26 @@ pub trait IntMod: /// Is the integer representation of `self` less than the modulus? fn is_reduced(&self) -> bool; + + /// Calls any setup required for this modulus. The implementation should internally use + /// `OnceBool` to ensure that setup is only called once. + fn set_up_once(); + + /// Returns whether the two integers are congrument modulo the modulus. + /// + /// # Safety + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + unsafe fn eq_impl(&self, other: &Self) -> bool; + + /// Add two elements. + /// + /// # Safety + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + unsafe fn add_ref(&self, other: &Self) -> Self; } // Ref: https://docs.rs/elliptic-curve/latest/elliptic_curve/ops/trait.Reduce.html @@ -232,3 +259,11 @@ pub trait Reduce: Sized { Self::reduce_le_bytes(&bytes.iter().rev().copied().collect::>()) } } + +// Note that we use a hint-based approach to prove whether the square root exists. +// This approach works for prime moduli, but not necessarily for composite moduli, +// which is why the Sqrt trait requires the Field trait, not just the IntMod trait. +pub trait Sqrt: Field { + /// Returns a square root of `self` if it exists. + fn sqrt(&self) -> Option; +} diff --git a/extensions/algebra/moduli-macros/Cargo.toml b/extensions/algebra/moduli-macros/Cargo.toml index 38abb9f248..0e56c1c0dc 100644 --- a/extensions/algebra/moduli-macros/Cargo.toml +++ b/extensions/algebra/moduli-macros/Cargo.toml @@ -1,12 +1,18 @@ [package] name = "openvm-algebra-moduli-macros" -version = { workspace = true } -edition = "2021" +description = "OpenVM algebra macros for modular arithmetic" +version.workspace = true +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] syn = { version = "2.0", features = ["full"] } quote = "1.0" openvm-macros-common = { workspace = true, default-features = false } +num-prime = { version = "0.4.4", default-features = false, features = ["big-int"] } +num-bigint = { workspace = true } [lib] -proc-macro = true \ No newline at end of file +proc-macro = true diff --git a/extensions/algebra/moduli-macros/README.md b/extensions/algebra/moduli-macros/README.md index 134209b0cc..d087b6299d 100644 --- a/extensions/algebra/moduli-macros/README.md +++ b/extensions/algebra/moduli-macros/README.md @@ -14,11 +14,14 @@ openvm_algebra_moduli_macros::moduli_declare! { Mersenne61 { modulus = "0x1fffffffffffffff" }, } +openvm::init!(); +/* The init! macro will expand to: openvm_algebra_moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", "1000000000000000003", "0x1fffffffffffffff", } +*/ ``` ## Full story @@ -48,6 +51,7 @@ extern "C" { fn mul_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize); fn div_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize); fn is_eq_extern_func_de0b6b3a7640003(rs1: usize, rs2: usize) -> bool; + fn moduli_setup_extern_func_de0b6b3a7640003(); } impl Mod1e18 { @@ -110,6 +114,14 @@ mod algebra_impl_0 { } // ... } + + impl Field for Mod1e18 { + // ... + } + + impl Sqrt for Mod1e18 { + // ... + } } ``` @@ -125,31 +137,39 @@ Here `add_extern_func_de0b6b3a7640003` is the name of the function that will be static OPENVM_SERIALIZED_MODULUS_2: [u8; 32] = [/* bytes of the modulus */]; #[cfg(target_os = "zkvm")] mod openvm_intrinsics_ffi { - fn add_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize); - fn sub_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize); - fn mul_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize); - fn div_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize); - fn is_eq_extern_func_de0b6b3a7640003(rs1: usize, rs2: usize) -> bool; + fn add_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize) { + // Implementation here + } + fn sub_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize) { + // Implementation here + } + fn mul_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize) { + // Implementation here + } + fn div_extern_func_de0b6b3a7640003(rd: usize, rs1: usize, rs2: usize) { + // Implementation here + } + fn is_eq_extern_func_de0b6b3a7640003(rs1: usize, rs2: usize) -> bool { + // Implementation here + } + fn moduli_setup_extern_func_de0b6b3a7640003() { + // Implementation here + } } #[allow(non_snake_case, non_upper_case_globals)] pub mod openvm_intrinsics_meta_do_not_type_this_by_yourself { // information about the bytes of all moduli } -#[allow(non_snake_case)] -pub fn setup_2() { - #[cfg(target_os = "zkvm")] - { - // send the setup instruction designed for the chip number 2 - } -} -pub fn setup_all_moduli() { - setup_0(); - setup_1(); - setup_2(); - // setup functions for all the other moduli provided in the `moduli_init!` function -} ``` -The setup operation (e.g., `setup_2`) consists of reading the value `OPENVM_SERIALIZED_MODULUS_2` from memory and constraining that the read value is equal to the modulus the chip has been configured with. For each used modulus, its corresponding setup instruction **must** be called before all other operations -- this currently must be checked by inspecting the program code; it is not enforced by the virtual machine. +The setup operation (e.g., `moduli_setup_extern_func_de0b6b3a7640003`) consists of reading the value `OPENVM_SERIALIZED_MODULUS_2` from memory and constraining that the read value is equal to the modulus the chip has been configured with. For each used modulus, the Rust bindings for the non-setup intrinsic instructions will automatically call the corresponding setup instruction on first use of any of its intrinsics. + +5. It follows from the above that the `moduli_declare!` invocations may be in multiple places in various compilation units, but all the `declare!`d moduli must be specified at least once in `moduli_init!` so that there will be no linker errors due to missing function implementations. Correspondingly, the `moduli_init!` macro should only be called once in the entire program (in the guest crate as the topmost compilation unit). Finally, the order of the moduli in `moduli_init!` has nothing to do with the `moduli_declare!` invocations, but it **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of numbers in `ModularExtension::supported_moduli`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). -5. It follows from the above that the `moduli_declare!` invocations may be in multiple places in various compilation units, but all the `declare!`d moduli must be specified at least once in `moduli_init!` so that there will be no linker errors due to missing function implementations. Correspondingly, the `moduli_init!` macro should only be called once in the entire program (in the guest crate as the topmost compilation unit). Finally, the order of the moduli in `moduli_init!` has nothing to do with the `moduli_declare!` invocations, but it **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of numbers in `ModularExtension::supported_modulus`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file. The plan is to obtain this value from the specific section of the ELF file at some point). +6. For convenience, running `cargo openvm build` will automatically generate an appropriate call to `moduli_declare!` based on the order of the moduli in `openvm.toml`. +More specifically, `cargo openvm build` will read `openvm.toml`, then generate a file named `openvm_init.rs` in the project's manifest directory (where `Cargo.toml` is located) containing a call to `moduli_declare!`. +Then, you must call `openvm::init!();` in your code, and this will insert the contents of `openvm_init.rs` file into your code in place of the `init!` macro. +You may specify an alternate name for the init file using the `--init-file-name` option of `cargo openvm build`. +To include the generated file in your code, pass its name into the `init!` macro (for ex. `init!("my_init_file.rs")`). +The custom filename will be interpreted as relative to the manifest directory. +If you are using the SDK to build your code, it will also automatically generate the init file based on your config. diff --git a/extensions/algebra/moduli-macros/src/lib.rs b/extensions/algebra/moduli-macros/src/lib.rs index 5d8d921f2f..d7d366a930 100644 --- a/extensions/algebra/moduli-macros/src/lib.rs +++ b/extensions/algebra/moduli-macros/src/lib.rs @@ -1,7 +1,10 @@ +extern crate alloc; extern crate proc_macro; use std::sync::atomic::AtomicUsize; +use num_bigint::BigUint; +use num_prime::nt_funcs::is_prime; use openvm_macros_common::{string_to_bytes, MacroArgs}; use proc_macro::TokenStream; use quote::format_ident; @@ -44,9 +47,12 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { { modulus = Some(value.value()); } else { - return syn::Error::new_spanned(param.value, "Expected a string literal") - .to_compile_error() - .into(); + return syn::Error::new_spanned( + param.value, + "Expected a string literal for macro argument `modulus`", + ) + .to_compile_error() + .into(); } } _ => { @@ -98,6 +104,9 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { create_extern_func!(mul_extern_func); create_extern_func!(div_extern_func); create_extern_func!(is_eq_extern_func); + create_extern_func!(hint_sqrt_extern_func); + create_extern_func!(hint_non_qr_extern_func); + create_extern_func!(moduli_setup_extern_func); let block_size = proc_macro::Literal::usize_unsuffixed(block_size); let block_size = syn::Lit::new(block_size.to_string().parse::<_>().unwrap()); @@ -126,6 +135,9 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { fn #mul_extern_func(rd: usize, rs1: usize, rs2: usize); fn #div_extern_func(rd: usize, rs1: usize, rs2: usize); fn #is_eq_extern_func(rs1: usize, rs2: usize) -> bool; + fn #hint_sqrt_extern_func(rs1: usize); + fn #hint_non_qr_extern_func(); + fn #moduli_setup_extern_func(); } impl #struct_name { @@ -152,6 +164,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #add_extern_func( self as *mut Self as usize, @@ -173,6 +186,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #sub_extern_func( self as *mut Self as usize, @@ -193,6 +207,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #mul_extern_func( self as *mut Self as usize, @@ -213,6 +228,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #div_extern_func( self as *mut Self as usize, @@ -223,10 +239,10 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } } - /// SAFETY: `dst_ptr` must be a raw pointer to `&mut Self`. - /// It will be written to only at the very end . + /// # Safety + /// - `dst_ptr` must be a raw pointer to `&mut Self`. It will be written to only at the very end. #[inline(always)] - unsafe fn add_refs_impl(&self, other: &Self, dst_ptr: *mut Self) { + unsafe fn add_refs_impl(&self, other: &Self, dst_ptr: *mut Self) { #[cfg(not(target_os = "zkvm"))] { let mut res = self.clone(); @@ -237,13 +253,14 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { - unsafe { - #add_extern_func( - dst_ptr as usize, - self as *const #struct_name as usize, - other as *const #struct_name as usize, - ); + if CHECK_SETUP { + Self::set_up_once(); } + #add_extern_func( + dst_ptr as usize, + self as *const #struct_name as usize, + other as *const #struct_name as usize, + ); } } @@ -261,6 +278,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #sub_extern_func( dst_ptr as usize, @@ -285,6 +303,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); unsafe { #mul_extern_func( dst_ptr as usize, @@ -305,6 +324,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + Self::set_up_once(); let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit(); unsafe { #div_extern_func( @@ -318,18 +338,35 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } #[inline(always)] - fn eq_impl(&self, other: &Self) -> bool { + unsafe fn eq_impl(&self, other: &Self) -> bool { #[cfg(not(target_os = "zkvm"))] { self.as_le_bytes() == other.as_le_bytes() } #[cfg(target_os = "zkvm")] { - unsafe { - #is_eq_extern_func(self as *const #struct_name as usize, other as *const #struct_name as usize) + if CHECK_SETUP { + Self::set_up_once(); } + #is_eq_extern_func(self as *const #struct_name as usize, other as *const #struct_name as usize) } } + + // Helper function to call the setup instruction on first use + #[inline(always)] + #[cfg(target_os = "zkvm")] + fn set_up_once() { + static is_setup: ::openvm_algebra_guest::once_cell::race::OnceBool = ::openvm_algebra_guest::once_cell::race::OnceBool::new(); + is_setup.get_or_init(|| { + unsafe { #moduli_setup_extern_func(); } + true + }); + } + #[inline(always)] + #[cfg(not(target_os = "zkvm"))] + fn set_up_once() { + // No-op for non-ZKVM targets + } } // Put trait implementations in a private module to avoid conflicts @@ -384,10 +421,12 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { Self(bytes) } + #[inline(always)] fn as_le_bytes(&self) -> &[u8] { &(self.0) } + #[inline(always)] fn to_be_bytes(&self) -> [u8; #limbs] { core::array::from_fn(|i| self.0[#limbs - 1 - i]) } @@ -407,6 +446,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { num_bigint::BigUint::from_bytes_le(self.as_le_bytes()) } + #[inline(always)] fn neg_assign(&mut self) { unsafe { // SAFETY: we borrow self as &Self and as *mut Self but @@ -415,14 +455,16 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } } + #[inline(always)] fn double_assign(&mut self) { unsafe { // SAFETY: we borrow self as &Self and as *mut Self but // the latter will only be written to at the very end. - self.add_refs_impl(self, self as *const Self as *mut Self); + self.add_refs_impl::(self, self as *const Self as *mut Self); } } + #[inline(always)] fn square_assign(&mut self) { unsafe { // SAFETY: we borrow self as &Self and as *mut Self but @@ -431,14 +473,17 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { } } + #[inline(always)] fn double(&self) -> Self { self + self } + #[inline(always)] fn square(&self) -> Self { self * self } + #[inline(always)] fn cube(&self) -> Self { &self.square() * self } @@ -464,6 +509,23 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { // At this point, all limbs are equal false } + + #[inline(always)] + fn set_up_once() { + Self::set_up_once(); + } + + #[inline(always)] + unsafe fn eq_impl(&self, other: &Self) -> bool { + Self::eq_impl::(self, other) + } + + #[inline(always)] + unsafe fn add_ref(&self, other: &Self) -> Self { + let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit(); + self.add_refs_impl::(other, uninit.as_mut_ptr()); + uninit.assume_init() + } } impl<'a> core::ops::AddAssign<&'a #struct_name> for #struct_name { @@ -502,11 +564,8 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { type Output = #struct_name; #[inline(always)] fn add(self, other: &'a #struct_name) -> Self::Output { - let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit(); - unsafe { - self.add_refs_impl(other, uninit.as_mut_ptr()); - uninit.assume_init() - } + // Safety: ensure setup + unsafe { self.add_ref::(other) } } } @@ -646,7 +705,8 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { impl PartialEq for #struct_name { #[inline(always)] fn eq(&self, other: &Self) -> bool { - self.eq_impl(other) + // Safety: must check setup + unsafe { self.eq_impl::(other) } } } @@ -676,6 +736,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { impl core::ops::Neg for #struct_name { type Output = #struct_name; + #[inline(always)] fn neg(self) -> Self::Output { #struct_name::ZERO - &self } @@ -683,6 +744,7 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { impl<'a> core::ops::Neg for &'a #struct_name { type Output = #struct_name; + #[inline(always)] fn neg(self) -> Self::Output { #struct_name::ZERO - self } @@ -699,10 +761,10 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { fn reduce_le_bytes(bytes: &[u8]) -> Self { let mut res = ::ZERO; // base should be 2 ^ #limbs which exceeds what Self can represent - let mut base = Self::from_le_bytes(&[255u8; #limbs]); + let mut base = ::from_le_bytes(&[255u8; #limbs]); base += ::ONE; for chunk in bytes.chunks(#limbs).rev() { - res = res * &base + Self::from_le_bytes(chunk); + res = res * &base + ::from_le_bytes(chunk); } res } @@ -710,6 +772,157 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream { }); output.push(result); + + let modulus_biguint = BigUint::from_bytes_le(&modulus_bytes); + let modulus_is_prime = is_prime(&modulus_biguint, None); + + if modulus_is_prime.probably() { + // implement Field and Sqrt traits for prime moduli + let field_and_sqrt_impl = TokenStream::from(quote::quote_spanned! { span.into() => + impl ::openvm_algebra_guest::Field for #struct_name { + const ZERO: Self = ::ZERO; + const ONE: Self = ::ONE; + + type SelfRef<'a> = &'a Self; + + fn double_assign(&mut self) { + ::openvm_algebra_guest::IntMod::double_assign(self); + } + + fn square_assign(&mut self) { + ::openvm_algebra_guest::IntMod::square_assign(self); + } + + } + + impl openvm_algebra_guest::Sqrt for #struct_name { + // Returns a sqrt of self if it exists, otherwise None. + // Note that we use a hint-based approach to prove whether the square root exists. + // This approach works for prime moduli, but not necessarily for composite moduli, + // which is why we have the sqrt method in the Field trait, not the IntMod trait. + fn sqrt(&self) -> Option { + match self.honest_host_sqrt() { + // self is a square + Some(Some(sqrt)) => Some(sqrt), + // self is not a square + Some(None) => None, + // host is dishonest + None => { + // host is dishonest, enter infinite loop + loop { + openvm::io::println("ERROR: Square root hint is invalid. Entering infinite loop."); + } + } + } + } + } + + impl #struct_name { + // Returns None if the hint is incorrect (i.e. the host is dishonest) + // Returns Some(None) if the hint proves that self is not a quadratic residue + // Otherwise, returns Some(Some(sqrt)) where sqrt is a square root of self + fn honest_host_sqrt(&self) -> Option> { + let (is_square, sqrt) = self.hint_sqrt_impl()?; + + if is_square { + // ensure sqrt < modulus + ::assert_reduced(&sqrt); + + if &(&sqrt * &sqrt) == self { + Some(Some(sqrt)) + } else { + None + } + } else { + // ensure sqrt < modulus + ::assert_reduced(&sqrt); + + if &sqrt * &sqrt == self * Self::get_non_qr() { + Some(None) + } else { + None + } + } + } + + + // Returns None if the hint is malformed. + // Otherwise, returns Some((is_square, sqrt)) where sqrt is a square root of self if is_square is true, + // and a square root of self * non_qr if is_square is false. + fn hint_sqrt_impl(&self) -> Option<(bool, Self)> { + #[cfg(not(target_os = "zkvm"))] + { + unimplemented!(); + } + #[cfg(target_os = "zkvm")] + { + use ::openvm_algebra_guest::{openvm_custom_insn, openvm_rv32im_guest}; // needed for hint_store_u32! and hint_buffer_u32! + + let is_square = core::mem::MaybeUninit::::uninit(); + let sqrt = core::mem::MaybeUninit::<#struct_name>::uninit(); + unsafe { + #hint_sqrt_extern_func(self as *const #struct_name as usize); + let is_square_ptr = is_square.as_ptr() as *const u32; + openvm_rv32im_guest::hint_store_u32!(is_square_ptr); + openvm_rv32im_guest::hint_buffer_u32!(sqrt.as_ptr() as *const u8, <#struct_name as ::openvm_algebra_guest::IntMod>::NUM_LIMBS / 4); + let is_square = is_square.assume_init(); + if is_square == 0 || is_square == 1 { + Some((is_square == 1, sqrt.assume_init())) + } else { + None + } + } + } + } + + // Generate a non quadratic residue by using a hint + fn init_non_qr() -> alloc::boxed::Box<#struct_name> { + #[cfg(not(target_os = "zkvm"))] + { + unimplemented!(); + } + #[cfg(target_os = "zkvm")] + { + use ::openvm_algebra_guest::{openvm_custom_insn, openvm_rv32im_guest}; // needed for hint_buffer_u32! + + let mut non_qr_uninit = core::mem::MaybeUninit::::uninit(); + let mut non_qr; + unsafe { + #hint_non_qr_extern_func(); + let ptr = non_qr_uninit.as_ptr() as *const u8; + openvm_rv32im_guest::hint_buffer_u32!(ptr, ::NUM_LIMBS / 4); + non_qr = non_qr_uninit.assume_init(); + } + // ensure non_qr < modulus + ::assert_reduced(&non_qr); + + use ::openvm_algebra_guest::{DivUnsafe, ExpBytes}; + // construct exp = (p-1)/2 as an integer by first constraining exp = (p-1)/2 (mod p) and then exp < p + let exp = -::ONE.div_unsafe(Self::from_const_u8(2)); + ::assert_reduced(&exp); + + if non_qr.exp_bytes(true, &::to_be_bytes(&exp)) != -::ONE + { + // non_qr is not a non quadratic residue, so host is dishonest + loop { + openvm::io::println("ERROR: Non quadratic residue hint is invalid. Entering infinite loop."); + } + } + + alloc::boxed::Box::new(non_qr) + } + } + + // This function is public for use in tests + pub fn get_non_qr() -> &'static #struct_name { + static non_qr: ::openvm_algebra_guest::once_cell::race::OnceBox<#struct_name> = ::openvm_algebra_guest::once_cell::race::OnceBox::new(); + &non_qr.get_or_init(Self::init_non_qr) + } + } + }); + + output.push(field_and_sqrt_impl); + } } TokenStream::from_iter(output) @@ -733,9 +946,7 @@ pub fn moduli_init(input: TokenStream) -> TokenStream { let ModuliDefine { items } = parse_macro_input!(input as ModuliDefine); let mut externs = Vec::new(); - let mut setups = Vec::new(); let mut openvm_section = Vec::new(); - let mut setup_all_moduli = Vec::new(); // List of all modular limbs in one (that is, with a compile-time known size) array. let mut two_modular_limbs_flattened_list = Vec::::new(); @@ -794,7 +1005,10 @@ pub fn moduli_init(input: TokenStream) -> TokenStream { span.into(), ); let serialized_len = serialized_modulus.len(); - let setup_function = syn::Ident::new(&format!("setup_{}", mod_idx), span.into()); + let setup_extern_func = syn::Ident::new( + &format!("moduli_setup_extern_func_{}", modulus_hex), + span.into(), + ); openvm_section.push(quote::quote_spanned! { span.into() => #[cfg(target_os = "zkvm")] @@ -848,23 +1062,59 @@ pub fn moduli_init(input: TokenStream) -> TokenStream { } }); - setup_all_moduli.push(quote::quote_spanned! { span.into() => - #setup_function(); + let hint_non_qr_extern_func = syn::Ident::new( + &format!("hint_non_qr_extern_func_{}", modulus_hex), + span.into(), + ); + externs.push(quote::quote_spanned! { span.into() => + #[no_mangle] + extern "C" fn #hint_non_qr_extern_func() { + openvm::platform::custom_insn_r!( + opcode = ::openvm_algebra_guest::OPCODE, + funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3 as usize, + funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::HintNonQr as usize + #mod_idx * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize), + rd = Const "x0", + rs1 = Const "x0", + rs2 = Const "x0" + ); + } + + }); - setups.push(quote::quote_spanned! { span.into() => - #[allow(non_snake_case)] - pub fn #setup_function() { + // This function will be defined regardless of whether the modulus is prime or not, + // but it will be called only if the modulus is prime. + let hint_sqrt_extern_func = syn::Ident::new( + &format!("hint_sqrt_extern_func_{}", modulus_hex), + span.into(), + ); + externs.push(quote::quote_spanned! { span.into() => + #[no_mangle] + extern "C" fn #hint_sqrt_extern_func(rs1: usize) { + openvm::platform::custom_insn_r!( + opcode = ::openvm_algebra_guest::OPCODE, + funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3 as usize, + funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::HintSqrt as usize + #mod_idx * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize), + rd = Const "x0", + rs1 = In rs1, + rs2 = Const "x0" + ); + } + }); + + externs.push(quote::quote_spanned! { span.into() => + #[no_mangle] + extern "C" fn #setup_extern_func() { #[cfg(target_os = "zkvm")] { let mut ptr = 0; - assert_eq!(#serialized_name[ptr], 1); + assert_eq!(super::#serialized_name[ptr], 1); ptr += 1; - assert_eq!(#serialized_name[ptr], #mod_idx as u8); + assert_eq!(super::#serialized_name[ptr], #mod_idx as u8); ptr += 1; - assert_eq!(#serialized_name[ptr..ptr+4].iter().rev().fold(0, |acc, &x| acc * 256 + x as usize), #limbs); + assert_eq!(super::#serialized_name[ptr..ptr+4].iter().rev().fold(0, |acc, &x| acc * 256 + x as usize), #limbs); ptr += 4; - let remaining = &#serialized_name[ptr..]; + let remaining = &super::#serialized_name[ptr..]; // To avoid importing #struct_name, we create a placeholder struct with the same size and alignment. #[repr(C, align(#block_size))] @@ -917,6 +1167,7 @@ pub fn moduli_init(input: TokenStream) -> TokenStream { let cnt_limbs_list_len = limb_list_borders.len(); TokenStream::from(quote::quote_spanned! { span.into() => #(#openvm_section)* + #[allow(non_snake_case)] #[cfg(target_os = "zkvm")] mod openvm_intrinsics_ffi { #(#externs)* @@ -926,9 +1177,5 @@ pub fn moduli_init(input: TokenStream) -> TokenStream { pub const two_modular_limbs_list: [u8; #total_limbs_cnt] = [#(#two_modular_limbs_flattened_list),*]; pub const limb_list_borders: [usize; #cnt_limbs_list_len] = [#(#limb_list_borders),*]; } - #(#setups)* - pub fn setup_all_moduli() { - #(#setup_all_moduli)* - } }) } diff --git a/extensions/algebra/tests/programs/Cargo.toml b/extensions/algebra/tests/programs/Cargo.toml index ef4a8c4c1b..fabf651e1c 100644 --- a/extensions/algebra/tests/programs/Cargo.toml +++ b/extensions/algebra/tests/programs/Cargo.toml @@ -11,6 +11,7 @@ openvm-platform = { path = "../../../../crates/toolchain/platform" } openvm-algebra-guest = { path = "../../guest" } openvm-algebra-moduli-macros = { path = "../../../algebra/moduli-macros", default-features = false } openvm-algebra-complex-macros = { path = "../../../algebra/complex-macros", default-features = false } + num-bigint = { version = "0.4", default-features = false } serde = { version = "1.0", default-features = false, features = [ "alloc", diff --git a/extensions/algebra/tests/programs/examples/complex-redundant-modulus.rs b/extensions/algebra/tests/programs/examples/complex_redundant_modulus.rs similarity index 69% rename from extensions/algebra/tests/programs/examples/complex-redundant-modulus.rs rename to extensions/algebra/tests/programs/examples/complex_redundant_modulus.rs index e3fd151026..5849c54e63 100644 --- a/extensions/algebra/tests/programs/examples/complex-redundant-modulus.rs +++ b/extensions/algebra/tests/programs/examples/complex_redundant_modulus.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use openvm_algebra_guest::IntMod; openvm::entry!(main); @@ -11,21 +13,14 @@ openvm_algebra_moduli_macros::moduli_declare! { Mod3 { modulus = "1000000009" }, Mod4 { modulus = "987898789" }, } -openvm_algebra_moduli_macros::moduli_init! { - "998244353", "1000000007", "1000000009", "987898789" -} openvm_algebra_complex_macros::complex_declare! { Complex2 { mod_type = Mod3 }, } -openvm_algebra_complex_macros::complex_init! { - Complex2 { mod_idx = 2 }, -} +openvm::init!("openvm_init_complex_redundant_modulus.rs"); pub fn main() { - setup_all_moduli(); - setup_all_complex_extensions(); let b = Complex2::new(Mod3::ZERO, Mod3::from_u32(1000000008)); assert_eq!(b.clone() * &b * &b * &b * &b, b); } diff --git a/extensions/algebra/tests/programs/examples/complex-secp256k1.rs b/extensions/algebra/tests/programs/examples/complex_secp256k1.rs similarity index 81% rename from extensions/algebra/tests/programs/examples/complex-secp256k1.rs rename to extensions/algebra/tests/programs/examples/complex_secp256k1.rs index 344d12ff6e..975797755e 100644 --- a/extensions/algebra/tests/programs/examples/complex-secp256k1.rs +++ b/extensions/algebra/tests/programs/examples/complex_secp256k1.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use openvm_algebra_guest::{field::ComplexConjugate, DivAssignUnsafe, DivUnsafe, IntMod}; openvm::entry!(main); @@ -8,21 +10,14 @@ openvm::entry!(main); openvm_algebra_moduli_macros::moduli_declare! { Secp256k1Coord { modulus = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" } } -openvm_algebra_moduli_macros::moduli_init!( - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" -); openvm_algebra_complex_macros::complex_declare! { Complex { mod_type = Secp256k1Coord } } -openvm_algebra_complex_macros::complex_init! { - Complex { mod_idx = 0}, -} +openvm::init!("openvm_init_complex_secp256k1.rs"); pub fn main() { - setup_all_moduli(); - setup_all_complex_extensions(); let mut a = Complex::new( Secp256k1Coord::from_repr(core::array::from_fn(|_| 10)), Secp256k1Coord::from_repr(core::array::from_fn(|_| 21)), diff --git a/extensions/algebra/tests/programs/examples/complex-two-modulos.rs b/extensions/algebra/tests/programs/examples/complex_two_moduli.rs similarity index 72% rename from extensions/algebra/tests/programs/examples/complex-two-modulos.rs rename to extensions/algebra/tests/programs/examples/complex_two_moduli.rs index b8cd0e46c7..5ad7721dfa 100644 --- a/extensions/algebra/tests/programs/examples/complex-two-modulos.rs +++ b/extensions/algebra/tests/programs/examples/complex_two_moduli.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use openvm_algebra_guest::IntMod; openvm::entry!(main); @@ -9,22 +11,15 @@ openvm_algebra_moduli_macros::moduli_declare! { Mod1 { modulus = "998244353" }, Mod2 { modulus = "1000000007" } } -openvm_algebra_moduli_macros::moduli_init! { - "998244353", "1000000007" -} openvm_algebra_complex_macros::complex_declare! { Complex1 { mod_type = Mod1 }, Complex2 { mod_type = Mod2 }, } -openvm_algebra_complex_macros::complex_init! { - Complex1 { mod_idx = 0 }, Complex2 { mod_idx = 1 }, -} +openvm::init!("openvm_init_complex_two_moduli.rs"); pub fn main() { - setup_all_moduli(); - setup_all_complex_extensions(); let a = Complex1::new(Mod1::ZERO, Mod1::from_u32(998244352)); let b = Complex2::new(Mod2::ZERO, Mod2::from_u32(1000000006)); assert_eq!(a.clone() * &a * &a * &a * &a, a); diff --git a/extensions/algebra/tests/programs/examples/invalid-setup.rs b/extensions/algebra/tests/programs/examples/invalid_setup.rs similarity index 82% rename from extensions/algebra/tests/programs/examples/invalid-setup.rs rename to extensions/algebra/tests/programs/examples/invalid_setup.rs index 3321dd3238..bb5a5be163 100644 --- a/extensions/algebra/tests/programs/examples/invalid-setup.rs +++ b/extensions/algebra/tests/programs/examples/invalid_setup.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use openvm_algebra_guest::IntMod; openvm::entry!(main); @@ -18,5 +20,7 @@ openvm_algebra_moduli_macros::moduli_init! { pub fn main() { // this should cause a debug assertion to fail - setup_all_moduli(); + let x = Mod1::from_u32(1); + let y = Mod1::from_u32(1); + let _z = x + y; } diff --git a/extensions/algebra/tests/programs/examples/little.rs b/extensions/algebra/tests/programs/examples/little.rs index dc1d2773a3..048c5da69e 100644 --- a/extensions/algebra/tests/programs/examples/little.rs +++ b/extensions/algebra/tests/programs/examples/little.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use openvm_algebra_guest::{DivUnsafe, IntMod}; openvm::entry!(main); @@ -9,12 +11,9 @@ openvm_algebra_moduli_macros::moduli_declare! { Secp256k1Coord { modulus = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" } } -openvm_algebra_moduli_macros::moduli_init!( - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" -); +openvm::init!("openvm_init_little.rs"); pub fn main() { - setup_all_moduli(); let mut pow = Secp256k1Coord::MODULUS; pow[0] -= 2; diff --git a/extensions/algebra/tests/programs/examples/moduli_setup.rs b/extensions/algebra/tests/programs/examples/moduli_setup.rs index d5467a3f2a..b9abec0a9f 100644 --- a/extensions/algebra/tests/programs/examples/moduli_setup.rs +++ b/extensions/algebra/tests/programs/examples/moduli_setup.rs @@ -15,14 +15,9 @@ openvm_algebra_moduli_macros::moduli_declare! { Mersenne61 { modulus = "0x1fffffffffffffff" }, } -openvm_algebra_moduli_macros::moduli_init! { - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", - "1000000000000000003", - "0x1fffffffffffffff", -} +openvm::init!("openvm_init_moduli_setup.rs"); pub fn main() { - setup_all_moduli(); let x = Bls12381::from_repr(core::array::from_fn(|i| i as u8)); assert_eq!(x.0.len(), 48); diff --git a/extensions/algebra/tests/programs/examples/sqrt.rs b/extensions/algebra/tests/programs/examples/sqrt.rs new file mode 100644 index 0000000000..a147d54dd0 --- /dev/null +++ b/extensions/algebra/tests/programs/examples/sqrt.rs @@ -0,0 +1,33 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use openvm_algebra_guest::{Field, IntMod, Sqrt}; + +openvm::entry!(main); + +openvm_algebra_moduli_macros::moduli_declare! { + Secp256k1Coord { + modulus = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", + } +} + +openvm::init!("openvm_init_sqrt.rs"); + +pub fn main() { + let a = Secp256k1Coord::from_u32(4); + let sqrt = a.sqrt(); + assert_eq!(sqrt, Some(Secp256k1Coord::from_u32(2))); + + let b = ::ZERO - ::ONE; + let sqrt = b.sqrt(); + // -1 is not a quadratic residue modulo p when p = 3 mod 4 + // See https://math.stackexchange.com/questions/735400/if-p-equiv-3-mod-4-with-p-prime-prove-1-is-a-non-quadratic-residue-modulo + assert_eq!(sqrt, None); + + let expected = b * Secp256k1Coord::from_u32(2).invert(); + let c = expected.square(); + let result = c.sqrt(); + assert!(result == Some(expected.clone()) || result == Some(-expected)); +} diff --git a/extensions/algebra/tests/programs/openvm_init_complex_redundant_modulus.rs b/extensions/algebra/tests/programs/openvm_init_complex_redundant_modulus.rs new file mode 100644 index 0000000000..c32e692510 --- /dev/null +++ b/extensions/algebra/tests/programs/openvm_init_complex_redundant_modulus.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "998244353", "1000000007", "1000000009", "987898789" } +openvm_algebra_guest::complex_macros::complex_init! { Complex2 { mod_idx = 2 } } diff --git a/extensions/algebra/tests/programs/openvm_init_complex_secp256k1.rs b/extensions/algebra/tests/programs/openvm_init_complex_secp256k1.rs new file mode 100644 index 0000000000..af98350ae4 --- /dev/null +++ b/extensions/algebra/tests/programs/openvm_init_complex_secp256k1.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663" } +openvm_algebra_guest::complex_macros::complex_init! { Complex { mod_idx = 0 } } diff --git a/extensions/algebra/tests/programs/openvm_init_complex_two_moduli.rs b/extensions/algebra/tests/programs/openvm_init_complex_two_moduli.rs new file mode 100644 index 0000000000..1de98b97a1 --- /dev/null +++ b/extensions/algebra/tests/programs/openvm_init_complex_two_moduli.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "998244353", "1000000007" } +openvm_algebra_guest::complex_macros::complex_init! { Complex1 { mod_idx = 0 }, Complex2 { mod_idx = 1 } } diff --git a/extensions/algebra/tests/programs/openvm_init_little.rs b/extensions/algebra/tests/programs/openvm_init_little.rs new file mode 100644 index 0000000000..31acaf4433 --- /dev/null +++ b/extensions/algebra/tests/programs/openvm_init_little.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663" } diff --git a/extensions/algebra/tests/programs/openvm_init_moduli_setup.rs b/extensions/algebra/tests/programs/openvm_init_moduli_setup.rs new file mode 100644 index 0000000000..af99ca2e94 --- /dev/null +++ b/extensions/algebra/tests/programs/openvm_init_moduli_setup.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", "1000000000000000003", "2305843009213693951" } diff --git a/extensions/algebra/tests/programs/openvm_init_sqrt.rs b/extensions/algebra/tests/programs/openvm_init_sqrt.rs new file mode 100644 index 0000000000..31acaf4433 --- /dev/null +++ b/extensions/algebra/tests/programs/openvm_init_sqrt.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663" } diff --git a/extensions/algebra/tests/src/lib.rs b/extensions/algebra/tests/src/lib.rs index 8a8051c4e3..181f592544 100644 --- a/extensions/algebra/tests/src/lib.rs +++ b/extensions/algebra/tests/src/lib.rs @@ -15,14 +15,17 @@ mod tests { Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, }; use openvm_stark_sdk::p3_baby_bear::BabyBear; - use openvm_toolchain_tests::{build_example_program_at_path, get_programs_dir}; + use openvm_toolchain_tests::{build_example_program_at_path, get_programs_dir, NoInitFile}; use openvm_transpiler::{transpiler::Transpiler, FromElf}; type F = BabyBear; #[test] fn test_moduli_setup() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "moduli_setup")?; + let moduli = ["4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", "1000000000000000003", "2305843009213693951"] + .map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + let elf = build_example_program_at_path(get_programs_dir!(), "moduli_setup", &config)?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -32,16 +35,14 @@ mod tests { .with_extension(ModularTranspilerExtension), )?; - let moduli = ["4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", "1000000000000000003", "2305843009213693951"] - .map(|s| BigUint::from_str(s).unwrap()); - let config = Rv32ModularConfig::new(moduli.to_vec()); air_test(config, openvm_exe); Ok(()) } #[test] fn test_modular() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "little")?; + let config = Rv32ModularConfig::new(vec![SECP256K1_CONFIG.modulus.clone()]); + let elf = build_example_program_at_path(get_programs_dir!(), "little", &config)?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -50,14 +51,24 @@ mod tests { .with_extension(Rv32IoTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - let config = Rv32ModularConfig::new(vec![SECP256K1_CONFIG.modulus.clone()]); air_test(config, openvm_exe); Ok(()) } #[test] fn test_complex_two_moduli() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "complex-two-modulos")?; + let config = Rv32ModularWithFp2Config::new(vec![ + ( + "Complex1".to_string(), + BigUint::from_str("998244353").unwrap(), + ), + ( + "Complex2".to_string(), + BigUint::from_str("1000000007").unwrap(), + ), + ]); + let elf = + build_example_program_at_path(get_programs_dir!(), "complex_two_moduli", &config)?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -67,26 +78,12 @@ mod tests { .with_extension(Fp2TranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - let config = Rv32ModularWithFp2Config::new(vec![ - BigUint::from_str("998244353").unwrap(), - BigUint::from_str("1000000007").unwrap(), - ]); air_test(config, openvm_exe); Ok(()) } #[test] fn test_complex_redundant_modulus() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "complex-redundant-modulus")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Fp2TranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; let config = Rv32ModularWithFp2Config { system: SystemConfig::default().with_continuations(), base: Default::default(), @@ -98,15 +95,36 @@ mod tests { BigUint::from_str("1000000009").unwrap(), BigUint::from_str("987898789").unwrap(), ]), - fp2: Fp2Extension::new(vec![BigUint::from_str("1000000009").unwrap()]), + fp2: Fp2Extension::new(vec![( + "Complex2".to_string(), + BigUint::from_str("1000000009").unwrap(), + )]), }; + let elf = build_example_program_at_path( + get_programs_dir!(), + "complex_redundant_modulus", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Fp2TranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; air_test(config, openvm_exe); Ok(()) } #[test] fn test_complex() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "complex-secp256k1")?; + let config = Rv32ModularWithFp2Config::new(vec![( + "Complex".to_string(), + SECP256K1_CONFIG.modulus.clone(), + )]); + let elf = build_example_program_at_path(get_programs_dir!(), "complex_secp256k1", &config)?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -116,7 +134,6 @@ mod tests { .with_extension(Fp2TranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - let config = Rv32ModularWithFp2Config::new(vec![SECP256K1_CONFIG.modulus.clone()]); air_test(config, openvm_exe); Ok(()) } @@ -124,7 +141,18 @@ mod tests { #[test] #[should_panic] fn test_invalid_setup() { - let elf = build_example_program_at_path(get_programs_dir!(), "invalid-setup").unwrap(); + let config = Rv32ModularConfig::new(vec![ + BigUint::from_str("998244353").unwrap(), + BigUint::from_str("1000000007").unwrap(), + ]); + let elf = build_example_program_at_path( + get_programs_dir!(), + "invalid_setup", + // We don't want init.rs to be generated for this test because we are testing an + // invalid moduli_init! call + &NoInitFile, + ) + .unwrap(); let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -135,10 +163,22 @@ mod tests { .with_extension(ModularTranspilerExtension), ) .unwrap(); - let config = Rv32ModularConfig::new(vec![ - BigUint::from_str("998244353").unwrap(), - BigUint::from_str("1000000007").unwrap(), - ]); air_test(config, openvm_exe); } + + #[test] + fn test_sqrt() -> Result<()> { + let config = Rv32ModularConfig::new(vec![SECP256K1_CONFIG.modulus.clone()]); + let elf = build_example_program_at_path(get_programs_dir!(), "sqrt", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } } diff --git a/extensions/algebra/transpiler/src/lib.rs b/extensions/algebra/transpiler/src/lib.rs index 8785a93480..74d3f9182a 100644 --- a/extensions/algebra/transpiler/src/lib.rs +++ b/extensions/algebra/transpiler/src/lib.rs @@ -3,7 +3,8 @@ use openvm_algebra_guest::{ MODULAR_ARITHMETIC_FUNCT3, OPCODE, }; use openvm_instructions::{ - instruction::Instruction, riscv::RV32_REGISTER_NUM_LIMBS, LocalOpcode, VmOpcode, + instruction::Instruction, riscv::RV32_REGISTER_NUM_LIMBS, LocalOpcode, PhantomDiscriminant, + VmOpcode, }; use openvm_instructions_derive::LocalOpcode; use openvm_stark_backend::p3_field::PrimeField32; @@ -28,6 +29,13 @@ pub enum Rv32ModularArithmeticOpcode { SETUP_ISEQ, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromRepr)] +#[repr(u16)] +pub enum ModularPhantom { + HintNonQr = 0x50, + HintSqrt = 0x51, +} + #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, EnumCount, EnumIter, FromRepr, LocalOpcode, )] @@ -73,10 +81,10 @@ impl TranspilerExtension for ModularTranspilerExtension { Rv32ModularArithmeticOpcode::COUNT <= ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize ); - let mod_idx_shift = ((dec_insn.funct7 as u8) + let mod_idx = ((dec_insn.funct7 as u8) / ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS) - as usize - * Rv32ModularArithmeticOpcode::COUNT; + as usize; + let mod_idx_shift = mod_idx * Rv32ModularArithmeticOpcode::COUNT; if base_funct7 == ModArithBaseFunct7::SetupMod as u8 { let local_opcode = match dec_insn.rs2 { 0 => Rv32ModularArithmeticOpcode::SETUP_ADDSUB, @@ -100,6 +108,25 @@ impl TranspilerExtension for ModularTranspilerExtension { F::ZERO, )) } + } else if base_funct7 == ModArithBaseFunct7::HintNonQr as u8 { + assert_eq!(dec_insn.rd, 0); + assert_eq!(dec_insn.rs1, 0); + assert_eq!(dec_insn.rs2, 0); + Some(Instruction::phantom( + PhantomDiscriminant(ModularPhantom::HintNonQr as u16), + F::ZERO, + F::ZERO, + mod_idx as u16, + )) + } else if base_funct7 == ModArithBaseFunct7::HintSqrt as u8 { + assert_eq!(dec_insn.rd, 0); + assert_eq!(dec_insn.rs2, 0); + Some(Instruction::phantom( + PhantomDiscriminant(ModularPhantom::HintSqrt as u16), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1), + F::ZERO, + mod_idx as u16, + )) } else { let global_opcode = match ModArithBaseFunct7::from_repr(base_funct7) { Some(ModArithBaseFunct7::AddMod) => { diff --git a/extensions/bigint/circuit/src/extension.rs b/extensions/bigint/circuit/src/extension.rs index b9eeeafd99..390b79cc63 100644 --- a/extensions/bigint/circuit/src/extension.rs +++ b/extensions/bigint/circuit/src/extension.rs @@ -5,7 +5,8 @@ use openvm_bigint_transpiler::{ }; use openvm_circuit::{ arch::{ - SystemConfig, SystemPort, VmExtension, VmInventory, VmInventoryBuilder, VmInventoryError, + InitFileGenerator, SystemConfig, SystemPort, VmExtension, VmInventory, VmInventoryBuilder, + VmInventoryError, }, system::phantom::PhantomChip, }; @@ -39,6 +40,9 @@ pub struct Int256Rv32Config { pub bigint: Int256, } +// Default implementation uses no init file +impl InitFileGenerator for Int256Rv32Config {} + impl Default for Int256Rv32Config { fn default() -> Self { Self { diff --git a/extensions/bigint/guest/Cargo.toml b/extensions/bigint/guest/Cargo.toml index 2213c11796..5c3e5a22f7 100644 --- a/extensions/bigint/guest/Cargo.toml +++ b/extensions/bigint/guest/Cargo.toml @@ -8,16 +8,11 @@ homepage.workspace = true repository.workspace = true [dependencies] -openvm = { workspace = true } openvm-platform = { workspace = true } strum_macros = { workspace = true } -serde = { workspace = true, features = ["alloc"] } -serde-big-array.workspace = true - -[target.'cfg(not(target_os = "zkvm"))'.dependencies] -num-bigint.workspace = true -num-traits.workspace = true [features] default = [] -export-intrinsics = [] # export extern no_mangle functions for external linkage +export-intrinsics = [ + "openvm-platform/rust-runtime", +] # export extern no_mangle functions for external linkage diff --git a/extensions/bigint/guest/src/externs.rs b/extensions/bigint/guest/src/externs.rs index c0ed0272c5..8d8c66a0e6 100644 --- a/extensions/bigint/guest/src/externs.rs +++ b/extensions/bigint/guest/src/externs.rs @@ -129,7 +129,7 @@ unsafe extern "C" fn zkvm_u256_eq_impl(a: *const u8, b: *const u8) -> bool { #[no_mangle] unsafe extern "C" fn zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Ordering { - let mut cmp_result = MaybeUninit::::uninit(); + let mut cmp_result = MaybeUninit::<[u8; 32]>::uninit(); custom_insn_r!( opcode = OPCODE, funct3 = INT256_FUNCT3, @@ -139,7 +139,7 @@ unsafe extern "C" fn zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Ordering rs2 = In b as *const u8 ); let mut cmp_result = cmp_result.assume_init(); - if cmp_result.as_le_bytes()[0] != 0 { + if cmp_result[0] != 0 { return Ordering::Less; } custom_insn_r!( @@ -150,7 +150,7 @@ unsafe extern "C" fn zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Ordering rs1 = In b as *const u8, rs2 = In a as *const u8 ); - if cmp_result.as_le_bytes()[0] != 0 { + if cmp_result[0] != 0 { return Ordering::Greater; } return Ordering::Equal; @@ -158,7 +158,7 @@ unsafe extern "C" fn zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Ordering #[no_mangle] unsafe extern "C" fn zkvm_u256_clone_impl(result: *mut u8, a: *const u8) { - let zero = &crate::U256::ZERO as *const _ as *const u8; + let zero = &[0u8; 32] as *const _ as *const u8; custom_insn_r!( opcode = OPCODE, funct3 = INT256_FUNCT3, diff --git a/extensions/bigint/guest/src/i256.rs b/extensions/bigint/guest/src/i256.rs deleted file mode 100644 index 2cb4078b8c..0000000000 --- a/extensions/bigint/guest/src/i256.rs +++ /dev/null @@ -1,285 +0,0 @@ -use core::{ - cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, - ops::{ - Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Mul, - MulAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, - }, -}; - -use serde::{Deserialize, Serialize}; -use serde_big_array::BigArray; -#[cfg(not(target_os = "zkvm"))] -use {super::bigint_to_limbs, num_bigint::BigInt}; -#[cfg(target_os = "zkvm")] -use { - super::{Int256Funct7, BEQ256_FUNCT3, INT256_FUNCT3, OPCODE}, - core::{arch::asm, mem::MaybeUninit}, - openvm_platform::custom_insn_r, -}; - -use crate::impl_bin_op; - -/// A 256-bit signed integer type. -#[derive(Debug, Serialize, Deserialize)] -#[repr(align(32), C)] -pub struct I256 { - #[serde(with = "BigArray")] - limbs: [u8; 32], -} - -impl I256 { - /// The minimum value of an I256. - pub const MIN: Self = Self::generate_min(); - - /// The maximum value of an I256. - pub const MAX: Self = Self::generate_max(); - - /// The zero constant. - pub const ZERO: Self = Self { limbs: [0u8; 32] }; - - /// Construct [I256] from little-endian bytes. - pub const fn from_le_bytes(bytes: [u8; 32]) -> Self { - Self { limbs: bytes } - } - - /// Value of this I256 as a BigInt. - #[cfg(not(target_os = "zkvm"))] - pub fn as_bigint(&self) -> BigInt { - BigInt::from_signed_bytes_le(&self.limbs) - } - - /// Creates a new I256 from a BigInt. - #[cfg(not(target_os = "zkvm"))] - pub fn from_bigint(value: &BigInt) -> Self { - Self { - limbs: bigint_to_limbs(value), - } - } - - /// Creates a new I256 that equals to the given i8 value. - pub fn from_i8(value: i8) -> Self { - let mut limbs = if value < 0 { [u8::MAX; 32] } else { [0u8; 32] }; - limbs[0] = value as u8; - Self { limbs } - } - - /// Creates a new I256 that equals to the given i32 value. - pub fn from_i32(value: i32) -> Self { - let mut limbs = if value < 0 { [u8::MAX; 32] } else { [0u8; 32] }; - let value = value as u32; - limbs[..4].copy_from_slice(&value.to_le_bytes()); - Self { limbs } - } - - /// Creates a new I256 that equals to the given i64 value. - pub fn from_i64(value: i64) -> Self { - let mut limbs = if value < 0 { [u8::MAX; 32] } else { [0u8; 32] }; - let value = value as u64; - limbs[..8].copy_from_slice(&value.to_le_bytes()); - Self { limbs } - } - - /// A constant private helper function to generate the minimum value of an I256. - const fn generate_min() -> Self { - let mut limbs = [0u8; 32]; - limbs[31] = i8::MIN as u8; - Self { limbs } - } - - /// A constant private helper function to generate the maximum value of an I256. - const fn generate_max() -> Self { - let mut limbs = [u8::MAX; 32]; - limbs[31] = i8::MAX as u8; - Self { limbs } - } -} - -impl_bin_op!( - I256, - Add, - AddAssign, - add, - add_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Add as u8, - +=, - |lhs: &I256, rhs: &I256| -> I256 {I256::from_bigint(&(lhs.as_bigint() + rhs.as_bigint()))} -); - -impl_bin_op!( - I256, - Sub, - SubAssign, - sub, - sub_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Sub as u8, - -=, - |lhs: &I256, rhs: &I256| -> I256 {I256::from_bigint(&(lhs.as_bigint() - rhs.as_bigint()))} -); - -impl_bin_op!( - I256, - Mul, - MulAssign, - mul, - mul_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Mul as u8, - *=, - |lhs: &I256, rhs: &I256| -> I256 {I256::from_bigint(&(lhs.as_bigint() * rhs.as_bigint()))} -); - -impl_bin_op!( - I256, - BitXor, - BitXorAssign, - bitxor, - bitxor_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Xor as u8, - ^=, - |lhs: &I256, rhs: &I256| -> I256 {I256::from_bigint(&(lhs.as_bigint() ^ rhs.as_bigint()))} -); - -impl_bin_op!( - I256, - BitAnd, - BitAndAssign, - bitand, - bitand_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::And as u8, - &=, - |lhs: &I256, rhs: &I256| -> I256 {I256::from_bigint(&(lhs.as_bigint() & rhs.as_bigint()))} -); - -impl_bin_op!( - I256, - BitOr, - BitOrAssign, - bitor, - bitor_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Or as u8, - |=, - |lhs: &I256, rhs: &I256| -> I256 {I256::from_bigint(&(lhs.as_bigint() | rhs.as_bigint()))} -); - -impl_bin_op!( - I256, - Shl, - ShlAssign, - shl, - shl_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Sll as u8, - <<=, - |lhs: &I256, rhs: &I256| -> I256 {I256::from_bigint(&(lhs.as_bigint() << rhs.limbs[0] as usize))} -); - -impl_bin_op!( - I256, - Shr, - ShrAssign, - shr, - shr_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Sra as u8, - >>=, - |lhs: &I256, rhs: &I256| -> I256 {I256::from_bigint(&(lhs.as_bigint() >> rhs.limbs[0] as usize))} -); - -impl PartialEq for I256 { - fn eq(&self, other: &Self) -> bool { - #[cfg(target_os = "zkvm")] - { - let mut is_equal: u32; - unsafe { - asm!("li {res}, 1", - ".insn b {opcode}, {func3}, {rs1}, {rs2}, 8", - "li {res}, 0", - opcode = const OPCODE, - func3 = const BEQ256_FUNCT3, - rs1 = in(reg) self as *const Self, - rs2 = in(reg) other as *const Self, - res = out(reg) is_equal - ); - } - return is_equal == 1; - } - #[cfg(not(target_os = "zkvm"))] - return self.as_bigint() == other.as_bigint(); - } -} - -impl Eq for I256 {} - -impl PartialOrd for I256 { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for I256 { - fn cmp(&self, other: &Self) -> Ordering { - #[cfg(target_os = "zkvm")] - { - let mut cmp_result = MaybeUninit::::uninit(); - custom_insn_r!( - opcode = OPCODE, - funct3 = INT256_FUNCT3, - funct7 = Int256Funct7::Slt as u8, - rd = In cmp_result.as_mut_ptr(), - rs1 = In self as *const Self, - rs2 = In other as *const Self - ); - let mut cmp_result = unsafe { cmp_result.assume_init() }; - if cmp_result.limbs[0] != 0 { - return Ordering::Less; - } - custom_insn_r!( - opcode = OPCODE, - funct3 = INT256_FUNCT3, - funct7 = Int256Funct7::Slt as u8, - rd = In &mut cmp_result as *mut I256, - rs1 = In other as *const Self, - rs2 = In self as *const Self - ); - if cmp_result.limbs[0] != 0 { - return Ordering::Greater; - } - return Ordering::Equal; - } - #[cfg(not(target_os = "zkvm"))] - return self.as_bigint().cmp(&other.as_bigint()); - } -} - -impl Clone for I256 { - fn clone(&self) -> Self { - #[cfg(target_os = "zkvm")] - { - let mut uninit: MaybeUninit = MaybeUninit::uninit(); - custom_insn_r!( - opcode = OPCODE, - funct3 = INT256_FUNCT3, - funct7 = Int256Funct7::Add as u8, - rd = In uninit.as_mut_ptr(), - rs1 = In self as *const Self, - rs2 = In &Self::ZERO as *const Self - ); - unsafe { uninit.assume_init() } - } - #[cfg(not(target_os = "zkvm"))] - return Self { limbs: self.limbs }; - } -} diff --git a/extensions/bigint/guest/src/lib.rs b/extensions/bigint/guest/src/lib.rs index d10a131e74..66bb5fa6a5 100644 --- a/extensions/bigint/guest/src/lib.rs +++ b/extensions/bigint/guest/src/lib.rs @@ -1,15 +1,6 @@ #![no_std] -mod i256; -mod u256; - -pub use i256::*; use strum_macros::FromRepr; -pub use u256::*; - -mod utils; -#[allow(unused)] -pub use utils::*; /// This is custom-0 defined in RISC-V spec document pub const OPCODE: u8 = 0x0b; diff --git a/extensions/bigint/guest/src/u256.rs b/extensions/bigint/guest/src/u256.rs deleted file mode 100644 index c15b36be9e..0000000000 --- a/extensions/bigint/guest/src/u256.rs +++ /dev/null @@ -1,276 +0,0 @@ -use core::{ - cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, - ops::{ - Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Mul, - MulAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, - }, -}; - -use serde::{Deserialize, Serialize}; -use serde_big_array::BigArray; -#[cfg(target_os = "zkvm")] -use { - super::{Int256Funct7, BEQ256_FUNCT3, INT256_FUNCT3, OPCODE}, - core::{arch::asm, mem::MaybeUninit}, - openvm_platform::custom_insn_r, -}; -#[cfg(not(target_os = "zkvm"))] -use {num_bigint::BigUint, num_traits::One, openvm::utils::biguint_to_limbs}; - -use crate::impl_bin_op; - -/// A 256-bit unsigned integer type. -#[derive(Debug, Serialize, Deserialize)] -#[repr(align(32), C)] -pub struct U256 { - #[serde(with = "BigArray")] - limbs: [u8; 32], -} - -impl U256 { - /// The maximum value of a U256. - pub const MAX: Self = Self { - limbs: [u8::MAX; 32], - }; - - /// The minimum value of a U256. - pub const MIN: Self = Self { limbs: [0u8; 32] }; - - /// The zero constant. - pub const ZERO: Self = Self { limbs: [0u8; 32] }; - - /// Construct [U256] from little-endian bytes. - pub const fn from_le_bytes(bytes: [u8; 32]) -> Self { - Self { limbs: bytes } - } - - /// Value of this U256 as a BigUint. - #[cfg(not(target_os = "zkvm"))] - pub fn as_biguint(&self) -> BigUint { - BigUint::from_bytes_le(&self.limbs) - } - - /// Creates a new U256 from a BigUint. - #[cfg(not(target_os = "zkvm"))] - pub fn from_biguint(value: &BigUint) -> Self { - Self { - limbs: biguint_to_limbs(value), - } - } - - /// Creates a new U256 that equals to the given u8 value. - pub fn from_u8(value: u8) -> Self { - let mut limbs = [0u8; 32]; - limbs[0] = value; - Self { limbs } - } - - /// Creates a new U256 that equals to the given u32 value. - pub fn from_u32(value: u32) -> Self { - let mut limbs = [0u8; 32]; - limbs[..4].copy_from_slice(&value.to_le_bytes()); - Self { limbs } - } - - /// Creates a new U256 that equals to the given u64 value. - pub fn from_u64(value: u64) -> Self { - let mut limbs = [0u8; 32]; - limbs[..8].copy_from_slice(&value.to_le_bytes()); - Self { limbs } - } - - /// The little-endian byte representation of this U256. - pub fn as_le_bytes(&self) -> &[u8; 32] { - &self.limbs - } -} - -impl_bin_op!( - U256, - Add, - AddAssign, - add, - add_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Add as u8, - +=, - |lhs: &U256, rhs: &U256| -> U256 {U256::from_biguint(&(lhs.as_biguint() + rhs.as_biguint()))} -); - -impl_bin_op!( - U256, - Sub, - SubAssign, - sub, - sub_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Sub as u8, - -=, - |lhs: &U256, rhs: &U256| -> U256 {U256::from_biguint(&(U256::MAX.as_biguint() + BigUint::one() + lhs.as_biguint() - rhs.as_biguint()))} -); - -impl_bin_op!( - U256, - Mul, - MulAssign, - mul, - mul_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Mul as u8, - *=, - |lhs: &U256, rhs: &U256| -> U256 {U256::from_biguint(&(lhs.as_biguint() * rhs.as_biguint()))} -); - -impl_bin_op!( - U256, - BitXor, - BitXorAssign, - bitxor, - bitxor_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Xor as u8, - ^=, - |lhs: &U256, rhs: &U256| -> U256 {U256::from_biguint(&(lhs.as_biguint() ^ rhs.as_biguint()))} -); - -impl_bin_op!( - U256, - BitAnd, - BitAndAssign, - bitand, - bitand_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::And as u8, - &=, - |lhs: &U256, rhs: &U256| -> U256 {U256::from_biguint(&(lhs.as_biguint() & rhs.as_biguint()))} -); - -impl_bin_op!( - U256, - BitOr, - BitOrAssign, - bitor, - bitor_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Or as u8, - |=, - |lhs: &U256, rhs: &U256| -> U256 {U256::from_biguint(&(lhs.as_biguint() | rhs.as_biguint()))} -); - -impl_bin_op!( - U256, - Shl, - ShlAssign, - shl, - shl_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Sll as u8, - <<=, - |lhs: &U256, rhs: &U256| -> U256 {U256::from_biguint(&(lhs.as_biguint() << rhs.limbs[0] as usize))} -); - -impl_bin_op!( - U256, - Shr, - ShrAssign, - shr, - shr_assign, - OPCODE, - INT256_FUNCT3, - Int256Funct7::Srl as u8, - >>=, - |lhs: &U256, rhs: &U256| -> U256 {U256::from_biguint(&(lhs.as_biguint() >> rhs.limbs[0] as usize))} -); - -impl PartialEq for U256 { - fn eq(&self, other: &Self) -> bool { - #[cfg(target_os = "zkvm")] - { - let mut is_equal: u32; - unsafe { - asm!("li {res}, 1", - ".insn b {opcode}, {func3}, {rs1}, {rs2}, 8", - "li {res}, 0", - opcode = const OPCODE, - func3 = const BEQ256_FUNCT3, - rs1 = in(reg) self as *const Self, - rs2 = in(reg) other as *const Self, - res = out(reg) is_equal - ); - } - return is_equal == 1; - } - #[cfg(not(target_os = "zkvm"))] - return self.as_biguint() == other.as_biguint(); - } -} - -impl Eq for U256 {} - -impl PartialOrd for U256 { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for U256 { - fn cmp(&self, other: &Self) -> Ordering { - #[cfg(target_os = "zkvm")] - { - let mut cmp_result = MaybeUninit::::uninit(); - custom_insn_r!( - opcode = OPCODE, - funct3 = INT256_FUNCT3, - funct7 = Int256Funct7::Sltu as u8, - rd = In cmp_result.as_mut_ptr(), - rs1 = In self as *const Self, - rs2 = In other as *const Self - ); - let mut cmp_result = unsafe { cmp_result.assume_init() }; - if cmp_result.limbs[0] != 0 { - return Ordering::Less; - } - custom_insn_r!( - opcode = OPCODE, - funct3 = INT256_FUNCT3, - funct7 = Int256Funct7::Sltu as u8, - rd = In &mut cmp_result as *mut U256, - rs1 = In other as *const Self, - rs2 = In self as *const Self - ); - if cmp_result.limbs[0] != 0 { - return Ordering::Greater; - } - return Ordering::Equal; - } - #[cfg(not(target_os = "zkvm"))] - return self.as_biguint().cmp(&other.as_biguint()); - } -} - -impl Clone for U256 { - fn clone(&self) -> Self { - #[cfg(target_os = "zkvm")] - { - let mut uninit: MaybeUninit = MaybeUninit::uninit(); - custom_insn_r!( - opcode = OPCODE, - funct3 = INT256_FUNCT3, - funct7 = Int256Funct7::Add as u8, - rd = In uninit.as_mut_ptr(), - rs1 = In self as *const Self, - rs2 = In &Self::ZERO as *const Self - ); - unsafe { uninit.assume_init() } - } - #[cfg(not(target_os = "zkvm"))] - return Self { limbs: self.limbs }; - } -} diff --git a/extensions/bigint/guest/src/utils.rs b/extensions/bigint/guest/src/utils.rs deleted file mode 100644 index 5eebb8b90a..0000000000 --- a/extensions/bigint/guest/src/utils.rs +++ /dev/null @@ -1,115 +0,0 @@ -#[cfg(not(target_os = "zkvm"))] -use num_bigint::BigInt; - -#[inline] -#[cfg(not(target_os = "zkvm"))] -#[allow(dead_code)] -/// Convert a `BigInt` to a `[u8; NUM_LIMBS]` in two's complement little-endian format. -pub(super) fn bigint_to_limbs(x: &BigInt) -> [u8; NUM_LIMBS] { - let mut sm = x.to_signed_bytes_le(); - let mut ext = 0; - if let Some(last) = sm.last() { - if (*last as i8) < 0 { - ext = u8::MAX; - } - } - sm.resize(NUM_LIMBS, ext); - sm.try_into().unwrap() -} - -/// A macro that implements all the following for the given struct and operation: -/// a op= b, a op= &b, a op b, a op &b, &a op b, &a op &b -/// Description of the parameters (see [u256.rs] for an example): -/// - $struct_name: The struct to implement the operation for. -/// - $trait_name: The trait name of the operation to implement. -/// - $trait_assign_name: The trait name of the assignment operation to implement. -/// - $trait_fn: The trait function name to implement. -/// - $trait_assign_fn: The assignment trait function name to implement. -/// - $opcode: The custom opcode of the operation in openvm. -/// - $func3: The func3 of the operation in openvm. -/// - $func7: The func7 of the operation in openvm. -/// - $op_sym: The symbol to use for the operation. -/// - $rust_expr: A closure to get the result of the operation if target is non-zkvm. -#[macro_export] -macro_rules! impl_bin_op { - ($struct_name:ty, $trait_name:ident, - $trait_assign_name:ident, $trait_fn:ident, - $trait_assign_fn:ident, $opcode:expr, - $func3:expr, $func7:expr, $op_sym:tt, - $rust_expr:expr) => { - impl<'a> $trait_assign_name<&'a $struct_name> for $struct_name { - #[inline(always)] - fn $trait_assign_fn(&mut self, rhs: &'a $struct_name) { - #[cfg(target_os = "zkvm")] - custom_insn_r!( - opcode = $opcode, - funct3 = $func3, - funct7 = $func7, - rd = In self as *mut Self, - rs1 = In self as *const Self, - rs2 = In rhs as *const Self - ); - #[cfg(not(target_os = "zkvm"))] - { - *self = $rust_expr(self, rhs); - } - } - } - - impl $trait_assign_name<$struct_name> for $struct_name { - #[inline(always)] - fn $trait_assign_fn(&mut self, rhs: $struct_name) { - *self $op_sym &rhs; - } - } - - impl<'a> $trait_name<&'a $struct_name> for &$struct_name { - type Output = $struct_name; - #[inline(always)] - fn $trait_fn(self, rhs: &'a $struct_name) -> Self::Output { - #[cfg(target_os = "zkvm")] - { - let mut uninit: MaybeUninit<$struct_name> = MaybeUninit::uninit(); - custom_insn_r!( - opcode = $opcode, - funct3 = $func3, - funct7 = $func7, - rd = In uninit.as_mut_ptr(), - rs1 = In self as *const $struct_name, - rs2 = In rhs as *const $struct_name - ); - unsafe { uninit.assume_init() } - } - #[cfg(not(target_os = "zkvm"))] - return $rust_expr(self, rhs); - } - } - - impl<'a> $trait_name<&'a $struct_name> for $struct_name { - type Output = $struct_name; - #[inline(always)] - fn $trait_fn(mut self, rhs: &'a $struct_name) -> Self::Output { - self $op_sym rhs; - self - } - } - - impl $trait_name<$struct_name> for $struct_name { - type Output = $struct_name; - #[inline(always)] - fn $trait_fn(mut self, rhs: $struct_name) -> Self::Output { - self $op_sym &rhs; - self - } - } - - impl $trait_name<$struct_name> for &$struct_name { - type Output = $struct_name; - #[inline(always)] - fn $trait_fn(self, mut rhs: $struct_name) -> Self::Output { - rhs $op_sym self; - rhs - } - } - }; -} diff --git a/extensions/bigint/tests/Cargo.toml b/extensions/bigint/tests/Cargo.toml deleted file mode 100644 index ae32b3e7ef..0000000000 --- a/extensions/bigint/tests/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "openvm-bigint-integration-tests" -description = "Integration tests for the OpenVM bigint extension" -version.workspace = true -authors.workspace = true -edition.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -openvm-instructions = { workspace = true } -openvm-stark-sdk.workspace = true -openvm-circuit = { workspace = true, features = ["test-utils"] } -openvm-transpiler.workspace = true -openvm-bigint-transpiler.workspace = true -openvm-bigint-circuit.workspace = true -openvm-rv32im-transpiler.workspace = true -openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } -eyre.workspace = true - -[features] -default = ["parallel"] -parallel = ["openvm-circuit/parallel"] diff --git a/extensions/bigint/tests/programs/examples/matrix-power-signed.rs b/extensions/bigint/tests/programs/examples/matrix-power-signed.rs deleted file mode 100644 index 9684fe2a35..0000000000 --- a/extensions/bigint/tests/programs/examples/matrix-power-signed.rs +++ /dev/null @@ -1,142 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_main)] -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::needless_range_loop)] -#![allow(clippy::eq_op)] -use core::array; - -use openvm::io::print; -use openvm_bigint_guest::I256; -openvm::entry!(main); - -const N: usize = 16; -type Matrix = [[I256; N]; N]; - -pub fn get_matrix(val: i8) -> Matrix { - array::from_fn(|_| array::from_fn(|_| I256::from_i8(val))) -} - -pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { - let mut c = get_matrix(0); - for i in 0..N { - for j in 0..N { - for k in 0..N { - c[i][j] += &a[i][k] * &b[k][j]; - } - } - } - c -} - -pub fn get_identity_matrix() -> Matrix { - let mut res = get_matrix(0); - for i in 0..N { - res[i][i] = I256::from_i8(1); - } - res -} - -/// Computes base^exp using binary exponentiation. -pub fn matrix_exp(mut base: Matrix, mut exp: I256) -> Matrix { - let mut result = get_identity_matrix(); - let one = I256::from_i8(1); - while exp > I256::from_i8(0) { - if (&exp & &one) == one { - result = mult(&result, &base); - } - base = mult(&base, &base); - exp >>= &one; - } - result -} - -pub fn main() { - let a: Matrix = get_identity_matrix(); - let c = matrix_exp(a, I256::from_i32(1234567)); - if c != get_identity_matrix() { - print("FAIL: the resulting matrix should have been the identity matrix"); - panic!(); - } - - let one = I256::from_i8(1); - let neg_one = I256::from_i8(-1); - let zero = I256::from_i8(0); - - let a: Matrix = get_matrix(-1); - let c = matrix_exp(a, I256::from_i8(51)); - let two_to_200 = &neg_one << &I256::from_i32(200); - - for i in 0..N { - for j in 0..N { - if c[i][j] != two_to_200 { - print("FAIL: the resulting matrix is incorrect"); - panic!(); - } - } - } - - // Shift right tests - if &two_to_200 >> &I256::from_i32(200) != neg_one { - print("FAIL: -2^200 >> 200 == -1 test failed"); - panic!(); - } - if &two_to_200 >> &I256::from_i32(201) != neg_one { - print("FAIL: -2^200 >> 201 == -1 test failed"); - panic!(); - } - - if &neg_one >> &I256::from_i32(200) != neg_one { - print("FAIL: -1 >> 200 == -1 test failed"); - panic!(); - } - - // Xor tests - if &two_to_200 ^ &two_to_200 != zero { - print("FAIL: -2^200 ^ -2^200 == 0 test failed"); - panic!(); - } - - if &two_to_200 ^ &one != &two_to_200 + &one { - print("FAIL: -2^200 ^ 1 == -2^200 + 1 test failed"); - panic!(); - } - - // Or tests - - if &one | &one != one { - print("FAIL: 1 | 1 == 1 test failed"); - panic!(); - } - - if &two_to_200 | &one != &two_to_200 + &one { - print("FAIL: -2^200 | 1 = -2^200 + 1 test failed"); - panic!(); - } - - // Other tests - if &zero - &one >= zero { - print("FAIL: 0 - 1 <= 0 test failed"); - panic!(); - } - - if neg_one >= zero { - print("FAIL: -1 <= 0 test failed"); - panic!(); - } - - if &zero - &one + &one != zero { - print("FAIL: 0 - 1 + 1 == 0 test failed (should have wrapped)"); - panic!(); - } - - if &one << &I256::from_i32(256) != one { - print("FAIL: 1 << 256 == 1 test failed"); - panic!(); - } - - if two_to_200.clone() != two_to_200 { - print("FAIL: 2^200 clone test failed"); - panic!(); - } - - print("PASS"); -} diff --git a/extensions/ecc/circuit/Cargo.toml b/extensions/ecc/circuit/Cargo.toml index 28ea661248..adfce69f36 100644 --- a/extensions/ecc/circuit/Cargo.toml +++ b/extensions/ecc/circuit/Cargo.toml @@ -30,7 +30,8 @@ eyre = { workspace = true } num-integer = { workspace = true } serde = { workspace = true } serde_with = { workspace = true } -rand = { workspace = true, features = ["std_rng"] } +lazy_static = { workspace = true } +hex-literal = { workspace = true } [dev-dependencies] openvm-stark-sdk = { workspace = true } @@ -38,11 +39,3 @@ openvm-mod-circuit-builder = { workspace = true, features = ["test-utils"] } openvm-circuit = { workspace = true, features = ["test-utils"] } openvm-rv32-adapters = { workspace = true, features = ["test-utils"] } lazy_static = { workspace = true } - -[target.'cfg(not(target_os = "zkvm"))'.dependencies] -openvm-ecc-guest = { workspace = true, features = [ - "halo2curves", - "k256", - "p256", -] } -openvm-algebra-guest = { workspace = true } diff --git a/extensions/ecc/circuit/src/config.rs b/extensions/ecc/circuit/src/config.rs index ab8809aa6d..a959938be9 100644 --- a/extensions/ecc/circuit/src/config.rs +++ b/extensions/ecc/circuit/src/config.rs @@ -1,5 +1,5 @@ use openvm_algebra_circuit::*; -use openvm_circuit::arch::SystemConfig; +use openvm_circuit::arch::{InitFileGenerator, SystemConfig}; use openvm_circuit_derive::VmConfig; use openvm_rv32im_circuit::*; use openvm_stark_backend::p3_field::PrimeField32; @@ -39,3 +39,13 @@ impl Rv32WeierstrassConfig { } } } + +impl InitFileGenerator for Rv32WeierstrassConfig { + fn generate_init_file_contents(&self) -> Option { + Some(format!( + "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n", + self.modular.generate_moduli_init(), + self.weierstrass.generate_sw_init() + )) + } +} diff --git a/extensions/ecc/circuit/src/weierstrass_extension.rs b/extensions/ecc/circuit/src/weierstrass_extension.rs index c5b23ccd0d..afe2def687 100644 --- a/extensions/ecc/circuit/src/weierstrass_extension.rs +++ b/extensions/ecc/circuit/src/weierstrass_extension.rs @@ -1,8 +1,9 @@ use derive_more::derive::From; +use hex_literal::hex; +use lazy_static::lazy_static; use num_bigint::BigUint; use num_traits::{FromPrimitive, Zero}; use once_cell::sync::Lazy; -use openvm_algebra_guest::IntMod; use openvm_circuit::{ arch::{SystemPort, VmExtension, VmInventory, VmInventoryBuilder, VmInventoryError}, system::phantom::PhantomChip, @@ -12,10 +13,6 @@ use openvm_circuit_primitives::bitwise_op_lookup::{ BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip, }; use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter}; -use openvm_ecc_guest::{ - k256::{SECP256K1_MODULUS, SECP256K1_ORDER}, - p256::{CURVE_A as P256_A, CURVE_B as P256_B, P256_MODULUS, P256_ORDER}, -}; use openvm_ecc_transpiler::{EccPhantom, Rv32WeierstrassOpcode}; use openvm_instructions::{LocalOpcode, PhantomDiscriminant, VmOpcode}; use openvm_mod_circuit_builder::ExprBuilderConfig; @@ -30,6 +27,8 @@ use super::{EcAddNeChip, EcDoubleChip}; #[serde_as] #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct CurveConfig { + /// The name of the curve struct as defined by moduli_declare. + pub struct_name: String, /// The coordinate modulus of the curve. #[serde_as(as = "DisplayFromStr")] pub modulus: BigUint, @@ -45,6 +44,7 @@ pub struct CurveConfig { } pub static SECP256K1_CONFIG: Lazy = Lazy::new(|| CurveConfig { + struct_name: SECP256K1_ECC_STRUCT_NAME.to_string(), modulus: SECP256K1_MODULUS.clone(), scalar: SECP256K1_ORDER.clone(), a: BigUint::zero(), @@ -52,10 +52,11 @@ pub static SECP256K1_CONFIG: Lazy = Lazy::new(|| CurveConfig { }); pub static P256_CONFIG: Lazy = Lazy::new(|| CurveConfig { + struct_name: P256_ECC_STRUCT_NAME.to_string(), modulus: P256_MODULUS.clone(), scalar: P256_ORDER.clone(), - a: BigUint::from_bytes_le(P256_A.as_le_bytes()), - b: BigUint::from_bytes_le(P256_B.as_le_bytes()), + a: BigUint::from_bytes_le(&P256_A), + b: BigUint::from_bytes_le(&P256_B), }); #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] @@ -63,6 +64,19 @@ pub struct WeierstrassExtension { pub supported_curves: Vec, } +impl WeierstrassExtension { + pub fn generate_sw_init(&self) -> String { + let supported_curves = self + .supported_curves + .iter() + .map(|curve_config| curve_config.struct_name.to_string()) + .collect::>() + .join(", "); + + format!("openvm_ecc_guest::sw_macros::sw_init! {{ {supported_curves} }}") + } +} + #[derive(Chip, ChipUsageGetter, InstructionExecutor, AnyEnum)] pub enum WeierstrassExtensionExecutor { // 32 limbs prime @@ -231,21 +245,29 @@ pub(crate) mod phantom { }; use eyre::bail; - use num_bigint::{BigUint, RandBigInt}; + use num_bigint::BigUint; use num_integer::Integer; - use num_traits::{FromPrimitive, One}; + use num_traits::One; + use openvm_algebra_circuit::{find_non_qr, mod_sqrt}; use openvm_circuit::{ arch::{PhantomSubExecutor, Streams}, system::memory::MemoryController, }; - use openvm_ecc_guest::weierstrass::DecompressionHint; use openvm_instructions::{riscv::RV32_MEMORY_AS, PhantomDiscriminant}; use openvm_rv32im_circuit::adapters::unsafe_read_rv32_register; use openvm_stark_backend::p3_field::PrimeField32; - use rand::{rngs::StdRng, SeedableRng}; use super::CurveConfig; + // Hint for a decompression + // if possible is true, then `sqrt` is the decompressed y-coordinate + // if possible is false, then `sqrt` is such that + // `sqrt^2 = rhs * non_qr` where `rhs` is the rhs of the curve equation + pub struct DecompressionHint { + pub possible: bool, + pub sqrt: T, + } + #[derive(derive_new::new)] pub struct DecompressHintSubEx(NonQrHintSubEx); @@ -365,61 +387,6 @@ pub(crate) mod phantom { } } - /// Find the square root of `x` modulo `modulus` with `non_qr` a - /// quadratic nonresidue of the field. - pub fn mod_sqrt(x: &BigUint, modulus: &BigUint, non_qr: &BigUint) -> Option { - if modulus % 4u32 == BigUint::from_u8(3).unwrap() { - // x^(1/2) = x^((p+1)/4) when p = 3 mod 4 - let exponent = (modulus + BigUint::one()) >> 2; - let ret = x.modpow(&exponent, modulus); - if &ret * &ret % modulus == x % modulus { - Some(ret) - } else { - None - } - } else { - // Tonelli-Shanks algorithm - // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm#The_algorithm - let mut q = modulus - BigUint::one(); - let mut s = 0; - while &q % 2u32 == BigUint::ZERO { - s += 1; - q /= 2u32; - } - let z = non_qr; - let mut m = s; - let mut c = z.modpow(&q, modulus); - let mut t = x.modpow(&q, modulus); - let mut r = x.modpow(&((q + BigUint::one()) >> 1), modulus); - loop { - if t == BigUint::ZERO { - return Some(BigUint::ZERO); - } - if t == BigUint::one() { - return Some(r); - } - let mut i = 0; - let mut tmp = t.clone(); - while tmp != BigUint::one() && i < m { - tmp = &tmp * &tmp % modulus; - i += 1; - } - if i == m { - // self is not a quadratic residue - return None; - } - for _ in 0..m - i - 1 { - c = &c * &c % modulus; - } - let b = c; - m = i; - c = &b * &b % modulus; - t = ((t * &b % modulus) * &b) % modulus; - r = (r * b) % modulus; - } - } - } - #[derive(Clone)] pub struct NonQrHintSubEx { pub supported_curves: Vec, @@ -477,33 +444,32 @@ pub(crate) mod phantom { Ok(()) } } +} - // Returns a non-quadratic residue in the field - fn find_non_qr(modulus: &BigUint) -> BigUint { - if modulus % 4u32 == BigUint::from(3u8) { - // p = 3 mod 4 then -1 is a quadratic residue - modulus - BigUint::one() - } else if modulus % 8u32 == BigUint::from(5u8) { - // p = 5 mod 8 then 2 is a non-quadratic residue - // since 2^((p-1)/2) = (-1)^((p^2-1)/8) - BigUint::from_u8(2u8).unwrap() - } else { - let mut rng = StdRng::from_entropy(); - let mut non_qr = rng.gen_biguint_range( - &BigUint::from_u8(2).unwrap(), - &(modulus - BigUint::from_u8(1).unwrap()), - ); - // To check if non_qr is a quadratic nonresidue, we compute non_qr^((p-1)/2) - // If the result is p-1, then non_qr is a quadratic nonresidue - // Otherwise, non_qr is a quadratic residue - let exponent = (modulus - BigUint::one()) >> 1; - while non_qr.modpow(&exponent, modulus) != modulus - BigUint::one() { - non_qr = rng.gen_biguint_range( - &BigUint::from_u8(2).unwrap(), - &(modulus - BigUint::from_u8(1).unwrap()), - ); - } - non_qr - } - } +// Convenience constants for constructors +lazy_static! { + // The constants are taken from: https://en.bitcoin.it/wiki/Secp256k1 + pub static ref SECP256K1_MODULUS: BigUint = BigUint::from_bytes_be(&hex!( + "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" + )); + pub static ref SECP256K1_ORDER: BigUint = BigUint::from_bytes_be(&hex!( + "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" + )); } + +lazy_static! { + // The constants are taken from: https://neuromancer.sk/std/secg/secp256r1 + pub static ref P256_MODULUS: BigUint = BigUint::from_bytes_be(&hex!( + "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff" + )); + pub static ref P256_ORDER: BigUint = BigUint::from_bytes_be(&hex!( + "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551" + )); +} +// little-endian +const P256_A: [u8; 32] = hex!("fcffffffffffffffffffffff00000000000000000000000001000000ffffffff"); +// little-endian +const P256_B: [u8; 32] = hex!("4b60d2273e3cce3bf6b053ccb0061d65bc86987655bdebb3e7933aaad835c65a"); + +pub const SECP256K1_ECC_STRUCT_NAME: &str = "Secp256k1Point"; +pub const P256_ECC_STRUCT_NAME: &str = "P256Point"; diff --git a/extensions/ecc/guest/Cargo.toml b/extensions/ecc/guest/Cargo.toml index dbc1749fc2..e5251eb366 100644 --- a/extensions/ecc/guest/Cargo.toml +++ b/extensions/ecc/guest/Cargo.toml @@ -11,34 +11,28 @@ repository.workspace = true openvm = { workspace = true } serde = { workspace = true } strum_macros.workspace = true -ecdsa = { workspace = true, features = ["verifying"] } +ecdsa-core = { workspace = true, features = ["verifying"] } elliptic-curve = { workspace = true, features = ["arithmetic", "sec1"] } -k256 = { workspace = true, optional = true } -p256 = { workspace = true, optional = true } -hex-literal = { workspace = true } openvm-custom-insn = { workspace = true } openvm-rv32im-guest = { workspace = true } openvm-algebra-guest = { workspace = true } openvm-ecc-sw-macros = { workspace = true } -openvm-algebra-moduli-macros = { workspace = true } once_cell = { workspace = true, features = ["race", "alloc"] } # Used for `halo2curves` feature halo2curves-axiom = { workspace = true, optional = true } -# halo2curves = { version = "0.7.0", optional = true } group = "0.13.0" -[target.'cfg(not(target_os = "zkvm"))'.dependencies] -num-bigint.workspace = true -lazy_static.workspace = true - [features] default = [] -# features to enable specific curves in guest programs -# only enable for the curves you use as it affects the init! macro -k256 = ["dep:k256"] -p256 = ["dep:p256"] halo2curves = ["dep:halo2curves-axiom", "openvm-algebra-guest/halo2curves"] +std = ["alloc"] +alloc = [] [package.metadata.cargo-shear] -ignored = ["openvm", "openvm-custom-insn", "openvm-rv32im-guest"] +ignored = [ + "openvm", + "openvm-custom-insn", + "openvm-rv32im-guest", + "halo2curves-axiom", +] diff --git a/extensions/ecc/guest/src/ecdsa.rs b/extensions/ecc/guest/src/ecdsa.rs index ebcf975f08..fcc0d21e5e 100644 --- a/extensions/ecc/guest/src/ecdsa.rs +++ b/extensions/ecc/guest/src/ecdsa.rs @@ -1,8 +1,22 @@ use alloc::vec::Vec; use core::ops::{Add, AddAssign, Mul}; -use ecdsa::{self, hazmat::bits2field, Error, RecoveryId, Result}; -use elliptic_curve::{sec1::Tag, PrimeCurve}; +use ecdsa_core::{ + self, + hazmat::{bits2field, DigestPrimitive}, + signature::{ + digest::{Digest, FixedOutput}, + hazmat::PrehashVerifier, + DigestVerifier, Verifier, + }, + EncodedPoint, Error, RecoveryId, Result, Signature, SignatureSize, +}; +use elliptic_curve::{ + generic_array::ArrayLength, + sec1::{FromEncodedPoint, ModulusSize, Tag, ToEncodedPoint}, + subtle::{Choice, ConditionallySelectable, CtOption}, + CurveArithmetic, FieldBytesSize, PrimeCurve, +}; use openvm_algebra_guest::{DivUnsafe, IntMod, Reduce}; use crate::{ @@ -10,33 +24,80 @@ use crate::{ CyclicGroup, Group, }; -pub type Coordinate = <::Point as WeierstrassPoint>::Coordinate; -pub type Scalar = ::Scalar; +type Coordinate = <::Point as WeierstrassPoint>::Coordinate; +type Scalar = ::Scalar; +type AffinePoint = ::Point; + +// +// Signing implementations are placeholders to support patching compilation +// + +/// This is placeholder struct for compatibility purposes with the `ecdsa` crate. +/// Signing from private keys is not supported yet. +#[derive(Clone)] +pub struct SigningKey { + /// ECDSA signing keys are non-zero elements of a given curve's scalar field. + #[allow(dead_code)] + secret_scalar: NonZeroScalar, + + /// Verifying key which corresponds to this signing key. + verifying_key: VerifyingKey, +} + +#[allow(dead_code)] +#[derive(Clone)] +pub struct NonZeroScalar { + scalar: Scalar, +} + +impl SigningKey { + pub fn from_slice(_bytes: &[u8]) -> Result { + todo!("signing is not yet implemented") + } + pub fn verifying_key(&self) -> &VerifyingKey { + &self.verifying_key + } +} + +impl SigningKey +where + C: IntrinsicCurve + PrimeCurve, +{ + pub fn sign_prehash_recoverable(&self, _prehash: &[u8]) -> Result<(Signature, RecoveryId)> { + todo!("signing is not yet implemented") + } +} + +// This struct is public because it is used by the VerifyPrimitive impl in the k256 and p256 guest +// libraries. #[repr(C)] #[derive(Clone)] pub struct VerifyingKey { pub(crate) inner: PublicKey, } +// This struct is public because it is used by the VerifyPrimitive impl in the k256 and p256 guest #[repr(C)] #[derive(Clone)] pub struct PublicKey { /// Affine point - point: ::Point, + point: AffinePoint, } impl PublicKey where C::Point: WeierstrassPoint + Group + FromCompressed>, Coordinate: IntMod, - for<'a> &'a Coordinate: Mul<&'a Coordinate, Output = Coordinate>, { pub fn new(point: ::Point) -> Self { Self { point } } - pub fn from_sec1_bytes(bytes: &[u8]) -> Result { + pub fn from_sec1_bytes(bytes: &[u8]) -> Result + where + for<'a> &'a Coordinate: Mul<&'a Coordinate, Output = Coordinate>, + { if bytes.is_empty() { return Err(Error::new()); } @@ -101,9 +162,13 @@ where } } - pub fn as_affine(&self) -> &::Point { + pub fn as_affine(&self) -> &AffinePoint { &self.point } + + pub fn into_affine(self) -> AffinePoint { + self.point + } } impl VerifyingKey @@ -133,11 +198,182 @@ where pub fn as_affine(&self) -> &::Point { self.inner.as_affine() } + + pub fn into_affine(self) -> ::Point { + self.inner.into_affine() + } } +// Functions for compatibility with `ecdsa` crate impl VerifyingKey +where + C: IntrinsicCurve + PrimeCurve, + C::Point: WeierstrassPoint + CyclicGroup + FromCompressed>, + Coordinate: IntMod, + C::Scalar: IntMod + Reduce, + for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>, + for<'a> &'a Coordinate: Mul<&'a Coordinate, Output = Coordinate>, + FieldBytesSize: ModulusSize, + SignatureSize: ArrayLength, +{ + /// Recover a [`VerifyingKey`] from the given message, signature, and + /// [`RecoveryId`]. + /// + /// The message is first hashed using this curve's [`DigestPrimitive`]. + pub fn recover_from_msg( + msg: &[u8], + signature: &Signature, + recovery_id: RecoveryId, + ) -> Result + where + C: DigestPrimitive, + { + Self::recover_from_digest(C::Digest::new_with_prefix(msg), signature, recovery_id) + } + + /// Recover a [`VerifyingKey`] from the given message [`Digest`], + /// signature, and [`RecoveryId`]. + pub fn recover_from_digest( + msg_digest: D, + signature: &Signature, + recovery_id: RecoveryId, + ) -> Result + where + D: Digest, + { + Self::recover_from_prehash(&msg_digest.finalize(), signature, recovery_id) + } + + /// Recover a [`VerifyingKey`] from the given `prehash` of a message, the + /// signature over that prehashed message, and a [`RecoveryId`]. + /// Note that this function does not verify the signature with the recovered key. + pub fn recover_from_prehash( + prehash: &[u8], + signature: &Signature, + recovery_id: RecoveryId, + ) -> Result { + let sig = signature.to_bytes(); + Self::recover_from_prehash_noverify(prehash, &sig, recovery_id) + } +} + +// +// `*Verifier` trait impls +// + +impl DigestVerifier> for VerifyingKey where C: PrimeCurve + IntrinsicCurve, + D: Digest + FixedOutput>, + SignatureSize: ArrayLength, + C::Point: WeierstrassPoint + CyclicGroup + FromCompressed>, + Coordinate: IntMod, + ::Scalar: IntMod + Reduce, + for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>, + for<'a> &'a Scalar: DivUnsafe<&'a Scalar, Output = Scalar>, +{ + fn verify_digest(&self, msg_digest: D, signature: &Signature) -> Result<()> { + verify_prehashed::( + self.inner.as_affine().clone(), + &msg_digest.finalize_fixed(), + &signature.to_bytes(), + ) + } +} + +impl PrehashVerifier> for VerifyingKey +where + C: PrimeCurve + IntrinsicCurve, + SignatureSize: ArrayLength, + C::Point: WeierstrassPoint + CyclicGroup + FromCompressed>, + Coordinate: IntMod, + C::Scalar: IntMod + Reduce, + for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>, + for<'a> &'a Scalar: DivUnsafe<&'a Scalar, Output = Scalar>, +{ + fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> Result<()> { + verify_prehashed::( + self.inner.as_affine().clone(), + prehash, + &signature.to_bytes(), + ) + } +} + +impl Verifier> for VerifyingKey +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive + IntrinsicCurve, + SignatureSize: ArrayLength, + C::Point: WeierstrassPoint + CyclicGroup + FromCompressed>, + Coordinate: IntMod, + ::Scalar: IntMod + Reduce, + for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>, + for<'a> &'a Scalar: DivUnsafe<&'a Scalar, Output = Scalar>, +{ + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<()> { + self.verify_digest(C::Digest::new_with_prefix(msg), signature) + } +} + +// +// copied from `ecdsa` +// +impl VerifyingKey +where + C: CurveArithmetic + IntrinsicCurve, + AffinePoint: FromEncodedPoint + ToEncodedPoint + Default + ConditionallySelectable, + FieldBytesSize: ModulusSize, +{ + /// Initialize [`VerifyingKey`] from an [`EncodedPoint`]. + pub fn from_encoded_point(public_key: &EncodedPoint) -> Result { + Option::from(PublicKey::::from_encoded_point(public_key)) + .map(|public_key| Self { inner: public_key }) + .ok_or_else(Error::new) + } + + /// Serialize this [`VerifyingKey`] as a SEC1 [`EncodedPoint`], optionally + /// applying point compression. + pub fn to_encoded_point(&self, compress: bool) -> EncodedPoint { + self.inner.to_encoded_point(compress) + } +} + +// +// sec1 traits copied from elliptic_curve +// +impl FromEncodedPoint for PublicKey +where + C: CurveArithmetic + IntrinsicCurve, + AffinePoint: FromEncodedPoint + ToEncodedPoint + Default + ConditionallySelectable, + FieldBytesSize: ModulusSize, +{ + /// Initialize [`PublicKey`] from an [`EncodedPoint`] + fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { + AffinePoint::::from_encoded_point(encoded_point).and_then(|point| { + // Defeating the point of `subtle`, but the use case is specifically a public key + let is_identity = Choice::from(u8::from(encoded_point.is_identity())); + CtOption::new(PublicKey { point }, !is_identity) + }) + } +} + +impl ToEncodedPoint for PublicKey +where + C: CurveArithmetic + IntrinsicCurve, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + /// Serialize this [`PublicKey`] as a SEC1 [`EncodedPoint`], optionally applying + /// point compression + fn to_encoded_point(&self, compress: bool) -> EncodedPoint { + self.point.to_encoded_point(compress) + } +} + +// Custom openvm implementations +impl VerifyingKey +where + C: IntrinsicCurve + PrimeCurve, C::Point: WeierstrassPoint + CyclicGroup + FromCompressed>, Coordinate: IntMod, C::Scalar: IntMod + Reduce, @@ -197,52 +433,67 @@ where Ok(VerifyingKey { inner: public_key }) } +} - // Ref: https://docs.rs/ecdsa/latest/src/ecdsa/hazmat.rs.html#270 - #[allow(non_snake_case)] - pub fn verify_prehashed(self, prehash: &[u8], sig: &[u8]) -> Result<()> - where - for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>, - for<'a> &'a Scalar: DivUnsafe<&'a Scalar, Output = Scalar>, - { - // This should get compiled out: - assert!(Scalar::::NUM_LIMBS <= Coordinate::::NUM_LIMBS); - // IntMod limbs are currently always bytes - assert_eq!(sig.len(), Scalar::::NUM_LIMBS * 2); - // Signature is default encoded in big endian bytes - let (r_be, s_be) = sig.split_at(::Scalar::NUM_LIMBS); - // Note: Scalar internally stores using little endian - let r = Scalar::::from_be_bytes(r_be); - let s = Scalar::::from_be_bytes(s_be); - if !r.is_reduced() || !s.is_reduced() { - return Err(Error::new()); - } - if r == Scalar::::ZERO || s == Scalar::::ZERO { - return Err(Error::new()); - } +// Ref: https://docs.rs/ecdsa/latest/src/ecdsa/hazmat.rs.html#270 +#[allow(non_snake_case)] +pub fn verify_prehashed(pubkey: AffinePoint, prehash: &[u8], sig: &[u8]) -> Result<()> +where + C: IntrinsicCurve + PrimeCurve, + C::Point: WeierstrassPoint + CyclicGroup + FromCompressed>, + Coordinate: IntMod, + C::Scalar: IntMod + Reduce, + for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>, + for<'a> &'a Scalar: DivUnsafe<&'a Scalar, Output = Scalar>, +{ + // This should get compiled out: + assert!(Scalar::::NUM_LIMBS <= Coordinate::::NUM_LIMBS); + // IntMod limbs are currently always bytes + assert_eq!(sig.len(), Scalar::::NUM_LIMBS * 2); + // Signature is default encoded in big endian bytes + let (r_be, s_be) = sig.split_at(::Scalar::NUM_LIMBS); + // Note: Scalar internally stores using little endian + let r = Scalar::::from_be_bytes(r_be); + let s = Scalar::::from_be_bytes(s_be); + if !r.is_reduced() || !s.is_reduced() { + return Err(Error::new()); + } + if r == Scalar::::ZERO || s == Scalar::::ZERO { + return Err(Error::new()); + } - // Perf: don't use bits2field from ::ecdsa - let z = ::Scalar::from_be_bytes( - bits2field::(prehash).unwrap().as_ref(), - ); - - let u1 = z.div_unsafe(&s); - let u2 = (&r).div_unsafe(&s); - - let G = C::Point::GENERATOR; - // public key - let Q = self.inner.point; - let R = ::msm(&[u1, u2], &[G, Q]); - if R.is_identity() { - return Err(Error::new()); - } - let (x_1, _) = R.into_coords(); - // Scalar and Coordinate may be different byte lengths, so we use an inefficient reduction - let x_mod_n = Scalar::::reduce_le_bytes(x_1.as_le_bytes()); - if x_mod_n == r { - Ok(()) - } else { - Err(Error::new()) - } + // Perf: don't use bits2field from ::ecdsa + let z = + ::Scalar::from_be_bytes(bits2field::(prehash).unwrap().as_ref()); + + let u1 = z.div_unsafe(&s); + let u2 = (&r).div_unsafe(&s); + + let G = C::Point::GENERATOR; + // public key + let Q = pubkey; + let R = ::msm(&[u1, u2], &[G, Q]); + if R.is_identity() { + return Err(Error::new()); + } + let (x_1, _) = R.into_coords(); + // Scalar and Coordinate may be different byte lengths, so we use an inefficient reduction + let x_mod_n = Scalar::::reduce_le_bytes(x_1.as_le_bytes()); + if x_mod_n == r { + Ok(()) + } else { + Err(Error::new()) + } +} + +impl AsRef> for VerifyingKey { + fn as_ref(&self) -> &AffinePoint { + &self.inner.point + } +} + +impl AsRef> for PublicKey { + fn as_ref(&self) -> &AffinePoint { + &self.point } } diff --git a/extensions/ecc/guest/src/group.rs b/extensions/ecc/guest/src/group.rs index c2f8cde146..860d884d5c 100644 --- a/extensions/ecc/guest/src/group.rs +++ b/extensions/ecc/guest/src/group.rs @@ -24,9 +24,7 @@ pub trait Group: const IDENTITY: Self; - fn is_identity(&self) -> bool { - self == &Self::IDENTITY - } + fn is_identity(&self) -> bool; fn double(&self) -> Self; fn double_assign(&mut self); diff --git a/extensions/ecc/guest/src/lib.rs b/extensions/ecc/guest/src/lib.rs index e780ccf891..1d7f15a0d9 100644 --- a/extensions/ecc/guest/src/lib.rs +++ b/extensions/ecc/guest/src/lib.rs @@ -3,8 +3,6 @@ extern crate self as openvm_ecc_guest; #[macro_use] extern crate alloc; -#[cfg(feature = "halo2curves")] -pub use halo2curves_axiom as halo2curves; pub use once_cell; pub use openvm_algebra_guest as algebra; pub use openvm_ecc_sw_macros as sw_macros; @@ -17,22 +15,11 @@ pub use group::*; mod msm; pub use msm::*; -/// ECDSA +/// Optimized ECDSA implementation with the same functional interface as the `ecdsa` crate pub mod ecdsa; /// Weierstrass curve traits pub mod weierstrass; -/// Types for Secp256k1 curve with intrinsic functions. Implements traits necessary for ECDSA. -#[cfg(feature = "k256")] -pub mod k256; - -/// a.k.a. Secp256r1 -#[cfg(feature = "p256")] -pub mod p256; - -#[cfg(all(test, feature = "k256", feature = "p256", not(target_os = "zkvm")))] -mod tests; - /// This is custom-1 defined in RISC-V spec document pub const OPCODE: u8 = 0x2b; pub const SW_FUNCT3: u8 = 0b001; diff --git a/extensions/ecc/guest/src/tests.rs b/extensions/ecc/guest/src/tests.rs deleted file mode 100644 index 1d71d93be2..0000000000 --- a/extensions/ecc/guest/src/tests.rs +++ /dev/null @@ -1,115 +0,0 @@ -use hex_literal::hex; -use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{ - k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}, - msm, - p256::{P256Coord, P256Point, P256Scalar}, - weierstrass::WeierstrassPoint, - Group, -}; - -#[test] -fn test_secp256k1() { - // Sample points got from https://asecuritysite.com/ecc/ecc_points2 and - // https://learnmeabitcoin.com/technical/cryptography/elliptic-curve/#add - let x1 = Secp256k1Coord::from_u32(1); - let y1 = Secp256k1Coord::from_le_bytes(&hex!( - "EEA7767E580D75BC6FDD7F58D2A84C2614FB22586068DB63B346C6E60AF21842" - )); - let x2 = Secp256k1Coord::from_u32(2); - let y2 = Secp256k1Coord::from_le_bytes(&hex!( - "D1A847A8F879E0AEE32544DA5BA0B3BD1703A1F52867A5601FF6454DD8180499" - )); - // This is the sum of (x1, y1) and (x2, y2). - let x3 = Secp256k1Coord::from_le_bytes(&hex!( - "BE675E31F8AC8200CBCC6B10CECCD6EB93FB07D99BB9E7C99CC9245C862D3AF2" - )); - let y3 = Secp256k1Coord::from_le_bytes(&hex!( - "B44573B48FD3416DD256A8C0E1BAD03E88A78BF176778682589B9CB478FC1D79" - )); - // This is the double of (x2, y2). - let x4 = Secp256k1Coord::from_le_bytes(&hex!( - "3BFFFFFF32333333333333333333333333333333333333333333333333333333" - )); - let y4 = Secp256k1Coord::from_le_bytes(&hex!( - "AC54ECC4254A4EDCAB10CC557A9811ED1EF7CB8AFDC64820C6803D2C5F481639" - )); - - let mut p1 = Secp256k1Point::from_xy(x1.clone(), y1.clone()).unwrap(); - let mut p2 = Secp256k1Point::from_xy(x2, y2).unwrap(); - - // Generic add can handle equal or unequal points. - let p3 = &p1 + &p2; - if p3.x() != &x3 || p3.y() != &y3 { - panic!(); - } - let p4 = &p2 + &p2; - if p4.x() != &x4 || p4.y() != &y4 { - panic!(); - } - - // Add assign and double assign - p1 += &p2; - if p1.x() != &x3 || p1.y() != &y3 { - panic!(); - } - p2.double_assign(); - if p2.x() != &x4 || p2.y() != &y4 { - panic!(); - } - - // Ec Mul - let p1 = Secp256k1Point::from_xy(x1, y1).unwrap(); - let scalar = Secp256k1Scalar::from_u32(12345678); - // Calculated with https://learnmeabitcoin.com/technical/cryptography/elliptic-curve/#ec-multiply-tool - let x5 = Secp256k1Coord::from_le_bytes(&hex!( - "194A93387F790803D972AF9C4A40CB89D106A36F58EE2F31DC48A41768216D6D" - )); - let y5 = Secp256k1Coord::from_le_bytes(&hex!( - "9E272F746DA7BED171E522610212B6AEEAAFDB2AD9F4B530B8E1B27293B19B2C" - )); - let result = msm(&[scalar], &[p1]); - if result.x() != &x5 || result.y() != &y5 { - panic!(); - } -} - -#[test] -fn test_p256() { - // Sample points got from https://asecuritysite.com/ecc/p256p - let x1 = P256Coord::from_u32(5); - let y1 = P256Coord::from_le_bytes(&hex!( - "ccfb4832085c4133c5a3d9643c50ca11de7a8199ce3b91fe061858aab9439245" - )); - let p1 = P256Point::from_xy(x1.clone(), y1.clone()).unwrap(); - let x2 = P256Coord::from_u32(6); - let y2 = P256Coord::from_le_bytes(&hex!( - "cb23828228510d22e9c0e70fb802d1dc47007233e5856946c20a25542c4cb236" - )); - let p2 = P256Point::from_xy(x2.clone(), y2.clone()).unwrap(); - - // Generic add can handle equal or unequal points. - let p3 = &p1 + &p2; - let p4 = &p2 + &p2; - - // Add assign and double assign - let mut sum = P256Point::from_xy(x1.clone(), y1.clone()).unwrap(); - sum += &p2; - if sum.x() != p3.x() || sum.y() != p3.y() { - panic!(); - } - let mut double = P256Point::from_xy(x2, y2).unwrap(); - double.double_assign(); - if double.x() != p4.x() || double.y() != p4.y() { - panic!(); - } - - // Ec Mul - let p1 = P256Point::from_xy(x1, y1).unwrap(); - let scalar = P256Scalar::from_u32(3); - let p2 = &p1.double() + &p1; - let result = msm(&[scalar], &[p1]); - if result.x() != p2.x() || result.y() != p2.y() { - panic!(); - } -} diff --git a/extensions/ecc/guest/src/weierstrass.rs b/extensions/ecc/guest/src/weierstrass.rs index e1783e429d..6b3103606b 100644 --- a/extensions/ecc/guest/src/weierstrass.rs +++ b/extensions/ecc/guest/src/weierstrass.rs @@ -1,12 +1,12 @@ use alloc::vec::Vec; -use core::ops::{AddAssign, Mul}; +use core::ops::Mul; use openvm_algebra_guest::{Field, IntMod}; use super::group::Group; /// Short Weierstrass curve affine point. -pub trait WeierstrassPoint: Sized { +pub trait WeierstrassPoint: Clone + Sized { /// The `a` coefficient in the Weierstrass curve equation `y^2 = x^3 + a x + b`. const CURVE_A: Self::Coordinate; /// The `b` coefficient in the Weierstrass curve equation `y^2 = x^3 + a x + b`. @@ -30,19 +30,64 @@ pub trait WeierstrassPoint: Sized { fn x_mut(&mut self) -> &mut Self::Coordinate; fn y_mut(&mut self) -> &mut Self::Coordinate; - /// Hazmat: Assumes self != +- p2 and self != identity and p2 != identity. - fn add_ne_nonidentity(&self, p2: &Self) -> Self; - /// Hazmat: Assumes self != +- p2 and self != identity and p2 != identity. - fn add_ne_assign_nonidentity(&mut self, p2: &Self); - /// Hazmat: Assumes self != +- p2 and self != identity and p2 != identity. - fn sub_ne_nonidentity(&self, p2: &Self) -> Self; - /// Hazmat: Assumes self != +- p2 and self != identity and p2 != identity. - fn sub_ne_assign_nonidentity(&mut self, p2: &Self); - /// Hazmat: Assumes self != identity and 2 * self != identity. - fn double_nonidentity(&self) -> Self; - /// Hazmat: Assumes self != identity and 2 * self != identity. - fn double_assign_nonidentity(&mut self); + /// Calls any setup required for this curve. The implementation should internally use `OnceBool` + /// to ensure that setup is only called once. + fn set_up_once(); + /// Add implementation that handles identity and whether points are equal or not. + /// + /// # Safety + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + fn add_assign_impl(&mut self, p2: &Self); + + /// Double implementation that handles identity. + /// + /// # Safety + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + fn double_assign_impl(&mut self); + + /// # Safety + /// - Assumes self != +- p2 and self != identity and p2 != identity. + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + unsafe fn add_ne_nonidentity(&self, p2: &Self) -> Self; + /// # Safety + /// - Assumes self != +- p2 and self != identity and p2 != identity. + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + unsafe fn add_ne_assign_nonidentity(&mut self, p2: &Self); + /// # Safety + /// - Assumes self != +- p2 and self != identity and p2 != identity. + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + unsafe fn sub_ne_nonidentity(&self, p2: &Self) -> Self; + /// # Safety + /// - Assumes self != +- p2 and self != identity and p2 != identity. + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + unsafe fn sub_ne_assign_nonidentity(&mut self, p2: &Self); + /// # Safety + /// - Assumes self != identity and 2 * self != identity. + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + unsafe fn double_nonidentity(&self) -> Self; + /// # Safety + /// - Assumes self != identity and 2 * self != identity. + /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls + /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has + /// been called already. + unsafe fn double_assign_nonidentity(&mut self); + + #[inline(always)] fn from_xy(x: Self::Coordinate, y: Self::Coordinate) -> Option where for<'a> &'a Self::Coordinate: Mul<&'a Self::Coordinate, Output = Self::Coordinate>, @@ -54,6 +99,7 @@ pub trait WeierstrassPoint: Sized { } } + #[inline(always)] fn from_xy_nonidentity(x: Self::Coordinate, y: Self::Coordinate) -> Option where for<'a> &'a Self::Coordinate: Mul<&'a Self::Coordinate, Output = Self::Coordinate>, @@ -67,15 +113,6 @@ pub trait WeierstrassPoint: Sized { } } -// Hint for a decompression -// if possible is true, then `sqrt` is the decompressed y-coordinate -// if possible is false, then `sqrt` is such that -// `sqrt^2 = rhs * non_qr` where `rhs` is the rhs of the curve equation -pub struct DecompressionHint { - pub possible: bool, - pub sqrt: T, -} - pub trait FromCompressed { /// Given `x`-coordinate, /// @@ -87,18 +124,6 @@ pub trait FromCompressed { fn decompress(x: Coordinate, rec_id: &u8) -> Option where Self: core::marker::Sized; - - /// If it exists, hints the unique `y` coordinate that is less than `Coordinate::MODULUS` - /// such that `(x, y)` is a point on the curve and `y` has parity equal to `rec_id`. - /// If such `y` does not exist, hints a coordinate `sqrt` such that `sqrt^2 = rhs * non_qr` - /// where `rhs` is the rhs of the curve equation and `non_qr` is the non-quadratic residue - /// for this curve that was initialized in the setup function. - /// - /// This is only a hint, and the returned value does not guarantee any of the above properties. - /// They must be checked separately. Normal users should use `decompress` directly. - /// - /// Returns None if the `DecompressionHint::possible` flag in the hint stream is not a boolean. - fn hint_decompress(x: &Coordinate, rec_id: &u8) -> Option>; } /// A trait for elliptic curves that bridges the openvm types and external types with @@ -144,7 +169,9 @@ where /// /// Assumes that `window_bits` is less than (number of bits - 1) of the order of /// subgroup generated by each non-identity `base`. + #[inline] pub fn new_with_prime_order(bases: &'a [C::Point], window_bits: usize) -> Self { + C::Point::set_up_once(); assert!(window_bits > 0); let window_size = 1 << window_bits; let table = bases @@ -161,8 +188,10 @@ where // j * base + base != identity let multiple = multiples .last() - .map(|last| WeierstrassPoint::add_ne_nonidentity(last, base)) - .unwrap_or_else(|| base.double_nonidentity()); + .map(|last| unsafe { + WeierstrassPoint::add_ne_nonidentity::(last, base) + }) + .unwrap_or_else(|| unsafe { base.double_nonidentity::() }); multiples.push(multiple); } multiples @@ -178,6 +207,7 @@ where } } + #[inline(always)] fn get_multiple(&self, base_idx: usize, scalar: usize) -> &C::Point { if scalar == 0 { &self.identity @@ -192,7 +222,9 @@ where /// /// For implementation simplicity, currently only implemented when /// `window_bits` divides 8 (number of bits in a byte). + #[inline] pub fn windowed_mul(&self, scalars: &[C::Scalar]) -> C::Point { + C::Point::set_up_once(); assert_eq!(8 % self.window_bits, 0); assert_eq!(scalars.len(), self.bases.len()); let windows_per_byte = 8 / self.window_bits; @@ -220,14 +252,16 @@ where if outer != 0 { for _ in 0..self.window_bits { // Note: this handles identity - res.double_assign(); + // setup has been called above + res.double_assign_impl::(); } } for (base_idx, scalar) in scalars.iter().enumerate() { let scalar = (scalar.as_le_bytes()[limb_idx] >> bit_idx) & mask; let summand = self.get_multiple(base_idx, scalar as usize); // handles identity - res.add_assign(summand); + // setup has been called above + res.add_assign_impl::(summand); } } res @@ -297,8 +331,46 @@ macro_rules! impl_sw_affine { &mut self.0.y } - fn double_nonidentity(&self) -> Self { - use ::openvm_algebra_guest::DivUnsafe; + fn set_up_once() { + // There are no special opcodes for curve operations in this case, so no additional + // setup is required. + // + // Since the `Self::Coordinate` is abstract, any set up required by the field is not + // handled here. + } + + fn add_assign_impl(&mut self, p2: &Self) { + if self == &::IDENTITY { + *self = p2.clone(); + } else if p2 == &::IDENTITY { + // do nothing + } else if self.x() == p2.x() { + if self.y() + p2.y() == ::ZERO + { + *self = ::IDENTITY; + } else { + unsafe { + self.double_assign_nonidentity::(); + } + } + } else { + unsafe { + self.add_ne_assign_nonidentity::(p2); + } + } + } + + #[inline(always)] + fn double_assign_impl(&mut self) { + if self != &::IDENTITY { + unsafe { + self.double_assign_nonidentity::(); + } + } + } + + unsafe fn double_nonidentity(&self) -> Self { + use openvm_algebra_guest::DivUnsafe; // lambda = (3*x1^2+a)/(2*y1) // assume a = 0 let lambda = (&THREE * self.x() * self.x()).div_unsafe(self.y() + self.y()); @@ -309,12 +381,13 @@ macro_rules! impl_sw_affine { Self(AffinePoint::new(x3, y3)) } - fn double_assign_nonidentity(&mut self) { - *self = self.double_nonidentity(); + #[inline(always)] + unsafe fn double_assign_nonidentity(&mut self) { + *self = self.double_nonidentity::(); } - fn add_ne_nonidentity(&self, p2: &Self) -> Self { - use ::openvm_algebra_guest::DivUnsafe; + unsafe fn add_ne_nonidentity(&self, p2: &Self) -> Self { + use openvm_algebra_guest::DivUnsafe; // lambda = (y2-y1)/(x2-x1) // x3 = lambda^2-x1-x2 // y3 = lambda*(x1-x3)-y1 @@ -324,12 +397,13 @@ macro_rules! impl_sw_affine { Self(AffinePoint::new(x3, y3)) } - fn add_ne_assign_nonidentity(&mut self, p2: &Self) { - *self = self.add_ne_nonidentity(p2); + #[inline(always)] + unsafe fn add_ne_assign_nonidentity(&mut self, p2: &Self) { + *self = self.add_ne_nonidentity::(p2); } - fn sub_ne_nonidentity(&self, p2: &Self) -> Self { - use ::openvm_algebra_guest::DivUnsafe; + unsafe fn sub_ne_nonidentity(&self, p2: &Self) -> Self { + use openvm_algebra_guest::DivUnsafe; // lambda = (y2+y1)/(x1-x2) // x3 = lambda^2-x1-x2 // y3 = lambda*(x1-x3)-y1 @@ -339,14 +413,16 @@ macro_rules! impl_sw_affine { Self(AffinePoint::new(x3, y3)) } - fn sub_ne_assign_nonidentity(&mut self, p2: &Self) { - *self = self.sub_ne_nonidentity(p2); + #[inline(always)] + unsafe fn sub_ne_assign_nonidentity(&mut self, p2: &Self) { + *self = self.sub_ne_nonidentity::(p2); } } impl core::ops::Neg for $struct_name { type Output = Self; + #[inline(always)] fn neg(mut self) -> Self::Output { self.0.y.neg_assign(); self @@ -356,6 +432,7 @@ macro_rules! impl_sw_affine { impl core::ops::Neg for &$struct_name { type Output = $struct_name; + #[inline(always)] fn neg(self) -> Self::Output { self.clone().neg() } @@ -385,24 +462,35 @@ macro_rules! impl_sw_group_ops { const IDENTITY: Self = ::IDENTITY; + #[inline(always)] fn double(&self) -> Self { if self.is_identity() { self.clone() } else { - self.double_nonidentity() + unsafe { self.double_nonidentity::() } } } + #[inline(always)] fn double_assign(&mut self) { - if !self.is_identity() { - self.double_assign_nonidentity(); - } + self.double_assign_impl::(); + } + + // This implementation is the same as the default implementation in the `Group` trait, + // but it was found that overriding the default implementation reduced the cycle count + // by 50% on the ecrecover benchmark. + // We hypothesize that this is due to compiler optimizations that are not possible when + // the `is_identity` function is defined in a different source file. + #[inline(always)] + fn is_identity(&self) -> bool { + self == &::IDENTITY } } impl core::ops::Add<&$struct_name> for $struct_name { type Output = Self; + #[inline(always)] fn add(mut self, p2: &$struct_name) -> Self::Output { use core::ops::AddAssign; self.add_assign(p2); @@ -413,6 +501,7 @@ macro_rules! impl_sw_group_ops { impl core::ops::Add for $struct_name { type Output = Self; + #[inline(always)] fn add(self, rhs: Self) -> Self::Output { self.add(&rhs) } @@ -421,42 +510,33 @@ macro_rules! impl_sw_group_ops { impl core::ops::Add<&$struct_name> for &$struct_name { type Output = $struct_name; + #[inline(always)] fn add(self, p2: &$struct_name) -> Self::Output { if self.is_identity() { p2.clone() } else if p2.is_identity() { self.clone() - } else if self.x() == p2.x() { + } else if WeierstrassPoint::x(self) == WeierstrassPoint::x(p2) { if self.y() + p2.y() == <$field as openvm_algebra_guest::Field>::ZERO { <$struct_name as WeierstrassPoint>::IDENTITY } else { - self.double_nonidentity() + unsafe { self.double_nonidentity::() } } } else { - self.add_ne_nonidentity(p2) + unsafe { self.add_ne_nonidentity::(p2) } } } } impl core::ops::AddAssign<&$struct_name> for $struct_name { + #[inline(always)] fn add_assign(&mut self, p2: &$struct_name) { - if self.is_identity() { - *self = p2.clone(); - } else if p2.is_identity() { - // do nothing - } else if self.x() == p2.x() { - if self.y() + p2.y() == <$field as openvm_algebra_guest::Field>::ZERO { - *self = <$struct_name as WeierstrassPoint>::IDENTITY; - } else { - self.double_assign_nonidentity(); - } - } else { - self.add_ne_assign_nonidentity(p2); - } + self.add_assign_impl::(p2); } } impl core::ops::AddAssign for $struct_name { + #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.add_assign(&rhs); } @@ -465,6 +545,7 @@ macro_rules! impl_sw_group_ops { impl core::ops::Sub<&$struct_name> for $struct_name { type Output = Self; + #[inline(always)] fn sub(self, rhs: &$struct_name) -> Self::Output { core::ops::Sub::sub(&self, rhs) } @@ -473,6 +554,7 @@ macro_rules! impl_sw_group_ops { impl core::ops::Sub for $struct_name { type Output = $struct_name; + #[inline(always)] fn sub(self, rhs: Self) -> Self::Output { self.sub(&rhs) } @@ -481,42 +563,49 @@ macro_rules! impl_sw_group_ops { impl core::ops::Sub<&$struct_name> for &$struct_name { type Output = $struct_name; + #[inline(always)] fn sub(self, p2: &$struct_name) -> Self::Output { if p2.is_identity() { self.clone() } else if self.is_identity() { core::ops::Neg::neg(p2) - } else if self.x() == p2.x() { + } else if WeierstrassPoint::x(self) == WeierstrassPoint::x(p2) { if self.y() == p2.y() { <$struct_name as WeierstrassPoint>::IDENTITY } else { - self.double_nonidentity() + unsafe { self.double_nonidentity::() } } } else { - self.sub_ne_nonidentity(p2) + unsafe { self.sub_ne_nonidentity::(p2) } } } } impl core::ops::SubAssign<&$struct_name> for $struct_name { + #[inline(always)] fn sub_assign(&mut self, p2: &$struct_name) { if p2.is_identity() { // do nothing } else if self.is_identity() { *self = core::ops::Neg::neg(p2); - } else if self.x() == p2.x() { + } else if WeierstrassPoint::x(self) == WeierstrassPoint::x(p2) { if self.y() == p2.y() { *self = <$struct_name as WeierstrassPoint>::IDENTITY } else { - self.double_assign_nonidentity(); + unsafe { + self.double_assign_nonidentity::(); + } } } else { - self.sub_ne_assign_nonidentity(p2); + unsafe { + self.sub_ne_assign_nonidentity::(p2); + } } } } impl core::ops::SubAssign for $struct_name { + #[inline(always)] fn sub_assign(&mut self, rhs: Self) { self.sub_assign(&rhs); } diff --git a/extensions/ecc/sw-macros/Cargo.toml b/extensions/ecc/sw-macros/Cargo.toml index 743f90a955..81d767e51d 100644 --- a/extensions/ecc/sw-macros/Cargo.toml +++ b/extensions/ecc/sw-macros/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "openvm-ecc-sw-macros" +description = "OpenVM elliptic curve macros for short Weierstrass curves" version.workspace = true authors.workspace = true edition.workspace = true diff --git a/extensions/ecc/sw-macros/README.md b/extensions/ecc/sw-macros/README.md index 5c247959f1..71f8d553f4 100644 --- a/extensions/ecc/sw-macros/README.md +++ b/extensions/ecc/sw-macros/README.md @@ -20,6 +20,8 @@ sw_declare! { Secp256k1Point { mod_type = Secp256k1Coord, b = CURVE_B }, } +openvm::init!(); +/* The init! macro will expand to: openvm_algebra_guest::moduli_macros::moduli_init! { "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" @@ -28,10 +30,9 @@ openvm_algebra_guest::moduli_macros::moduli_init! { openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point, } +*/ pub fn main() { - setup_all_moduli(); - setup_all_curves(); // ... } ``` @@ -63,13 +64,13 @@ Similar to `moduli_declare!`, this macro also creates extern functions for arith extern "C" { fn sw_add_extern_func_Secp256k1Point(rd: usize, rs1: usize, rs2: usize); fn sw_double_extern_func_Secp256k1Point(rd: usize, rs1: usize); - fn hint_decompress_extern_func_Secp256k1Point(rs1: usize, rs2: usize); } ``` 2. Again, `sw_init!` macro implements these extern functions and defines the setup functions for the sw struct. ```rust +#[allow(non_snake_case)] #[cfg(target_os = "zkvm")] mod openvm_intrinsics_ffi_2 { use :openvm_ecc_guest::{OPCODE, SW_FUNCT3, SwBaseFunct7}; @@ -79,21 +80,18 @@ mod openvm_intrinsics_ffi_2 { // ... } // other externs -} -#[allow(non_snake_case)] -pub fn setup_sw_Secp256k1Point() { - #[cfg(target_os = "zkvm")] - { - // ... + + #[no_mangle] + extern "C" fn sw_setup_extern_func_Secp256k1Point() { + #[cfg(target_os = "zkvm")] + { + // ... + } } } -pub fn setup_all_curves() { - setup_sw_Secp256k1Point(); - // other setups -} ``` -3. Again, the `setup` function for every used curve must be called before any other instructions for that curve. If all curves are used, one can call `setup_all_curves()` to setup all of them. +3. Again, if using the Rust bindings, then the `sw_setup_extern_func_*` function for every curve is automatically called on first use of any of the curve's intrinsics. 4. The order of the items in `sw_init!` **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of `CurveConfig`s in `WeierstrassExtension::supported_curves`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). @@ -114,3 +112,7 @@ sw_init! { ``` The reason is that, for example, the function `sw_add_extern_func_Secp256k1Point` remains unimplemented, but we implement `sw_add_extern_func_Sw`. + +6. `cargo openvm build` will automatically generate a call to `sw_init!` based on `openvm.toml`. +Note that `openvm.toml` must contain the name of each struct created by `sw_declare!` as a string (in the example at the top of this document, its `"Secp256k1"`). +The SDK also supports this feature. diff --git a/extensions/ecc/sw-macros/src/lib.rs b/extensions/ecc/sw-macros/src/lib.rs index c739b1965c..2c16462e4b 100644 --- a/extensions/ecc/sw-macros/src/lib.rs +++ b/extensions/ecc/sw-macros/src/lib.rs @@ -87,8 +87,7 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { } create_extern_func!(sw_add_ne_extern_func); create_extern_func!(sw_double_extern_func); - create_extern_func!(hint_decompress_extern_func); - create_extern_func!(hint_non_qr_extern_func); + create_extern_func!(sw_setup_extern_func); let group_ops_mod_name = format_ident!("{}_ops", struct_name.to_string().to_lowercase()); @@ -96,8 +95,7 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { extern "C" { fn #sw_add_ne_extern_func(rd: usize, rs1: usize, rs2: usize); fn #sw_double_extern_func(rd: usize, rs1: usize); - fn #hint_decompress_extern_func(rs1: usize, rs2: usize); - fn #hint_non_qr_extern_func(); + fn #sw_setup_extern_func(); } #[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -118,7 +116,7 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { // Below are wrapper functions for the intrinsic instructions. // Should not be called directly. #[inline(always)] - fn add_ne(p1: &#struct_name, p2: &#struct_name) -> #struct_name { + unsafe fn add_ne(p1: &#struct_name, p2: &#struct_name) -> #struct_name { #[cfg(not(target_os = "zkvm"))] { use openvm_algebra_guest::DivUnsafe; @@ -129,20 +127,21 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + if CHECK_SETUP { + Self::set_up_once(); + } let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit(); - unsafe { - #sw_add_ne_extern_func( - uninit.as_mut_ptr() as usize, - p1 as *const #struct_name as usize, - p2 as *const #struct_name as usize - ) - }; - unsafe { uninit.assume_init() } + #sw_add_ne_extern_func( + uninit.as_mut_ptr() as usize, + p1 as *const #struct_name as usize, + p2 as *const #struct_name as usize + ); + uninit.assume_init() } } #[inline(always)] - fn add_ne_assign(&mut self, p2: &#struct_name) { + unsafe fn add_ne_assign(&mut self, p2: &#struct_name) { #[cfg(not(target_os = "zkvm"))] { use openvm_algebra_guest::DivUnsafe; @@ -154,19 +153,20 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { - unsafe { - #sw_add_ne_extern_func( - self as *mut #struct_name as usize, - self as *const #struct_name as usize, - p2 as *const #struct_name as usize - ) - }; + if CHECK_SETUP { + Self::set_up_once(); + } + #sw_add_ne_extern_func( + self as *mut #struct_name as usize, + self as *const #struct_name as usize, + p2 as *const #struct_name as usize + ); } } /// Assumes that `p` is not identity. #[inline(always)] - fn double_impl(p: &#struct_name) -> #struct_name { + unsafe fn double_impl(p: &#struct_name) -> #struct_name { #[cfg(not(target_os = "zkvm"))] { use openvm_algebra_guest::DivUnsafe; @@ -179,34 +179,44 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { } #[cfg(target_os = "zkvm")] { + if CHECK_SETUP { + Self::set_up_once(); + } let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit(); - unsafe { - #sw_double_extern_func( - uninit.as_mut_ptr() as usize, - p as *const #struct_name as usize, - ) - }; - unsafe { uninit.assume_init() } + #sw_double_extern_func( + uninit.as_mut_ptr() as usize, + p as *const #struct_name as usize, + ); + uninit.assume_init() } } + // Helper function to call the setup instruction on first use #[inline(always)] - fn double_assign_impl(&mut self) { - #[cfg(not(target_os = "zkvm"))] - { - *self = Self::double_impl(self); - } - #[cfg(target_os = "zkvm")] - { - unsafe { - #sw_double_extern_func( - self as *mut #struct_name as usize, - self as *const #struct_name as usize - ) - }; - } + #[cfg(target_os = "zkvm")] + fn set_up_once() { + static is_setup: ::openvm_ecc_guest::once_cell::race::OnceBool = ::openvm_ecc_guest::once_cell::race::OnceBool::new(); + is_setup.get_or_init(|| { + unsafe { #sw_setup_extern_func(); } + <#intmod_type as openvm_algebra_guest::IntMod>::set_up_once(); + true + }); } + #[inline(always)] + #[cfg(not(target_os = "zkvm"))] + fn set_up_once() { + // No-op for non-ZKVM targets + } + + #[inline(always)] + fn is_identity_impl(&self) -> bool { + use openvm_algebra_guest::IntMod; + // Safety: Self::set_up_once() ensures IntMod::set_up_once() has been called. + unsafe { + self.x.eq_impl::(&#intmod_type::ZERO) && self.y.eq_impl::(&#intmod_type::ZERO) + } + } } impl ::openvm_ecc_guest::weierstrass::WeierstrassPoint for #struct_name { @@ -217,56 +227,126 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { /// SAFETY: assumes that #intmod_type has a memory representation /// such that with repr(C), two coordinates are packed contiguously. + #[inline(always)] fn as_le_bytes(&self) -> &[u8] { unsafe { &*core::ptr::slice_from_raw_parts(self as *const Self as *const u8, <#intmod_type as openvm_algebra_guest::IntMod>::NUM_LIMBS * 2) } } + #[inline(always)] fn from_xy_unchecked(x: Self::Coordinate, y: Self::Coordinate) -> Self { Self { x, y } } + #[inline(always)] fn x(&self) -> &Self::Coordinate { &self.x } + #[inline(always)] fn y(&self) -> &Self::Coordinate { &self.y } + #[inline(always)] fn x_mut(&mut self) -> &mut Self::Coordinate { &mut self.x } + #[inline(always)] fn y_mut(&mut self) -> &mut Self::Coordinate { &mut self.y } + #[inline(always)] fn into_coords(self) -> (Self::Coordinate, Self::Coordinate) { (self.x, self.y) } - fn add_ne_nonidentity(&self, p2: &Self) -> Self { - Self::add_ne(self, p2) + #[inline(always)] + fn set_up_once() { + Self::set_up_once(); } - fn add_ne_assign_nonidentity(&mut self, p2: &Self) { - Self::add_ne_assign(self, p2); + #[inline] + fn add_assign_impl(&mut self, p2: &Self) { + use openvm_algebra_guest::IntMod; + + if CHECK_SETUP { + // Call setup here so we skip it below + #intmod_type::set_up_once(); + } + + if self.is_identity_impl::() { + *self = p2.clone(); + } else if p2.is_identity_impl::() { + // do nothing + } else if unsafe { self.x.eq_impl::(&p2.x) } { // Safety: we called IntMod setup above + let sum_ys = unsafe { self.y.add_ref::(&p2.y) }; + // Safety: we called IntMod setup above + if unsafe { IntMod::eq_impl::(&sum_ys, &<#intmod_type as IntMod>::ZERO) } { + *self = Self::identity(); + } else { + unsafe { + self.double_assign_nonidentity::(); + } + } + } else { + unsafe { + self.add_ne_assign_nonidentity::(p2); + } + } } - fn sub_ne_nonidentity(&self, p2: &Self) -> Self { - Self::add_ne(self, &p2.clone().neg()) + #[inline(always)] + fn double_assign_impl(&mut self) { + if !self.is_identity_impl::() { + unsafe { + self.double_assign_nonidentity::(); + } + } } - fn sub_ne_assign_nonidentity(&mut self, p2: &Self) { - Self::add_ne_assign(self, &p2.clone().neg()); + #[inline(always)] + unsafe fn add_ne_nonidentity(&self, p2: &Self) -> Self { + Self::add_ne::(self, p2) } - fn double_nonidentity(&self) -> Self { - Self::double_impl(self) + #[inline(always)] + unsafe fn add_ne_assign_nonidentity(&mut self, p2: &Self) { + Self::add_ne_assign::(self, p2); } - fn double_assign_nonidentity(&mut self) { - Self::double_assign_impl(self); + #[inline(always)] + unsafe fn sub_ne_nonidentity(&self, p2: &Self) -> Self { + Self::add_ne::(self, &p2.clone().neg()) + } + + #[inline(always)] + unsafe fn sub_ne_assign_nonidentity(&mut self, p2: &Self) { + Self::add_ne_assign::(self, &p2.clone().neg()); + } + + #[inline(always)] + unsafe fn double_nonidentity(&self) -> Self { + Self::double_impl::(self) + } + + #[inline(always)] + unsafe fn double_assign_nonidentity(&mut self) { + #[cfg(not(target_os = "zkvm"))] + { + *self = Self::double_impl::(self); + } + #[cfg(target_os = "zkvm")] + { + if CHECK_SETUP { + Self::set_up_once(); + } + #sw_double_extern_func( + self as *mut #struct_name as usize, + self as *const #struct_name as usize + ); + } } } @@ -293,123 +373,29 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { } mod #group_ops_mod_name { - use ::openvm_ecc_guest::{weierstrass::{WeierstrassPoint, FromCompressed, DecompressionHint}, impl_sw_group_ops, algebra::{IntMod, DivUnsafe, DivAssignUnsafe, ExpBytes}}; + use ::openvm_ecc_guest::{weierstrass::{WeierstrassPoint, FromCompressed}, impl_sw_group_ops, algebra::IntMod}; use super::*; impl_sw_group_ops!(#struct_name, #intmod_type); impl FromCompressed<#intmod_type> for #struct_name { fn decompress(x: #intmod_type, rec_id: &u8) -> Option { - match Self::honest_host_decompress(&x, rec_id) { - // successfully decompressed - Some(Some(ret)) => Some(ret), - // successfully proved that the point cannot be decompressed - Some(None) => None, - None => { - // host is dishonest, enter infinite loop - loop { - openvm::io::println("ERROR: Decompression hint is invalid. Entering infinite loop."); - } - } - } - } - - fn hint_decompress(x: &#intmod_type, rec_id: &u8) -> Option> { - #[cfg(not(target_os = "zkvm"))] - { - unimplemented!() - } - #[cfg(target_os = "zkvm")] - { - use openvm::platform as openvm_platform; // needed for hint_store_u32! - - let possible = core::mem::MaybeUninit::::uninit(); - let sqrt = core::mem::MaybeUninit::<#intmod_type>::uninit(); - unsafe { - #hint_decompress_extern_func(x as *const _ as usize, rec_id as *const u8 as usize); - let possible_ptr = possible.as_ptr() as *const u32; - openvm_rv32im_guest::hint_store_u32!(possible_ptr); - openvm_rv32im_guest::hint_buffer_u32!(sqrt.as_ptr() as *const u8, <#intmod_type as openvm_algebra_guest::IntMod>::NUM_LIMBS / 4); - let possible = possible.assume_init(); - if possible == 0 || possible == 1 { - Some(DecompressionHint { possible: possible == 1, sqrt: sqrt.assume_init() }) + use openvm_algebra_guest::Sqrt; + let y_squared = &x * &x * &x + &<#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::CURVE_A * &x + &<#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::CURVE_B; + let y = y_squared.sqrt(); + match y { + None => None, + Some(y) => { + let correct_y = if y.as_le_bytes()[0] & 1 == *rec_id & 1 { + y } else { - None - } - } - } - } - } - - impl #struct_name { - // Returns None if the hint is incorrect (i.e. the host is dishonest) - // Returns Some(None) if the hint proves that the point cannot be decompressed - fn honest_host_decompress(x: &#intmod_type, rec_id: &u8) -> Option> { - let hint = <#struct_name as FromCompressed<#intmod_type>>::hint_decompress(x, rec_id)?; - - if hint.possible { - // ensure y < modulus - hint.sqrt.assert_reduced(); - - if hint.sqrt.as_le_bytes()[0] & 1 != *rec_id & 1 { - None - } else { - let ret = <#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::from_xy_nonidentity(x.clone(), hint.sqrt)?; - Some(Some(ret)) - } - } else { - // ensure sqrt < modulus - hint.sqrt.assert_reduced(); - - let alpha = (x * x * x) + (x * &<#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::CURVE_A) + &<#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::CURVE_B; - if &hint.sqrt * &hint.sqrt == alpha * Self::get_non_qr() { - Some(None) - } else { - None - } - } - } - - // Generate a non quadratic residue in the coordinate field by using a hint - fn init_non_qr() -> alloc::boxed::Box<::Coordinate> { - #[cfg(not(target_os = "zkvm"))] - { - unimplemented!(); - } - #[cfg(target_os = "zkvm")] - { - use openvm::platform as openvm_platform; // needed for hint_buffer_u32 - let mut non_qr_uninit = core::mem::MaybeUninit::<#intmod_type>::uninit(); - let mut non_qr; - unsafe { - #hint_non_qr_extern_func(); - let ptr = non_qr_uninit.as_ptr() as *const u8; - openvm_rv32im_guest::hint_buffer_u32!(ptr, <#intmod_type as openvm_algebra_guest::IntMod>::NUM_LIMBS / 4); - non_qr = non_qr_uninit.assume_init(); + -y + }; + // In order for sqrt() to return Some, we are guaranteed that y * y == y_squared, which already proves (x, correct_y) is on the curve + Some(<#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::from_xy_unchecked(x, correct_y)) } - // ensure non_qr < modulus - non_qr.assert_reduced(); - - // construct exp = (p-1)/2 as an integer by first constraining exp = (p-1)/2 (mod p) and then exp < p - let exp = -<#intmod_type as openvm_algebra_guest::IntMod>::ONE.div_unsafe(#intmod_type::from_const_u8(2)); - exp.assert_reduced(); - - if non_qr.exp_bytes(true, &exp.to_be_bytes()) != -<#intmod_type as openvm_algebra_guest::IntMod>::ONE - { - // non_qr is not a non quadratic residue, so host is dishonest - loop { - openvm::io::println("ERROR: Non quadratic residue hint is invalid. Entering infinite loop."); - } - } - - alloc::boxed::Box::new(non_qr) } } - - pub fn get_non_qr() -> &'static #intmod_type { - static non_qr: ::openvm_ecc_guest::once_cell::race::OnceBox<#intmod_type> = ::openvm_ecc_guest::once_cell::race::OnceBox::new(); - &non_qr.get_or_init(Self::init_non_qr) - } } } }); @@ -446,8 +432,6 @@ pub fn sw_init(input: TokenStream) -> TokenStream { let SwDefine { items } = parse_macro_input!(input as SwDefine); let mut externs = Vec::new(); - let mut setups = Vec::new(); - let mut setup_all_curves = Vec::new(); let span = proc_macro::Span::call_site(); @@ -462,14 +446,9 @@ pub fn sw_init(input: TokenStream) -> TokenStream { syn::Ident::new(&format!("sw_add_ne_extern_func_{}", str_path), span.into()); let double_extern_func = syn::Ident::new(&format!("sw_double_extern_func_{}", str_path), span.into()); - let hint_decompress_extern_func = syn::Ident::new( - &format!("hint_decompress_extern_func_{}", str_path), - span.into(), - ); - let hint_non_qr_extern_func = syn::Ident::new( - &format!("hint_non_qr_extern_func_{}", str_path), - span.into(), - ); + let setup_extern_func = + syn::Ident::new(&format!("sw_setup_extern_func_{}", str_path), span.into()); + externs.push(quote::quote_spanned! { span.into() => #[no_mangle] extern "C" fn #add_ne_extern_func(rd: usize, rs1: usize, rs2: usize) { @@ -498,38 +477,10 @@ pub fn sw_init(input: TokenStream) -> TokenStream { } #[no_mangle] - extern "C" fn #hint_decompress_extern_func(rs1: usize, rs2: usize) { - openvm::platform::custom_insn_r!( - opcode = OPCODE, - funct3 = SW_FUNCT3 as usize, - funct7 = SwBaseFunct7::HintDecompress as usize + #ec_idx - * (SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS as usize), - rd = Const "x0", - rs1 = In rs1, - rs2 = In rs2 - ); - } - - #[no_mangle] - extern "C" fn #hint_non_qr_extern_func() { - openvm::platform::custom_insn_r!( - opcode = OPCODE, - funct3 = SW_FUNCT3 as usize, - funct7 = SwBaseFunct7::HintNonQr as usize + #ec_idx - * (SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS as usize), - rd = Const "x0", - rs1 = Const "x0", - rs2 = Const "x0" - ); - } - }); - - let setup_function = syn::Ident::new(&format!("setup_sw_{}", str_path), span.into()); - setups.push(quote::quote_spanned! { span.into() => - #[allow(non_snake_case)] - pub fn #setup_function() { + extern "C" fn #setup_extern_func() { #[cfg(target_os = "zkvm")] { + use super::#item; // p1 is (x1, y1), and x1 must be the modulus. // y1 can be anything for SetupEcAdd, but must equal `a` for SetupEcDouble let modulus_bytes = <<#item as openvm_ecc_guest::weierstrass::WeierstrassPoint>::Coordinate as openvm_algebra_guest::IntMod>::MODULUS; @@ -564,22 +515,15 @@ pub fn sw_init(input: TokenStream) -> TokenStream { } } }); - - setup_all_curves.push(quote::quote_spanned! { span.into() => - #setup_function(); - }); } TokenStream::from(quote::quote_spanned! { span.into() => + #[allow(non_snake_case)] #[cfg(target_os = "zkvm")] mod openvm_intrinsics_ffi_2 { use ::openvm_ecc_guest::{OPCODE, SW_FUNCT3, SwBaseFunct7}; #(#externs)* } - #(#setups)* - pub fn setup_all_curves() { - #(#setup_all_curves)* - } }) } diff --git a/extensions/ecc/tests/Cargo.toml b/extensions/ecc/tests/Cargo.toml index 9a743ac00b..14afb36453 100644 --- a/extensions/ecc/tests/Cargo.toml +++ b/extensions/ecc/tests/Cargo.toml @@ -15,7 +15,6 @@ openvm-algebra-circuit.workspace = true openvm-algebra-transpiler.workspace = true openvm-ecc-transpiler.workspace = true openvm-ecc-circuit.workspace = true -openvm-ecc-guest.workspace = true openvm-rv32im-transpiler.workspace = true openvm-keccak256-transpiler.workspace = true openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } @@ -23,6 +22,8 @@ openvm-sdk.workspace = true eyre.workspace = true hex-literal.workspace = true num-bigint.workspace = true +halo2curves-axiom = { workspace = true } + [features] default = ["parallel"] parallel = ["openvm-circuit/parallel"] diff --git a/extensions/ecc/tests/programs/Cargo.toml b/extensions/ecc/tests/programs/Cargo.toml index ccc3ba7387..8f53bf5ae1 100644 --- a/extensions/ecc/tests/programs/Cargo.toml +++ b/extensions/ecc/tests/programs/Cargo.toml @@ -13,19 +13,22 @@ openvm-ecc-guest = { path = "../../guest", default-features = false } openvm-ecc-sw-macros = { path = "../../../../extensions/ecc/sw-macros", default-features = false } openvm-algebra-guest = { path = "../../../algebra/guest", default-features = false } openvm-algebra-moduli-macros = { path = "../../../algebra/moduli-macros", default-features = false } -openvm-keccak256-guest = { path = "../../../keccak256/guest", default-features = false } openvm-rv32im-guest = { path = "../../../../extensions/rv32im/guest", default-features = false } +openvm-keccak256 = { path = "../../../../guest-libs/keccak256/" } +openvm-k256 = { path = "../../../../guest-libs/k256", package = "k256", features = [ + "ecdsa", +], optional = true } +openvm-p256 = { path = "../../../../guest-libs/p256", package = "p256", features = [ + "ecdsa", +], optional = true } + serde = { version = "1.0", default-features = false, features = [ "alloc", "derive", ] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } hex-literal = { version = "0.4.1", default-features = false } -k256 = { version = "0.13.3", default-features = false, features = [ - "ecdsa-core", - "ecdsa", -], optional = true } [target.'cfg(not(target_os = "zkvm"))'.dependencies] num-bigint = "0.4.6" @@ -33,17 +36,8 @@ num-bigint = "0.4.6" [features] default = [] std = ["serde/std", "openvm/std"] -k256 = ["openvm-ecc-guest/k256", "dep:k256"] -p256 = ["openvm-ecc-guest/p256"] - -# Features to enable specific tests within decompress_invalid_hint.rs -# Note that these tests are expected to not terminate -test_secp256k1_possible = [] -test_secp256k1_impossible = [] -test_curvepoint5mod8_possible = [] -test_curvepoint5mod8_impossible = [] -test_curvepoint1mod4_possible = [] -test_curvepoint1mod4_impossible = [] +k256 = ["dep:openvm-k256"] +p256 = ["dep:openvm-p256"] [profile.release] panic = "abort" @@ -66,10 +60,6 @@ required-features = ["k256", "p256"] name = "decompress" required-features = ["k256"] -[[example]] -name = "decompress_invalid_hint" -required-features = ["k256"] - [[example]] name = "ecdsa" required-features = ["k256"] diff --git a/extensions/ecc/tests/programs/examples/decompress.rs b/extensions/ecc/tests/programs/examples/decompress.rs index 6f549d311c..d00976c046 100644 --- a/extensions/ecc/tests/programs/examples/decompress.rs +++ b/extensions/ecc/tests/programs/examples/decompress.rs @@ -7,11 +7,11 @@ extern crate alloc; use hex_literal::hex; use openvm::io::read_vec; use openvm_ecc_guest::{ - algebra::{Field, IntMod}, - k256::{Secp256k1Coord, Secp256k1Point}, + algebra::IntMod, weierstrass::{FromCompressed, WeierstrassPoint}, Group, }; +use openvm_k256::{Secp256k1Coord, Secp256k1Point}; openvm::entry!(main); @@ -22,32 +22,8 @@ openvm_algebra_moduli_macros::moduli_declare! { Fp1mod4 { modulus = "0xffffffffffffffffffffffffffffffff000000000000000000000001" }, } -openvm_algebra_moduli_macros::moduli_init! { - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141", - "115792089237316195423570985008687907853269984665640564039457584007913129639501", - "1000000007", - "0xffffffffffffffffffffffffffffffff000000000000000000000001", - "0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", -} - const CURVE_B_5MOD8: Fp5mod8 = Fp5mod8::from_const_u8(3); -impl Field for Fp5mod8 { - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - type SelfRef<'a> = &'a Self; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} - const CURVE_A_1MOD4: Fp1mod4 = Fp1mod4::from_const_bytes(hex!( "FEFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000" )); @@ -55,21 +31,6 @@ const CURVE_B_1MOD4: Fp1mod4 = Fp1mod4::from_const_bytes(hex!( "B4FF552343390B27BAD8BFD7B7B04450563241F5ABB3040C850A05B400000000" )); -impl Field for Fp1mod4 { - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - type SelfRef<'a> = &'a Self; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} - openvm_ecc_sw_macros::sw_declare! { CurvePoint5mod8 { mod_type = Fp5mod8, @@ -82,19 +43,10 @@ openvm_ecc_sw_macros::sw_declare! { }, } -openvm_ecc_sw_macros::sw_init! { - Secp256k1Point, - CurvePoint5mod8, - CurvePoint1mod4, -} +openvm::init!("openvm_init_decompress_k256.rs"); // test decompression under an honest host pub fn main() { - setup_0(); - setup_2(); - setup_4(); - setup_all_curves(); - let bytes = read_vec(); let x = Secp256k1Coord::from_le_bytes(&bytes[..32]); let y = Secp256k1Coord::from_le_bytes(&bytes[32..64]); @@ -102,7 +54,7 @@ pub fn main() { test_possible_decompression::(&x, &y, rec_id); // x = 5 is not on the x-coordinate of any point on the Secp256k1 curve - test_impossible_decompression_secp256k1(&Secp256k1Coord::from_u8(5), rec_id); + test_impossible_decompression::(&Secp256k1Coord::from_u8(5), rec_id); let x = Fp5mod8::from_le_bytes(&bytes[64..96]); let y = Fp5mod8::from_le_bytes(&bytes[96..128]); @@ -110,7 +62,7 @@ pub fn main() { test_possible_decompression::(&x, &y, rec_id); // x = 3 is not on the x-coordinate of any point on the CurvePoint5mod8 curve - test_impossible_decompression_curvepoint5mod8(&Fp5mod8::from_u8(3), rec_id); + test_impossible_decompression::(&Fp5mod8::from_u8(3), rec_id); let x = Fp1mod4::from_le_bytes(&bytes[128..160]); let y = Fp1mod4::from_le_bytes(&bytes[160..192]); @@ -118,7 +70,7 @@ pub fn main() { test_possible_decompression::(&x, &y, rec_id); // x = 1 is not on the x-coordinate of any point on the CurvePoint1mod4 curve - test_impossible_decompression_curvepoint1mod4(&Fp1mod4::from_u8(1), rec_id); + test_impossible_decompression::(&Fp1mod4::from_u8(1), rec_id); } fn test_possible_decompression>( @@ -126,63 +78,15 @@ fn test_possible_decompression::CURVE_A - + &::CURVE_B; - assert_eq!(&hint.sqrt * &hint.sqrt, rhs * CurvePoint5mod8::get_non_qr()); - } - - let p = CurvePoint5mod8::decompress(x.clone(), &rec_id); - assert!(p.is_none()); -} - -fn test_impossible_decompression_secp256k1(x: &Secp256k1Coord, rec_id: u8) { - let hint = Secp256k1Point::hint_decompress(x, &rec_id).expect("hint should be well-formed"); - if hint.possible { - panic!("decompression should be impossible"); - } else { - let rhs = x * x * x - + x * &::CURVE_A - + &::CURVE_B; - assert_eq!(&hint.sqrt * &hint.sqrt, rhs * Secp256k1Point::get_non_qr()); - } - - let p = Secp256k1Point::decompress(x.clone(), &rec_id); - assert!(p.is_none()); -} - -fn test_impossible_decompression_curvepoint1mod4(x: &Fp1mod4, rec_id: u8) { - let hint = CurvePoint1mod4::hint_decompress(x, &rec_id).expect("hint should be well-formed"); - if hint.possible { - panic!("decompression should be impossible"); - } else { - let rhs = x * x * x - + x * &::CURVE_A - + &::CURVE_B; - assert_eq!(&hint.sqrt * &hint.sqrt, rhs * CurvePoint1mod4::get_non_qr()); - } - - let p = CurvePoint1mod4::decompress(x.clone(), &rec_id); +fn test_impossible_decompression>( + x: &P::Coordinate, + rec_id: u8, +) { + let p = P::decompress(x.clone(), &rec_id); assert!(p.is_none()); } diff --git a/extensions/ecc/tests/programs/examples/decompress_invalid_hint.rs b/extensions/ecc/tests/programs/examples/decompress_invalid_hint.rs deleted file mode 100644 index b73e068132..0000000000 --- a/extensions/ecc/tests/programs/examples/decompress_invalid_hint.rs +++ /dev/null @@ -1,254 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_main)] -#![cfg_attr(not(feature = "std"), no_std)] - -use core::ops::{Mul, Neg}; -extern crate alloc; - -use hex_literal::hex; -use openvm::io::read_vec; -use openvm_ecc_guest::{ - algebra::{Field, IntMod}, - k256::{Secp256k1Coord, Secp256k1Point}, - weierstrass::{DecompressionHint, FromCompressed, WeierstrassPoint}, - Group, -}; - -openvm::entry!(main); - -openvm_algebra_moduli_macros::moduli_declare! { - // a prime that is 5 mod 8 - Fp5mod8 { modulus = "115792089237316195423570985008687907853269984665640564039457584007913129639501" }, - // a prime that is 1 mod 4 - Fp1mod4 { modulus = "0xffffffffffffffffffffffffffffffff000000000000000000000001" }, -} - -openvm_algebra_moduli_macros::moduli_init! { - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141", - "115792089237316195423570985008687907853269984665640564039457584007913129639501", - "1000000007", - "0xffffffffffffffffffffffffffffffff000000000000000000000001", - "0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", -} - -const CURVE_B_5MOD8: Fp5mod8 = Fp5mod8::from_const_u8(3); - -impl Field for Fp5mod8 { - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - type SelfRef<'a> = &'a Self; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} - -const CURVE_A_1MOD4: Fp1mod4 = Fp1mod4::from_const_bytes(hex!( - "FEFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000" -)); -const CURVE_B_1MOD4: Fp1mod4 = Fp1mod4::from_const_bytes(hex!( - "B4FF552343390B27BAD8BFD7B7B04450563241F5ABB3040C850A05B400000000" -)); - -impl Field for Fp1mod4 { - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - type SelfRef<'a> = &'a Self; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} - -openvm_ecc_sw_macros::sw_declare! { - CurvePoint5mod8 { - mod_type = Fp5mod8, - b = CURVE_B_5MOD8, - }, - CurvePoint1mod4 { - mod_type = Fp1mod4, - a = CURVE_A_1MOD4, - b = CURVE_B_1MOD4, - }, -} - -openvm_ecc_sw_macros::sw_init! { - Secp256k1Point, - CurvePoint5mod8, - CurvePoint1mod4, -} - -trait NonQr { - fn get_non_qr() -> &'static P::Coordinate; -} - -impl NonQr for Secp256k1Point { - fn get_non_qr() -> &'static ::Coordinate { - Secp256k1Point::get_non_qr() - } -} - -impl NonQr for CurvePoint5mod8 { - fn get_non_qr() -> &'static ::Coordinate { - CurvePoint5mod8::get_non_qr() - } -} - -impl NonQr for CurvePoint1mod4 { - fn get_non_qr() -> &'static ::Coordinate { - CurvePoint1mod4::get_non_qr() - } -} - -// Wrapper to override hint_decompress -struct CurvePointWrapper(P); - -// Implement FromCompressed generically -impl FromCompressed for CurvePointWrapper

-where - P: WeierstrassPoint + NonQr

, - P::Coordinate: IntMod + 'static, - for<'a> &'a P::Coordinate: Mul<&'a P::Coordinate, Output = P::Coordinate>, -{ - fn decompress(x: P::Coordinate, rec_id: &u8) -> Option { - match Self::honest_host_decompress(&x, rec_id) { - // successfully decompressed - Some(Some(ret)) => Some(ret), - // successfully proved that the point cannot be decompressed - Some(None) => None, - None => loop { - openvm::io::println( - "ERROR: Decompression hint is invalid. Entering infinite loop.", - ); - }, - } - } - - #[allow(unused_variables)] - fn hint_decompress( - _x: &P::Coordinate, - rec_id: &u8, - ) -> Option> { - #[cfg(not(target_os = "zkvm"))] - { - unimplemented!() - } - #[cfg(target_os = "zkvm")] - { - // Test both possible and impossible hints - if *rec_id & 1 == 0 { - Some(DecompressionHint { - possible: false, - sqrt: P::Coordinate::from_u32(0), - }) - } else { - Some(DecompressionHint { - possible: true, - sqrt: P::Coordinate::from_u32(0), - }) - } - } - } -} - -impl CurvePointWrapper

-where - P: WeierstrassPoint + NonQr

, - P::Coordinate: IntMod + 'static, - for<'a> &'a P::Coordinate: Mul<&'a P::Coordinate, Output = P::Coordinate>, -{ - // copied from Secp256k1Point::honest_host_decompress implementation in sw-macros - fn honest_host_decompress(x: &P::Coordinate, rec_id: &u8) -> Option> { - let hint = Self::hint_decompress(x, rec_id)?; - - if hint.possible { - // ensure y < modulus - hint.sqrt.assert_reduced(); - - if hint.sqrt.as_le_bytes()[0] & 1 != *rec_id & 1 { - None - } else { - let ret = P::from_xy_nonidentity(x.clone(), hint.sqrt)?; - Some(Some(CurvePointWrapper(ret))) - } - } else { - // ensure sqrt < modulus - hint.sqrt.assert_reduced(); - - let alpha = (x * x * x) + (x * &P::CURVE_A) + &P::CURVE_B; - if &hint.sqrt * &hint.sqrt == alpha * P::get_non_qr() { - Some(None) - } else { - None - } - } - } -} - -// Create type aliases for each specific curve -#[allow(dead_code)] -type Secp256k1PointWrapper = CurvePointWrapper; -#[allow(dead_code)] -type CurvePoint5mod8Wrapper = CurvePointWrapper; -#[allow(dead_code)] -type CurvePoint1mod4Wrapper = CurvePointWrapper; - -// Check that decompress enters an infinite loop when hint_decompress returns an incorrect value. -pub fn main() { - setup_0(); - setup_2(); - setup_4(); - setup_all_curves(); - - let bytes = read_vec(); - - test_p_3_mod_4(&bytes[..32], &bytes[32..64]); - test_p_5_mod_8(&bytes[64..96], &bytes[96..128]); - test_p_1_mod_4(&bytes[128..160], &bytes[160..192]); -} - -// Secp256k1 modulus is 3 mod 4 -#[allow(unused_variables)] -fn test_p_3_mod_4(x: &[u8], y: &[u8]) { - let x = Secp256k1Coord::from_le_bytes(x); - let _ = Secp256k1Coord::from_le_bytes(y); - - #[cfg(feature = "test_secp256k1_possible")] - let p = Secp256k1PointWrapper::decompress(x.clone(), &1); - #[cfg(feature = "test_secp256k1_impossible")] - let p = Secp256k1PointWrapper::decompress(x.clone(), &0); -} - -// CurvePoint5mod8 modulus is 5 mod 8 -#[allow(unused_variables)] -fn test_p_5_mod_8(x: &[u8], y: &[u8]) { - let x = ::Coordinate::from_le_bytes(x); - let _ = ::Coordinate::from_le_bytes(y); - - #[cfg(feature = "test_curvepoint5mod8_possible")] - let p = CurvePoint5mod8Wrapper::decompress(x.clone(), &1); - #[cfg(feature = "test_curvepoint5mod8_impossible")] - let p = CurvePoint5mod8Wrapper::decompress(x.clone(), &0); -} - -// CurvePoint1mod4 modulus is 1 mod 4 -#[allow(unused_variables)] -fn test_p_1_mod_4(x: &[u8], y: &[u8]) { - let x = ::Coordinate::from_le_bytes(x); - let _ = ::Coordinate::from_le_bytes(y); - - #[cfg(feature = "test_curvepoint1mod4_possible")] - let p = CurvePoint1mod4Wrapper::decompress(x.clone(), &1); - #[cfg(feature = "test_curvepoint1mod4_impossible")] - let p = CurvePoint1mod4Wrapper::decompress(x.clone(), &0); -} diff --git a/extensions/ecc/tests/programs/examples/ec.rs b/extensions/ecc/tests/programs/examples/ec.rs index cb4f63e62a..52b3568548 100644 --- a/extensions/ecc/tests/programs/examples/ec.rs +++ b/extensions/ecc/tests/programs/examples/ec.rs @@ -3,28 +3,14 @@ use hex_literal::hex; use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{ - k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}, - msm, - weierstrass::WeierstrassPoint, - Group, -}; +use openvm_ecc_guest::{msm, weierstrass::WeierstrassPoint, Group}; +use openvm_k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}; -openvm_algebra_moduli_macros::moduli_init! { - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" -} - -openvm_ecc_sw_macros::sw_init! { - Secp256k1Point, -} +openvm::init!("openvm_init_ec_k256.rs"); openvm::entry!(main); pub fn main() { - setup_all_moduli(); - setup_all_curves(); - // Sample points got from https://asecuritysite.com/ecc/ecc_points2 and // https://learnmeabitcoin.com/technical/cryptography/elliptic-curve/#add let x1 = Secp256k1Coord::from_u32(1); diff --git a/extensions/ecc/tests/programs/examples/ec_nonzero_a.rs b/extensions/ecc/tests/programs/examples/ec_nonzero_a.rs index 18720e1ae1..ea0a0387cd 100644 --- a/extensions/ecc/tests/programs/examples/ec_nonzero_a.rs +++ b/extensions/ecc/tests/programs/examples/ec_nonzero_a.rs @@ -3,27 +3,14 @@ use hex_literal::hex; use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{ - p256::{P256Coord, P256Point}, - weierstrass::WeierstrassPoint, - CyclicGroup, Group, -}; +use openvm_ecc_guest::{weierstrass::WeierstrassPoint, CyclicGroup, Group}; +use openvm_p256::{P256Coord, P256Point}; openvm::entry!(main); -openvm_algebra_moduli_macros::moduli_init! { - "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff", - "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551" -} - -openvm_ecc_sw_macros::sw_init! { - P256Point, -} +openvm::init!("openvm_init_ec_nonzero_a_p256.rs"); pub fn main() { - setup_all_moduli(); - setup_all_curves(); - // Sample points got from https://asecuritysite.com/ecc/p256p let x1 = P256Coord::from_u32(5); let y1 = P256Coord::from_le_bytes(&hex!( diff --git a/extensions/ecc/tests/programs/examples/ec_two_curves.rs b/extensions/ecc/tests/programs/examples/ec_two_curves.rs index ab96e9d240..3daa6a9c30 100644 --- a/extensions/ecc/tests/programs/examples/ec_two_curves.rs +++ b/extensions/ecc/tests/programs/examples/ec_two_curves.rs @@ -3,32 +3,15 @@ use hex_literal::hex; use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{ - k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}, - msm, - p256::{P256Coord, P256Point}, - weierstrass::WeierstrassPoint, - Group, -}; +use openvm_ecc_guest::{msm, weierstrass::WeierstrassPoint, Group}; +use openvm_k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}; +use openvm_p256::{P256Coord, P256Point}; -openvm_algebra_moduli_macros::moduli_init! { - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141", - "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff", - "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551" -} - -openvm_ecc_sw_macros::sw_init! { - Secp256k1Point, - P256Point, -} +openvm::init!("openvm_init_ec_two_curves_k256_p256.rs"); openvm::entry!(main); pub fn main() { - setup_all_moduli(); - setup_all_curves(); - // Sample points got from https://asecuritysite.com/ecc/ecc_points2 and // https://learnmeabitcoin.com/technical/cryptography/elliptic-curve/#add let x1 = Secp256k1Coord::from_u32(1); diff --git a/extensions/ecc/tests/programs/examples/ecdsa.rs b/extensions/ecc/tests/programs/examples/ecdsa.rs index 3fc3c12f4e..3b6c2b3c13 100644 --- a/extensions/ecc/tests/programs/examples/ecdsa.rs +++ b/extensions/ecc/tests/programs/examples/ecdsa.rs @@ -5,29 +5,23 @@ use core::hint::black_box; use hex_literal::hex; -use k256::{ - ecdsa::{self, RecoveryId}, - Secp256k1, -}; use openvm_ecc_guest::{ - algebra::IntMod, ecdsa::VerifyingKey, k256::Secp256k1Point, weierstrass::WeierstrassPoint, + algebra::IntMod, + ecdsa::{verify_prehashed, VerifyingKey}, + weierstrass::WeierstrassPoint, +}; +use openvm_k256::{ + ecdsa::{self, signature::hazmat::PrehashVerifier, RecoveryId, Signature}, + Secp256k1, Secp256k1Point, }; -use openvm_keccak256_guest::keccak256; +use openvm_keccak256::keccak256; + openvm::entry!(main); -openvm_algebra_moduli_macros::moduli_init! { - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" -} -openvm_ecc_sw_macros::sw_init! { - Secp256k1Point, -} +openvm::init!("openvm_init_ecdsa_k256.rs"); // Ref: https://docs.rs/k256/latest/k256/ecdsa/index.html pub fn main() { - setup_all_moduli(); - setup_all_curves(); - let msg = b"example message"; let signature = hex!( @@ -75,8 +69,7 @@ pub fn main() { // Test verification recovered_key - .clone() - .verify_prehashed(&prehash, &signature) + .verify_prehash(&prehash, &Signature::from_slice(&signature).unwrap()) .unwrap(); // Test bad signature @@ -93,9 +86,11 @@ pub fn main() { &prehash, &bad_sig, recid ) .is_err()); - assert!(recovered_key - .clone() - .verify_prehashed(&prehash, &bad_sig) - .is_err()); + assert!(verify_prehashed::( + recovered_key.as_affine().clone(), + &prehash, + &bad_sig + ) + .is_err()); } } diff --git a/extensions/ecc/tests/programs/examples/invalid_setup.rs b/extensions/ecc/tests/programs/examples/invalid_setup.rs index 9c6e8a3a51..6ee2ba4f58 100644 --- a/extensions/ecc/tests/programs/examples/invalid_setup.rs +++ b/extensions/ecc/tests/programs/examples/invalid_setup.rs @@ -1,8 +1,9 @@ #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] +use openvm_ecc_guest::CyclicGroup; #[allow(unused_imports)] -use openvm_ecc_guest::{k256::Secp256k1Point, p256::P256Point}; +use {openvm_k256::Secp256k1Point, openvm_p256::P256Point}; openvm_algebra_moduli_macros::moduli_init! { "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", @@ -20,7 +21,7 @@ openvm_ecc_sw_macros::sw_init! { openvm::entry!(main); pub fn main() { - setup_all_moduli(); // this should cause a debug assertion to fail - setup_all_curves(); + let p1 = Secp256k1Point::GENERATOR; + let _p2 = &p1 + &p1; } diff --git a/extensions/ecc/tests/programs/openvm_init_decompress_k256.rs b/extensions/ecc/tests/programs/openvm_init_decompress_k256.rs new file mode 100644 index 0000000000..b6137ae9ee --- /dev/null +++ b/extensions/ecc/tests/programs/openvm_init_decompress_k256.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337", "115792089237316195423570985008687907853269984665640564039457584007913129639501", "1000000007", "26959946667150639794667015087019630673557916260026308143510066298881", "26959946667150639794667015087019625940457807714424391721682722368061" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point, CurvePoint5mod8, CurvePoint1mod4 } diff --git a/extensions/ecc/tests/programs/openvm_init_ec_k256.rs b/extensions/ecc/tests/programs/openvm_init_ec_k256.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/extensions/ecc/tests/programs/openvm_init_ec_k256.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/extensions/ecc/tests/programs/openvm_init_ec_nonzero_a_p256.rs b/extensions/ecc/tests/programs/openvm_init_ec_nonzero_a_p256.rs new file mode 100644 index 0000000000..02f8b5c05d --- /dev/null +++ b/extensions/ecc/tests/programs/openvm_init_ec_nonzero_a_p256.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } +openvm_ecc_guest::sw_macros::sw_init! { P256Point } diff --git a/extensions/ecc/tests/programs/openvm_init_ec_two_curves_k256_p256.rs b/extensions/ecc/tests/programs/openvm_init_ec_two_curves_k256_p256.rs new file mode 100644 index 0000000000..8689190544 --- /dev/null +++ b/extensions/ecc/tests/programs/openvm_init_ec_two_curves_k256_p256.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337", "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point, P256Point } diff --git a/extensions/ecc/tests/programs/openvm_init_ecdsa_k256.rs b/extensions/ecc/tests/programs/openvm_init_ecdsa_k256.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/extensions/ecc/tests/programs/openvm_init_ecdsa_k256.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/extensions/ecc/tests/src/lib.rs b/extensions/ecc/tests/src/lib.rs index 6d90241156..523973f098 100644 --- a/extensions/ecc/tests/src/lib.rs +++ b/extensions/ecc/tests/src/lib.rs @@ -22,13 +22,21 @@ mod tests { use openvm_sdk::config::SdkVmConfig; use openvm_stark_backend::p3_field::FieldAlgebra; use openvm_stark_sdk::{openvm_stark_backend, p3_baby_bear::BabyBear}; - use openvm_toolchain_tests::{build_example_program_at_path_with_features, get_programs_dir}; + use openvm_toolchain_tests::{ + build_example_program_at_path_with_features, get_programs_dir, NoInitFile, + }; use openvm_transpiler::{transpiler::Transpiler, FromElf}; type F = BabyBear; #[test] fn test_ec() -> Result<()> { - let elf = build_example_program_at_path_with_features(get_programs_dir!(), "ec", ["k256"])?; + let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "ec", + ["k256"], + &config, + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -38,17 +46,18 @@ mod tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); air_test(config, openvm_exe); Ok(()) } #[test] fn test_ec_nonzero_a() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![P256_CONFIG.clone()]); let elf = build_example_program_at_path_with_features( get_programs_dir!(), "ec_nonzero_a", ["p256"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -59,17 +68,19 @@ mod tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - let config = Rv32WeierstrassConfig::new(vec![P256_CONFIG.clone()]); air_test(config, openvm_exe); Ok(()) } #[test] fn test_ec_two_curves() -> Result<()> { + let config = + Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone(), P256_CONFIG.clone()]); let elf = build_example_program_at_path_with_features( get_programs_dir!(), "ec_two_curves", ["k256", "p256"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -80,33 +91,18 @@ mod tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - let config = - Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone(), P256_CONFIG.clone()]); air_test(config, openvm_exe); Ok(()) } #[test] fn test_decompress() -> Result<()> { - use openvm_ecc_guest::halo2curves::{group::Curve, secp256k1::Secp256k1Affine}; + use halo2curves_axiom::{group::Curve, secp256k1::Secp256k1Affine}; - let elf = build_example_program_at_path_with_features( - get_programs_dir!(), - "decompress", - ["k256"], - )?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(EccTranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone(), CurveConfig { + struct_name: "CurvePoint5mod8".to_string(), modulus: BigUint::from_str("115792089237316195423570985008687907853269984665640564039457584007913129639501") .unwrap(), // unused, set to 10e9 + 7 @@ -116,6 +112,7 @@ mod tests { b: BigUint::from_str("3").unwrap(), }, CurveConfig { + struct_name: "CurvePoint1mod4".to_string(), modulus: BigUint::from_radix_be(&hex!("ffffffffffffffffffffffffffffffff000000000000000000000001"), 256) .unwrap(), scalar: BigUint::from_radix_be(&hex!("ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d"), 256) @@ -127,34 +124,11 @@ mod tests { }, ]); - let p = Secp256k1Affine::generator(); - let p = (p + p + p).to_affine(); - println!("decompressed: {:?}", p); - let q_x: [u8; 32] = - hex!("0100000000000000000000000000000000000000000000000000000000000000"); - let q_y: [u8; 32] = - hex!("0200000000000000000000000000000000000000000000000000000000000000"); - let r_x: [u8; 32] = - hex!("211D5C11D68032342211C256D3C1034AB99013327FBFB46BBD0C0EB700000000"); - let r_y: [u8; 32] = - hex!("347E00859981D5446447075AA07543CDE6DF224CFB23F7B5886337BD00000000"); - - let coords = [p.x.to_bytes(), p.y.to_bytes(), q_x, q_y, r_x, r_y] - .concat() - .into_iter() - .map(FieldAlgebra::from_canonical_u8) - .collect(); - air_test_with_min_segments(config, openvm_exe, vec![coords], 1); - Ok(()) - } - - fn test_decompress_invalid_specific_test(test_type: &str) -> Result<()> { - use openvm_ecc_guest::halo2curves::{group::Curve, secp256k1::Secp256k1Affine}; - let elf = build_example_program_at_path_with_features( get_programs_dir!(), - "decompress_invalid_hint", - ["k256", test_type], + "decompress", + ["k256"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -165,28 +139,6 @@ mod tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - let config = - Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone(), - CurveConfig { - modulus: BigUint::from_str("115792089237316195423570985008687907853269984665640564039457584007913129639501") - .unwrap(), - // unused, set to 10e9 + 7 - scalar: BigUint::from_str("1000000007") - .unwrap(), - a: BigUint::ZERO, - b: BigUint::from_str("3").unwrap(), - }, - CurveConfig { - modulus: BigUint::from_radix_be(&hex!("ffffffffffffffffffffffffffffffff000000000000000000000001"), 256) - .unwrap(), - scalar: BigUint::from_radix_be(&hex!("ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d"), 256) - .unwrap(), - a: BigUint::from_radix_be(&hex!("fffffffffffffffffffffffffffffffefffffffffffffffffffffffe"), 256) - .unwrap(), - b: BigUint::from_radix_be(&hex!("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4"), 256) - .unwrap(), - }, - ]); let p = Secp256k1Affine::generator(); let p = (p + p + p).to_affine(); @@ -209,46 +161,8 @@ mod tests { Ok(()) } - #[ignore = "expected to infinite loop"] - #[test] - fn test_decompress_invalid_hint_secp256k1_possible() -> Result<()> { - test_decompress_invalid_specific_test("test_secp256k1_possible") - } - - #[ignore = "expected to infinite loop"] - #[test] - fn test_decompress_invalid_hint_secp256k1_impossible() -> Result<()> { - test_decompress_invalid_specific_test("test_secp256k1_impossible") - } - - #[ignore = "expected to infinite loop"] - #[test] - fn test_decompress_invalid_hint_curvepoint5mod8_possible() -> Result<()> { - test_decompress_invalid_specific_test("test_curvepoint5mod8_possible") - } - - #[ignore = "expected to infinite loop"] - #[test] - fn test_decompress_invalid_hint_curvepoint5mod8_impossible() -> Result<()> { - test_decompress_invalid_specific_test("test_curvepoint5mod8_impossible") - } - - #[ignore = "expected to infinite loop"] - #[test] - fn test_decompress_invalid_hint_curvepoint1mod4_possible() -> Result<()> { - test_decompress_invalid_specific_test("test_curvepoint1mod4_possible") - } - - #[ignore = "expected to infinite loop"] - #[test] - fn test_decompress_invalid_hint_curvepoint1mod4_impossible() -> Result<()> { - test_decompress_invalid_specific_test("test_curvepoint1mod4_impossible") - } - #[test] fn test_ecdsa() -> Result<()> { - let elf = - build_example_program_at_path_with_features(get_programs_dir!(), "ecdsa", ["k256"])?; let config = SdkVmConfig::builder() .system(SystemConfig::default().with_continuations().into()) .rv32i(Default::default()) @@ -261,6 +175,13 @@ mod tests { .keccak(Default::default()) .ecc(WeierstrassExtension::new(vec![SECP256K1_CONFIG.clone()])) .build(); + + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "ecdsa", + ["k256"], + &config, + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -282,6 +203,7 @@ mod tests { get_programs_dir!(), "invalid_setup", ["k256", "p256"], + &NoInitFile, // don't use build script since we are testing invalid setup ) .unwrap(); let openvm_exe = VmExe::from_elf( diff --git a/extensions/keccak256/circuit/src/extension.rs b/extensions/keccak256/circuit/src/extension.rs index d24681fb55..5993f69eda 100644 --- a/extensions/keccak256/circuit/src/extension.rs +++ b/extensions/keccak256/circuit/src/extension.rs @@ -1,7 +1,8 @@ use derive_more::derive::From; use openvm_circuit::{ arch::{ - SystemConfig, SystemPort, VmExtension, VmInventory, VmInventoryBuilder, VmInventoryError, + InitFileGenerator, SystemConfig, SystemPort, VmExtension, VmInventory, VmInventoryBuilder, + VmInventoryError, }, system::phantom::PhantomChip, }; @@ -45,6 +46,9 @@ impl Default for Keccak256Rv32Config { } } +// Default implementation uses no init file +impl InitFileGenerator for Keccak256Rv32Config {} + #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] pub struct Keccak256; diff --git a/extensions/keccak256/guest/Cargo.toml b/extensions/keccak256/guest/Cargo.toml index 593412bdc9..f2e86a53fe 100644 --- a/extensions/keccak256/guest/Cargo.toml +++ b/extensions/keccak256/guest/Cargo.toml @@ -10,8 +10,5 @@ repository.workspace = true [dependencies] openvm-platform = { workspace = true } -[target.'cfg(not(target_os = "zkvm"))'.dependencies] -tiny-keccak.workspace = true - [features] default = [] diff --git a/extensions/keccak256/guest/src/lib.rs b/extensions/keccak256/guest/src/lib.rs index 459c4c910d..7e2bb3da54 100644 --- a/extensions/keccak256/guest/src/lib.rs +++ b/extensions/keccak256/guest/src/lib.rs @@ -1,30 +1,10 @@ #![no_std] -#[cfg(target_os = "zkvm")] -use core::mem::MaybeUninit; - /// This is custom-0 defined in RISC-V spec document pub const OPCODE: u8 = 0x0b; pub const KECCAK256_FUNCT3: u8 = 0b100; pub const KECCAK256_FUNCT7: u8 = 0; -/// The keccak256 cryptographic hash function. -#[inline(always)] -pub fn keccak256(input: &[u8]) -> [u8; 32] { - #[cfg(not(target_os = "zkvm"))] - { - let mut output = [0u8; 32]; - set_keccak256(input, &mut output); - output - } - #[cfg(target_os = "zkvm")] - { - let mut output = MaybeUninit::<[u8; 32]>::uninit(); - native_keccak256(input.as_ptr(), input.len(), output.as_mut_ptr() as *mut u8); - unsafe { output.assume_init() } - } -} - /// Native hook for keccak256 for use with `alloy-primitives` "native-keccak" feature. /// /// # Safety @@ -40,7 +20,7 @@ pub fn keccak256(input: &[u8]) -> [u8; 32] { #[cfg(target_os = "zkvm")] #[inline(always)] #[no_mangle] -extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8) { +pub extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8) { openvm_platform::custom_insn_r!( opcode = OPCODE, funct3 = KECCAK256_FUNCT3, @@ -50,16 +30,3 @@ extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8) { rs2 = In len ); } - -/// Sets `output` to the keccak256 hash of `input`. -pub fn set_keccak256(input: &[u8], output: &mut [u8; 32]) { - #[cfg(not(target_os = "zkvm"))] - { - use tiny_keccak::Hasher; - let mut hasher = tiny_keccak::Keccak::v256(); - hasher.update(input); - hasher.finalize(output); - } - #[cfg(target_os = "zkvm")] - native_keccak256(input.as_ptr(), input.len(), output.as_mut_ptr() as *mut u8); -} diff --git a/extensions/keccak256/tests/Cargo.toml b/extensions/keccak256/tests/Cargo.toml deleted file mode 100644 index d79e18dd5f..0000000000 --- a/extensions/keccak256/tests/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "openvm-keccak256-integration-tests" -description = "Integration tests for the OpenVM keccak256 extension" -version.workspace = true -authors.workspace = true -edition.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -openvm-instructions = { workspace = true } -openvm-stark-sdk.workspace = true -openvm-circuit = { workspace = true, features = ["test-utils"] } -openvm-transpiler.workspace = true -openvm-keccak256-transpiler.workspace = true -openvm-keccak256-circuit.workspace = true -openvm-rv32im-transpiler.workspace = true -openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } -eyre.workspace = true - -[features] -default = ["parallel"] -parallel = ["openvm-circuit/parallel"] diff --git a/extensions/native/circuit/src/extension.rs b/extensions/native/circuit/src/extension.rs index 8f73423e40..385c9392ac 100644 --- a/extensions/native/circuit/src/extension.rs +++ b/extensions/native/circuit/src/extension.rs @@ -6,8 +6,8 @@ use loadstore_native_adapter::NativeLoadStoreAdapterChip; use native_vectorized_adapter::NativeVectorizedAdapterChip; use openvm_circuit::{ arch::{ - ExecutionBridge, MemoryConfig, SystemConfig, SystemPort, VmExtension, VmInventory, - VmInventoryBuilder, VmInventoryError, + ExecutionBridge, InitFileGenerator, MemoryConfig, SystemConfig, SystemPort, VmExtension, + VmInventory, VmInventoryBuilder, VmInventoryError, }, system::phantom::PhantomChip, }; @@ -60,6 +60,9 @@ impl NativeConfig { } } +// Default implementation uses no init file +impl InitFileGenerator for NativeConfig {} + #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] pub struct Native; @@ -433,3 +436,6 @@ impl Default for Rv32WithKernelsConfig { } } } + +// Default implementation uses no init file +impl InitFileGenerator for Rv32WithKernelsConfig {} diff --git a/extensions/native/circuit/src/loadstore/core.rs b/extensions/native/circuit/src/loadstore/core.rs index 84b38b9841..f737c339b2 100644 --- a/extensions/native/circuit/src/loadstore/core.rs +++ b/extensions/native/circuit/src/loadstore/core.rs @@ -120,7 +120,6 @@ where } } -#[derive(Debug)] pub struct NativeLoadStoreCoreChip { pub air: NativeLoadStoreCoreAir, pub streams: OnceLock>>>, @@ -134,7 +133,10 @@ impl NativeLoadStoreCoreChip { } } pub fn set_streams(&mut self, streams: Arc>>) { - self.streams.set(streams).unwrap(); + self.streams + .set(streams) + .map_err(|_| "streams have already been set.") + .unwrap(); } } diff --git a/extensions/native/compiler/src/asm/compiler.rs b/extensions/native/compiler/src/asm/compiler.rs index 7d32358a94..a09c8a217e 100644 --- a/extensions/native/compiler/src/asm/compiler.rs +++ b/extensions/native/compiler/src/asm/compiler.rs @@ -15,7 +15,7 @@ pub const MEMORY_BITS: usize = 29; pub const MEMORY_TOP: u32 = (1 << MEMORY_BITS) - 4; // The memory location for the start of the heap. -pub(crate) const HEAP_START_ADDRESS: i32 = 1 << 24; +pub const HEAP_START_ADDRESS: i32 = 1 << 24; /// The heap pointer address. pub(crate) const HEAP_PTR: i32 = HEAP_START_ADDRESS - 4; diff --git a/extensions/native/compiler/src/conversion/mod.rs b/extensions/native/compiler/src/conversion/mod.rs index edf10467cf..9c3fc8d752 100644 --- a/extensions/native/compiler/src/conversion/mod.rs +++ b/extensions/native/compiler/src/conversion/mod.rs @@ -1,7 +1,7 @@ use openvm_circuit::arch::instructions::program::Program; use openvm_instructions::{ instruction::{DebugInfo, Instruction}, - program::{DEFAULT_MAX_NUM_PUBLIC_VALUES, DEFAULT_PC_STEP}, + program::DEFAULT_PC_STEP, LocalOpcode, PhantomDiscriminant, PublishOpcode, SysPhantom, SystemOpcode, VmOpcode, }; use openvm_rv32im_transpiler::BranchEqualOpcode; @@ -565,7 +565,7 @@ pub fn convert_program>( } } - let mut result = Program::new_empty(DEFAULT_PC_STEP, 0, DEFAULT_MAX_NUM_PUBLIC_VALUES); + let mut result = Program::new_empty(DEFAULT_PC_STEP, 0); result.push_instruction_and_debug_info(init_register_0, init_debug_info); for block in program.blocks.iter() { for (instruction, debug_info) in block.0.iter().zip(block.1.iter()) { diff --git a/extensions/pairing/circuit/Cargo.toml b/extensions/pairing/circuit/Cargo.toml index 6ec8891541..af16f7eeab 100644 --- a/extensions/pairing/circuit/Cargo.toml +++ b/extensions/pairing/circuit/Cargo.toml @@ -36,6 +36,7 @@ rand = { workspace = true } itertools = { workspace = true } eyre = { workspace = true } serde = { workspace = true, features = ["derive", "std"] } +halo2curves-axiom = { workspace = true } [target.'cfg(not(target_os = "zkvm"))'.dependencies] openvm-pairing-guest = { workspace = true } diff --git a/extensions/pairing/circuit/src/config.rs b/extensions/pairing/circuit/src/config.rs index 4914fa433c..d63bac664e 100644 --- a/extensions/pairing/circuit/src/config.rs +++ b/extensions/pairing/circuit/src/config.rs @@ -1,5 +1,5 @@ use openvm_algebra_circuit::*; -use openvm_circuit::arch::SystemConfig; +use openvm_circuit::arch::{InitFileGenerator, SystemConfig}; use openvm_circuit_derive::VmConfig; use openvm_ecc_circuit::*; use openvm_rv32im_circuit::*; @@ -29,19 +29,25 @@ pub struct Rv32PairingConfig { } impl Rv32PairingConfig { - pub fn new(curves: Vec) -> Self { - let mut primes: Vec<_> = curves + pub fn new(curves: Vec, complex_struct_names: Vec) -> Self { + let modulus_primes: Vec<_> = curves .iter() .map(|c| c.curve_config().modulus.clone()) .collect(); - primes.extend(curves.iter().map(|c| c.curve_config().scalar.clone())); + let mut modulus_and_scalar_primes = modulus_primes.clone(); + modulus_and_scalar_primes.extend(curves.iter().map(|c| c.curve_config().scalar.clone())); Self { system: SystemConfig::default().with_continuations(), base: Default::default(), mul: Default::default(), io: Default::default(), - modular: ModularExtension::new(primes.to_vec()), - fp2: Fp2Extension::new(primes.to_vec()), + modular: ModularExtension::new(modulus_and_scalar_primes), + fp2: Fp2Extension::new( + complex_struct_names + .into_iter() + .zip(modulus_primes) + .collect(), + ), weierstrass: WeierstrassExtension::new( curves.iter().map(|c| c.curve_config()).collect(), ), @@ -49,3 +55,14 @@ impl Rv32PairingConfig { } } } + +impl InitFileGenerator for Rv32PairingConfig { + fn generate_init_file_contents(&self) -> Option { + Some(format!( + "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n{}\n", + self.modular.generate_moduli_init(), + self.fp2.generate_complex_init(&self.modular), + self.weierstrass.generate_sw_init() + )) + } +} diff --git a/extensions/pairing/circuit/src/pairing_extension.rs b/extensions/pairing/circuit/src/pairing_extension.rs index eca4cea8dd..c75687f404 100644 --- a/extensions/pairing/circuit/src/pairing_extension.rs +++ b/extensions/pairing/circuit/src/pairing_extension.rs @@ -11,8 +11,10 @@ use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter}; use openvm_ecc_circuit::CurveConfig; use openvm_instructions::PhantomDiscriminant; use openvm_pairing_guest::{ - bls12_381::{BLS12_381_MODULUS, BLS12_381_ORDER, BLS12_381_XI_ISIZE}, - bn254::{BN254_MODULUS, BN254_ORDER, BN254_XI_ISIZE}, + bls12_381::{ + BLS12_381_ECC_STRUCT_NAME, BLS12_381_MODULUS, BLS12_381_ORDER, BLS12_381_XI_ISIZE, + }, + bn254::{BN254_ECC_STRUCT_NAME, BN254_MODULUS, BN254_ORDER, BN254_XI_ISIZE}, }; use openvm_pairing_transpiler::PairingPhantom; use openvm_stark_backend::p3_field::PrimeField32; @@ -33,12 +35,14 @@ impl PairingCurve { pub fn curve_config(&self) -> CurveConfig { match self { PairingCurve::Bn254 => CurveConfig::new( + BN254_ECC_STRUCT_NAME.to_string(), BN254_MODULUS.clone(), BN254_ORDER.clone(), BigUint::zero(), BigUint::from_u8(3).unwrap(), ), PairingCurve::Bls12_381 => CurveConfig::new( + BLS12_381_ECC_STRUCT_NAME.to_string(), BLS12_381_MODULUS.clone(), BLS12_381_ORDER.clone(), BigUint::zero(), @@ -99,11 +103,12 @@ pub(crate) mod phantom { use std::collections::VecDeque; use eyre::bail; + use halo2curves_axiom::ff; use openvm_circuit::{ arch::{PhantomSubExecutor, Streams}, system::memory::MemoryController, }; - use openvm_ecc_guest::{algebra::field::FieldExtension, halo2curves::ff, AffinePoint}; + use openvm_ecc_guest::{algebra::field::FieldExtension, AffinePoint}; use openvm_instructions::{ riscv::{RV32_MEMORY_AS, RV32_REGISTER_NUM_LIMBS}, PhantomDiscriminant, @@ -164,7 +169,7 @@ pub(crate) mod phantom { match PairingCurve::from_repr(c_upper as usize) { Some(PairingCurve::Bn254) => { - use openvm_ecc_guest::halo2curves::bn256::{Fq, Fq12, Fq2}; + use halo2curves_axiom::bn256::{Fq, Fq12, Fq2}; use openvm_pairing_guest::halo2curves_shims::bn254::Bn254; const N: usize = BN254_NUM_LIMBS; if p_len != q_len { @@ -206,7 +211,7 @@ pub(crate) mod phantom { ); } Some(PairingCurve::Bls12_381) => { - use openvm_ecc_guest::halo2curves::bls12_381::{Fq, Fq12, Fq2}; + use halo2curves_axiom::bls12_381::{Fq, Fq12, Fq2}; use openvm_pairing_guest::halo2curves_shims::bls12_381::Bls12_381; const N: usize = BLS12_381_NUM_LIMBS; if p_len != q_len { diff --git a/extensions/pairing/guest/Cargo.toml b/extensions/pairing/guest/Cargo.toml index b67f68cec3..299e865f39 100644 --- a/extensions/pairing/guest/Cargo.toml +++ b/extensions/pairing/guest/Cargo.toml @@ -9,7 +9,6 @@ repository.workspace = true [dependencies] openvm = { workspace = true } -openvm-platform = { workspace = true } serde = { workspace = true } itertools = { workspace = true, features = ["use_alloc"] } rand.workspace = true @@ -18,14 +17,10 @@ hex-literal = { workspace = true } openvm-algebra-guest = { workspace = true } openvm-algebra-moduli-macros = { workspace = true } openvm-ecc-guest = { workspace = true } -openvm-ecc-sw-macros = { workspace = true } -openvm-algebra-complex-macros = { workspace = true } openvm-custom-insn = { workspace = true } -openvm-rv32im-guest = { workspace = true } # Used for `halo2curves` feature halo2curves-axiom = { workspace = true, optional = true } -group = "0.13.0" [target.'cfg(not(target_os = "zkvm"))'.dependencies] num-bigint.workspace = true @@ -45,4 +40,4 @@ bn254 = [] bls12_381 = [] [package.metadata.cargo-shear] -ignored = ["openvm", "openvm-custom-insn"] +ignored = ["openvm", "openvm-custom-insn", "serde" ] diff --git a/extensions/pairing/guest/src/bls12_381/mod.rs b/extensions/pairing/guest/src/bls12_381/mod.rs index a23d35d507..08808e10da 100644 --- a/extensions/pairing/guest/src/bls12_381/mod.rs +++ b/extensions/pairing/guest/src/bls12_381/mod.rs @@ -1,25 +1,8 @@ -use core::ops::Neg; - -use openvm_algebra_guest::{Field, IntMod}; -use openvm_algebra_moduli_macros::moduli_declare; -use openvm_ecc_guest::{weierstrass::IntrinsicCurve, CyclicGroup, Group}; - -mod fp12; -mod fp2; -mod pairing; -#[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] -pub(crate) mod utils; - -pub use fp12::*; -pub use fp2::*; use hex_literal::hex; #[cfg(not(target_os = "zkvm"))] use lazy_static::lazy_static; #[cfg(not(target_os = "zkvm"))] use num_bigint::BigUint; -use openvm_ecc_sw_macros::sw_declare; - -use crate::pairing::PairingIntrinsics; #[cfg(all(test, feature = "halo2curves", not(target_os = "zkvm")))] mod tests; @@ -49,630 +32,10 @@ pub const BLS12_381_PSEUDO_BINARY_ENCODING: [i8; 64] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, ]; -moduli_declare! { - Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" }, - Bls12_381Scalar { modulus = "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" }, -} - -const CURVE_B: Bls12_381Fp = Bls12_381Fp::from_const_u8(4); - -sw_declare! { - Bls12_381G1Affine { mod_type = Bls12_381Fp, b = CURVE_B }, -} - -pub type Fp = Bls12_381Fp; -pub type Scalar = Bls12_381Scalar; -/// Affine point representation of `Fp` points of BLS12-381. -/// **Note**: an instance of this type may be constructed that lies -/// on the curve but not necessarily in the prime order subgroup -/// because the group has cofactors. -pub type G1Affine = Bls12_381G1Affine; -pub use g2::G2Affine; - -impl Field for Fp { - type SelfRef<'a> = &'a Self; - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} - -impl Field for Scalar { - type SelfRef<'a> = &'a Self; - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} - -// https://hackmd.io/@benjaminion/bls12-381#Cofactor -// BLS12-381: The from_xy function will allow constructing elements that lie on the curve -// but aren't actually in the cyclic subgroup of prime order that is usually called G1. -impl CyclicGroup for G1Affine { - // https://github.com/zcash/librustzcash/blob/6e0364cd42a2b3d2b958a54771ef51a8db79dd29/pairing/src/bls12_381/README.md#generators - const GENERATOR: Self = G1Affine { - x: Bls12_381Fp::from_const_bytes(hex!( - "BBC622DB0AF03AFBEF1A7AF93FE8556C58AC1B173F3A4EA105B974974F8C68C30FACA94F8C63952694D79731A7D3F117" - )), - y: Bls12_381Fp::from_const_bytes(hex!( - "E1E7C5462923AA0CE48A88A244C73CD0EDB3042CCB18DB00F60AD0D595E0F5FCE48A1D74ED309EA0F1A0AAE381F4B308" - )), - }; - const NEG_GENERATOR: Self = G1Affine { - x: Bls12_381Fp::from_const_bytes(hex!( - "BBC622DB0AF03AFBEF1A7AF93FE8556C58AC1B173F3A4EA105B974974F8C68C30FACA94F8C63952694D79731A7D3F117" - )), - y: Bls12_381Fp::from_const_bytes(hex!( - "CAC239B9D6DC54AD1B75CB0EBA386F4E3642ACCAD5B95566C907B51DEF6A8167F2212ECFC8767DAAA845D555681D4D11" - )), - }; -} - -pub struct Bls12_381; - -impl IntrinsicCurve for Bls12_381 { - type Scalar = Scalar; - type Point = G1Affine; - - fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point { - openvm_ecc_guest::msm(coeffs, bases) - } -} - -// Define a G2Affine struct that implements curve operations using `Fp2` intrinsics -// but not special E(Fp2) intrinsics. -mod g2 { - use openvm_algebra_guest::Field; - use openvm_ecc_guest::{ - impl_sw_affine, impl_sw_group_ops, weierstrass::WeierstrassPoint, AffinePoint, Group, - }; - - use super::{Fp, Fp2}; - - const THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::ZERO); - const B: Fp2 = Fp2::new(Fp::from_const_u8(4), Fp::from_const_u8(4)); - impl_sw_affine!(G2Affine, Fp2, THREE, B); - impl_sw_group_ops!(G2Affine, Fp2); -} - -impl PairingIntrinsics for Bls12_381 { - type Fp = Fp; - type Fp2 = Fp2; - type Fp12 = Fp12; - - const PAIRING_IDX: usize = 1; - // The sextic extension `Fp12` is `Fp2[X] / (X^6 - \xi)`, where `\xi` is a non-residue. - const XI: Fp2 = Fp2::new(Fp::from_const_u8(1), Fp::from_const_u8(1)); - const FP2_TWO: Fp2 = Fp2::new(Fp::from_const_u8(2), Fp::from_const_u8(0)); - const FP2_THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::from_const_u8(0)); - - // Multiplication constants for the Frobenius map for coefficients in Fp2 c1..=c5 for powers - // 0..12 FROBENIUS_COEFFS\[i\]\[j\] = \xi^{(j + 1) * (p^i - 1)/6} when p = 1 (mod 6) - // These are validated against `halo2curves::bls12_381::FROBENIUS_COEFF_FQ12_C1` in tests.rs - const FROBENIUS_COEFFS: [[Self::Fp2; 5]; 12] = [ - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ")), - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ")), - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ")), - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ")), - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ")), - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "b85f2392ed75078d3d81e7633da57ef6c4b9ba84d743247b4f5fbd3cfd03d60f1f0d2c20b4be31c26706bb02bfd30419" - )), - c1: Bls12_381Fp(hex!( - "f34adc6d128af72cc27e6c4dc15a2d285f3cf671c98e0cec6fb3c7b68747a154b89f1f2302e9e98832e0c4362b3efc00" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )), - c1: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "adaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "16810780e9fa189b32877f256e3e3ac666059c8e4ddfea8bee8f0b0c241698f345e0b1486bfa47dfd85f3a01d9cfb205" - )), - c1: Bls12_381Fp(hex!( - "9529f87f1605e61ecd78d48b90c17158bdf0146853f345dbd08279e76035df7091cc99fa4aadd36bc186453811424e14" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "fffffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "adaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )), - c1: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )), - c1: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )), - c1: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "9529f87f1605e61ecd78d48b90c17158bdf0146853f345dbd08279e76035df7091cc99fa4aadd36bc186453811424e14" - )), - c1: Bls12_381Fp(hex!( - "16810780e9fa189b32877f256e3e3ac666059c8e4ddfea8bee8f0b0c241698f345e0b1486bfa47dfd85f3a01d9cfb205" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )), - c1: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "fffffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "f34adc6d128af72cc27e6c4dc15a2d285f3cf671c98e0cec6fb3c7b68747a154b89f1f2302e9e98832e0c4362b3efc00" - )), - c1: Bls12_381Fp(hex!( - "b85f2392ed75078d3d81e7633da57ef6c4b9ba84d743247b4f5fbd3cfd03d60f1f0d2c20b4be31c26706bb02bfd30419" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "f34adc6d128af72cc27e6c4dc15a2d285f3cf671c98e0cec6fb3c7b68747a154b89f1f2302e9e98832e0c4362b3efc00" - )), - c1: Bls12_381Fp(hex!( - "b85f2392ed75078d3d81e7633da57ef6c4b9ba84d743247b4f5fbd3cfd03d60f1f0d2c20b4be31c26706bb02bfd30419" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )), - c1: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "adaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "9529f87f1605e61ecd78d48b90c17158bdf0146853f345dbd08279e76035df7091cc99fa4aadd36bc186453811424e14" - )), - c1: Bls12_381Fp(hex!( - "16810780e9fa189b32877f256e3e3ac666059c8e4ddfea8bee8f0b0c241698f345e0b1486bfa47dfd85f3a01d9cfb205" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )), - c1: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )), - c1: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )), - c1: Bls12_381Fp(hex!( - "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "adaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "fffffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - ], - [ - Fp2 { - c0: Bls12_381Fp(hex!( - "16810780e9fa189b32877f256e3e3ac666059c8e4ddfea8bee8f0b0c241698f345e0b1486bfa47dfd85f3a01d9cfb205" - )), - c1: Bls12_381Fp(hex!( - "9529f87f1605e61ecd78d48b90c17158bdf0146853f345dbd08279e76035df7091cc99fa4aadd36bc186453811424e14" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bls12_381Fp(hex!( - "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )), - c1: Bls12_381Fp(hex!( - "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "fffffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" - )), - c1: Bls12_381Fp(hex!( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - )) - }, - Fp2 { - c0: Bls12_381Fp(hex!( - "b85f2392ed75078d3d81e7633da57ef6c4b9ba84d743247b4f5fbd3cfd03d60f1f0d2c20b4be31c26706bb02bfd30419" - )), - c1: Bls12_381Fp(hex!( - "f34adc6d128af72cc27e6c4dc15a2d285f3cf671c98e0cec6fb3c7b68747a154b89f1f2302e9e98832e0c4362b3efc00" - )) - }, - ], - ]; -} +#[cfg(not(target_os = "zkvm"))] +// Used in WeierstrassExtension config +pub const BLS12_381_ECC_STRUCT_NAME: &str = "Bls12_381G1Affine"; -impl Bls12_381 { - // FINAL_EXPONENT = (p^12 - 1) / r in big-endian - // Validated by a test in test.rs - pub const FINAL_EXPONENT: [u8; 540] = hex!( - "02ee1db5dcc825b7e1bda9c0496a1c0a89ee0193d4977b3f7d4507d07363baa13f8d14a917848517badc3a43d1073776ab353f2c30698e8cc7deada9c0aadff5e9cfee9a074e43b9a660835cc872ee83ff3a0f0f1c0ad0d6106feaf4e347aa68ad49466fa927e7bb9375331807a0dce2630d9aa4b113f414386b0e8819328148978e2b0dd39099b86e1ab656d2670d93e4d7acdd350da5359bc73ab61a0c5bf24c374693c49f570bcd2b01f3077ffb10bf24dde41064837f27611212596bc293c8d4c01f25118790f4684d0b9c40a68eb74bb22a40ee7169cdc1041296532fef459f12438dfc8e2886ef965e61a474c5c85b0129127a1b5ad0463434724538411d1676a53b5a62eb34c05739334f46c02c3f0bd0c55d3109cd15948d0a1fad20044ce6ad4c6bec3ec03ef19592004cedd556952c6d8823b19dadd7c2498345c6e5308f1c511291097db60b1749bf9b71a9f9e0100418a3ef0bc627751bbd81367066bca6a4c1b6dcfc5cceb73fc56947a403577dfa9e13c24ea820b09c1d9f7c31759c3635de3f7a3639991708e88adce88177456c49637fd7961be1a4c7e79fb02faa732e2f3ec2bea83d196283313492caa9d4aff1c910e9622d2a73f62537f2701aaef6539314043f7bbce5b78c7869aeb2181a67e49eeed2161daf3f881bd88592d767f67c4717489119226c2f011d4cab803e9d71650a6f80698e2f8491d12191a04406fbc8fbd5f48925f98630e68bfb24c0bcb9b55df57510" - ); -} +#[cfg(not(target_os = "zkvm"))] +// Used in Fp2Extension config +pub const BLS12_381_COMPLEX_STRUCT_NAME: &str = "Bls12_381Fp2"; diff --git a/extensions/pairing/guest/src/bls12_381/tests.rs b/extensions/pairing/guest/src/bls12_381/tests.rs index 9ca38d8586..04d1f23d0f 100644 --- a/extensions/pairing/guest/src/bls12_381/tests.rs +++ b/extensions/pairing/guest/src/bls12_381/tests.rs @@ -1,309 +1,4 @@ -use group::ff::Field; -use halo2curves_axiom::bls12_381::{ - Fq, Fq12, Fq2, Fq6, G1Affine, G2Affine, G2Prepared, MillerLoopResult, FROBENIUS_COEFF_FQ12_C1, -}; -use num_bigint::BigUint; -use num_traits::One; -use openvm_algebra_guest::{field::FieldExtension, IntMod}; -use openvm_ecc_guest::{weierstrass::WeierstrassPoint, AffinePoint}; -use rand::{rngs::StdRng, SeedableRng}; - -use super::{Fp, Fp12, Fp2, BLS12_381_MODULUS, BLS12_381_ORDER}; -use crate::{ - bls12_381::{ - utils::{ - convert_bls12381_fp12_to_halo2_fq12, convert_bls12381_halo2_fq12_to_fp12, - convert_bls12381_halo2_fq2_to_fp2, convert_bls12381_halo2_fq_to_fp, - convert_g2_affine_halo2_to_openvm, - }, - Bls12_381, G2Affine as OpenVmG2Affine, BLS12_381_PSEUDO_BINARY_ENCODING, - BLS12_381_SEED_ABS, - }, - pairing::{ - fp2_invert_assign, fp6_invert_assign, fp6_square_assign, FinalExp, MultiMillerLoop, - PairingCheck, PairingIntrinsics, - }, -}; - -#[test] -fn test_bls12381_frobenius_coeffs() { - #[allow(clippy::needless_range_loop)] - for i in 0..12 { - for j in 0..5 { - assert_eq!( - Bls12_381::FROBENIUS_COEFFS[i][j], - convert_bls12381_halo2_fq2_to_fp2(FROBENIUS_COEFF_FQ12_C1[i].pow([j as u64 + 1])), - "FROBENIUS_COEFFS[{}][{}] failed", - i, - j - ) - } - } -} - -#[test] -fn test_bls12381_frobenius() { - let mut rng = StdRng::seed_from_u64(15); - for pow in 0..12 { - let fq = Fq12::random(&mut rng); - let mut fq_frob = fq; - for _ in 0..pow { - fq_frob = fq_frob.frobenius_map(); - } - - let fp = convert_bls12381_halo2_fq12_to_fp12(fq); - let fp_frob = fp.frobenius_map(pow); - - assert_eq!(fp_frob, convert_bls12381_halo2_fq12_to_fp12(fq_frob)); - } -} - -#[test] -fn test_fp12_invert() { - let mut rng = StdRng::seed_from_u64(15); - let fq = Fq12::random(&mut rng); - let fq_inv = fq.invert().unwrap(); - - let fp = convert_bls12381_halo2_fq12_to_fp12(fq); - let fp_inv = fp.invert(); - assert_eq!(fp_inv, convert_bls12381_halo2_fq12_to_fp12(fq_inv)); -} - -#[test] -fn test_fp6_invert() { - let mut rng = StdRng::seed_from_u64(20); - let fq6 = Fq6 { - c0: Fq2::random(&mut rng), - c1: Fq2::random(&mut rng), - c2: Fq2::random(&mut rng), - }; - let fq6_inv = fq6.invert().unwrap(); - - let fp6c0 = convert_bls12381_halo2_fq2_to_fp2(fq6.c0); - let fp6c1 = convert_bls12381_halo2_fq2_to_fp2(fq6.c1); - let fp6c2 = convert_bls12381_halo2_fq2_to_fp2(fq6.c2); - let mut fp6 = [fp6c0, fp6c1, fp6c2]; - fp6_invert_assign::(&mut fp6, &Bls12_381::XI); - - let fq6_invc0 = convert_bls12381_halo2_fq2_to_fp2(fq6_inv.c0); - let fq6_invc1 = convert_bls12381_halo2_fq2_to_fp2(fq6_inv.c1); - let fq6_invc2 = convert_bls12381_halo2_fq2_to_fp2(fq6_inv.c2); - let fq6_inv = [fq6_invc0, fq6_invc1, fq6_invc2]; - assert_eq!(fp6, fq6_inv); -} - -#[test] -fn test_fp2_invert() { - let mut rng = StdRng::seed_from_u64(25); - let fq2 = Fq2::random(&mut rng); - let fq2_inv = fq2.invert().unwrap(); - - let mut fp2 = convert_bls12381_halo2_fq2_to_fp2(fq2).to_coeffs(); - fp2_invert_assign::(&mut fp2); - assert_eq!(fp2, convert_bls12381_halo2_fq2_to_fp2(fq2_inv).to_coeffs()); -} - -#[test] -fn test_fp6_square() { - let mut rng = StdRng::seed_from_u64(45); - let fq6 = Fq6 { - c0: Fq2::random(&mut rng), - c1: Fq2::random(&mut rng), - c2: Fq2::random(&mut rng), - }; - let fq6_sq = fq6.square(); - - let fp6c0 = convert_bls12381_halo2_fq2_to_fp2(fq6.c0); - let fp6c1 = convert_bls12381_halo2_fq2_to_fp2(fq6.c1); - let fp6c2 = convert_bls12381_halo2_fq2_to_fp2(fq6.c2); - let mut fp6 = [fp6c0, fp6c1, fp6c2]; - fp6_square_assign::(&mut fp6, &Bls12_381::XI); - - let fq6_sqc0 = convert_bls12381_halo2_fq2_to_fp2(fq6_sq.c0); - let fq6_sqc1 = convert_bls12381_halo2_fq2_to_fp2(fq6_sq.c1); - let fq6_sqc2 = convert_bls12381_halo2_fq2_to_fp2(fq6_sq.c2); - let fq6_sq = [fq6_sqc0, fq6_sqc1, fq6_sqc2]; - assert_eq!(fp6, fq6_sq); -} - -#[test] -fn test_fp2_square() { - let mut rng = StdRng::seed_from_u64(55); - let fq2 = Fq2::random(&mut rng); - let fq2_sq = fq2.square(); - - let fp2 = convert_bls12381_halo2_fq2_to_fp2(fq2); - let fp2_sq = &fp2 * &fp2; - assert_eq!(fp2_sq, convert_bls12381_halo2_fq2_to_fp2(fq2_sq)); -} - -#[test] -fn test_fp_add() { - let mut rng = StdRng::seed_from_u64(65); - let fq = Fq::random(&mut rng); - let fq_res = fq + Fq::one(); - - let fp = convert_bls12381_halo2_fq_to_fp(fq); - let fp_res = fp + Fp::ONE; - assert_eq!(fp_res, convert_bls12381_halo2_fq_to_fp(fq_res)); -} - -#[test] -fn test_fp_one() { - let fp_one = Fp::ONE; - let fq_one = Fq::ONE; - assert_eq!(fp_one, convert_bls12381_halo2_fq_to_fp(fq_one)); -} - -// Gt(Fq12) is not public -fn assert_miller_results_eq(a: MillerLoopResult, b: Fp12) { - let b = convert_bls12381_fp12_to_halo2_fq12(b); - crate::halo2curves_shims::bls12_381::tests::assert_miller_results_eq(a, b); -} - -#[test] -fn test_bls12381_miller_loop() { - let mut rng = StdRng::seed_from_u64(65); - let h2c_p = G1Affine::random(&mut rng); - let h2c_q = G2Affine::random(&mut rng); - - let p = AffinePoint { - x: convert_bls12381_halo2_fq_to_fp(h2c_p.x), - y: convert_bls12381_halo2_fq_to_fp(h2c_p.y), - }; - let q = AffinePoint { - x: convert_bls12381_halo2_fq2_to_fp2(h2c_q.x), - y: convert_bls12381_halo2_fq2_to_fp2(h2c_q.y), - }; - - // Compare against halo2curves implementation - let h2c_q_prepared = G2Prepared::from(h2c_q); - let compare_miller = - halo2curves_axiom::bls12_381::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); - let f = Bls12_381::multi_miller_loop(&[p], &[q]); - assert_miller_results_eq(compare_miller, f); -} - -#[test] -fn test_bls12381_miller_loop_identity() { - let mut rng = StdRng::seed_from_u64(33); - let h2c_p = G1Affine::identity(); - let h2c_q = G2Affine::random(&mut rng); - - let p = AffinePoint { - x: convert_bls12381_halo2_fq_to_fp(Fq::ZERO), - y: convert_bls12381_halo2_fq_to_fp(Fq::ZERO), - }; - let q = AffinePoint { - x: convert_bls12381_halo2_fq2_to_fp2(h2c_q.x), - y: convert_bls12381_halo2_fq2_to_fp2(h2c_q.y), - }; - - let f = Bls12_381::multi_miller_loop(&[p], &[q]); - // halo2curves implementation - let h2c_q_prepared = G2Prepared::from(h2c_q); - let compare_miller = - halo2curves_axiom::bls12_381::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); - assert_miller_results_eq(compare_miller, f); -} - -#[test] -fn test_bls12381_miller_loop_identity_2() { - let mut rng = StdRng::seed_from_u64(33); - let h2c_p = G1Affine::random(&mut rng); - let h2c_q = G2Affine::identity(); - let p = AffinePoint { - x: convert_bls12381_halo2_fq_to_fp(h2c_p.x), - y: convert_bls12381_halo2_fq_to_fp(h2c_p.y), - }; - let q = AffinePoint { - x: convert_bls12381_halo2_fq2_to_fp2(Fq2::ZERO), - y: convert_bls12381_halo2_fq2_to_fp2(Fq2::ZERO), - }; - - let f = Bls12_381::multi_miller_loop(&[p], &[q]); - // halo2curves implementation - let h2c_q_prepared = G2Prepared::from(h2c_q); - let compare_miller = - halo2curves_axiom::bls12_381::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); - assert_miller_results_eq(compare_miller, f); -} - -// test on host is enough since we are testing the curve formulas and not anything -// about intrinsic functions -#[test] -fn test_bls12381_g2_affine() { - let mut rng = StdRng::seed_from_u64(34); - for _ in 0..10 { - let p = G2Affine::random(&mut rng); - let q = G2Affine::random(&mut rng); - let expected_add = G2Affine::from(p + q); - let expected_sub = G2Affine::from(p - q); - let expected_neg = -p; - let expected_double = G2Affine::from(p + p); - let [p, q] = [p, q].map(|p| { - let x = convert_bls12381_halo2_fq2_to_fp2(p.x); - let y = convert_bls12381_halo2_fq2_to_fp2(p.y); - // check on curve - OpenVmG2Affine::from_xy(x, y).unwrap() - }); - let r_add = &p + &q; - let r_sub = &p - &q; - let r_neg = -&p; - let r_double = &p + &p; - - for (expected, actual) in [ - (expected_add, r_add), - (expected_sub, r_sub), - (expected_neg, r_neg), - (expected_double, r_double), - ] { - assert_eq!(convert_g2_affine_halo2_to_openvm(expected), actual); - } - } -} - -#[test] -fn test_bls12381_pairing_check_hint_host() { - let mut rng = StdRng::seed_from_u64(83); - let h2c_p = G1Affine::random(&mut rng); - let h2c_q = G2Affine::random(&mut rng); - - let p = AffinePoint { - x: convert_bls12381_halo2_fq_to_fp(h2c_p.x), - y: convert_bls12381_halo2_fq_to_fp(h2c_p.y), - }; - let q = AffinePoint { - x: convert_bls12381_halo2_fq2_to_fp2(h2c_q.x), - y: convert_bls12381_halo2_fq2_to_fp2(h2c_q.y), - }; - - let (c, s) = Bls12_381::pairing_check_hint(&[p], &[q]); - - let p_cmp = AffinePoint { - x: h2c_p.x, - y: h2c_p.y, - }; - let q_cmp = AffinePoint { - x: h2c_q.x, - y: h2c_q.y, - }; - - let f_cmp = - crate::halo2curves_shims::bls12_381::Bls12_381::multi_miller_loop(&[p_cmp], &[q_cmp]); - let (c_cmp, s_cmp) = crate::halo2curves_shims::bls12_381::Bls12_381::final_exp_hint(&f_cmp); - let c_cmp = convert_bls12381_halo2_fq12_to_fp12(c_cmp); - let s_cmp = convert_bls12381_halo2_fq12_to_fp12(s_cmp); - - assert_eq!(c, c_cmp); - assert_eq!(s, s_cmp); -} - -#[test] -fn test_bls12381_final_exponent() { - let final_exp = (BLS12_381_MODULUS.pow(12) - BigUint::one()) / BLS12_381_ORDER.clone(); - assert_eq!(Bls12_381::FINAL_EXPONENT.to_vec(), final_exp.to_bytes_be()); -} +use crate::bls12_381::{BLS12_381_PSEUDO_BINARY_ENCODING, BLS12_381_SEED_ABS}; #[test] fn test_bls12381_pseudo_binary_encoding() { diff --git a/extensions/pairing/guest/src/bn254/mod.rs b/extensions/pairing/guest/src/bn254/mod.rs index 7a620e74e9..1e30a51945 100644 --- a/extensions/pairing/guest/src/bn254/mod.rs +++ b/extensions/pairing/guest/src/bn254/mod.rs @@ -1,28 +1,9 @@ -use core::ops::{Add, Neg}; - +#[cfg(not(target_os = "zkvm"))] use hex_literal::hex; #[cfg(not(target_os = "zkvm"))] use lazy_static::lazy_static; #[cfg(not(target_os = "zkvm"))] use num_bigint::BigUint; -use openvm_algebra_guest::{Field, IntMod}; -use openvm_algebra_moduli_macros::moduli_declare; -use openvm_ecc_guest::{ - weierstrass::{CachedMulTable, IntrinsicCurve}, - CyclicGroup, Group, -}; -use openvm_ecc_sw_macros::sw_declare; - -use crate::pairing::PairingIntrinsics; - -mod fp12; -mod fp2; -pub mod pairing; -#[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] -pub(crate) mod utils; - -pub use fp12::*; -pub use fp2::*; #[cfg(all(test, feature = "halo2curves", not(target_os = "zkvm")))] pub mod tests; @@ -53,688 +34,10 @@ pub const BN254_PSEUDO_BINARY_ENCODING: [i8; 66] = [ 0, 0, 1, 0, -1, 0, 1, ]; -moduli_declare! { - Bn254Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" }, - Bn254Scalar { modulus = "21888242871839275222246405745257275088548364400416034343698204186575808495617" }, -} - -const CURVE_B: Bn254Fp = Bn254Fp::from_const_bytes(hex!( - "0300000000000000000000000000000000000000000000000000000000000000" -)); - -sw_declare! { - Bn254G1Affine { mod_type = Bn254Fp, b = CURVE_B }, -} - -pub type Fp = Bn254Fp; -pub type Scalar = Bn254Scalar; -pub type G1Affine = Bn254G1Affine; -pub use g2::G2Affine; - -impl Field for Fp { - type SelfRef<'a> = &'a Self; - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} - -impl Field for Scalar { - type SelfRef<'a> = &'a Self; - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} - -impl CyclicGroup for G1Affine { - // https://eips.ethereum.org/EIPS/eip-197 - const GENERATOR: Self = G1Affine { - x: Bn254Fp::from_const_u8(1), - y: Bn254Fp::from_const_u8(2), - }; - const NEG_GENERATOR: Self = G1Affine { - x: Bn254Fp::from_const_u8(1), - y: Bn254Fp::from_const_bytes(hex!( - "45FD7CD8168C203C8DCA7168916A81975D588181B64550B829A031E1724E6430" - )), - }; -} - -// Define a G2Affine struct that implements curve operations using `Fp2` intrinsics -// but not special E(Fp2) intrinsics. -mod g2 { - use hex_literal::hex; - use openvm_algebra_guest::Field; - use openvm_ecc_guest::{ - impl_sw_affine, impl_sw_group_ops, weierstrass::WeierstrassPoint, AffinePoint, Group, - }; - - use super::{Fp, Fp2}; - - const THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::ZERO); - // 3 / (9 + u) - // validated by a test below - const B: Fp2 = Fp2::new( - Fp::from_const_bytes(hex!( - "e538a124dce66732a3efdb59e5c5b4b5c36ae01b9918be81aeaab8ce409d142b" - )), - Fp::from_const_bytes(hex!( - "d215c38506bda2e452182de584a04fa7f4fdd8eeadaf2ccdd4fef03ab0139700" - )), - ); - impl_sw_affine!(G2Affine, Fp2, THREE, B); - impl_sw_group_ops!(G2Affine, Fp2); - - #[test] - fn test_g2_curve_equation_b() { - use openvm_algebra_guest::DivUnsafe; - let b = Fp2::new(Fp::from_const_u8(3), Fp::ZERO) - .div_unsafe(Fp2::new(Fp::from_const_u8(9), Fp::ONE)); - assert_eq!(b, B); - } -} - -pub struct Bn254; - -impl Bn254 { - // Same as the values from halo2curves_shims - // Validated by a test in tests.rs - pub const FROBENIUS_COEFF_FQ6_C1: [Fp2; 3] = [ - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "3d556f175795e3990c33c3c210c38cb743b159f53cec0b4cf711794f9847b32f" - )), - c1: Bn254Fp(hex!( - "a2cb0f641cd56516ce9d7c0b1d2aae3294075ad78bcca44b20aeeb6150e5c916" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - ]; - - // Same as the values from halo2curves_shims - // Validated by a test in tests.rs - pub const XI_TO_Q_MINUS_1_OVER_2: Fp2 = Fp2 { - c0: Bn254Fp(hex!( - "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" - )), - c1: Bn254Fp(hex!( - "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" - )), - }; - - // FINAL_EXPONENT = (p^12 - 1) / r in big-endian - // Validated by a test in test.rs - pub const FINAL_EXPONENT: [u8; 349] = hex!( - "2f4b6dc97020fddadf107d20bc842d43bf6369b1ff6a1c71015f3f7be2e1e30a73bb94fec0daf15466b2383a5d3ec3d15ad524d8f70c54efee1bd8c3b21377e563a09a1b705887e72eceaddea3790364a61f676baaf977870e88d5c6c8fef0781361e443ae77f5b63a2a2264487f2940a8b1ddb3d15062cd0fb2015dfc6668449aed3cc48a82d0d602d268c7daab6a41294c0cc4ebe5664568dfc50e1648a45a4a1e3a5195846a3ed011a337a02088ec80e0ebae8755cfe107acf3aafb40494e406f804216bb10cf430b0f37856b42db8dc5514724ee93dfb10826f0dd4a0364b9580291d2cd65664814fde37ca80bb4ea44eacc5e641bbadf423f9a2cbf813b8d145da90029baee7ddadda71c7f3811c4105262945bba1668c3be69a3c230974d83561841d766f9c9d570bb7fbe04c7e8a6c3c760c0de81def35692da361102b6b9b2b918837fa97896e84abb40a4efb7e54523a486964b64ca86f120" - ); -} - -impl IntrinsicCurve for Bn254 { - type Scalar = Scalar; - type Point = G1Affine; - - fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point - where - for<'a> &'a Self::Point: Add<&'a Self::Point, Output = Self::Point>, - { - // heuristic - if coeffs.len() < 25 { - // BN254(Fp) is of prime order by Weil conjecture: - // - let table = CachedMulTable::::new_with_prime_order(bases, 4); - table.windowed_mul(coeffs) - } else { - openvm_ecc_guest::msm(coeffs, bases) - } - } -} - -impl PairingIntrinsics for Bn254 { - type Fp = Fp; - type Fp2 = Fp2; - type Fp12 = Fp12; +#[cfg(not(target_os = "zkvm"))] +// Used in WeierstrassExtension config +pub const BN254_ECC_STRUCT_NAME: &str = "Bn254G1Affine"; - const PAIRING_IDX: usize = 0; - // The sextic extension `Fp12` is `Fp2[X] / (X^6 - \xi)`, where `\xi` is a non-residue. - const XI: Fp2 = Fp2::new(Fp::from_const_u8(9), Fp::from_const_u8(1)); - const FP2_TWO: Fp2 = Fp2::new(Fp::from_const_u8(2), Fp::from_const_u8(0)); - const FP2_THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::from_const_u8(0)); - // Multiplication constants for the Frobenius map for coefficients in Fp2 c1..=c5 for powers - // 0..12 FROBENIUS_COEFFS\[i\]\[j\] = \xi^{(j + 1) * (p^i - 1)/6} when p = 1 (mod 6) - // These are validated against `halo2curves::bn256::FROBENIUS_COEFF_FQ12_C1` in tests.rs - // (Note that bn256 here is another name for bn254) - const FROBENIUS_COEFFS: [[Self::Fp2; 5]; 12] = [ - [ - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "70e4c9dcda350bd676212f29081e525c608be676dd9fb9e8dfa765281cb78412" - )), - c1: Bn254Fp(hex!( - "ac62f3805ff05ccae5c7ee8e779279748e0b1512fe7c32a6e6e7fab4f3966924" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "3d556f175795e3990c33c3c210c38cb743b159f53cec0b4cf711794f9847b32f" - )), - c1: Bn254Fp(hex!( - "a2cb0f641cd56516ce9d7c0b1d2aae3294075ad78bcca44b20aeeb6150e5c916" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" - )), - c1: Bn254Fp(hex!( - "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "62a71e92551f8a8472ec94bef76533d3841e185ab7c0f38001a8ee645e4fb505" - )), - c1: Bn254Fp(hex!( - "26812bcd11473bc163c7de1bead28536921c0b3bb0803a9fee8afde7db5e142c" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "2f69b7ea10c8a22ed31baa559b455c42f43f35a461363ae94986794fe7c18301" - )), - c1: Bn254Fp(hex!( - "4b2c0c6eeeb8c624c02a8e6799cb80b07d9f72c746b27fa27506fd76caf2ac12" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "49fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "ffffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "7fa6d41e397d6fe84ad255be8db34c8990aaacd08c60e9efbbe482cccf81dc19" - )), - c1: Bn254Fp(hex!( - "01c1c0f42baa9476ec39d497e3a5037f9d137635e3eecb06737de70bb6f8ab00" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "6dfbdc7be86e747bd342695d3dfd5f80ac259f95771cffba0aef55b778e05608" - )), - c1: Bn254Fp(hex!( - "de86a5aa2bab0c383126ff98bf31df0f4f0926ec6d0ef3a96f76d1b341def104" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "ede9dc66d08acc5ff470a8bea389d6bba35e9eca1d7ff1db4caa96986d5b272a" - )), - c1: Bn254Fp(hex!( - "644c59b2b30c4db9ba6ecfd8c7ec007632e907950e904bb18f9bf034b611a428" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "66f0cb3cbc921a0ecb6bb075450933e64e44b2b5f7e0be19ab8dc011668cc50b" - )), - c1: Bn254Fp(hex!( - "9f230c739dede35fe5967f73089e4aa4041dd20ceff6b0fe120a91e199e9d523" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "431b26767084deeba5847c969880d62e693f4d3bfa99167105092c954490c413" - )), - c1: Bn254Fp(hex!( - "992428841304251f21800220eada2d3e3d63482a28b2b19f0bddb1596a36db16" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "0fc20a425e476412d4b026958595fa2c301fc659afc02f07dc3c1da4b3ca5707" - )), - c1: Bn254Fp(hex!( - "9c5b4a4ce34558e8933c5771fd7d0ba26c60e2a49bb7e918b6351e3835b0a60c" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "e4a9ad1dee13e9623a1fb7b0d41416f7cad90978b8829569513f94bbd474be28" - )), - c1: Bn254Fp(hex!( - "c7aac7c9ce0baeed8d06f6c3b40ef4547a4701bebc6ab8c2997b74cbe08aa814" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" - )), - c1: Bn254Fp(hex!( - "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "7f65920905da7ba94f722c3454fb1ade89f5b67107a49d1d7d6a826aae72e91e" - )), - c1: Bn254Fp(hex!( - "c955c2707ee32157d136854130643254247725bbcd13b5d251abd4f86f54de10" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "14b26e8b5fbc3bbdd268d240fd3a7aec74ff17979863dc87bb82b2455dce4012" - )), - c1: Bn254Fp(hex!( - "4ef81b16254b5efa605574b8500fad8dbfc3d562e1ff31fd95d6b4e29f432e04" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "d718b3fb3b56156616a9423f894c2f3bfdcc9a0ad9a596cf49f8cbb85697df1d" - )), - c1: Bn254Fp(hex!( - "9b9a8957b79bc371a70283d919d80723cf4c6c6fb8c81d1243b8362c7fb7fa0b" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "3d556f175795e3990c33c3c210c38cb743b159f53cec0b4cf711794f9847b32f" - )), - c1: Bn254Fp(hex!( - "a2cb0f641cd56516ce9d7c0b1d2aae3294075ad78bcca44b20aeeb6150e5c916" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "ede9dc66d08acc5ff470a8bea389d6bba35e9eca1d7ff1db4caa96986d5b272a" - )), - c1: Bn254Fp(hex!( - "644c59b2b30c4db9ba6ecfd8c7ec007632e907950e904bb18f9bf034b611a428" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "62a71e92551f8a8472ec94bef76533d3841e185ab7c0f38001a8ee645e4fb505" - )), - c1: Bn254Fp(hex!( - "26812bcd11473bc163c7de1bead28536921c0b3bb0803a9fee8afde7db5e142c" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "1894c5ed05c47d0dbaaec712f624255569184cdd540f16cfdf19b8918b8ce02e" - )), - c1: Bn254Fp(hex!( - "fcd0706a28d35917cd9fe300f89e00e7dfb80eba6f93d015b499346aa85bb71d" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "0100000000000000000000000000000000000000000000000000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "c856a8b9dd0eb15342f81baa03b7340ecdadd4b029e566c86dbbae14a3cc8716" - )), - c1: Bn254Fp(hex!( - "463cbce3eae18bc5a0909dd0adc47d18c0440b4cd35684b1b6224ad5bc55b82f" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "6dfbdc7be86e747bd342695d3dfd5f80ac259f95771cffba0aef55b778e05608" - )), - c1: Bn254Fp(hex!( - "de86a5aa2bab0c383126ff98bf31df0f4f0926ec6d0ef3a96f76d1b341def104" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" - )), - c1: Bn254Fp(hex!( - "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "66f0cb3cbc921a0ecb6bb075450933e64e44b2b5f7e0be19ab8dc011668cc50b" - )), - c1: Bn254Fp(hex!( - "9f230c739dede35fe5967f73089e4aa4041dd20ceff6b0fe120a91e199e9d523" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "04e25662a6074250e745f5d1f8e9aa68f4183446bcab39472497054c2ebe9f1c" - )), - c1: Bn254Fp(hex!( - "aed854540388fb1c6c4a6f48a78f535920f538578e939e181ec37f8708188919" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "ffffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "49fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - ], - [ - Fp2 { - c0: Bn254Fp(hex!( - "383b7296b844bc29b9194bd30bd5866a2d39bb27078520b14d63143dbf830c29" - )), - c1: Bn254Fp(hex!( - "aba1328c3346c853f98d1af793ec75f5f0f79edc1a8e669f736a13a93d9ebd23" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "e4a9ad1dee13e9623a1fb7b0d41416f7cad90978b8829569513f94bbd474be28" - )), - c1: Bn254Fp(hex!( - "c7aac7c9ce0baeed8d06f6c3b40ef4547a4701bebc6ab8c2997b74cbe08aa814" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "ede9dc66d08acc5ff470a8bea389d6bba35e9eca1d7ff1db4caa96986d5b272a" - )), - c1: Bn254Fp(hex!( - "644c59b2b30c4db9ba6ecfd8c7ec007632e907950e904bb18f9bf034b611a428" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "7f65920905da7ba94f722c3454fb1ade89f5b67107a49d1d7d6a826aae72e91e" - )), - c1: Bn254Fp(hex!( - "c955c2707ee32157d136854130643254247725bbcd13b5d251abd4f86f54de10" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "334b0e4db7cfe47eba619f27942f07abe85869ea1de273306e1d7f9b1580231e" - )), - c1: Bn254Fp(hex!( - "f90461c2f140c2412c75fdaf405bd4099e94ab1ed5451ebb93c97cfed20a362c" - )), - }, - ], - ]; -} +#[cfg(not(target_os = "zkvm"))] +// Used in Fp2Extension config +pub const BN254_COMPLEX_STRUCT_NAME: &str = "Bn254Fp2"; diff --git a/extensions/pairing/guest/src/bn254/tests.rs b/extensions/pairing/guest/src/bn254/tests.rs index 331bb64692..54fa3fc840 100644 --- a/extensions/pairing/guest/src/bn254/tests.rs +++ b/extensions/pairing/guest/src/bn254/tests.rs @@ -1,308 +1,4 @@ -use group::{ff::Field, prime::PrimeCurveAffine}; -use halo2curves_axiom::bn256::{ - Fq, Fq12, Fq2, Fq6, G1Affine, G2Affine, G2Prepared, Gt, FROBENIUS_COEFF_FQ12_C1, - FROBENIUS_COEFF_FQ6_C1, XI_TO_Q_MINUS_1_OVER_2, -}; -use num_bigint::BigUint; -use num_traits::One; -use openvm_algebra_guest::{field::FieldExtension, IntMod}; -use openvm_ecc_guest::{weierstrass::WeierstrassPoint, AffinePoint}; -use rand::{rngs::StdRng, SeedableRng}; - -use super::{Fp, Fp12, Fp2}; -use crate::{ - bn254::{ - utils::{ - convert_bn254_fp12_to_halo2_fq12, convert_bn254_halo2_fq12_to_fp12, - convert_bn254_halo2_fq2_to_fp2, convert_bn254_halo2_fq_to_fp, - convert_g2_affine_halo2_to_openvm, - }, - Bn254, G2Affine as OpenVmG2Affine, BN254_MODULUS, BN254_ORDER, - BN254_PSEUDO_BINARY_ENCODING, BN254_SEED, - }, - pairing::{ - fp2_invert_assign, fp6_invert_assign, fp6_square_assign, FinalExp, MultiMillerLoop, - PairingCheck, PairingIntrinsics, - }, -}; - -#[test] -fn test_bn254_frobenius_coeffs() { - #[allow(clippy::needless_range_loop)] - for i in 0..12 { - for j in 0..5 { - assert_eq!( - Bn254::FROBENIUS_COEFFS[i][j], - convert_bn254_halo2_fq2_to_fp2(FROBENIUS_COEFF_FQ12_C1[i].pow([j as u64 + 1])), - "FROBENIUS_COEFFS[{}][{}] failed", - i, - j - ) - } - } -} - -#[test] -fn test_bn254_frobenius() { - let mut rng = StdRng::seed_from_u64(15); - for pow in 0..12 { - let fq = Fq12::random(&mut rng); - let fq_frob = fq.frobenius_map(pow); - - let fp = convert_bn254_halo2_fq12_to_fp12(fq); - let fp_frob = fp.frobenius_map(pow); - - assert_eq!(fp_frob, convert_bn254_halo2_fq12_to_fp12(fq_frob)); - } -} - -#[test] -fn test_fp12_invert() { - let mut rng = StdRng::seed_from_u64(15); - let fq = Fq12::random(&mut rng); - let fq_inv = fq.invert().unwrap(); - - let fp = convert_bn254_halo2_fq12_to_fp12(fq); - let fp_inv = fp.invert(); - assert_eq!(fp_inv, convert_bn254_halo2_fq12_to_fp12(fq_inv)); -} - -#[test] -fn test_fp6_invert() { - let mut rng = StdRng::seed_from_u64(20); - let fq6 = Fq6::random(&mut rng); - let fq6_inv = fq6.invert().unwrap(); - - let fp6c0 = convert_bn254_halo2_fq2_to_fp2(fq6.c0); - let fp6c1 = convert_bn254_halo2_fq2_to_fp2(fq6.c1); - let fp6c2 = convert_bn254_halo2_fq2_to_fp2(fq6.c2); - let mut fp6 = [fp6c0, fp6c1, fp6c2]; - fp6_invert_assign::(&mut fp6, &Bn254::XI); - - let fq6_invc0 = convert_bn254_halo2_fq2_to_fp2(fq6_inv.c0); - let fq6_invc1 = convert_bn254_halo2_fq2_to_fp2(fq6_inv.c1); - let fq6_invc2 = convert_bn254_halo2_fq2_to_fp2(fq6_inv.c2); - let fq6_inv = [fq6_invc0, fq6_invc1, fq6_invc2]; - assert_eq!(fp6, fq6_inv); -} - -#[test] -fn test_fp2_invert() { - let mut rng = StdRng::seed_from_u64(25); - let fq2 = Fq2::random(&mut rng); - let fq2_inv = fq2.invert().unwrap(); - - let mut fp2 = convert_bn254_halo2_fq2_to_fp2(fq2).to_coeffs(); - fp2_invert_assign::(&mut fp2); - assert_eq!(fp2, convert_bn254_halo2_fq2_to_fp2(fq2_inv).to_coeffs()); -} - -#[test] -fn test_fp6_square() { - let mut rng = StdRng::seed_from_u64(45); - let fq6 = Fq6::random(&mut rng); - let fq6_sq = fq6.square(); - - let fp6c0 = convert_bn254_halo2_fq2_to_fp2(fq6.c0); - let fp6c1 = convert_bn254_halo2_fq2_to_fp2(fq6.c1); - let fp6c2 = convert_bn254_halo2_fq2_to_fp2(fq6.c2); - let mut fp6 = [fp6c0, fp6c1, fp6c2]; - fp6_square_assign::(&mut fp6, &Bn254::XI); - - let fq6_sqc0 = convert_bn254_halo2_fq2_to_fp2(fq6_sq.c0); - let fq6_sqc1 = convert_bn254_halo2_fq2_to_fp2(fq6_sq.c1); - let fq6_sqc2 = convert_bn254_halo2_fq2_to_fp2(fq6_sq.c2); - let fq6_sq = [fq6_sqc0, fq6_sqc1, fq6_sqc2]; - assert_eq!(fp6, fq6_sq); -} - -#[test] -fn test_fp2_square() { - let mut rng = StdRng::seed_from_u64(55); - let fq2 = Fq2::random(&mut rng); - let fq2_sq = fq2.square(); - - let fp2 = convert_bn254_halo2_fq2_to_fp2(fq2); - let fp2_sq = &fp2 * &fp2; - assert_eq!(fp2_sq, convert_bn254_halo2_fq2_to_fp2(fq2_sq)); -} - -#[test] -fn test_fp_add() { - let mut rng = StdRng::seed_from_u64(65); - let fq = Fq::random(&mut rng); - let fq_res = fq + Fq::one(); - - let fp = convert_bn254_halo2_fq_to_fp(fq); - let fp_res = fp + Fp::ONE; - assert_eq!(fp_res, convert_bn254_halo2_fq_to_fp(fq_res)); -} - -#[test] -fn test_fp_one() { - let fp_one = Fp::ONE; - let fq_one = Fq::ONE; - assert_eq!(fp_one, convert_bn254_halo2_fq_to_fp(fq_one)); -} - -// Gt(Fq12) is not public -fn assert_miller_results_eq(a: Gt, b: Fp12) { - let b = convert_bn254_fp12_to_halo2_fq12(b); - crate::halo2curves_shims::bn254::tests::assert_miller_results_eq(a, b); -} - -#[test] -fn test_bn254_miller_loop() { - let mut rng = StdRng::seed_from_u64(53); - let h2c_p = G1Affine::random(&mut rng); - let h2c_q = G2Affine::random(&mut rng); - - let p = AffinePoint { - x: convert_bn254_halo2_fq_to_fp(h2c_p.x), - y: convert_bn254_halo2_fq_to_fp(h2c_p.y), - }; - let q = AffinePoint { - x: convert_bn254_halo2_fq2_to_fp2(h2c_q.x), - y: convert_bn254_halo2_fq2_to_fp2(h2c_q.y), - }; - - // Compare against halo2curves implementation - let h2c_q_prepared = G2Prepared::from(h2c_q); - let compare_miller = halo2curves_axiom::bn256::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); - let f = Bn254::multi_miller_loop(&[p], &[q]); - assert_miller_results_eq(compare_miller, f); -} - -#[test] -fn test_bn254_miller_loop_identity() { - let mut rng = StdRng::seed_from_u64(33); - let h2c_p = G1Affine::identity(); - let h2c_q = G2Affine::random(&mut rng); - - let p = AffinePoint { - x: convert_bn254_halo2_fq_to_fp(Fq::ZERO), - y: convert_bn254_halo2_fq_to_fp(Fq::ZERO), - }; - let q = AffinePoint { - x: convert_bn254_halo2_fq2_to_fp2(h2c_q.x), - y: convert_bn254_halo2_fq2_to_fp2(h2c_q.y), - }; - - let f = Bn254::multi_miller_loop(&[p], &[q]); - // halo2curves implementation - let h2c_q_prepared = G2Prepared::from(h2c_q); - let compare_miller = halo2curves_axiom::bn256::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); - assert_miller_results_eq(compare_miller, f); -} - -#[test] -fn test_bn254_miller_loop_identity_2() { - let mut rng = StdRng::seed_from_u64(33); - let h2c_p = G1Affine::random(&mut rng); - let h2c_q = G2Affine::identity(); - let p = AffinePoint { - x: convert_bn254_halo2_fq_to_fp(h2c_p.x), - y: convert_bn254_halo2_fq_to_fp(h2c_p.y), - }; - let q = AffinePoint { - x: convert_bn254_halo2_fq2_to_fp2(Fq2::ZERO), - y: convert_bn254_halo2_fq2_to_fp2(Fq2::ZERO), - }; - - let f = Bn254::multi_miller_loop(&[p], &[q]); - // halo2curves implementation - let h2c_q_prepared = G2Prepared::from(h2c_q); - let compare_miller = halo2curves_axiom::bn256::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); - assert_miller_results_eq(compare_miller, f); -} - -// test on host is enough since we are testing the curve formulas and not anything -// about intrinsic functions -#[test] -fn test_bn254_g2_affine() { - let mut rng = StdRng::seed_from_u64(34); - for _ in 0..10 { - let p = G2Affine::random(&mut rng); - let q = G2Affine::random(&mut rng); - let expected_add = G2Affine::from(p + q); - let expected_sub = G2Affine::from(p - q); - let expected_neg = -p; - let expected_double = G2Affine::from(p + p); - let [p, q] = [p, q].map(|p| { - let x = convert_bn254_halo2_fq2_to_fp2(p.x); - let y = convert_bn254_halo2_fq2_to_fp2(p.y); - // check on curve - OpenVmG2Affine::from_xy(x, y).unwrap() - }); - let r_add = &p + &q; - let r_sub = &p - &q; - let r_neg = -&p; - let r_double = &p + &p; - - for (expected, actual) in [ - (expected_add, r_add), - (expected_sub, r_sub), - (expected_neg, r_neg), - (expected_double, r_double), - ] { - assert_eq!(convert_g2_affine_halo2_to_openvm(expected), actual); - } - } -} - -#[test] -fn test_bn254_pairing_check_hint_host() { - let mut rng = StdRng::seed_from_u64(83); - let h2c_p = G1Affine::random(&mut rng); - let h2c_q = G2Affine::random(&mut rng); - - let p = AffinePoint { - x: convert_bn254_halo2_fq_to_fp(h2c_p.x), - y: convert_bn254_halo2_fq_to_fp(h2c_p.y), - }; - let q = AffinePoint { - x: convert_bn254_halo2_fq2_to_fp2(h2c_q.x), - y: convert_bn254_halo2_fq2_to_fp2(h2c_q.y), - }; - - let (c, u) = Bn254::pairing_check_hint(&[p], &[q]); - - let p_cmp = AffinePoint { - x: h2c_p.x, - y: h2c_p.y, - }; - let q_cmp = AffinePoint { - x: h2c_q.x, - y: h2c_q.y, - }; - - let f_cmp = crate::halo2curves_shims::bn254::Bn254::multi_miller_loop(&[p_cmp], &[q_cmp]); - let (c_cmp, u_cmp) = crate::halo2curves_shims::bn254::Bn254::final_exp_hint(&f_cmp); - let c_cmp = convert_bn254_halo2_fq12_to_fp12(c_cmp); - let u_cmp = convert_bn254_halo2_fq12_to_fp12(u_cmp); - - assert_eq!(c, c_cmp); - assert_eq!(u, u_cmp); -} - -#[test] -fn test_bn254_final_exponent() { - let final_exp = (BN254_MODULUS.pow(12) - BigUint::one()) / BN254_ORDER.clone(); - assert_eq!(Bn254::FINAL_EXPONENT.to_vec(), final_exp.to_bytes_be()); -} - -#[test] -fn test_bn254_frobenius_coeffs_fq6() { - #[allow(clippy::needless_range_loop)] - for i in 0..3 { - assert_eq!( - Bn254::FROBENIUS_COEFF_FQ6_C1[i], - convert_bn254_halo2_fq2_to_fp2(FROBENIUS_COEFF_FQ6_C1[i]), - "FROBENIUS_COEFFS_FQ6_C1[{}] failed", - i, - ) - } -} +use crate::bn254::{BN254_PSEUDO_BINARY_ENCODING, BN254_SEED}; #[test] fn test_bn254_pseudo_binary_encoding() { @@ -314,12 +10,3 @@ fn test_bn254_pseudo_binary_encoding() { } assert_eq!(x.unsigned_abs(), 6 * (BN254_SEED as u128) + 2); } - -#[test] -fn test_bn254_xi_to_q_minus_1_over_2() { - assert_eq!( - Bn254::XI_TO_Q_MINUS_1_OVER_2, - convert_bn254_halo2_fq2_to_fp2(XI_TO_Q_MINUS_1_OVER_2), - "XI_TO_Q_MINUS_1_OVER_2 failed", - ) -} diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/miller_loop.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/miller_loop.rs index add12a4b23..f2720fb52b 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/miller_loop.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/miller_loop.rs @@ -8,13 +8,30 @@ use openvm_ecc_guest::{ }; use super::Bls12_381; -use crate::{ - bls12_381::{BLS12_381_PSEUDO_BINARY_ENCODING, BLS12_381_SEED_ABS}, - pairing::{ - Evaluatable, EvaluatedLine, LineMulMType, MillerStep, MultiMillerLoop, UnevaluatedLine, - }, +use crate::pairing::{ + Evaluatable, EvaluatedLine, LineMulMType, MillerStep, MultiMillerLoop, UnevaluatedLine, }; +pub const BLS12_381_SEED_ABS: u64 = 0xd201000000010000; +// Encodes the Bls12_381 seed, x. +// x = sum_i BLS12_381_PSEUDO_BINARY_ENCODING[i] * 2^i +// where BLS12_381_PSEUDO_BINARY_ENCODING[i] is in {-1, 0, 1} +pub const BLS12_381_PSEUDO_BINARY_ENCODING: [i8; 64] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 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, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, +]; + +#[test] +fn test_bls12381_pseudo_binary_encoding() { + let mut x: i128 = 0; + let mut power_of_2 = 1; + for b in BLS12_381_PSEUDO_BINARY_ENCODING.iter() { + x += (*b as i128) * power_of_2; + power_of_2 *= 2; + } + assert_eq!(x.unsigned_abs(), BLS12_381_SEED_ABS as u128); +} + impl MillerStep for Bls12_381 { type Fp2 = Fq2; diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/mod.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/mod.rs index 9e9c0235f0..93613e0d0e 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/mod.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/mod.rs @@ -9,6 +9,9 @@ pub use line::*; #[cfg(test)] pub mod tests; +// Make public for use by tests in guest-libs/pairing/ +pub mod test_utils; + use halo2curves_axiom::bls12_381::{Fq, Fq12, Fq2}; use openvm_algebra_guest::field::FieldExtension; diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/test_utils.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/test_utils.rs new file mode 100644 index 0000000000..3d4cd7ea2f --- /dev/null +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/test_utils.rs @@ -0,0 +1,33 @@ +use core::mem::transmute; + +use halo2curves_axiom::bls12_381::{Fq12, MillerLoopResult}; +use hex_literal::hex; +use lazy_static::lazy_static; +use num_bigint::BigUint; +use num_traits::Pow; +use openvm_algebra_guest::ExpBytes; + +lazy_static! { + pub static ref BLS12_381_MODULUS: BigUint = BigUint::from_bytes_be(&hex!( + "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" + )); + pub static ref BLS12_381_ORDER: BigUint = BigUint::from_bytes_be(&hex!( + "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" + )); +} + +// Manual final exponentiation because halo2curves `MillerLoopResult` doesn't have constructor +pub fn final_exp(f: Fq12) -> Fq12 { + let p = BLS12_381_MODULUS.clone(); + let r = BLS12_381_ORDER.clone(); + let exp: BigUint = (p.pow(12u32) - BigUint::from(1u32)) / r; + ExpBytes::exp_bytes(&f, true, &exp.to_bytes_be()) +} + +// Gt(Fq12) is not public +pub fn assert_miller_results_eq(a: MillerLoopResult, b: Fq12) { + // [jpw] This doesn't work: + // assert_eq!(a.final_exponentiation(), unsafe { transmute(final_exp(b)) }); + let a = unsafe { transmute::(a) }; + assert_eq!(final_exp(a), final_exp(b)); +} diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/mod.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/mod.rs index 56f7282612..954ebf3056 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/mod.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/mod.rs @@ -1,16 +1,10 @@ use alloc::vec::Vec; -use core::mem::transmute; -use halo2curves_axiom::bls12_381::{Fq, Fq12, Fq2, G1Affine, G2Affine, MillerLoopResult}; +use halo2curves_axiom::bls12_381::{Fq, Fq2, G1Affine, G2Affine}; use itertools::izip; -use num_bigint::BigUint; -use num_traits::Pow; -use openvm_algebra_guest::ExpBytes; use openvm_ecc_guest::AffinePoint; use rand::{rngs::StdRng, SeedableRng}; -use crate::bls12_381::{BLS12_381_MODULUS, BLS12_381_ORDER}; - #[cfg(test)] mod test_final_exp; #[cfg(test)] @@ -18,22 +12,7 @@ mod test_line; #[cfg(test)] mod test_miller_loop; -// Manual final exponentiation because halo2curves `MillerLoopResult` doesn't have constructor -pub fn final_exp(f: Fq12) -> Fq12 { - let p = BLS12_381_MODULUS.clone(); - let r = BLS12_381_ORDER.clone(); - let exp: BigUint = (p.pow(12u32) - BigUint::from(1u32)) / r; - ExpBytes::exp_bytes(&f, true, &exp.to_bytes_be()) -} - -// Gt(Fq12) is not public -pub fn assert_miller_results_eq(a: MillerLoopResult, b: Fq12) { - // [jpw] This doesn't work: - // assert_eq!(a.final_exponentiation(), unsafe { transmute(final_exp(b)) }); - let a = unsafe { transmute::(a) }; - assert_eq!(final_exp(a), final_exp(b)); -} - +#[cfg(not(target_os = "zkvm"))] #[allow(non_snake_case)] #[allow(clippy::type_complexity)] pub fn generate_test_points_bls12_381( diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/test_miller_loop.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/test_miller_loop.rs index 28e31e5878..47c06b0f64 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/test_miller_loop.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/test_miller_loop.rs @@ -8,7 +8,7 @@ use subtle::ConditionallySelectable; use super::generate_test_points_bls12_381; use crate::{ halo2curves_shims::bls12_381::{ - tests::{assert_miller_results_eq, final_exp}, + test_utils::{assert_miller_results_eq, final_exp}, Bls12_381, }, pairing::{Evaluatable, LineMulMType, MillerStep, MultiMillerLoop}, diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/miller_loop.rs b/extensions/pairing/guest/src/halo2curves_shims/bn254/miller_loop.rs index 9d4ff02557..9fc42b97ba 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bn254/miller_loop.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bn254/miller_loop.rs @@ -8,13 +8,31 @@ use openvm_ecc_guest::{ }; use super::Bn254; -use crate::{ - bn254::{BN254_PSEUDO_BINARY_ENCODING, BN254_SEED}, - pairing::{ - Evaluatable, EvaluatedLine, LineMulDType, MillerStep, MultiMillerLoop, UnevaluatedLine, - }, +use crate::pairing::{ + Evaluatable, EvaluatedLine, LineMulDType, MillerStep, MultiMillerLoop, UnevaluatedLine, }; +pub const BN254_SEED: u64 = 0x44e992b44a6909f1; +// Encodes 6x+2 where x is the BN254 seed. +// 6*x+2 = sum_i BN254_PSEUDO_BINARY_ENCODING[i] * 2^i +// where BN254_PSEUDO_BINARY_ENCODING[i] is in {-1, 0, 1} +pub const BN254_PSEUDO_BINARY_ENCODING: [i8; 66] = [ + 0, 0, 0, 1, 0, 1, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, -1, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0, + -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, -1, 0, 1, 0, -1, 0, 0, 0, -1, 0, -1, 0, + 0, 0, 1, 0, -1, 0, 1, +]; + +#[test] +fn test_bn254_pseudo_binary_encoding() { + let mut x: i128 = 0; + let mut power_of_2 = 1; + for b in BN254_PSEUDO_BINARY_ENCODING.iter() { + x += (*b as i128) * power_of_2; + power_of_2 *= 2; + } + assert_eq!(x.unsigned_abs(), 6 * (BN254_SEED as u128) + 2); +} + impl MillerStep for Bn254 { type Fp2 = Fq2; diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/mod.rs b/extensions/pairing/guest/src/halo2curves_shims/bn254/mod.rs index 4481c67259..51e64dd0a1 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bn254/mod.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bn254/mod.rs @@ -9,6 +9,9 @@ pub use line::*; #[cfg(test)] pub mod tests; +// Make public for use by tests in guest-libs/pairing/ +pub mod test_utils; + use halo2curves_axiom::bn256::{Fq, Fq12, Fq2}; use openvm_algebra_guest::field::FieldExtension; diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/test_utils.rs b/extensions/pairing/guest/src/halo2curves_shims/bn254/test_utils.rs new file mode 100644 index 0000000000..6cbea50085 --- /dev/null +++ b/extensions/pairing/guest/src/halo2curves_shims/bn254/test_utils.rs @@ -0,0 +1,35 @@ +use core::mem::transmute; + +use halo2curves_axiom::{ + bn256::{Fq12, Gt}, + pairing::MillerLoopResult, +}; +use hex_literal::hex; +use lazy_static::lazy_static; +use num_bigint::BigUint; +use num_traits::Pow; +use openvm_algebra_guest::ExpBytes; + +lazy_static! { + pub static ref BN254_MODULUS: BigUint = BigUint::from_bytes_be(&hex!( + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47" + )); + pub static ref BN254_ORDER: BigUint = BigUint::from_bytes_be(&hex!( + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" + )); +} + +// Manual final exponentiation because halo2curves `MillerLoopResult` doesn't have constructor +pub fn final_exp(f: Fq12) -> Fq12 { + let p = BN254_MODULUS.clone(); + let r = BN254_ORDER.clone(); + let exp: BigUint = (p.pow(12u32) - BigUint::from(1u32)) / r; + ExpBytes::exp_bytes(&f, true, &exp.to_bytes_be()) +} + +// Gt(Fq12) is not public +pub fn assert_miller_results_eq(a: Gt, b: Fq12) { + let a = a.final_exponentiation(); + let b = final_exp(b); + assert_eq!(unsafe { transmute::(a) }, b); +} diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/tests/mod.rs b/extensions/pairing/guest/src/halo2curves_shims/bn254/tests/mod.rs index 70d484dc79..723f78fbf7 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bn254/tests/mod.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bn254/tests/mod.rs @@ -1,19 +1,10 @@ use alloc::vec::Vec; -use core::mem::transmute; -use halo2curves_axiom::{ - bn256::{Fq, Fq12, Fq2, G1Affine, G2Affine, Gt}, - pairing::MillerLoopResult, -}; +use halo2curves_axiom::bn256::{Fq, Fq2, G1Affine, G2Affine}; use itertools::izip; -use num_bigint::BigUint; -use num_traits::Pow; -use openvm_algebra_guest::ExpBytes; use openvm_ecc_guest::AffinePoint; use rand::{rngs::StdRng, SeedableRng}; -use crate::bn254::{BN254_MODULUS, BN254_ORDER}; - #[cfg(test)] mod test_final_exp; #[cfg(test)] @@ -21,21 +12,6 @@ mod test_line; #[cfg(test)] mod test_miller_loop; -// Manual final exponentiation because halo2curves `MillerLoopResult` doesn't have constructor -pub fn final_exp(f: Fq12) -> Fq12 { - let p = BN254_MODULUS.clone(); - let r = BN254_ORDER.clone(); - let exp: BigUint = (p.pow(12u32) - BigUint::from(1u32)) / r; - ExpBytes::exp_bytes(&f, true, &exp.to_bytes_be()) -} - -// Gt(Fq12) is not public -pub fn assert_miller_results_eq(a: Gt, b: Fq12) { - let a = a.final_exponentiation(); - let b = final_exp(b); - assert_eq!(unsafe { transmute::(a) }, b); -} - #[allow(non_snake_case)] #[allow(clippy::type_complexity)] pub fn generate_test_points_bn254( diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/tests/test_miller_loop.rs b/extensions/pairing/guest/src/halo2curves_shims/bn254/tests/test_miller_loop.rs index 1b073c30c8..e4003064a8 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bn254/tests/test_miller_loop.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bn254/tests/test_miller_loop.rs @@ -2,8 +2,11 @@ use alloc::vec::Vec; use halo2curves_axiom::bn256::G2Prepared; -use super::{assert_miller_results_eq, generate_test_points_bn254}; -use crate::{halo2curves_shims::bn254::Bn254, pairing::MultiMillerLoop}; +use super::generate_test_points_bn254; +use crate::{ + halo2curves_shims::bn254::{test_utils::assert_miller_results_eq, Bn254}, + pairing::MultiMillerLoop, +}; #[allow(non_snake_case)] fn run_miller_loop_test(rand_seeds: &[u64]) { diff --git a/extensions/pairing/guest/src/lib.rs b/extensions/pairing/guest/src/lib.rs index d0fdcf5eab..3570c36b22 100644 --- a/extensions/pairing/guest/src/lib.rs +++ b/extensions/pairing/guest/src/lib.rs @@ -28,6 +28,7 @@ pub use openvm_algebra_guest as algebra; /// These should **only** be importable on a host machine. #[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] pub mod halo2curves_shims; + /// Traits for optimal Ate pairing check using intrinsic functions. pub mod pairing; diff --git a/extensions/pairing/guest/src/pairing/mod.rs b/extensions/pairing/guest/src/pairing/mod.rs index 3a751025e4..b0f3be102f 100644 --- a/extensions/pairing/guest/src/pairing/mod.rs +++ b/extensions/pairing/guest/src/pairing/mod.rs @@ -2,8 +2,6 @@ mod final_exp; mod line; mod miller_loop; mod miller_step; -mod operations; -mod sextic_ext_field; pub use final_exp::*; pub use line::*; @@ -14,9 +12,6 @@ use openvm_algebra_guest::{ ExpBytes, Field, IntMod, }; use openvm_ecc_guest::AffinePoint; -#[allow(unused_imports)] -pub(crate) use operations::*; -pub use sextic_ext_field::*; use crate::PairingBaseFunct7; @@ -101,20 +96,6 @@ mod tests { "13", } - impl Field for F13 { - type SelfRef<'a> = &'a Self; - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } - } - #[test] fn test_pairing_check_fallback() { let a = F13::from_u8(2); diff --git a/extensions/pairing/tests/Cargo.toml b/extensions/pairing/tests/Cargo.toml deleted file mode 100644 index a88caa0de3..0000000000 --- a/extensions/pairing/tests/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "openvm-pairing-integration-tests" -description = "Integration tests for the OpenVM pairing extension" -version.workspace = true -authors.workspace = true -edition.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -openvm-instructions = { workspace = true } -openvm-stark-sdk.workspace = true -openvm-circuit = { workspace = true, features = ["test-utils"] } -openvm-transpiler.workspace = true -openvm-algebra-circuit.workspace = true -openvm-algebra-transpiler.workspace = true -openvm-pairing-circuit.workspace = true -openvm-pairing-transpiler.workspace = true -openvm-pairing-guest.workspace = true -openvm-ecc-circuit.workspace = true -openvm-ecc-guest.workspace = true -openvm-ecc-transpiler.workspace = true -openvm-rv32im-transpiler.workspace = true -openvm = { workspace = true } -openvm-toolchain-tests = { workspace = true } -eyre.workspace = true -rand.workspace = true -num-bigint.workspace = true -num-traits.workspace = true - -[features] -default = ["parallel"] -parallel = ["openvm-circuit/parallel"] diff --git a/extensions/pairing/tests/programs/examples/bls_ec.rs b/extensions/pairing/tests/programs/examples/bls_ec.rs deleted file mode 100644 index dc59c7d702..0000000000 --- a/extensions/pairing/tests/programs/examples/bls_ec.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_main)] -#![cfg_attr(not(feature = "std"), no_std)] - -#[allow(unused_imports)] -use openvm_pairing_guest::bls12_381::Bls12_381G1Affine; - -openvm_algebra_moduli_macros::moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" -} - -openvm_ecc_sw_macros::sw_init! { - Bls12_381G1Affine, -} - -openvm::entry!(main); - -pub fn main() { - setup_all_moduli(); - setup_all_curves(); -} diff --git a/extensions/rv32im/circuit/src/divrem/core.rs b/extensions/rv32im/circuit/src/divrem/core.rs index ccb73dc26c..ba74d99502 100644 --- a/extensions/rv32im/circuit/src/divrem/core.rs +++ b/extensions/rv32im/circuit/src/divrem/core.rs @@ -31,7 +31,7 @@ use strum::IntoEnumIterator; #[repr(C)] #[derive(AlignedBorrow, StructReflection)] pub struct DivRemCoreCols { - // b = c * q + r for some 0 <= |r| < |c| and sign(r) = sign(b). + // b = c * q + r for some 0 <= |r| < |c| and sign(r) = sign(b) or r = 0. pub b: [T; NUM_LIMBS], pub c: [T; NUM_LIMBS], pub q: [T; NUM_LIMBS], diff --git a/extensions/rv32im/circuit/src/extension.rs b/extensions/rv32im/circuit/src/extension.rs index f1f67d3994..f8dd2fbf54 100644 --- a/extensions/rv32im/circuit/src/extension.rs +++ b/extensions/rv32im/circuit/src/extension.rs @@ -1,7 +1,8 @@ use derive_more::derive::From; use openvm_circuit::{ arch::{ - SystemConfig, SystemPort, VmExtension, VmInventory, VmInventoryBuilder, VmInventoryError, + InitFileGenerator, SystemConfig, SystemPort, VmExtension, VmInventory, VmInventoryBuilder, + VmInventoryError, }, system::phantom::PhantomChip, }; @@ -34,6 +35,9 @@ pub struct Rv32IConfig { pub io: Rv32Io, } +// Default implementation uses no init file +impl InitFileGenerator for Rv32IConfig {} + /// Config for a VM with base extension, IO extension, and multiplication extension #[derive(Clone, Debug, Default, VmConfig, derive_new::new, Serialize, Deserialize)] pub struct Rv32ImConfig { @@ -43,6 +47,9 @@ pub struct Rv32ImConfig { pub mul: Rv32M, } +// Default implementation uses no init file +impl InitFileGenerator for Rv32ImConfig {} + impl Default for Rv32IConfig { fn default() -> Self { let system = SystemConfig::default().with_continuations(); @@ -352,6 +359,10 @@ impl VmExtension for Rv32I { phantom::Rv32PrintStrSubEx, PhantomDiscriminant(Rv32Phantom::PrintStr as u16), )?; + builder.add_phantom_sub_executor( + phantom::Rv32HintLoadByKeySubEx, + PhantomDiscriminant(Rv32Phantom::HintLoadByKey as u16), + )?; Ok(inventory) } @@ -504,6 +515,7 @@ mod phantom { } } pub struct Rv32PrintStrSubEx; + pub struct Rv32HintLoadByKeySubEx; impl PhantomSubExecutor for Rv32HintInputSubEx { fn phantom_execute( @@ -579,4 +591,59 @@ mod phantom { Ok(()) } } + + impl PhantomSubExecutor for Rv32HintLoadByKeySubEx { + fn phantom_execute( + &mut self, + memory: &MemoryController, + streams: &mut Streams, + _: PhantomDiscriminant, + a: F, + b: F, + _: u16, + ) -> eyre::Result<()> { + let ptr = unsafe_read_rv32_register(memory, a); + let len = unsafe_read_rv32_register(memory, b); + let key: Vec = (0..len) + .map(|i| { + memory + .unsafe_read_cell(F::TWO, F::from_canonical_u32(ptr + i)) + .as_canonical_u32() as u8 + }) + .collect(); + if let Some(val) = streams.kv_store.get(&key) { + let to_push = hint_load_by_key_decode::(val); + for input in to_push.into_iter().rev() { + streams.input_stream.push_front(input); + } + } else { + bail!("Rv32HintLoadByKey: key not found"); + } + Ok(()) + } + } + + pub fn hint_load_by_key_decode(value: &[u8]) -> Vec> { + let mut offset = 0; + let len = extract_u32(value, offset) as usize; + offset += 4; + let mut ret = Vec::with_capacity(len); + for _ in 0..len { + let v_len = extract_u32(value, offset) as usize; + offset += 4; + let v = (0..v_len) + .map(|_| { + let ret = F::from_canonical_u32(extract_u32(value, offset)); + offset += 4; + ret + }) + .collect(); + ret.push(v); + } + ret + } + + fn extract_u32(value: &[u8], offset: usize) -> u32 { + u32::from_le_bytes(value[offset..offset + 4].try_into().unwrap()) + } } diff --git a/extensions/rv32im/circuit/src/hintstore/mod.rs b/extensions/rv32im/circuit/src/hintstore/mod.rs index ef8ecd87e0..60de0840d8 100644 --- a/extensions/rv32im/circuit/src/hintstore/mod.rs +++ b/extensions/rv32im/circuit/src/hintstore/mod.rs @@ -320,7 +320,10 @@ impl Rv32HintStoreChip { } } pub fn set_streams(&mut self, streams: Arc>>) { - self.streams.set(streams).unwrap(); + self.streams + .set(streams) + .map_err(|_| "streams have already been set.") + .unwrap(); } } diff --git a/extensions/rv32im/guest/Cargo.toml b/extensions/rv32im/guest/Cargo.toml index 75ed32ec1e..d98173bcdf 100644 --- a/extensions/rv32im/guest/Cargo.toml +++ b/extensions/rv32im/guest/Cargo.toml @@ -11,5 +11,8 @@ repository.workspace = true openvm-custom-insn = { workspace = true } strum_macros = { workspace = true } +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +p3-field = { workspace = true } + [features] default = [] diff --git a/extensions/rv32im/guest/src/io.rs b/extensions/rv32im/guest/src/io.rs index 5f70533f1b..664b9b1117 100644 --- a/extensions/rv32im/guest/src/io.rs +++ b/extensions/rv32im/guest/src/io.rs @@ -55,6 +55,18 @@ pub fn hint_random(len: usize) { ); } +/// Hint the VM to load values with key = [ptr: len] into input streams. +#[inline(always)] +pub fn hint_load_by_key(ptr: *const u8, len: u32) { + openvm_custom_insn::custom_insn_i!( + opcode = SYSTEM_OPCODE, + funct3 = PHANTOM_FUNCT3, + rd = In ptr, + rs1 = In len, + imm = Const PhantomImm::HintLoadByKey as u16, + ); +} + /// Store rs1 to [[rd] + imm]_3. #[macro_export] macro_rules! reveal { @@ -69,6 +81,21 @@ macro_rules! reveal { }; } +/// Store rs1 to [[rd]]_4. +#[macro_export] +macro_rules! store_to_native { + ($rd:ident, $rs1:ident) => { + openvm_custom_insn::custom_insn_r!( + opcode = openvm_rv32im_guest::SYSTEM_OPCODE, + funct3 = openvm_rv32im_guest::NATIVE_STOREW_FUNCT3, + funct7 = openvm_rv32im_guest::NATIVE_STOREW_FUNCT7, + rd = In $rd, + rs1 = In $rs1, + rs2 = In $rs1, + ) + }; +} + /// Print UTF-8 string encoded as bytes to host stdout for debugging purposes. #[inline(always)] pub fn print_str_from_bytes(str_as_bytes: &[u8]) { diff --git a/extensions/rv32im/guest/src/lib.rs b/extensions/rv32im/guest/src/lib.rs index d4c860b822..99f1a6f97f 100644 --- a/extensions/rv32im/guest/src/lib.rs +++ b/extensions/rv32im/guest/src/lib.rs @@ -1,8 +1,10 @@ #![no_std] +extern crate alloc; /// Library functions for user input/output. #[cfg(target_os = "zkvm")] mod io; + #[cfg(target_os = "zkvm")] pub use io::*; use strum_macros::FromRepr; @@ -12,6 +14,8 @@ pub const SYSTEM_OPCODE: u8 = 0x0b; pub const CSR_OPCODE: u8 = 0b1110011; pub const RV32_ALU_OPCODE: u8 = 0b0110011; pub const RV32M_FUNCT7: u8 = 0x01; +pub const NATIVE_STOREW_FUNCT3: u8 = 0b111; +pub const NATIVE_STOREW_FUNCT7: u32 = 2; pub const TERMINATE_FUNCT3: u8 = 0b000; pub const HINT_FUNCT3: u8 = 0b001; @@ -28,4 +32,19 @@ pub enum PhantomImm { HintInput = 0, PrintStr, HintRandom, + HintLoadByKey, +} + +/// Encode a 2d-array of field elements into bytes for `hint_load_by_key` +#[cfg(not(target_os = "zkvm"))] +pub fn hint_load_by_key_encode( + value: &[alloc::vec::Vec], +) -> alloc::vec::Vec { + let len = value.len(); + let mut ret = (len as u32).to_le_bytes().to_vec(); + for v in value { + ret.extend((v.len() as u32).to_le_bytes()); + ret.extend(v.iter().flat_map(|x| x.as_canonical_u32().to_le_bytes())); + } + ret } diff --git a/extensions/rv32im/tests/Cargo.toml b/extensions/rv32im/tests/Cargo.toml index 21282f9b2f..2e68359532 100644 --- a/extensions/rv32im/tests/Cargo.toml +++ b/extensions/rv32im/tests/Cargo.toml @@ -13,6 +13,7 @@ openvm-stark-sdk.workspace = true openvm-circuit = { workspace = true, features = ["test-utils"] } openvm-transpiler.workspace = true openvm-rv32im-circuit.workspace = true +openvm-rv32im-guest.workspace = true openvm-rv32im-transpiler.workspace = true openvm = { workspace = true } openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } diff --git a/extensions/rv32im/tests/programs/Cargo.toml b/extensions/rv32im/tests/programs/Cargo.toml index b052b7be9a..12915c0da5 100644 --- a/extensions/rv32im/tests/programs/Cargo.toml +++ b/extensions/rv32im/tests/programs/Cargo.toml @@ -13,13 +13,20 @@ serde = { version = "1.0", default-features = false, features = [ "alloc", "derive", ] } +getrandom = { version = "0.3", optional = true } [features] default = [] std = ["serde/std", "openvm/std"] heap-embedded-alloc = ["openvm/heap-embedded-alloc"] +getrandom-unsupported = ["openvm/getrandom-unsupported"] +getrandom = ["dep:getrandom"] [profile.release] panic = "abort" lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing # strip = "symbols" + +[[example]] +name = "getrandom" +required-features = ["getrandom"] diff --git a/extensions/rv32im/tests/programs/examples/getrandom.rs b/extensions/rv32im/tests/programs/examples/getrandom.rs new file mode 100644 index 0000000000..353bf47355 --- /dev/null +++ b/extensions/rv32im/tests/programs/examples/getrandom.rs @@ -0,0 +1,39 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +use getrandom::Error; + +fn get_random_u128() -> Result { + let mut buf = [0u8; 16]; + getrandom::fill(&mut buf)?; + Ok(u128::from_ne_bytes(buf)) +} + +openvm::entry!(main); + +pub fn main() { + // do unrelated stuff + let mut c = core::hint::black_box(0); + for _ in 0..10 { + c += 1; + } + + #[cfg(not(feature = "getrandom-unsupported"))] + { + // not a good random function! + assert_eq!(get_random_u128(), Ok(0)); + } + #[cfg(feature = "getrandom-unsupported")] + { + assert!(get_random_u128().is_err()); + } +} + +// custom user-specified getrandom +#[cfg(all(feature = "getrandom", not(feature = "getrandom-unsupported")))] +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error> { + for i in 0..len { + *dest.add(i) = 0u8; + } + Ok(()) +} diff --git a/extensions/rv32im/tests/programs/examples/hint_load_by_key.rs b/extensions/rv32im/tests/programs/examples/hint_load_by_key.rs new file mode 100644 index 0000000000..c770ac2268 --- /dev/null +++ b/extensions/rv32im/tests/programs/examples/hint_load_by_key.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +use openvm::io::{hint_load_by_key, read_vec}; + +openvm::entry!(main); + +pub fn main() { + const KEY: &str = "key"; + hint_load_by_key(KEY.as_bytes()); + + let vec = read_vec(); + // assert_eq!(vec.len(), 4); + if vec.len() != 4 { + openvm::process::panic(); + } + #[allow(clippy::needless_range_loop)] + for i in 0..4 { + if vec[i] != i as u8 { + openvm::process::panic(); + } + } + let vec = read_vec(); + if vec.len() != 3 { + openvm::process::panic(); + } + #[allow(clippy::needless_range_loop)] + for i in 0..3 { + if vec[i] != i as u8 { + openvm::process::panic(); + } + } +} diff --git a/extensions/rv32im/tests/src/lib.rs b/extensions/rv32im/tests/src/lib.rs index e59088eac8..8bd4b42763 100644 --- a/extensions/rv32im/tests/src/lib.rs +++ b/extensions/rv32im/tests/src/lib.rs @@ -1,13 +1,16 @@ #[cfg(test)] mod tests { + use std::{collections::HashMap, sync::Arc}; + use eyre::Result; use openvm_circuit::{ - arch::{hasher::poseidon2::vm_poseidon2_hasher, ExecutionError, VmExecutor}, + arch::{hasher::poseidon2::vm_poseidon2_hasher, ExecutionError, Streams, VmExecutor}, system::memory::tree::public_values::UserPublicValuesProof, utils::{air_test, air_test_with_min_segments}, }; use openvm_instructions::exe::VmExe; use openvm_rv32im_circuit::{Rv32IConfig, Rv32ImConfig}; + use openvm_rv32im_guest::hint_load_by_key_encode; use openvm_rv32im_transpiler::{ Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, }; @@ -23,7 +26,8 @@ mod tests { #[test_case("fibonacci", 1)] fn test_rv32i(example_name: &str, min_segments: usize) -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), example_name)?; + let config = Rv32IConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), example_name, &config)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -31,14 +35,14 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; - let config = Rv32IConfig::default(); air_test_with_min_segments(config, exe, vec![], min_segments); Ok(()) } #[test_case("collatz", 1)] fn test_rv32im(example_name: &str, min_segments: usize) -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), example_name)?; + let config = Rv32ImConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), example_name, &config)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -46,7 +50,6 @@ mod tests { .with_extension(Rv32IoTranspilerExtension) .with_extension(Rv32MTranspilerExtension), )?; - let config = Rv32ImConfig::default(); air_test_with_min_segments(config, exe, vec![], min_segments); Ok(()) } @@ -54,10 +57,12 @@ mod tests { // #[test_case("fibonacci", 1)] #[test_case("collatz", 1)] fn test_rv32im_std(example_name: &str, min_segments: usize) -> Result<()> { + let config = Rv32ImConfig::default(); let elf = build_example_program_at_path_with_features( get_programs_dir!(), example_name, ["std"], + &config, )?; let exe = VmExe::from_elf( elf, @@ -66,14 +71,14 @@ mod tests { .with_extension(Rv32IoTranspilerExtension) .with_extension(Rv32MTranspilerExtension), )?; - let config = Rv32ImConfig::default(); air_test_with_min_segments(config, exe, vec![], min_segments); Ok(()) } #[test] fn test_read_vec() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "hint")?; + let config = Rv32IConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), "hint", &config)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -81,15 +86,15 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; - let config = Rv32IConfig::default(); let input = vec![[0, 1, 2, 3].map(F::from_canonical_u8).to_vec()]; air_test_with_min_segments(config, exe, input, 1); Ok(()) } #[test] - fn test_read() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "read")?; + fn test_hint_load_by_key() -> Result<()> { + let config = Rv32IConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), "hint_load_by_key", &config)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -97,7 +102,29 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; + // stdin will be read after reading kv_store + let stdin = vec![[0, 1, 2].map(F::from_canonical_u8).to_vec()]; + let mut streams: Streams = stdin.into(); + let input = vec![[0, 1, 2, 3].map(F::from_canonical_u8).to_vec()]; + streams.kv_store = Arc::new(HashMap::from([( + "key".as_bytes().to_vec(), + hint_load_by_key_encode(&input), + )])); + air_test_with_min_segments(config, exe, streams, 1); + Ok(()) + } + + #[test] + fn test_read() -> Result<()> { let config = Rv32IConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), "read", &config)?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; #[derive(serde::Serialize)] struct Foo { @@ -120,7 +147,8 @@ mod tests { #[test] fn test_reveal() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "reveal")?; + let config = Rv32IConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), "reveal", &config)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -128,7 +156,6 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; - let config = Rv32IConfig::default(); let executor = VmExecutor::::new(config.clone()); let final_memory = executor.execute(exe, vec![])?.unwrap(); let hasher = vm_poseidon2_hasher(); @@ -159,7 +186,8 @@ mod tests { #[test] fn test_print() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "print")?; + let config = Rv32IConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), "print", &config)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -167,14 +195,14 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; - let config = Rv32IConfig::default(); air_test(config, exe); Ok(()) } #[test] fn test_heap_overflow() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "heap_overflow")?; + let config = Rv32ImConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), "heap_overflow", &config)?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -182,7 +210,6 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; - let config = Rv32ImConfig::default(); let executor = VmExecutor::::new(config.clone()); match executor.execute(exe, vec![[0, 0, 0, 1].map(F::from_canonical_u8).to_vec()]) { @@ -194,8 +221,13 @@ mod tests { #[test] fn test_hashmap() -> Result<()> { - let elf = - build_example_program_at_path_with_features(get_programs_dir!(), "hashmap", ["std"])?; + let config = Rv32ImConfig::default(); + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "hashmap", + ["std"], + &config, + )?; let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -203,17 +235,18 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; - let config = Rv32ImConfig::default(); air_test(config, exe); Ok(()) } #[test] fn test_tiny_mem_test() -> Result<()> { + let config = Rv32ImConfig::default(); let elf = build_example_program_at_path_with_features( get_programs_dir!(), "tiny-mem-test", ["heap-embedded-alloc"], + &config, )?; let exe = VmExe::from_elf( elf, @@ -222,7 +255,6 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; - let config = Rv32ImConfig::default(); air_test(config, exe); Ok(()) } @@ -230,7 +262,8 @@ mod tests { #[test] #[should_panic] fn test_load_x0() { - let elf = build_example_program_at_path(get_programs_dir!(), "load_x0").unwrap(); + let config = Rv32ImConfig::default(); + let elf = build_example_program_at_path(get_programs_dir!(), "load_x0", &config).unwrap(); let exe = VmExe::from_elf( elf, Transpiler::::default() @@ -239,8 +272,29 @@ mod tests { .with_extension(Rv32IoTranspilerExtension), ) .unwrap(); - let config = Rv32ImConfig::default(); let executor = VmExecutor::::new(config.clone()); executor.execute(exe, vec![]).unwrap(); } + + #[test_case(vec!["getrandom", "getrandom-unsupported"])] + #[test_case(vec!["getrandom"])] + fn test_getrandom_unsupported(features: Vec<&str>) { + let config = Rv32ImConfig::default(); + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "getrandom", + &features, + &config, + ) + .unwrap(); + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + ) + .unwrap(); + air_test(config, exe); + } } diff --git a/extensions/rv32im/transpiler/src/instructions.rs b/extensions/rv32im/transpiler/src/instructions.rs index 0cd013a8ba..38c3b42419 100644 --- a/extensions/rv32im/transpiler/src/instructions.rs +++ b/extensions/rv32im/transpiler/src/instructions.rs @@ -279,4 +279,6 @@ pub enum Rv32Phantom { PrintStr, /// Prepare given amount of random numbers for hinting. HintRandom, + /// Hint the VM to load values from the stream KV store into input streams. + HintLoadByKey, } diff --git a/extensions/rv32im/transpiler/src/lib.rs b/extensions/rv32im/transpiler/src/lib.rs index 445ef9f43e..abd4413022 100644 --- a/extensions/rv32im/transpiler/src/lib.rs +++ b/extensions/rv32im/transpiler/src/lib.rs @@ -6,7 +6,8 @@ use openvm_instructions::{ }; use openvm_rv32im_guest::{ PhantomImm, CSRRW_FUNCT3, CSR_OPCODE, HINT_BUFFER_IMM, HINT_FUNCT3, HINT_STOREW_IMM, - PHANTOM_FUNCT3, REVEAL_FUNCT3, RV32M_FUNCT7, RV32_ALU_OPCODE, SYSTEM_OPCODE, TERMINATE_FUNCT3, + NATIVE_STOREW_FUNCT3, NATIVE_STOREW_FUNCT7, PHANTOM_FUNCT3, REVEAL_FUNCT3, RV32M_FUNCT7, + RV32_ALU_OPCODE, SYSTEM_OPCODE, TERMINATE_FUNCT3, }; use openvm_stark_backend::p3_field::PrimeField32; use openvm_transpiler::{ @@ -91,6 +92,12 @@ impl TranspilerExtension for Rv32ITranspilerExtension { F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1), 0, ), + PhantomImm::HintLoadByKey => Instruction::phantom( + PhantomDiscriminant(Rv32Phantom::HintLoadByKey as u16), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1), + 0, + ), }) } (RV32_ALU_OPCODE, _) => { @@ -149,9 +156,6 @@ impl TranspilerExtension for Rv32IoTranspilerExtension { if opcode != SYSTEM_OPCODE { return None; } - if funct3 != HINT_FUNCT3 && funct3 != REVEAL_FUNCT3 { - return None; - } let instruction = match funct3 { HINT_FUNCT3 => { @@ -192,6 +196,23 @@ impl TranspilerExtension for Rv32IoTranspilerExtension { (dec_insn.imm < 0) as isize, )) } + NATIVE_STOREW_FUNCT3 => { + // NATIVE_STOREW is a pseudo-instruction for STOREW_RV32 a,b,0,1,4 + let dec_insn = RType::new(instruction_u32); + if dec_insn.funct7 != NATIVE_STOREW_FUNCT7 { + return None; + } + Some(Instruction::large_from_isize( + Rv32LoadStoreOpcode::STOREW.global_opcode(), + (RV32_REGISTER_NUM_LIMBS * dec_insn.rs1) as isize, + (RV32_REGISTER_NUM_LIMBS * dec_insn.rd) as isize, + 0, + 1, + 4, + 1, + 0, + )) + } _ => return None, }; diff --git a/extensions/sha256/circuit/src/extension.rs b/extensions/sha256/circuit/src/extension.rs index 76a6c1ec0c..783bc54f63 100644 --- a/extensions/sha256/circuit/src/extension.rs +++ b/extensions/sha256/circuit/src/extension.rs @@ -1,6 +1,9 @@ use derive_more::derive::From; use openvm_circuit::{ - arch::{SystemConfig, VmExtension, VmInventory, VmInventoryBuilder, VmInventoryError}, + arch::{ + InitFileGenerator, SystemConfig, VmExtension, VmInventory, VmInventoryBuilder, + VmInventoryError, + }, system::phantom::PhantomChip, }; use openvm_circuit_derive::{AnyEnum, InstructionExecutor, VmConfig}; @@ -46,6 +49,9 @@ impl Default for Sha256Rv32Config { } } +// Default implementation uses no init file +impl InitFileGenerator for Sha256Rv32Config {} + #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] pub struct Sha256; diff --git a/extensions/sha256/guest/Cargo.toml b/extensions/sha256/guest/Cargo.toml index ee39453272..e9d28292b8 100644 --- a/extensions/sha256/guest/Cargo.toml +++ b/extensions/sha256/guest/Cargo.toml @@ -8,8 +8,5 @@ description = "Guest extension for Sha256" [dependencies] openvm-platform = { workspace = true } -[target.'cfg(not(target_os = "zkvm"))'.dependencies] -sha2 = { version = "0.10", default-features = false } - [features] default = [] diff --git a/extensions/sha256/guest/src/lib.rs b/extensions/sha256/guest/src/lib.rs index cb34bcd5aa..1c51a272fd 100644 --- a/extensions/sha256/guest/src/lib.rs +++ b/extensions/sha256/guest/src/lib.rs @@ -5,14 +5,6 @@ pub const OPCODE: u8 = 0x0b; pub const SHA256_FUNCT3: u8 = 0b100; pub const SHA256_FUNCT7: u8 = 0x1; -/// The sha256 cryptographic hash function. -#[inline(always)] -pub fn sha256(input: &[u8]) -> [u8; 32] { - let mut output = [0u8; 32]; - set_sha256(input, &mut output); - output -} - /// zkvm native implementation of sha256 /// # Safety /// @@ -25,21 +17,6 @@ pub fn sha256(input: &[u8]) -> [u8; 32] { #[cfg(target_os = "zkvm")] #[inline(always)] #[no_mangle] -extern "C" fn zkvm_sha256_impl(bytes: *const u8, len: usize, output: *mut u8) { +pub extern "C" fn zkvm_sha256_impl(bytes: *const u8, len: usize, output: *mut u8) { openvm_platform::custom_insn_r!(opcode = OPCODE, funct3 = SHA256_FUNCT3, funct7 = SHA256_FUNCT7, rd = In output, rs1 = In bytes, rs2 = In len); } - -/// Sets `output` to the sha256 hash of `input`. -pub fn set_sha256(input: &[u8], output: &mut [u8; 32]) { - #[cfg(not(target_os = "zkvm"))] - { - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - hasher.update(input); - output.copy_from_slice(hasher.finalize().as_ref()); - } - #[cfg(target_os = "zkvm")] - { - zkvm_sha256_impl(input.as_ptr(), input.len(), output.as_mut_ptr() as *mut u8); - } -} diff --git a/extensions/sha256/tests/Cargo.toml b/extensions/sha256/tests/Cargo.toml deleted file mode 100644 index 52fb11f9ea..0000000000 --- a/extensions/sha256/tests/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "openvm-sha256-integration-tests" -description = "Integration tests for the OpenVM sha256 extension" -version.workspace = true -authors.workspace = true -edition.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -openvm-instructions = { workspace = true } -openvm-stark-sdk.workspace = true -openvm-circuit = { workspace = true, features = ["test-utils"] } -openvm-transpiler.workspace = true -openvm-sha256-transpiler.workspace = true -openvm-sha256-circuit.workspace = true -openvm-rv32im-transpiler.workspace = true -openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } -eyre.workspace = true - -[features] -default = ["parallel"] -parallel = ["openvm-circuit/parallel"] diff --git a/guest-libs/ff_derive/Cargo.toml b/guest-libs/ff_derive/Cargo.toml new file mode 100644 index 0000000000..54d4628897 --- /dev/null +++ b/guest-libs/ff_derive/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "openvm-ff-derive" +description = "OpenVM fork of ff_derive for finite field arithmetic" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[features] +# enabled when generating bitvec code utilizing the version of ff's bitvec +bits = [] + +[lib] +proc-macro = true + +[dependencies] +addchain = "0.2" +num-bigint = "0.3" +num-traits = "0.2" +num-integer = "0.1" +proc-macro2 = "1" +quote = "1" +syn = { version = "1", features = ["full"] } + +[dev-dependencies] +openvm-instructions = { workspace = true } +openvm-stark-sdk = { workspace = true } +openvm-circuit = { workspace = true, features = ["test-utils", "parallel"]} +openvm-transpiler = { workspace = true } +openvm-algebra-transpiler = { workspace = true } +openvm-algebra-circuit = { workspace = true } +openvm-rv32im-transpiler = { workspace = true } +openvm-toolchain-tests = { workspace = true } + +eyre = { workspace = true } +num-bigint = { workspace = true } + diff --git a/guest-libs/ff_derive/src/lib.rs b/guest-libs/ff_derive/src/lib.rs new file mode 100644 index 0000000000..1dcf0c21cd --- /dev/null +++ b/guest-libs/ff_derive/src/lib.rs @@ -0,0 +1,1689 @@ +#![recursion_limit = "1024"] + +extern crate proc_macro; +extern crate proc_macro2; + +use std::{iter, str::FromStr}; + +use num_bigint::BigUint; +use num_integer::Integer; +use num_traits::{One, ToPrimitive, Zero}; +use quote::{quote, TokenStreamExt}; + +mod pow_fixed; + +enum ReprEndianness { + Big, + Little, +} + +impl FromStr for ReprEndianness { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "big" => Ok(ReprEndianness::Big), + "little" => Ok(ReprEndianness::Little), + _ => Err(()), + } + } +} + +impl ReprEndianness { + fn modulus_repr(&self, modulus: &BigUint, bytes: usize) -> Vec { + match self { + ReprEndianness::Big => { + let buf = modulus.to_bytes_be(); + iter::repeat(0).take(bytes - buf.len()).chain(buf).collect() + } + ReprEndianness::Little => { + let mut buf = modulus.to_bytes_le(); + buf.extend(iter::repeat(0).take(bytes - buf.len())); + buf + } + } + } + + // Clippy things methods named from_* don't take self as a parameter + #[allow(clippy::wrong_self_convention)] + fn from_repr(&self, name: &syn::Ident, limbs: usize) -> proc_macro2::TokenStream { + let read_repr = match self { + ReprEndianness::Big => quote! { + <#name as ::openvm_algebra_guest::IntMod>::from_be_bytes(&r.as_ref()) + }, + ReprEndianness::Little => quote! { + <#name as ::openvm_algebra_guest::IntMod>::from_le_bytes(&r.as_ref()) + }, + }; + + let zkvm_impl = quote! { + #read_repr + }; + + let read_repr = match self { + ReprEndianness::Big => quote! { + ::ff::derive::byteorder::BigEndian::read_u64_into(r.as_ref(), &mut inner[..]); + inner.reverse(); + }, + ReprEndianness::Little => quote! { + ::ff::derive::byteorder::LittleEndian::read_u64_into(r.as_ref(), &mut inner[..]); + }, + }; + + let non_zkvm_impl = quote! { + { + use ::ff::derive::byteorder::ByteOrder; + + let mut inner = [0u64; #limbs]; + #read_repr + #name(inner) + } + }; + + quote! { + #[cfg(target_os = "zkvm")] + let r = #zkvm_impl; + #[cfg(not(target_os = "zkvm"))] + let r = #non_zkvm_impl; + } + } + + fn to_repr( + &self, + repr: proc_macro2::TokenStream, + mont_reduce_self_params: &proc_macro2::TokenStream, + limbs: usize, + ) -> proc_macro2::TokenStream { + let bytes = limbs * 8; + + let write_repr = match self { + ReprEndianness::Big => quote! { + ::to_be_bytes(self)[..#bytes].try_into().unwrap() + }, + ReprEndianness::Little => quote! { + ::as_le_bytes(self)[..#bytes].try_into().unwrap() + }, + }; + + let zkvm_impl = quote! { + #repr(#write_repr) + }; + + let write_repr = match self { + ReprEndianness::Big => quote! { + r.0.reverse(); + ::ff::derive::byteorder::BigEndian::write_u64_into(&r.0, &mut repr[..]); + }, + ReprEndianness::Little => quote! { + ::ff::derive::byteorder::LittleEndian::write_u64_into(&r.0, &mut repr[..]); + }, + }; + + let non_zkvm_impl = quote! { + use ::ff::derive::byteorder::ByteOrder; + + let mut r = *self; + r.mont_reduce( + #mont_reduce_self_params + ); + + let mut repr = [0u8; #bytes]; + #write_repr + #repr(repr) + }; + + quote! { + #[cfg(target_os = "zkvm")] + { + #zkvm_impl + } + #[cfg(not(target_os = "zkvm"))] + { + #non_zkvm_impl + } + } + } + + fn iter_be(&self) -> proc_macro2::TokenStream { + // We aren't implementing for zkvm here because this function is only used in the prime + // field repr impl which is a plain array of bytes + match self { + ReprEndianness::Big => quote! {self.0.iter()}, + ReprEndianness::Little => quote! {self.0.iter().rev()}, + } + } +} + +/// Derive the `PrimeField` trait. +/// **Warning**: This macro removes the struct definition and inserts our own zkvm-compatible struct +/// into the token stream. +/// Currently the memory layout of the new struct will always be either 32 or 48-bytes, where the +/// smallest that will fit the field's modulus is used. Moduli that are larger than 48-bytes are not +/// yet supported. +// Required attributes: PrimeFieldModulus, PrimeFieldGenerator, PrimeFieldReprEndianness +// Note: In our fork, we changed the macro from a derive macro to an attribute-style macro because +// we need to be able to remove the struct definition and insert our own into the token stream. +#[proc_macro_attribute] +pub fn openvm_prime_field( + _: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + // Parse the type definition + let ast: syn::Item = syn::parse_macro_input!(input); + + // The attribute should be applied to a struct. + let mut ast = match ast { + syn::Item::Struct(ast) => ast, + _ => { + return syn::Error::new_spanned(ast, "PrimeField derive only works for structs.") + .to_compile_error() + .into(); + } + }; + + // We're given the modulus p of the prime field + let modulus: BigUint = fetch_attr("PrimeFieldModulus", &ast.attrs) + .expect("Please supply a PrimeFieldModulus attribute") + .parse() + .expect("PrimeFieldModulus should be a number"); + + // We may be provided with a generator of p - 1 order. It is required that this generator be + // quadratic nonresidue. + // TODO: Compute this ourselves. + let generator: BigUint = fetch_attr("PrimeFieldGenerator", &ast.attrs) + .expect("Please supply a PrimeFieldGenerator attribute") + .parse() + .expect("PrimeFieldGenerator should be a number"); + + // Field element representations may be in little-endian or big-endian. + let endianness = fetch_attr("PrimeFieldReprEndianness", &ast.attrs) + .expect("Please supply a PrimeFieldReprEndianness attribute") + .parse() + .expect("PrimeFieldReprEndianness should be 'big' or 'little'"); + + // The arithmetic in this library only works if the modulus*2 is smaller than the backing + // representation. Compute the number of 64-bit limbs we need. + let mut limbs = 1; + { + let mod2 = (&modulus) << 1; // modulus * 2 + let mut cur = BigUint::one() << 64; // always 64-bit limbs for now + while cur < mod2 { + limbs += 1; + cur <<= 64; + } + } + + let bytes = modulus.bits().div_ceil(8); + let zkvm_limbs = if bytes <= 32 { + 32 + } else if bytes <= 48 { + 48 + } else { + // A limitation of our zkvm implementation is that we only support moduli up to 48 bytes. + return syn::Error::new_spanned( + ast, + "PrimeField modulus is too large. Only 48 byte moduli are supported.", + ) + .to_compile_error() + .into(); + }; + + // The struct we're deriving for must be a wrapper around `pub [u64; limbs]`. + if let Some(err) = validate_struct(&ast, limbs) { + return err.into(); + } + + // Generate the identifier for the "Repr" type we must construct. + let repr_ident = syn::Ident::new( + &format!("{}Repr", ast.ident), + proc_macro2::Span::call_site(), + ); + + let mut gen = proc_macro2::TokenStream::new(); + + // Remove the attributes from the struct so we can insert it back into the code + ast.attrs.clear(); + + // Call moduli_declare! to define the struct + let openvm_struct = openvm_struct_impl(&ast, &modulus); + + // TODO: test the non-zkvm case + gen.extend(quote! { + #[cfg(target_os = "zkvm")] + #openvm_struct + #[cfg(not(target_os = "zkvm"))] + #ast + }); + + let (constants_impl, sqrt_impl) = + prime_field_constants_and_sqrt(&ast.ident, &modulus, limbs, zkvm_limbs, generator); + + gen.extend(constants_impl); + gen.extend(prime_field_repr_impl(&repr_ident, &endianness, limbs * 8)); + gen.extend(prime_field_impl( + &ast.ident, + &repr_ident, + &modulus, + &endianness, + limbs, + zkvm_limbs, + sqrt_impl, + )); + + // Return the generated impl + gen.into() +} + +fn openvm_struct_impl(ast: &syn::ItemStruct, modulus: &BigUint) -> proc_macro2::TokenStream { + let struct_ident = &ast.ident; + let modulus_str = modulus.to_str_radix(10); + quote! { + ::openvm_algebra_moduli_macros::moduli_declare! { + #struct_ident { + modulus = #modulus_str + } + } + } +} + +/// Checks that `body` contains `pub [u64; limbs]`. +fn validate_struct(ast: &syn::ItemStruct, limbs: usize) -> Option { + // The struct should contain a single unnamed field. + let fields = match &ast.fields { + syn::Fields::Unnamed(x) if x.unnamed.len() == 1 => x, + _ => { + return Some( + syn::Error::new_spanned( + &ast.ident, + format!( + "The struct must contain an array of limbs. Change this to `{}([u64; {}])`", + ast.ident, limbs, + ), + ) + .to_compile_error(), + ) + } + }; + let field = &fields.unnamed[0]; + + // The field should be an array. + let arr = match &field.ty { + syn::Type::Array(x) => x, + _ => { + return Some( + syn::Error::new_spanned( + field, + format!( + "The inner field must be an array of limbs. Change this to `[u64; {}]`", + limbs, + ), + ) + .to_compile_error(), + ) + } + }; + + // The array's element type should be `u64`. + if match arr.elem.as_ref() { + syn::Type::Path(path) => path.path.get_ident().map(|x| *x != "u64").unwrap_or(true), + _ => true, + } { + return Some( + syn::Error::new_spanned( + arr, + format!( + "PrimeField derive requires 64-bit limbs. Change this to `[u64; {}]", + limbs + ), + ) + .to_compile_error(), + ); + } + + // The array's length should be a literal int equal to `limbs`. + let expr_lit = match &arr.len { + syn::Expr::Lit(expr_lit) => Some(&expr_lit.lit), + syn::Expr::Group(expr_group) => match &*expr_group.expr { + syn::Expr::Lit(expr_lit) => Some(&expr_lit.lit), + _ => None, + }, + _ => None, + }; + let lit_int = match match expr_lit { + Some(syn::Lit::Int(lit_int)) => Some(lit_int), + _ => None, + } { + Some(x) => x, + _ => { + return Some( + syn::Error::new_spanned( + arr, + format!("To derive PrimeField, change this to `[u64; {}]`.", limbs), + ) + .to_compile_error(), + ) + } + }; + if lit_int.base10_digits() != limbs.to_string() { + return Some( + syn::Error::new_spanned( + lit_int, + format!("The given modulus requires {} limbs.", limbs), + ) + .to_compile_error(), + ); + } + + // The field should not be public. + match &field.vis { + syn::Visibility::Inherited => (), + _ => { + return Some( + syn::Error::new_spanned(&field.vis, "Field must not be public.").to_compile_error(), + ) + } + } + + // Valid! + None +} + +/// Fetch an attribute string from the derived struct. +fn fetch_attr(name: &str, attrs: &[syn::Attribute]) -> Option { + for attr in attrs { + if let Ok(meta) = attr.parse_meta() { + match meta { + syn::Meta::NameValue(nv) => { + if nv.path.get_ident().map(|i| i.to_string()) == Some(name.to_string()) { + match nv.lit { + syn::Lit::Str(ref s) => return Some(s.value()), + _ => { + panic!("attribute {} should be a string", name); + } + } + } + } + _ => { + panic!("attribute {} should be a string", name); + } + } + } + } + + None +} + +// Implement the wrapped ident `repr` with `bytes` bytes. +fn prime_field_repr_impl( + repr: &syn::Ident, + endianness: &ReprEndianness, + bytes: usize, +) -> proc_macro2::TokenStream { + let repr_iter_be = endianness.iter_be(); + + quote! { + #[derive(Copy, Clone)] + pub struct #repr(pub [u8; #bytes]); + + impl ::ff::derive::subtle::ConstantTimeEq for #repr { + fn ct_eq(&self, other: &#repr) -> ::ff::derive::subtle::Choice { + self.0 + .iter() + .zip(other.0.iter()) + .map(|(a, b)| a.ct_eq(b)) + .fold(1.into(), |acc, x| acc & x) + } + } + + impl ::core::cmp::PartialEq for #repr { + fn eq(&self, other: &#repr) -> bool { + use ::ff::derive::subtle::ConstantTimeEq; + self.ct_eq(other).into() + } + } + + impl ::core::cmp::Eq for #repr { } + + impl ::core::default::Default for #repr { + fn default() -> #repr { + #repr([0u8; #bytes]) + } + } + + impl ::core::fmt::Debug for #repr + { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "0x")?; + for i in #repr_iter_be { + write!(f, "{:02x}", *i)?; + } + + Ok(()) + } + } + + impl AsRef<[u8]> for #repr { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for #repr { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + } +} + +/// Convert BigUint into a vector of 64-bit limbs. +fn biguint_to_real_u64_vec(mut v: BigUint, limbs: usize) -> Vec { + let m = BigUint::one() << 64; + let mut ret = vec![]; + + while v > BigUint::zero() { + let limb: BigUint = &v % &m; + ret.push(limb.to_u64().unwrap()); + v >>= 64; + } + + while ret.len() < limbs { + ret.push(0); + } + + assert!(ret.len() == limbs); + + ret +} + +/// Convert BigUint into a tokenized vector of 64-bit limbs. +fn biguint_to_u64_vec(v: BigUint, limbs: usize) -> proc_macro2::TokenStream { + let ret = biguint_to_real_u64_vec(v, limbs); + quote!([#(#ret,)*]) +} + +/// Returns a token stream containing a little-endian bytes representation of `v`. +fn biguint_to_u8_vec(v: BigUint, limbs: usize) -> proc_macro2::TokenStream { + let mut bytes = v.to_bytes_le(); + while bytes.len() < limbs { + bytes.push(0); + } + quote!([#(#bytes,)*]) +} + +fn biguint_num_bits(mut v: BigUint) -> u32 { + let mut bits = 0; + + while v != BigUint::zero() { + v >>= 1; + bits += 1; + } + + bits +} + +/// BigUint modular exponentiation by square-and-multiply. +fn exp(base: BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint { + let mut ret = BigUint::one(); + + for i in exp + .to_bytes_be() + .into_iter() + .flat_map(|x| (0..8).rev().map(move |i| (x >> i).is_odd())) + { + ret = (&ret * &ret) % modulus; + if i { + ret = (ret * &base) % modulus; + } + } + + ret +} + +#[test] +fn test_exp() { + assert_eq!( + exp( + BigUint::from_str("4398572349857239485729348572983472345").unwrap(), + &BigUint::from_str("5489673498567349856734895").unwrap(), + &BigUint::from_str( + "52435875175126190479447740508185965837690552500527637822603658699938581184513" + ) + .unwrap() + ), + BigUint::from_str( + "4371221214068404307866768905142520595925044802278091865033317963560480051536" + ) + .unwrap() + ); +} + +fn prime_field_constants_and_sqrt( + name: &syn::Ident, + modulus: &BigUint, + limbs: usize, + zkvm_limbs: usize, + generator: BigUint, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let bytes = limbs * 8; + let modulus_num_bits = biguint_num_bits(modulus.clone()); + + // The number of bits we should "shave" from a randomly sampled representation, i.e., + // if our modulus is 381 bits and our representation is 384 bits, we should shave + // 3 bits from the beginning of a randomly sampled 384 bit representation to + // reduce the cost of rejection sampling. + let repr_shave_bits = (64 * limbs as u32) - biguint_num_bits(modulus.clone()); + + // Compute R = 2**(64 * limbs) mod m + let r = (BigUint::one() << (limbs * 64)) % modulus; + let to_mont = |v| (v * &r) % modulus; + + let two = BigUint::from_str("2").unwrap(); + let p_minus_2 = modulus - &two; + let invert = |v| exp(v, &p_minus_2, modulus); + + // 2^-1 mod m + let two_inv = biguint_to_u64_vec(to_mont(invert(two.clone())), limbs); + let two_inv_bytes = biguint_to_u8_vec(invert(two), zkvm_limbs); + + // modulus - 1 = 2^s * t + let mut s: u32 = 0; + let mut t = modulus - BigUint::from_str("1").unwrap(); + while t.is_even() { + t >>= 1; + s += 1; + } + + // Compute 2^s root of unity given the generator + let root_of_unity_biguint = exp(generator.clone(), &t, modulus); + + let root_of_unity_inv_biguint = invert(root_of_unity_biguint.clone()); + let root_of_unity_inv = biguint_to_u64_vec(to_mont(root_of_unity_inv_biguint.clone()), limbs); + let root_of_unity_inv_bytes = biguint_to_u8_vec(root_of_unity_inv_biguint, zkvm_limbs); + + let root_of_unity = biguint_to_u64_vec(to_mont(root_of_unity_biguint.clone()), limbs); + let root_of_unity_bytes = biguint_to_u8_vec(root_of_unity_biguint, zkvm_limbs); + + let delta_biguint = exp(generator.clone(), &(BigUint::one() << s), modulus); + let delta = biguint_to_u64_vec(to_mont(delta_biguint.clone()), limbs); + let delta_bytes = biguint_to_u8_vec(delta_biguint, zkvm_limbs); + + let generator_u64_limbs = biguint_to_u64_vec(to_mont(generator.clone()), limbs); + let generator_bytes = biguint_to_u8_vec(generator, zkvm_limbs); + + let sqrt_impl = + if (modulus % BigUint::from_str("4").unwrap()) == BigUint::from_str("3").unwrap() { + // Addition chain for (r + 1) // 4 + let mod_plus_1_over_4 = pow_fixed::generate( + "e! {self}, + (modulus + BigUint::from_str("1").unwrap()) >> 2, + ); + + quote! { + use ::ff::derive::subtle::ConstantTimeEq; + + // Because r = 3 (mod 4) + // sqrt can be done with only one exponentiation, + // via the computation of self^((r + 1) // 4) (mod r) + let sqrt = { + #mod_plus_1_over_4 + }; + + ::ff::derive::subtle::CtOption::new( + sqrt, + (sqrt * &sqrt).ct_eq(self), // Only return Some if it's the square root. + ) + } + } else { + // Addition chain for (t - 1) // 2 + let t_minus_1_over_2 = if t == BigUint::one() { + quote!( #name::ONE ) + } else { + pow_fixed::generate("e! {self}, (&t - BigUint::one()) >> 1) + }; + + quote! { + // Tonelli-Shanks algorithm works for every remaining odd prime. + // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) + use ::ff::derive::subtle::{ConditionallySelectable, ConstantTimeEq}; + + // w = self^((t - 1) // 2) + let w = { + #t_minus_1_over_2 + }; + + let mut v = S; + let mut x = *self * &w; + let mut b = x * &w; + + // Initialize z as the 2^S root of unity. + let mut z = ROOT_OF_UNITY; + + for max_v in (1..=S).rev() { + let mut k = 1; + let mut tmp = b.square(); + let mut j_less_than_v: ::ff::derive::subtle::Choice = 1.into(); + + for j in 2..max_v { + let tmp_is_one = tmp.ct_eq(&#name::ONE); + let squared = #name::conditional_select(&tmp, &z, tmp_is_one).square(); + tmp = #name::conditional_select(&squared, &tmp, tmp_is_one); + let new_z = #name::conditional_select(&z, &squared, tmp_is_one); + j_less_than_v &= !j.ct_eq(&v); + k = u32::conditional_select(&j, &k, tmp_is_one); + z = #name::conditional_select(&z, &new_z, j_less_than_v); + } + + let result = x * &z; + x = #name::conditional_select(&result, &x, b.ct_eq(&#name::ONE)); + z = z.square(); + b *= &z; + v = k; + } + + ::ff::derive::subtle::CtOption::new( + x, + (x * &x).ct_eq(self), // Only return Some if it's the square root. + ) + } + }; + + // Compute R^2 mod m + let r2 = biguint_to_u64_vec((&r * &r) % modulus, limbs); + + let r = biguint_to_u64_vec(r, limbs); + let modulus_le_bytes = ReprEndianness::Little.modulus_repr(modulus, limbs * 8); + let modulus_str = format!("0x{}", modulus.to_str_radix(16)); + let modulus = biguint_to_real_u64_vec(modulus.clone(), limbs); + + // Compute -m^-1 mod 2**64 by exponentiating by totient(2**64) - 1 + let mut inv = 1u64; + for _ in 0..63 { + inv = inv.wrapping_mul(inv); + inv = inv.wrapping_mul(modulus[0]); + } + inv = inv.wrapping_neg(); + + ( + quote! { + type REPR_BYTES = [u8; #bytes]; + type REPR_BITS = REPR_BYTES; + + /// This is the modulus m of the prime field + const MODULUS: REPR_BITS = [#(#modulus_le_bytes,)*]; + + /// This is the modulus m of the prime field in limb form + #[cfg(not(target_os = "zkvm"))] + const MODULUS_LIMBS: #name = #name([#(#modulus,)*]); + + /// This is the modulus m of the prime field in hex string form + const MODULUS_STR: &'static str = #modulus_str; + + /// The number of bits needed to represent the modulus. + const MODULUS_BITS: u32 = #modulus_num_bits; + + /// The number of bits that must be shaved from the beginning of + /// the representation when randomly sampling. + const REPR_SHAVE_BITS: u32 = #repr_shave_bits; + + /// 2^{limbs*64} mod m + #[cfg(not(target_os = "zkvm"))] + const R: #name = #name(#r); + + /// 2^{limbs*64*2} mod m + #[cfg(not(target_os = "zkvm"))] + const R2: #name = #name(#r2); + + /// -(m^{-1} mod m) mod m + #[cfg(not(target_os = "zkvm"))] + const INV: u64 = #inv; + + /// 2^{-1} mod m + #[cfg(target_os = "zkvm")] + const TWO_INV: #name = <#name>::from_const_bytes(#two_inv_bytes); + #[cfg(not(target_os = "zkvm"))] + const TWO_INV: #name = #name(#two_inv); + + /// Multiplicative generator of `MODULUS` - 1 order, also quadratic + /// nonresidue. + #[cfg(target_os = "zkvm")] + const GENERATOR: #name = <#name>::from_const_bytes(#generator_bytes); + #[cfg(not(target_os = "zkvm"))] + const GENERATOR: #name = #name(#generator_u64_limbs); + + /// 2^s * t = MODULUS - 1 with t odd + const S: u32 = #s; + + /// 2^s root of unity computed by GENERATOR^t + #[cfg(target_os = "zkvm")] + const ROOT_OF_UNITY: #name = <#name>::from_const_bytes(#root_of_unity_bytes); + #[cfg(not(target_os = "zkvm"))] + const ROOT_OF_UNITY: #name = #name(#root_of_unity); + + /// (2^s)^{-1} mod m + #[cfg(target_os = "zkvm")] + const ROOT_OF_UNITY_INV: #name = <#name>::from_const_bytes(#root_of_unity_inv_bytes); + #[cfg(not(target_os = "zkvm"))] + const ROOT_OF_UNITY_INV: #name = #name(#root_of_unity_inv); + + /// GENERATOR^{2^s} + #[cfg(target_os = "zkvm")] + const DELTA: #name = <#name>::from_const_bytes(#delta_bytes); + #[cfg(not(target_os = "zkvm"))] + const DELTA: #name = #name(#delta); + }, + sqrt_impl, + ) +} + +/// Implement PrimeField for the derived type. +fn prime_field_impl( + name: &syn::Ident, + repr: &syn::Ident, + modulus: &BigUint, + endianness: &ReprEndianness, + limbs: usize, + zkvm_limbs: usize, + sqrt_impl: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + // Returns r{n} as an ident. + fn get_temp(n: usize) -> syn::Ident { + syn::Ident::new(&format!("r{}", n), proc_macro2::Span::call_site()) + } + + // The parameter list for the mont_reduce() internal method. + // r0: u64, mut r1: u64, mut r2: u64, ... + let mut mont_paramlist = proc_macro2::TokenStream::new(); + mont_paramlist.append_separated( + (0..(limbs * 2)).map(|i| (i, get_temp(i))).map(|(i, x)| { + if i != 0 { + quote! {mut #x: u64} + } else { + quote! {#x: u64} + } + }), + proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone), + ); + + // Implement montgomery reduction for some number of limbs + fn mont_impl(limbs: usize) -> proc_macro2::TokenStream { + #[cfg(target_os = "zkvm")] + { + quote! { + unimplemented!(); + } + } + #[cfg(not(target_os = "zkvm"))] + { + let mut gen = proc_macro2::TokenStream::new(); + + for i in 0..limbs { + { + let temp = get_temp(i); + gen.extend(quote! { + let k = #temp.wrapping_mul(INV); + let (_, carry) = ::ff::derive::mac(#temp, k, MODULUS_LIMBS.0[0], 0); + }); + } + + for j in 1..limbs { + let temp = get_temp(i + j); + gen.extend(quote! { + let (#temp, carry) = ::ff::derive::mac(#temp, k, MODULUS_LIMBS.0[#j], carry); + }); + } + + let temp = get_temp(i + limbs); + + if i == 0 { + gen.extend(quote! { + let (#temp, carry2) = ::ff::derive::adc(#temp, 0, carry); + }); + } else { + gen.extend(quote! { + let (#temp, carry2) = ::ff::derive::adc(#temp, carry2, carry); + }); + } + } + + for i in 0..limbs { + let temp = get_temp(limbs + i); + + gen.extend(quote! { + self.0[#i] = #temp; + }); + } + + gen + } + } + + fn sqr_impl(a: proc_macro2::TokenStream, limbs: usize) -> proc_macro2::TokenStream { + let mut gen = proc_macro2::TokenStream::new(); + + if limbs > 1 { + for i in 0..(limbs - 1) { + gen.extend(quote! { + let carry = 0; + }); + + for j in (i + 1)..limbs { + let temp = get_temp(i + j); + if i == 0 { + gen.extend(quote! { + let (#temp, carry) = ::ff::derive::mac(0, #a.0[#i], #a.0[#j], carry); + }); + } else { + gen.extend(quote! { + let (#temp, carry) = ::ff::derive::mac(#temp, #a.0[#i], #a.0[#j], carry); + }); + } + } + + let temp = get_temp(i + limbs); + + gen.extend(quote! { + let #temp = carry; + }); + } + + for i in 1..(limbs * 2) { + let temp0 = get_temp(limbs * 2 - i); + let temp1 = get_temp(limbs * 2 - i - 1); + + if i == 1 { + gen.extend(quote! { + let #temp0 = #temp1 >> 63; + }); + } else if i == (limbs * 2 - 1) { + gen.extend(quote! { + let #temp0 = #temp0 << 1; + }); + } else { + gen.extend(quote! { + let #temp0 = (#temp0 << 1) | (#temp1 >> 63); + }); + } + } + } else { + let temp1 = get_temp(1); + gen.extend(quote! { + let #temp1 = 0; + }); + } + + for i in 0..limbs { + let temp0 = get_temp(i * 2); + let temp1 = get_temp(i * 2 + 1); + if i == 0 { + gen.extend(quote! { + let (#temp0, carry) = ::ff::derive::mac(0, #a.0[#i], #a.0[#i], 0); + }); + } else { + gen.extend(quote! { + let (#temp0, carry) = ::ff::derive::mac(#temp0, #a.0[#i], #a.0[#i], carry); + }); + } + + gen.extend(quote! { + let (#temp1, carry) = ::ff::derive::adc(#temp1, 0, carry); + }); + } + + let mut mont_calling = proc_macro2::TokenStream::new(); + mont_calling.append_separated( + (0..(limbs * 2)).map(get_temp), + proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone), + ); + + gen.extend(quote! { + let mut ret = *self; + ret.mont_reduce(#mont_calling); + ret + }); + + gen + } + + fn mul_impl( + a: proc_macro2::TokenStream, + b: proc_macro2::TokenStream, + limbs: usize, + ) -> proc_macro2::TokenStream { + let mut gen = proc_macro2::TokenStream::new(); + + for i in 0..limbs { + gen.extend(quote! { + let carry = 0; + }); + + for j in 0..limbs { + let temp = get_temp(i + j); + + if i == 0 { + gen.extend(quote! { + let (#temp, carry) = ::ff::derive::mac(0, #a.0[#i], #b.0[#j], carry); + }); + } else { + gen.extend(quote! { + let (#temp, carry) = ::ff::derive::mac(#temp, #a.0[#i], #b.0[#j], carry); + }); + } + } + + let temp = get_temp(i + limbs); + + gen.extend(quote! { + let #temp = carry; + }); + } + + let mut mont_calling = proc_macro2::TokenStream::new(); + mont_calling.append_separated( + (0..(limbs * 2)).map(get_temp), + proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone), + ); + + gen.extend(quote! { + self.mont_reduce(#mont_calling); + }); + + gen + } + + /// Generates an implementation of multiplicative inversion within the target prime + /// field. + fn inv_impl(a: proc_macro2::TokenStream, modulus: &BigUint) -> proc_macro2::TokenStream { + // Addition chain for p - 2 + let mod_minus_2 = pow_fixed::generate(&a, modulus - BigUint::from(2u64)); + + quote! { + use ::ff::derive::subtle::ConstantTimeEq; + + // By Euler's theorem, if `a` is coprime to `p` (i.e. `gcd(a, p) = 1`), then: + // a^-1 ≡ a^(phi(p) - 1) mod p + // + // `ff_derive` requires that `p` is prime; in this case, `phi(p) = p - 1`, and + // thus: + // a^-1 ≡ a^(p - 2) mod p + let inv = { + #mod_minus_2 + }; + + ::ff::derive::subtle::CtOption::new(inv, !#a.is_zero()) + } + } + + let squaring_impl = sqr_impl(quote! {self}, limbs); + let multiply_impl = mul_impl(quote! {self}, quote! {other}, limbs); + let invert_impl = inv_impl(quote! {self}, modulus); + let montgomery_impl = mont_impl(limbs); + + fn mont_reduce_params(a: proc_macro2::TokenStream, limbs: usize) -> proc_macro2::TokenStream { + // a.0[0], a.0[1], ..., 0, 0, 0, 0, ... + let mut mont_reduce_params = proc_macro2::TokenStream::new(); + mont_reduce_params.append_separated( + (0..limbs) + .map(|i| quote! { #a.0[#i] }) + .chain((0..limbs).map(|_| quote! {0})), + proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone), + ); + mont_reduce_params + } + + let mont_reduce_self_params = mont_reduce_params(quote! {self}, limbs); + let mont_reduce_other_params = mont_reduce_params(quote! {other}, limbs); + + let from_repr_impl = endianness.from_repr(name, limbs); + let to_repr_impl = endianness.to_repr(quote! {#repr}, &mont_reduce_self_params, limbs); + + let prime_field_bits_impl = if cfg!(feature = "bits") { + let to_le_bits_impl = ReprEndianness::Little.to_repr( + quote! {::ff::derive::bitvec::array::BitArray::new}, + &mont_reduce_self_params, + limbs, + ); + + Some(quote! { + impl ::ff::PrimeFieldBits for #name { + type ReprBits = REPR_BITS; + + fn to_le_bits(&self) -> ::ff::FieldBits { + #to_le_bits_impl + } + + fn char_le_bits() -> ::ff::FieldBits { + ::ff::FieldBits::new(MODULUS) + } + } + }) + } else { + None + }; + + let top_limb_index = limbs - 1; + + // Since moduli_declare! already implements some traits, we need to conditionally + // compile some of the trait impls depending on whether we're in zkvm or not. + // So, we create a new module with #[cfg(not(target_os = "zkvm"))] and place the impls in there. + let impl_module_ident = + syn::Ident::new(&format!("impl_{}", name), proc_macro2::Span::call_site()); + + let zero_impl = quote! { + #[cfg(target_os = "zkvm")] + const ZERO: Self = ::ZERO; + #[cfg(not(target_os = "zkvm"))] + const ZERO: Self = #name([0; #limbs]); + }; + let one_impl = quote! { + #[cfg(target_os = "zkvm")] + const ONE: Self = ::ONE; + #[cfg(not(target_os = "zkvm"))] + const ONE: Self = R; + }; + + quote! { + impl ::core::marker::Copy for #name { } + + impl ::core::default::Default for #name { + fn default() -> #name { + use ::ff::Field; + #name::ZERO + } + } + + impl ::ff::derive::subtle::ConstantTimeEq for #name { + fn ct_eq(&self, other: &#name) -> ::ff::derive::subtle::Choice { + use ::ff::PrimeField; + self.to_repr().ct_eq(&other.to_repr()) + } + } + + /// Elements are ordered lexicographically. + impl Ord for #name { + #[inline(always)] + fn cmp(&self, other: &#name) -> ::core::cmp::Ordering { + #[cfg(target_os = "zkvm")] + { + ::assert_reduced(self); + ::assert_reduced(other); + + self.cmp_native(other) + } + #[cfg(not(target_os = "zkvm"))] + { + let mut a = *self; + a.mont_reduce( + #mont_reduce_self_params + ); + + let mut b = *other; + b.mont_reduce( + #mont_reduce_other_params + ); + + a.cmp_native(&b) + } + } + } + + impl PartialOrd for #name { + #[inline(always)] + fn partial_cmp(&self, other: &#name) -> Option<::core::cmp::Ordering> { + Some(self.cmp(other)) + } + } + + impl From for #name { + #[inline(always)] + fn from(val: u64) -> #name { + #[cfg(target_os = "zkvm")] + { + <#name as ::openvm_algebra_guest::IntMod>::from_u64(val) + } + #[cfg(not(target_os = "zkvm"))] + { + let mut raw = [0u64; #limbs]; + raw[0] = val; + #name(raw) * R2 + } + } + } + + impl From<#name> for #repr { + fn from(e: #name) -> #repr { + use ::ff::PrimeField; + e.to_repr() + } + } + + impl<'a> From<&'a #name> for #repr { + fn from(e: &'a #name) -> #repr { + use ::ff::PrimeField; + e.to_repr() + } + } + + impl ::ff::derive::subtle::ConditionallySelectable for #name { + fn conditional_select(a: &#name, b: &#name, choice: ::ff::derive::subtle::Choice) -> #name { + #[cfg(target_os = "zkvm")] + { + let mut res = [0u8; #zkvm_limbs]; + let a_le_bytes = ::as_le_bytes(a); + let b_le_bytes = ::as_le_bytes(b); + for i in 0..#zkvm_limbs { + res[i] = u8::conditional_select(&a_le_bytes[i], &b_le_bytes[i], choice); + } + #name(res) + } + #[cfg(not(target_os = "zkvm"))] + { + let mut res = [0u64; #limbs]; + for i in 0..#limbs { + res[i] = u64::conditional_select(&a.0[i], &b.0[i], choice); + } + #name(res) + } + } + } + + // All the traits that are implemented in this module are already implemented + // on our zkvm-compatible struct, so we need to conditionally implement them + #[cfg(not(target_os = "zkvm"))] + mod #impl_module_ident { + use super::{#name, MODULUS_LIMBS}; + + impl ::core::clone::Clone for #name { + fn clone(&self) -> #name { + *self + } + } + + impl ::core::cmp::PartialEq for #name { + fn eq(&self, other: &#name) -> bool { + use ::ff::derive::subtle::ConstantTimeEq; + self.ct_eq(other).into() + } + } + + impl ::core::cmp::Eq for #name { } + + impl ::core::fmt::Debug for #name + { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use ::ff::PrimeField; + write!(f, "{}({:?})", stringify!(#name), self.to_repr()) + } + } + + + impl ::core::ops::Neg for #name { + type Output = #name; + + #[inline] + fn neg(self) -> #name { + use ::ff::Field; + + let mut ret = self; + if !ret.is_zero_vartime() { + let mut tmp = MODULUS_LIMBS; + tmp.sub_noborrow(&ret); + ret = tmp; + } + ret + } + } + + impl<'r> ::core::ops::Add<&'r #name> for #name { + type Output = #name; + + #[inline] + fn add(self, other: &#name) -> #name { + use ::core::ops::AddAssign; + + let mut ret = self; + ret.add_assign(other); + ret + } + } + + impl ::core::ops::Add for #name { + type Output = #name; + + #[inline] + fn add(self, other: #name) -> Self { + self + &other + } + } + + impl<'r> ::core::ops::AddAssign<&'r #name> for #name { + #[inline] + fn add_assign(&mut self, other: &#name) { + // This cannot exceed the backing capacity. + self.add_nocarry(other); + + // However, it may need to be reduced. + self.reduce(); + } + } + + impl ::core::ops::AddAssign for #name { + #[inline] + fn add_assign(&mut self, other: #name) { + self.add_assign(&other); + } + } + + impl<'r> ::core::ops::Sub<&'r #name> for #name { + type Output = #name; + + #[inline] + fn sub(self, other: &#name) -> Self { + use ::core::ops::SubAssign; + + let mut ret = self; + ret.sub_assign(other); + ret + } + } + + impl ::core::ops::Sub for #name { + type Output = #name; + + #[inline] + fn sub(self, other: #name) -> Self { + self - &other + } + } + + impl<'r> ::core::ops::SubAssign<&'r #name> for #name { + #[inline] + fn sub_assign(&mut self, other: &#name) { + // If `other` is larger than `self`, we'll need to add the modulus to self first. + if other.cmp_native(self) == ::core::cmp::Ordering::Greater { + self.add_nocarry(&MODULUS_LIMBS); + } + + self.sub_noborrow(other); + } + } + + impl ::core::ops::SubAssign for #name { + #[inline] + fn sub_assign(&mut self, other: #name) { + self.sub_assign(&other); + } + } + + impl<'r> ::core::ops::Mul<&'r #name> for #name { + type Output = #name; + + #[inline] + fn mul(self, other: &#name) -> Self { + use ::core::ops::MulAssign; + + let mut ret = self; + ret.mul_assign(other); + ret + } + } + + impl ::core::ops::Mul for #name { + type Output = #name; + + #[inline] + fn mul(self, other: #name) -> Self { + self * &other + } + } + + impl<'r> ::core::ops::MulAssign<&'r #name> for #name { + #[inline] + fn mul_assign(&mut self, other: &#name) + { + #multiply_impl + } + } + + impl ::core::ops::MulAssign for #name { + #[inline] + fn mul_assign(&mut self, other: #name) + { + self.mul_assign(&other); + } + } + + impl> ::core::iter::Sum for #name { + fn sum>(iter: I) -> Self { + use ::ff::Field; + + iter.fold(Self::ZERO, |acc, item| acc + item.borrow()) + } + } + + impl> ::core::iter::Product for #name { + fn product>(iter: I) -> Self { + use ::ff::Field; + + iter.fold(Self::ONE, |acc, item| acc * item.borrow()) + } + } + } + + impl ::ff::PrimeField for #name { + type Repr = #repr; + + fn from_repr(r: #repr) -> ::ff::derive::subtle::CtOption<#name> { + #from_repr_impl + + #[cfg(target_os = "zkvm")] + { + ::ff::derive::subtle::CtOption::new(r, r.constant_time_is_reduced()) + } + #[cfg(not(target_os = "zkvm"))] + { + // Try to subtract the modulus + let borrow = r.0.iter().zip(MODULUS_LIMBS.0.iter()).fold(0, |borrow, (a, b)| { + ::ff::derive::sbb(*a, *b, borrow).1 + }); + + // If the element is smaller than MODULUS then the + // subtraction will underflow, producing a borrow value + // of 0xffff...ffff. Otherwise, it'll be zero. + let is_some = ::ff::derive::subtle::Choice::from((borrow as u8) & 1); + + // Convert to Montgomery form by computing + // (a.R^0 * R^2) / R = a.R + ::ff::derive::subtle::CtOption::new(r * &R2, is_some) + } + } + + fn from_repr_vartime(r: #repr) -> Option<#name> { + #from_repr_impl + + #[cfg(target_os = "zkvm")] + { + if ::is_reduced(&r) { + Some(r) + } else { + None + } + } + #[cfg(not(target_os = "zkvm"))] + { + if r.is_valid() { + Some(r * R2) + } else { + None + } + + } + } + + fn to_repr(&self) -> #repr { + #to_repr_impl + } + + #[inline(always)] + fn is_odd(&self) -> ::ff::derive::subtle::Choice { + #[cfg(target_os = "zkvm")] + { + ::ff::derive::subtle::Choice::from((::as_le_bytes(self)[0] & 1) as u8) + } + #[cfg(not(target_os = "zkvm"))] + { + let mut r = *self; + r.mont_reduce( + #mont_reduce_self_params + ); + + // TODO: This looks like a constant-time result, but r.mont_reduce() is + // currently implemented using variable-time code. + ::ff::derive::subtle::Choice::from((r.0[0] & 1) as u8) + } + } + + const MODULUS: &'static str = MODULUS_STR; + + const NUM_BITS: u32 = MODULUS_BITS; + + const CAPACITY: u32 = Self::NUM_BITS - 1; + + const TWO_INV: Self = TWO_INV; + + const MULTIPLICATIVE_GENERATOR: Self = GENERATOR; + + const S: u32 = S; + + const ROOT_OF_UNITY: Self = ROOT_OF_UNITY; + + const ROOT_OF_UNITY_INV: Self = ROOT_OF_UNITY_INV; + + const DELTA: Self = DELTA; + } + + #prime_field_bits_impl + + impl ::ff::Field for #name { + #zero_impl + #one_impl + + /// Computes a uniformly random element using rejection sampling. + fn random(mut rng: impl ::ff::derive::rand_core::RngCore) -> Self { + #[cfg(target_os = "zkvm")] + { + panic!("randomn is not implemented for the zkvm"); + } + #[cfg(not(target_os = "zkvm"))] + { + loop { + let mut tmp = { + let mut repr = [0u64; #limbs]; + for i in 0..#limbs { + repr[i] = rng.next_u64(); + } + #name(repr) + }; + + // Mask away the unused most-significant bits. + // Note: In some edge cases, `REPR_SHAVE_BITS` could be 64, in which case + // `0xfff... >> REPR_SHAVE_BITS` overflows. So use `checked_shr` instead. + // This is always sufficient because we will have at most one spare limb + // to accommodate values of up to twice the modulus. + tmp.0[#top_limb_index] &= 0xffffffffffffffffu64.checked_shr(REPR_SHAVE_BITS).unwrap_or(0); + + if tmp.is_valid() { + return tmp + } + } + } + } + + #[inline] + fn is_zero_vartime(&self) -> bool { + #[cfg(target_os = "zkvm")] + { + self == &Self::ZERO + } + #[cfg(not(target_os = "zkvm"))] + { + self.0.iter().all(|&e| e == 0) + } + } + + #[inline] + fn double(&self) -> Self { + #[cfg(target_os = "zkvm")] + { + ::double(self) + } + #[cfg(not(target_os = "zkvm"))] + { + let mut ret = *self; + + // This cannot exceed the backing capacity. + let mut last = 0; + for i in &mut ret.0 { + let tmp = *i >> 63; + *i <<= 1; + *i |= last; + last = tmp; + } + + // However, it may need to be reduced. + ret.reduce(); + + ret + } + } + + /// Note that invert is not constant-time in the zkvm. + fn invert(&self) -> ::ff::derive::subtle::CtOption { + #[cfg(target_os = "zkvm")] + { + let is_self_zero = self.is_zero_vartime(); + let res = if is_self_zero { + ::ZERO + } else { + use ::openvm_algebra_guest::DivUnsafe; + ::ONE.div_unsafe(self) + }; + ::ff::derive::subtle::CtOption::new(res, (!is_self_zero as u8).into()) + } + #[cfg(not(target_os = "zkvm"))] + { + #invert_impl + } + } + + #[inline] + fn square(&self) -> Self + { + #[cfg(target_os = "zkvm")] + { + ::square(self) + } + #[cfg(not(target_os = "zkvm"))] + { + #squaring_impl + } + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (::ff::derive::subtle::Choice, Self) { + ::ff::helpers::sqrt_ratio_generic(num, div) + } + + /// Note that sqrt is not constant-time in the zkvm + fn sqrt(&self) -> ::ff::derive::subtle::CtOption { + #[cfg(target_os = "zkvm")] + { + use ::openvm_algebra_guest::Sqrt; + match Sqrt::sqrt(self) { + Some(sqrt) => ::ff::derive::subtle::CtOption::new(sqrt, 1u8.into()), + None => ::ff::derive::subtle::CtOption::new(Self::ZERO, 0u8.into()), + } + } + #[cfg(not(target_os = "zkvm"))] + { + #sqrt_impl + } + } + } + + impl #name { + /// Compares two elements in native representation. This is only used + /// internally. + #[inline(always)] + fn cmp_native(&self, other: &#name) -> ::core::cmp::Ordering { + for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { + if a < b { + return ::core::cmp::Ordering::Less + } else if a > b { + return ::core::cmp::Ordering::Greater + } + } + + ::core::cmp::Ordering::Equal + } + + /// Determines if the element is really in the field. This is only used + /// internally. + #[inline(always)] + #[cfg(not(target_os = "zkvm"))] + fn is_valid(&self) -> bool { + // The Ord impl calls `reduce`, which in turn calls `is_valid`, so we use + // this internal function to eliminate the cycle. + self.cmp_native(&MODULUS_LIMBS) == ::core::cmp::Ordering::Less + } + + #[inline(always)] + #[cfg(not(target_os = "zkvm"))] + fn add_nocarry(&mut self, other: &#name) { + let mut carry = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + let (new_a, new_carry) = ::ff::derive::adc(*a, *b, carry); + *a = new_a; + carry = new_carry; + } + } + + #[inline(always)] + #[cfg(not(target_os = "zkvm"))] + fn sub_noborrow(&mut self, other: &#name) { + let mut borrow = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + let (new_a, new_borrow) = ::ff::derive::sbb(*a, *b, borrow); + *a = new_a; + borrow = new_borrow; + } + } + + /// Subtracts the modulus from this element if this element is not in the + /// field. Only used internally. + #[inline(always)] + #[cfg(not(target_os = "zkvm"))] + fn reduce(&mut self) { + if !self.is_valid() { + self.sub_noborrow(&MODULUS_LIMBS); + } + } + + #[allow(clippy::too_many_arguments)] + #[inline(always)] + #[cfg(not(target_os = "zkvm"))] + fn mont_reduce( + &mut self, + #mont_paramlist + ) + { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + #montgomery_impl + + self.reduce(); + } + + // A variant of IntMod::is_reduced that runs in constant time + #[cfg(target_os = "zkvm")] + fn constant_time_is_reduced(&self) -> ::ff::derive::subtle::Choice { + let mut is_less = 0u8.into(); + // Iterate over limbs in little endian order and retain the result of the last non-equal comparison. + for (x_limb, p_limb) in self.0.iter().zip(::MODULUS.iter()) { + if x_limb < p_limb { + is_less = 1u8.into(); + } else if x_limb > p_limb { + is_less = 0u8.into(); + } + } + // If all limbs are equal, is_less is false + is_less + } + } + } +} diff --git a/guest-libs/ff_derive/src/pow_fixed.rs b/guest-libs/ff_derive/src/pow_fixed.rs new file mode 100644 index 0000000000..1d2b37ad29 --- /dev/null +++ b/guest-libs/ff_derive/src/pow_fixed.rs @@ -0,0 +1,56 @@ +//! Fixed-exponent variable-base exponentiation using addition chains. + +use addchain::{build_addition_chain, Step}; +use num_bigint::BigUint; +use quote::quote; +use syn::Ident; + +/// Returns t{n} as an ident. +fn get_temp(n: usize) -> Ident { + Ident::new(&format!("t{}", n), proc_macro2::Span::call_site()) +} + +pub(crate) fn generate( + base: &proc_macro2::TokenStream, + exponent: BigUint, +) -> proc_macro2::TokenStream { + let steps = build_addition_chain(exponent); + + let mut gen = proc_macro2::TokenStream::new(); + + // First entry in chain is one, i.e. the base. + let start = get_temp(0); + gen.extend(quote! { + let #start = #base; + }); + + let mut tmps = vec![start]; + for (i, step) in steps.into_iter().enumerate() { + let out = get_temp(i + 1); + + gen.extend(match step { + Step::Double { index } => { + let val = &tmps[index]; + quote! { + let #out = #val.square(); + } + } + Step::Add { left, right } => { + let left = &tmps[left]; + let right = &tmps[right]; + quote! { + let #out = #left * #right; + } + } + }); + + tmps.push(out.clone()); + } + + let end = tmps.last().expect("have last"); + gen.extend(quote! { + #end + }); + + gen +} diff --git a/guest-libs/ff_derive/tests/lib.rs b/guest-libs/ff_derive/tests/lib.rs new file mode 100644 index 0000000000..6df9a1d675 --- /dev/null +++ b/guest-libs/ff_derive/tests/lib.rs @@ -0,0 +1,180 @@ +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use eyre::Result; + use num_bigint::BigUint; + use openvm_algebra_circuit::Rv32ModularConfig; + use openvm_algebra_transpiler::ModularTranspilerExtension; + use openvm_circuit::utils::air_test; + use openvm_instructions::exe::VmExe; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_stark_sdk::p3_baby_bear::BabyBear; + use openvm_toolchain_tests::{ + build_example_program_at_path, build_example_program_at_path_with_features, + get_programs_dir, + }; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + + type F = BabyBear; + + #[test] + fn test_full_limbs() -> Result<()> { + let moduli = ["39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319"] + .map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "full_limbs", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_fermat() -> Result<()> { + let moduli = ["65537"].map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "fermat", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_sqrt() -> Result<()> { + let moduli = ["357686312646216567629137"].map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "sqrt", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_constants() -> Result<()> { + let moduli = + ["52435875175126190479447740508185965837690552500527637822603658699938581184513"] + .map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "constants", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_from_u128() -> Result<()> { + let moduli = + ["52435875175126190479447740508185965837690552500527637822603658699938581184513"] + .map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "from_u128", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_batch_inversion() -> Result<()> { + let moduli = + ["52435875175126190479447740508185965837690552500527637822603658699938581184513"] + .map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + let elf = build_example_program_at_path_with_features( + get_programs_dir!("tests/programs"), + "batch_inversion", + ["std"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_operations() -> Result<()> { + let moduli = + ["52435875175126190479447740508185965837690552500527637822603658699938581184513"] + .map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "operations", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + + air_test(config, openvm_exe); + Ok(()) + } +} diff --git a/guest-libs/ff_derive/tests/programs/Cargo.toml b/guest-libs/ff_derive/tests/programs/Cargo.toml new file mode 100644 index 0000000000..e948238be0 --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/Cargo.toml @@ -0,0 +1,31 @@ +[workspace] +[package] +name = "openvm-ff-derive-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-platform = { path = "../../../../crates/toolchain/platform" } +openvm-algebra-guest = { path = "../../../../extensions/algebra/guest" } +openvm-algebra-moduli-macros = { path = "../../../../extensions/algebra/moduli-macros" } + +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +serde = { version = "1.0", default-features = false, features = [ + "alloc", + "derive", +]} +ff = { version = "0.13.1", features = ["derive"] } +rand = { version = "0.9.1", default-features = false } +num-bigint = { version = "0.4.6", default-features = false } + +openvm-ff-derive = { path = "../../" } + +[features] +default = [] +std = ["serde/std", "openvm/std", "ff/std"] + +[profile.release] +panic = "abort" +lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing +# strip = "symbols" diff --git a/guest-libs/ff_derive/tests/programs/examples/batch_inversion.rs b/guest-libs/ff_derive/tests/programs/examples/batch_inversion.rs new file mode 100644 index 0000000000..c7b1bb7eda --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/examples/batch_inversion.rs @@ -0,0 +1,67 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +openvm::entry!(main); + +extern crate alloc; + +use alloc::{vec, vec::Vec}; + +use openvm_ff_derive::openvm_prime_field; + +/// The BLS12-381 scalar field. +#[openvm_prime_field] +#[PrimeFieldModulus = "52435875175126190479447740508185965837690552500527637822603658699938581184513"] +#[PrimeFieldGenerator = "7"] +#[PrimeFieldReprEndianness = "little"] +struct Bls381K12Scalar([u64; 4]); + +openvm::init!("openvm_init_batch_inversion_std.rs"); + +fn main() { + use ff::{BatchInverter, Field}; + + let one = Bls381K12Scalar::ONE; + + // [1, 2, 3, 4] + let values: Vec<_> = (0..4) + .scan(one, |acc, _| { + let ret = *acc; + *acc += &one; + Some(ret) + }) + .collect(); + + // Test BatchInverter::invert_with_external_scratch + { + let mut elements = values.clone(); + let mut scratch_space = vec![Bls381K12Scalar::ZERO; elements.len()]; + BatchInverter::invert_with_external_scratch(&mut elements, &mut scratch_space); + for (a, a_inv) in values.iter().zip(elements.into_iter()) { + assert_eq!(*a * a_inv, one); + } + } + + // Test BatchInverter::invert_with_internal_scratch + { + let mut items: Vec<_> = values.iter().cloned().map(|p| (p, one)).collect(); + BatchInverter::invert_with_internal_scratch( + &mut items, + |item| &mut item.0, + |item| &mut item.1, + ); + for (a, (a_inv, _)) in values.iter().zip(items.into_iter()) { + assert_eq!(*a * a_inv, one); + } + } + + // Test BatchInvert trait + #[cfg(feature = "std")] + { + use ff::BatchInvert; + let mut elements = values.clone(); + elements.iter_mut().batch_invert(); + for (a, a_inv) in values.iter().zip(elements.into_iter()) { + assert_eq!(*a * a_inv, one); + } + } +} diff --git a/guest-libs/ff_derive/tests/programs/examples/constants.rs b/guest-libs/ff_derive/tests/programs/examples/constants.rs new file mode 100644 index 0000000000..94cf777b25 --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/examples/constants.rs @@ -0,0 +1,52 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +openvm::entry!(main); + +use openvm_ff_derive::openvm_prime_field; + +extern crate alloc; + +/// The BLS12-381 scalar field. +#[openvm_prime_field] +#[PrimeFieldModulus = "52435875175126190479447740508185965837690552500527637822603658699938581184513"] +#[PrimeFieldGenerator = "7"] +#[PrimeFieldReprEndianness = "little"] +struct Bls381K12Scalar([u64; 4]); + +openvm::init!("openvm_init_constants.rs"); + +fn main() { + use ff::{Field, PrimeField}; + + assert_eq!( + Bls381K12Scalar::MODULUS, + "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", + ); + + assert_eq!( + Bls381K12Scalar::from(2) * Bls381K12Scalar::TWO_INV, + Bls381K12Scalar::ONE, + ); + + assert_eq!( + Bls381K12Scalar::ROOT_OF_UNITY * Bls381K12Scalar::ROOT_OF_UNITY_INV, + Bls381K12Scalar::ONE, + ); + + // ROOT_OF_UNITY^{2^s} mod m == 1 + assert_eq!( + Bls381K12Scalar::ROOT_OF_UNITY.pow([1u64 << Bls381K12Scalar::S, 0, 0, 0]), + Bls381K12Scalar::ONE, + ); + + // DELTA^{t} mod m == 1 + assert_eq!( + Bls381K12Scalar::DELTA.pow([ + 0xfffe5bfeffffffff, + 0x09a1d80553bda402, + 0x299d7d483339d808, + 0x73eda753, + ]), + Bls381K12Scalar::ONE, + ); +} diff --git a/guest-libs/ff_derive/tests/programs/examples/fermat.rs b/guest-libs/ff_derive/tests/programs/examples/fermat.rs new file mode 100644 index 0000000000..a1ddb1f33f --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/examples/fermat.rs @@ -0,0 +1,19 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +openvm::entry!(main); + +extern crate alloc; + +use openvm_ff_derive::openvm_prime_field; + +/// The largest known Fermat prime, used to test the case `t = 1`. +#[openvm_prime_field] +#[PrimeFieldModulus = "65537"] +#[PrimeFieldGenerator = "3"] +#[PrimeFieldReprEndianness = "little"] +struct Fermat65537Field([u64; 1]); + +openvm::init!("openvm_init_fermat.rs"); + +fn main() {} diff --git a/guest-libs/ff_derive/tests/programs/examples/from_u128.rs b/guest-libs/ff_derive/tests/programs/examples/from_u128.rs new file mode 100644 index 0000000000..0059cfd1fa --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/examples/from_u128.rs @@ -0,0 +1,27 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +openvm::entry!(main); + +use openvm_ff_derive::openvm_prime_field; + +extern crate alloc; + +/// The BLS12-381 scalar field. +#[openvm_prime_field] +#[PrimeFieldModulus = "52435875175126190479447740508185965837690552500527637822603658699938581184513"] +#[PrimeFieldGenerator = "7"] +#[PrimeFieldReprEndianness = "little"] +struct Bls381K12Scalar([u64; 4]); + +openvm::init!("openvm_init_from_u128.rs"); + +fn main() { + use ff::{Field, PrimeField}; + + assert_eq!(Bls381K12Scalar::from_u128(1), Bls381K12Scalar::ONE); + assert_eq!(Bls381K12Scalar::from_u128(2), Bls381K12Scalar::from(2)); + assert_eq!( + Bls381K12Scalar::from_u128(u128::MAX), + Bls381K12Scalar::from_str_vartime("340282366920938463463374607431768211455").unwrap(), + ); +} diff --git a/guest-libs/ff_derive/tests/programs/examples/full_limbs.rs b/guest-libs/ff_derive/tests/programs/examples/full_limbs.rs new file mode 100644 index 0000000000..330aea035b --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/examples/full_limbs.rs @@ -0,0 +1,36 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +openvm::entry!(main); + +extern crate alloc; + +use ff::{Field, PrimeField}; +use openvm_ff_derive::openvm_prime_field; + +#[openvm_prime_field] +#[PrimeFieldModulus = "39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319"] +#[PrimeFieldGenerator = "19"] +#[PrimeFieldReprEndianness = "little"] +struct F384p([u64; 7]); + +fn test(square_root: F384p) { + let square = square_root.square(); + let square_root = square.sqrt().unwrap(); + assert_eq!(square_root.square(), square); +} + +openvm::init!("openvm_init_full_limbs.rs"); + +// Test that random masking does not overflow +fn main() { + use ff::Field; + + // randomness is not supported in OpenVM + // use rand::rngs::OsRng; + // let _ = F384p::random(OsRng); + + test(F384p::ZERO); + test(F384p::ONE); + test(F384p::from(1234567890)); + test(F384p::from_str_vartime("19402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319").unwrap()); +} diff --git a/guest-libs/ff_derive/tests/programs/examples/operations.rs b/guest-libs/ff_derive/tests/programs/examples/operations.rs new file mode 100644 index 0000000000..d54ac1f648 --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/examples/operations.rs @@ -0,0 +1,122 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +openvm::entry!(main); + +use openvm_ff_derive::openvm_prime_field; + +extern crate alloc; + +/// The BLS12-381 scalar field. +#[openvm_prime_field] +#[PrimeFieldModulus = "52435875175126190479447740508185965837690552500527637822603658699938581184513"] +#[PrimeFieldGenerator = "7"] +#[PrimeFieldReprEndianness = "little"] +struct Bls381K12Scalar([u64; 4]); + +openvm::init!("openvm_init_from_u128.rs"); + +// Test arithmetic operations +fn main() { + use ff::{Field, PrimeField}; + + let neg_one = -Bls381K12Scalar::ONE; + assert_eq!( + neg_one, + Bls381K12Scalar::from_str_vartime( + "52435875175126190479447740508185965837690552500527637822603658699938581184512" + ) + .unwrap() + ); + + // Test Eq + #[allow(clippy::eq_op)] + { + assert_eq!(Bls381K12Scalar::ZERO, Bls381K12Scalar::ZERO); + assert_eq!(Bls381K12Scalar::ONE, Bls381K12Scalar::ONE); + assert_eq!(neg_one, neg_one); + } + + // Test is_zero + assert!(bool::from(Bls381K12Scalar::ZERO.is_zero())); + assert!(!bool::from(Bls381K12Scalar::ONE.is_zero())); + + // Test Add + assert_eq!( + neg_one + Bls381K12Scalar::from(10), + Bls381K12Scalar::from(9) + ); + + // Test AddAssign + let mut x = neg_one; + x += Bls381K12Scalar::from(10); + assert_eq!(x, Bls381K12Scalar::from(9)); + + // Test double + assert_eq!(Bls381K12Scalar::ONE.double(), Bls381K12Scalar::from(2)); + + // Test Neg + assert_eq!(-neg_one, Bls381K12Scalar::from(1)); + + // Test Mul + assert_eq!( + neg_one * Bls381K12Scalar::from(10), + -Bls381K12Scalar::from(10) + ); + + // Test MulAssign + let mut x = neg_one; + x *= Bls381K12Scalar::from(10); + assert_eq!(x, -Bls381K12Scalar::from(10)); + + // Test Sub + assert_eq!( + neg_one - Bls381K12Scalar::from(10), + -Bls381K12Scalar::from(11) + ); + + // Test SubAssign + let mut x = neg_one; + x -= Bls381K12Scalar::from(10); + assert_eq!(x, -Bls381K12Scalar::from(11)); + + // Test Sum + let sum: Bls381K12Scalar = (0..10).map(Bls381K12Scalar::from).sum(); + assert_eq!(sum, Bls381K12Scalar::from(45)); + + // Test Product + let product: Bls381K12Scalar = (1..10).map(Bls381K12Scalar::from).product(); + assert_eq!(product, Bls381K12Scalar::from(362880)); + + // Test Inv + assert_eq!( + Bls381K12Scalar::from(2).invert().unwrap(), + Bls381K12Scalar::TWO_INV + ); + assert!(bool::from(Bls381K12Scalar::ZERO.invert().is_none())); + + // Test square + assert_eq!( + Bls381K12Scalar::TWO_INV.square(), + Bls381K12Scalar::from(4).invert().unwrap() + ); + + // Test cube + assert_eq!( + Bls381K12Scalar::TWO_INV.cube(), + Bls381K12Scalar::from(8).invert().unwrap() + ); + + // Test Sqrt + assert!( + Bls381K12Scalar::from(4).sqrt().unwrap() == Bls381K12Scalar::from(2) + || Bls381K12Scalar::from(4).sqrt().unwrap() == -Bls381K12Scalar::from(2), + ); + // by quadratic reciprocity, 5 is not a square since p = 1 mod 12 + assert!(bool::from(Bls381K12Scalar::from(5).sqrt().is_none())); + + // Test pow + assert_eq!( + Bls381K12Scalar::from(2).pow([10]), + Bls381K12Scalar::from(1024) + ); +} diff --git a/guest-libs/ff_derive/tests/programs/examples/sqrt.rs b/guest-libs/ff_derive/tests/programs/examples/sqrt.rs new file mode 100644 index 0000000000..ffca52ec3b --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/examples/sqrt.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +openvm::entry!(main); + +extern crate alloc; + +use ff::Field; +use openvm_ff_derive::openvm_prime_field; + +// A field modulo a prime such that p = 1 mod 4 and p != 1 mod 16 +#[openvm_prime_field] +#[PrimeFieldModulus = "357686312646216567629137"] +#[PrimeFieldGenerator = "5"] +#[PrimeFieldReprEndianness = "little"] +struct Fp([u64; 2]); + +fn test(square_root: Fp) { + let square = square_root.square(); + let square_root = square.sqrt().unwrap(); + assert_eq!(square_root.square(), square); +} + +openvm::init!("openvm_init_sqrt.rs"); + +fn main() { + test(Fp::ZERO); + test(Fp::ONE); + // randomness is not supported in OpenVM + // use rand::rngs::OsRng; + // test(Fp::random(OsRng)); + test(Fp::from(1234567890)); +} diff --git a/guest-libs/ff_derive/tests/programs/openvm_init_batch_inversion_std.rs b/guest-libs/ff_derive/tests/programs/openvm_init_batch_inversion_std.rs new file mode 100644 index 0000000000..d49254909b --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/openvm_init_batch_inversion_std.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "52435875175126190479447740508185965837690552500527637822603658699938581184513" } diff --git a/guest-libs/ff_derive/tests/programs/openvm_init_constants.rs b/guest-libs/ff_derive/tests/programs/openvm_init_constants.rs new file mode 100644 index 0000000000..d49254909b --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/openvm_init_constants.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "52435875175126190479447740508185965837690552500527637822603658699938581184513" } diff --git a/guest-libs/ff_derive/tests/programs/openvm_init_fermat.rs b/guest-libs/ff_derive/tests/programs/openvm_init_fermat.rs new file mode 100644 index 0000000000..16d1ee2741 --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/openvm_init_fermat.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "65537" } diff --git a/guest-libs/ff_derive/tests/programs/openvm_init_from_u128.rs b/guest-libs/ff_derive/tests/programs/openvm_init_from_u128.rs new file mode 100644 index 0000000000..d49254909b --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/openvm_init_from_u128.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "52435875175126190479447740508185965837690552500527637822603658699938581184513" } diff --git a/guest-libs/ff_derive/tests/programs/openvm_init_full_limbs.rs b/guest-libs/ff_derive/tests/programs/openvm_init_full_limbs.rs new file mode 100644 index 0000000000..c95b01ca9f --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/openvm_init_full_limbs.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319" } diff --git a/guest-libs/ff_derive/tests/programs/openvm_init_operations.rs b/guest-libs/ff_derive/tests/programs/openvm_init_operations.rs new file mode 100644 index 0000000000..d49254909b --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/openvm_init_operations.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "52435875175126190479447740508185965837690552500527637822603658699938581184513" } diff --git a/guest-libs/ff_derive/tests/programs/openvm_init_sqrt.rs b/guest-libs/ff_derive/tests/programs/openvm_init_sqrt.rs new file mode 100644 index 0000000000..ad886f8fd0 --- /dev/null +++ b/guest-libs/ff_derive/tests/programs/openvm_init_sqrt.rs @@ -0,0 +1,2 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "357686312646216567629137" } diff --git a/guest-libs/k256/Cargo.toml b/guest-libs/k256/Cargo.toml new file mode 100644 index 0000000000..362df43b6f --- /dev/null +++ b/guest-libs/k256/Cargo.toml @@ -0,0 +1,87 @@ +[package] +# for patching purposes, the name must be the same as the original `k256` crate +name = "k256" +# for patching purposes, version must match that of original `k256` crate +version = "0.13.4" +description = "OpenVM fork of k256" +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +openvm = { workspace = true } +openvm-algebra-guest = { workspace = true } +openvm-algebra-moduli-macros = { workspace = true } +openvm-ecc-guest = { workspace = true } +openvm-ecc-sw-macros = { workspace = true } + +once_cell = { workspace = true, optional = true } +elliptic-curve = { workspace = true } +ecdsa-core = { workspace = true, optional = true } +serde = { workspace = true } +hex-literal = { workspace = true } +ff = { workspace = true } +signature = { version = "2", optional = true } + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +num-bigint = { workspace = true } + +[dev-dependencies] +openvm-circuit = { workspace = true, features = ["test-utils", "parallel"] } +openvm-transpiler.workspace = true +openvm-algebra-circuit.workspace = true +openvm-algebra-transpiler.workspace = true +openvm-ecc-transpiler.workspace = true +openvm-ecc-circuit.workspace = true +openvm-sha256-circuit.workspace = true +openvm-sha256-transpiler.workspace = true +openvm-rv32im-circuit.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-toolchain-tests.workspace = true + +openvm-stark-backend.workspace = true +openvm-stark-sdk.workspace = true + +serde.workspace = true +eyre.workspace = true +derive_more = { workspace = true, features = ["from"] } + +[features] +default = ["ecdsa"] +alloc = ["ecdsa-core?/alloc", "elliptic-curve/alloc"] +std = [ + "ecdsa-core?/std", + "elliptic-curve/std", + "once_cell?/std", + "openvm-ecc-guest/std", +] + +arithmetic = ["elliptic-curve/arithmetic"] +bits = ["arithmetic", "elliptic-curve/bits"] +critical-section = ["once_cell/critical-section", "precomputed-tables"] +digest = ["ecdsa-core/digest", "ecdsa-core/hazmat"] +ecdh = ["arithmetic", "elliptic-curve/ecdh"] +ecdsa = ["arithmetic", "ecdsa-core/signing", "ecdsa-core/verifying"] +expose-field = ["arithmetic"] +hash2curve = ["arithmetic", "elliptic-curve/hash2curve"] +jwk = ["elliptic-curve/jwk"] +pem = ["ecdsa-core/pem", "elliptic-curve/pem", "pkcs8"] +pkcs8 = ["ecdsa-core/pkcs8", "elliptic-curve/pkcs8"] +precomputed-tables = ["arithmetic", "once_cell"] +schnorr = ["arithmetic", "signature"] +serde = ["ecdsa-core/serde", "elliptic-curve/serde"] +sha256 = [] +test-vectors = [] + +[package.metadata.cargo-shear] +ignored = [ + "openvm", + "num-bigint", + "serde", + "derive_more", + "signature", + "once_cell", +] diff --git a/guest-libs/k256/src/coord.rs b/guest-libs/k256/src/coord.rs new file mode 100644 index 0000000000..cb9f55b080 --- /dev/null +++ b/guest-libs/k256/src/coord.rs @@ -0,0 +1,42 @@ +use alloc::vec::Vec; + +use elliptic_curve::subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use openvm_algebra_guest::IntMod; + +use crate::internal::Secp256k1Coord; + +// --- Implement elliptic_curve traits on Secp256k1Coord --- + +impl Copy for Secp256k1Coord {} + +impl ConditionallySelectable for Secp256k1Coord { + fn conditional_select( + a: &Secp256k1Coord, + b: &Secp256k1Coord, + choice: Choice, + ) -> Secp256k1Coord { + Secp256k1Coord::from_le_bytes( + &a.as_le_bytes() + .iter() + .zip(b.as_le_bytes().iter()) + .map(|(a, b)| u8::conditional_select(a, b, choice)) + .collect::>(), + ) + } +} + +impl ConstantTimeEq for Secp256k1Coord { + fn ct_eq(&self, other: &Secp256k1Coord) -> Choice { + #[cfg(not(target_os = "zkvm"))] + { + // Requires canonical form + self.as_le_bytes().ct_eq(other.as_le_bytes()) + } + #[cfg(target_os = "zkvm")] + { + // The zkVM implementation calls iseqmod opcode so it is constant time, _except_ a check + // of whether the setup opcode has been called already + Choice::from((self == other) as u8) + } + } +} diff --git a/guest-libs/k256/src/ecdsa.rs b/guest-libs/k256/src/ecdsa.rs new file mode 100644 index 0000000000..b83417f7d4 --- /dev/null +++ b/guest-libs/k256/src/ecdsa.rs @@ -0,0 +1,62 @@ +// re-export types that are visible in the k256 crate for API compatibility + +// Use these types instead of unpatched k256::ecdsa::{Signature, VerifyingKey} +// because those are type aliases that use non-zkvm implementations + +#[cfg(any(feature = "ecdsa", feature = "sha256"))] +pub use ecdsa_core::hazmat; +pub use ecdsa_core::{ + signature::{self, Error}, + RecoveryId, +}; +#[cfg(feature = "ecdsa")] +use { + super::{Scalar, Secp256k1Point}, + ecdsa_core::hazmat::{SignPrimitive, VerifyPrimitive}, + elliptic_curve::{ops::Invert, subtle::CtOption}, +}; + +use super::Secp256k1; + +/// ECDSA/secp256k1 signature (fixed-size) +pub type Signature = ecdsa_core::Signature; + +/// ECDSA/secp256k1 signing key +#[cfg(feature = "ecdsa")] +pub type SigningKey = openvm_ecc_guest::ecdsa::SigningKey; + +/// ECDSA/secp256k1 verification key (i.e. public key) +#[cfg(feature = "ecdsa")] +pub type VerifyingKey = openvm_ecc_guest::ecdsa::VerifyingKey; + +// We implement the trait so that patched libraries can compile when they only need ECDSA +// verification and not signing +#[cfg(feature = "ecdsa")] +impl SignPrimitive for Scalar { + fn try_sign_prehashed( + &self, + _k: K, + _z: &elliptic_curve::FieldBytes, + ) -> signature::Result<(Signature, Option)> + where + K: AsRef + Invert>, + { + todo!("ECDSA signing from private key is not yet implemented") + } +} + +#[cfg(feature = "ecdsa")] +impl VerifyPrimitive for Secp256k1Point { + fn verify_prehashed( + &self, + z: &crate::point::FieldBytes, + sig: &Signature, + ) -> Result<(), ecdsa_core::Error> { + openvm_ecc_guest::ecdsa::verify_prehashed::( + *self, + z.as_slice(), + sig.to_bytes().as_slice(), + ) + .map_err(|_| ecdsa_core::Error::new()) + } +} diff --git a/extensions/ecc/guest/src/k256.rs b/guest-libs/k256/src/internal.rs similarity index 59% rename from extensions/ecc/guest/src/k256.rs rename to guest-libs/k256/src/internal.rs index a3e63985ba..b8f8857dc9 100644 --- a/extensions/ecc/guest/src/k256.rs +++ b/guest-libs/k256/src/internal.rs @@ -1,35 +1,20 @@ -extern crate alloc; - use core::ops::{Add, Neg}; use hex_literal::hex; -#[cfg(not(target_os = "zkvm"))] -use lazy_static::lazy_static; -#[cfg(not(target_os = "zkvm"))] -use num_bigint::BigUint; -use openvm_algebra_guest::{Field, IntMod}; +use openvm_algebra_guest::IntMod; use openvm_algebra_moduli_macros::moduli_declare; +use openvm_ecc_guest::{ + weierstrass::{CachedMulTable, IntrinsicCurve, WeierstrassPoint}, + CyclicGroup, Group, +}; use openvm_ecc_sw_macros::sw_declare; -use super::group::{CyclicGroup, Group}; -use crate::weierstrass::{CachedMulTable, IntrinsicCurve}; +use crate::Secp256k1; -#[cfg(not(target_os = "zkvm"))] -lazy_static! { - // The constants are taken from: https://en.bitcoin.it/wiki/Secp256k1 - pub static ref SECP256K1_MODULUS: BigUint = BigUint::from_bytes_be(&hex!( - "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" - )); - pub static ref SECP256K1_ORDER: BigUint = BigUint::from_bytes_be(&hex!( - "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" - )); -} +// --- Define the OpenVM modular arithmetic and ecc types --- -pub const SECP256K1_NUM_LIMBS: usize = 32; -pub const SECP256K1_LIMB_BITS: usize = 8; -pub const SECP256K1_BLOCK_SIZE: usize = 32; const CURVE_B: Secp256k1Coord = Secp256k1Coord::from_const_bytes(seven_le()); -const fn seven_le() -> [u8; 32] { +pub const fn seven_le() -> [u8; 32] { let mut buf = [0u8; 32]; buf[0] = 7; buf @@ -44,20 +29,7 @@ sw_declare! { Secp256k1Point { mod_type = Secp256k1Coord, b = CURVE_B }, } -impl Field for Secp256k1Coord { - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - type SelfRef<'a> = &'a Self; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} +// --- Implement internal traits --- impl CyclicGroup for Secp256k1Point { // The constants are taken from: https://en.bitcoin.it/wiki/Secp256k1 @@ -80,7 +52,7 @@ impl CyclicGroup for Secp256k1Point { }; } -impl IntrinsicCurve for k256::Secp256k1 { +impl IntrinsicCurve for Secp256k1 { type Scalar = Secp256k1Scalar; type Point = Secp256k1Point; @@ -93,7 +65,19 @@ impl IntrinsicCurve for k256::Secp256k1 { let table = CachedMulTable::::new_with_prime_order(bases, 4); table.windowed_mul(coeffs) } else { - crate::msm(coeffs, bases) + openvm_ecc_guest::msm(coeffs, bases) } } } + +// --- Implement helpful methods mimicking the structs in k256 --- + +impl Secp256k1Point { + pub fn x_be_bytes(&self) -> [u8; 32] { + ::x(self).to_be_bytes() + } + + pub fn y_be_bytes(&self) -> [u8; 32] { + ::y(self).to_be_bytes() + } +} diff --git a/guest-libs/k256/src/lib.rs b/guest-libs/k256/src/lib.rs new file mode 100644 index 0000000000..992fd802cb --- /dev/null +++ b/guest-libs/k256/src/lib.rs @@ -0,0 +1,61 @@ +// Fork of RustCrypto's k256 crate https://docs.rs/k256/latest/k256/ +// that uses zkvm instructions + +#![no_std] +extern crate alloc; + +use elliptic_curve::{consts::U32, point::PointCompression, Curve, CurveArithmetic, PrimeCurve}; + +mod coord; +mod internal; +mod point; +mod scalar; + +#[cfg(feature = "ecdsa-core")] +pub mod ecdsa; + +pub use elliptic_curve::{self, bigint::U256}; +// Needs to be public so that the `sw_init` macro can access it +pub use internal::{ + Secp256k1Coord, Secp256k1Point, Secp256k1Point as AffinePoint, + Secp256k1Point as ProjectivePoint, Secp256k1Scalar as Scalar, Secp256k1Scalar, +}; + +// -- Define the ZST for implementing the elliptic curve traits -- +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct Secp256k1; + +// --- Implement the Curve trait on Secp256k1 --- + +/// Order of the secp256k1 elliptic curve in hexadecimal. +const ORDER_HEX: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"; + +/// Order of the secp256k1 elliptic curve. +const ORDER: U256 = U256::from_be_hex(ORDER_HEX); + +impl Curve for Secp256k1 { + /// 32-byte serialized field elements. + type FieldBytesSize = U32; + + // Perf: Use the U256 type from openvm_ruint here + type Uint = U256; + + /// Curve order. + const ORDER: U256 = ORDER; +} + +impl PrimeCurve for Secp256k1 {} + +impl CurveArithmetic for Secp256k1 { + type AffinePoint = AffinePoint; + type ProjectivePoint = ProjectivePoint; + type Scalar = Scalar; +} + +impl PointCompression for Secp256k1 { + /// secp256k1 points are typically compressed. + const COMPRESS_POINTS: bool = true; +} + +/// SEC1-encoded secp256k1 (K-256) curve point. +pub type EncodedPoint = elliptic_curve::sec1::EncodedPoint; diff --git a/guest-libs/k256/src/point.rs b/guest-libs/k256/src/point.rs new file mode 100644 index 0000000000..922457b8a4 --- /dev/null +++ b/guest-libs/k256/src/point.rs @@ -0,0 +1,259 @@ +use core::{ + iter::Sum, + ops::{Mul, MulAssign}, +}; + +use elliptic_curve::{ + bigint::{ArrayEncoding, U256}, + ops::{LinearCombination, MulByGenerator}, + point::{AffineCoordinates, DecompactPoint, DecompressPoint}, + rand_core::RngCore, + sec1::{FromEncodedPoint, ToEncodedPoint}, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, + FieldBytesEncoding, +}; +use openvm_algebra_guest::IntMod; +use openvm_ecc_guest::{ + weierstrass::{IntrinsicCurve, WeierstrassPoint}, + CyclicGroup, +}; + +use crate::{ + internal::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}, + EncodedPoint, Secp256k1, +}; + +// --- Implement elliptic_curve traits on Secp256k1Point --- + +/// secp256k1 (K-256) field element serialized as bytes. +/// +/// Byte array containing a serialized field element value (base field or scalar). +pub type FieldBytes = elliptic_curve::FieldBytes; + +impl FieldBytesEncoding for U256 { + fn decode_field_bytes(field_bytes: &FieldBytes) -> Self { + U256::from_be_byte_array(*field_bytes) + } + + fn encode_field_bytes(&self) -> FieldBytes { + self.to_be_byte_array() + } +} + +impl AffineCoordinates for Secp256k1Point { + type FieldRepr = FieldBytes; + + fn x(&self) -> FieldBytes { + *FieldBytes::from_slice(&::x(self).to_be_bytes()) + } + + fn y_is_odd(&self) -> Choice { + (self.y().as_le_bytes()[0] & 1).into() + } +} + +impl Copy for Secp256k1Point {} + +impl ConditionallySelectable for Secp256k1Point { + fn conditional_select( + a: &Secp256k1Point, + b: &Secp256k1Point, + choice: Choice, + ) -> Secp256k1Point { + Secp256k1Point::from_xy_unchecked( + Secp256k1Coord::conditional_select( + ::x(a), + ::x(b), + choice, + ), + Secp256k1Coord::conditional_select(a.y(), b.y(), choice), + ) + } +} + +impl ConstantTimeEq for Secp256k1Point { + fn ct_eq(&self, other: &Secp256k1Point) -> Choice { + ::x(self).ct_eq(::x(other)) + & self.y().ct_eq(other.y()) + } +} + +impl Default for Secp256k1Point { + fn default() -> Self { + ::IDENTITY + } +} + +impl DefaultIsZeroes for Secp256k1Point {} + +impl Sum for Secp256k1Point { + fn sum>(iter: I) -> Self { + iter.fold(::IDENTITY, |a, b| a + b) + } +} + +impl<'a> Sum<&'a Secp256k1Point> for Secp256k1Point { + fn sum>(iter: I) -> Self { + iter.cloned().sum() + } +} + +impl Mul for Secp256k1Point { + type Output = Secp256k1Point; + + fn mul(self, other: Secp256k1Scalar) -> Secp256k1Point { + Secp256k1::msm(&[other], &[self]) + } +} + +impl Mul<&Secp256k1Scalar> for &Secp256k1Point { + type Output = Secp256k1Point; + + fn mul(self, other: &Secp256k1Scalar) -> Secp256k1Point { + Secp256k1::msm(&[*other], &[*self]) + } +} + +impl Mul<&Secp256k1Scalar> for Secp256k1Point { + type Output = Secp256k1Point; + + fn mul(self, other: &Secp256k1Scalar) -> Secp256k1Point { + Secp256k1::msm(&[*other], &[self]) + } +} + +impl MulAssign for Secp256k1Point { + fn mul_assign(&mut self, rhs: Secp256k1Scalar) { + *self = Secp256k1::msm(&[rhs], &[*self]); + } +} + +impl MulAssign<&Secp256k1Scalar> for Secp256k1Point { + fn mul_assign(&mut self, rhs: &Secp256k1Scalar) { + *self = Secp256k1::msm(&[*rhs], &[*self]); + } +} + +impl elliptic_curve::Group for Secp256k1Point { + type Scalar = Secp256k1Scalar; + + fn random(mut _rng: impl RngCore) -> Self { + // Self::GENERATOR * Self::Scalar::random(&mut rng) + unimplemented!() + } + + fn identity() -> Self { + ::IDENTITY + } + + fn generator() -> Self { + Self::GENERATOR + } + + fn is_identity(&self) -> Choice { + (::is_identity(self) as u8).into() + } + + #[must_use] + fn double(&self) -> Self { + self + self + } +} + +impl elliptic_curve::group::Curve for Secp256k1Point { + type AffineRepr = Secp256k1Point; + + fn to_affine(&self) -> Secp256k1Point { + *self + } +} + +impl LinearCombination for Secp256k1Point { + fn lincomb(x: &Self, k: &Self::Scalar, y: &Self, l: &Self::Scalar) -> Self { + Secp256k1::msm(&[*k, *l], &[*x, *y]) + } +} + +// default implementation +impl MulByGenerator for Secp256k1Point {} + +impl DecompressPoint for Secp256k1Point { + /// Note that this is not constant time + fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { + use openvm_ecc_guest::weierstrass::FromCompressed; + + let x = Secp256k1Coord::from_be_bytes(x_bytes.as_slice()); + let rec_id = y_is_odd.unwrap_u8(); + let y = >::decompress(x, &rec_id); + match y { + Some(point) => CtOption::new(point, 1.into()), + None => CtOption::new(Secp256k1Point::default(), 0.into()), + } + } +} + +// Taken from https://docs.rs/k256/latest/src/k256/arithmetic/affine.rs.html#207 +impl DecompactPoint for Secp256k1Point { + fn decompact(x_bytes: &FieldBytes) -> CtOption { + Self::decompress(x_bytes, Choice::from(0)) + } +} + +impl FromEncodedPoint for Secp256k1Point { + /// Attempts to parse the given [`EncodedPoint`] as an SEC1-encoded [`Secp256k1Point`]. + /// + /// # Returns + /// + /// `None` value if `encoded_point` is not on the secp256k1 curve. + fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { + match openvm_ecc_guest::ecdsa::VerifyingKey::::from_sec1_bytes( + encoded_point.as_bytes(), + ) { + Ok(verifying_key) => CtOption::new(*verifying_key.as_affine(), 1.into()), + Err(_) => CtOption::new(Secp256k1Point::default(), 0.into()), + } + } +} + +impl ToEncodedPoint for Secp256k1Point { + fn to_encoded_point(&self, compress: bool) -> EncodedPoint { + EncodedPoint::conditional_select( + &EncodedPoint::from_affine_coordinates( + &::x(self).to_be_bytes().into(), + &::y(self).to_be_bytes().into(), + compress, + ), + &EncodedPoint::identity(), + elliptic_curve::Group::is_identity(self), + ) + } +} + +impl TryFrom for Secp256k1Point { + type Error = elliptic_curve::Error; + + fn try_from(point: EncodedPoint) -> elliptic_curve::Result { + Secp256k1Point::try_from(&point) + } +} + +impl TryFrom<&EncodedPoint> for Secp256k1Point { + type Error = elliptic_curve::Error; + + fn try_from(point: &EncodedPoint) -> elliptic_curve::Result { + Option::from(Secp256k1Point::from_encoded_point(point)).ok_or(elliptic_curve::Error) + } +} + +impl From for EncodedPoint { + fn from(affine_point: Secp256k1Point) -> EncodedPoint { + EncodedPoint::from(&affine_point) + } +} + +impl From<&Secp256k1Point> for EncodedPoint { + fn from(affine_point: &Secp256k1Point) -> EncodedPoint { + affine_point.to_encoded_point(true) + } +} diff --git a/guest-libs/k256/src/scalar.rs b/guest-libs/k256/src/scalar.rs new file mode 100644 index 0000000000..7618603d97 --- /dev/null +++ b/guest-libs/k256/src/scalar.rs @@ -0,0 +1,239 @@ +use alloc::vec::Vec; +use core::{cmp::Ordering, ops::ShrAssign}; + +use elliptic_curve::{ + bigint::{ArrayEncoding, Encoding, U256}, + ops::{Invert, Reduce}, + rand_core::RngCore, + scalar::{FromUintUnchecked, IsHigh}, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, + Field, PrimeField, ScalarPrimitive, +}; +use hex_literal::hex; +use openvm_algebra_guest::IntMod; + +use crate::{ + internal::{seven_le, Secp256k1Scalar}, + point::FieldBytes, + Secp256k1, ORDER_HEX, +}; + +impl Secp256k1Scalar { + /// Returns the SEC1 encoding of this scalar. + pub fn to_bytes(&self) -> FieldBytes { + self.to_be_bytes().into() + } +} +// --- Implement elliptic_curve traits on Secp256k1Scalar --- + +impl Copy for Secp256k1Scalar {} + +impl From for Secp256k1Scalar { + fn from(value: u64) -> Self { + Self::from_u64(value) + } +} + +impl Default for Secp256k1Scalar { + fn default() -> Self { + ::ZERO + } +} + +// Requires canonical form +impl ConstantTimeEq for Secp256k1Scalar { + fn ct_eq(&self, other: &Self) -> Choice { + self.as_le_bytes().ct_eq(other.as_le_bytes()) + } +} + +impl ConditionallySelectable for Secp256k1Scalar { + fn conditional_select( + a: &Secp256k1Scalar, + b: &Secp256k1Scalar, + choice: Choice, + ) -> Secp256k1Scalar { + Secp256k1Scalar::from_le_bytes( + &a.as_le_bytes() + .iter() + .zip(b.as_le_bytes().iter()) + .map(|(a, b)| u8::conditional_select(a, b, choice)) + .collect::>(), + ) + } +} + +impl Field for Secp256k1Scalar { + const ZERO: Self = ::ZERO; + const ONE: Self = ::ONE; + + fn random(mut _rng: impl RngCore) -> Self { + unimplemented!() + } + + #[must_use] + fn square(&self) -> Self { + self * self + } + + #[must_use] + fn double(&self) -> Self { + self + self + } + + fn invert(&self) -> CtOption { + // needs to be in canonical form for ct_eq + self.assert_reduced(); + let is_zero = self.ct_eq(&::ZERO); + CtOption::new( + ::invert(self), + !is_zero, + ) + } + + #[allow(clippy::many_single_char_names)] + fn sqrt(&self) -> CtOption { + match ::sqrt(self) { + Some(sqrt) => CtOption::new(sqrt, 1.into()), + None => CtOption::new(::ZERO, 0.into()), + } + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + ff::helpers::sqrt_ratio_generic(num, div) + } +} + +impl PrimeField for Secp256k1Scalar { + type Repr = FieldBytes; + + const MODULUS: &'static str = ORDER_HEX; + const NUM_BITS: u32 = 256; + const CAPACITY: u32 = 255; + const TWO_INV: Self = Self::from_const_bytes(hex!( + "a1201b68462fe9df1d50a457736e575dffffffffffffffffffffffffffffff7f" + )); + const MULTIPLICATIVE_GENERATOR: Self = Self::from_const_bytes(seven_le()); + const S: u32 = 6; + const ROOT_OF_UNITY: Self = Self::from_const_bytes(hex!( + "f252b002544b2f9945607580b6eabd98a883c4fba37998df8619a9e760c01d0c" + )); + const ROOT_OF_UNITY_INV: Self = Self::from_const_bytes(hex!( + "1c0d4f88a030fbb6c313a40a9175a27772bb8c5bc7b0c7ef96702df181e13afd" + )); + const DELTA: Self = Self::from_const_bytes(hex!( + "0176bbc0c81794191e34e180e7783bd6c86145fe21bc0c000000000000000000" + )); + + /// Attempts to parse the given byte array as an SEC1-encoded scalar. + /// + /// Returns None if the byte array does not contain a big-endian integer in the range + /// [0, p). + fn from_repr(bytes: FieldBytes) -> CtOption { + let ret = Self::from_be_bytes(bytes.as_slice()); + CtOption::new(ret, (ret.is_reduced() as u8).into()) + } + + // Endianness should match from_repr + fn to_repr(&self) -> FieldBytes { + *FieldBytes::from_slice(&self.to_be_bytes()) + } + + fn is_odd(&self) -> Choice { + (self.as_le_bytes()[0] & 1).into() + } +} + +impl ShrAssign for Secp256k1Scalar { + fn shr_assign(&mut self, _rhs: usize) { + // I don't think this is used anywhere + unimplemented!() + } +} + +impl Reduce for Secp256k1Scalar { + type Bytes = FieldBytes; + + fn reduce(w: U256) -> Self { + Self::from_le_bytes(&w.to_le_bytes()) + } + + #[inline] + fn reduce_bytes(bytes: &FieldBytes) -> Self { + Self::reduce(U256::from_be_byte_array(*bytes)) + } +} + +impl PartialOrd for Secp256k1Scalar { + // requires self and other to be in canonical form + fn partial_cmp(&self, other: &Self) -> Option { + self.assert_reduced(); + other.assert_reduced(); + Some( + self.to_be_bytes() + .iter() + .zip(other.to_be_bytes().iter()) + .map(|(a, b)| a.cmp(b)) + .find(|ord| *ord != Ordering::Equal) + .unwrap_or(Ordering::Equal), + ) + } +} + +impl IsHigh for Secp256k1Scalar { + fn is_high(&self) -> Choice { + // self > n/2 + // iff self + self overflows + // iff self + self < self + ((self + self < *self) as u8).into() + } +} + +impl Invert for Secp256k1Scalar { + type Output = CtOption; + + fn invert(&self) -> CtOption { + ::invert(self) + } +} + +impl FromUintUnchecked for Secp256k1Scalar { + type Uint = U256; + + fn from_uint_unchecked(uint: Self::Uint) -> Self { + Self::from_le_bytes(&uint.to_le_bytes()) + } +} + +impl From> for Secp256k1Scalar { + fn from(scalar: ScalarPrimitive) -> Self { + Self::from_le_bytes(&scalar.as_uint().to_le_bytes()) + } +} + +impl From for ScalarPrimitive { + fn from(scalar: Secp256k1Scalar) -> ScalarPrimitive { + ScalarPrimitive::from_slice(&scalar.to_be_bytes()).unwrap() + } +} + +impl DefaultIsZeroes for Secp256k1Scalar {} + +impl AsRef for Secp256k1Scalar { + fn as_ref(&self) -> &Secp256k1Scalar { + self + } +} + +impl From for U256 { + fn from(scalar: Secp256k1Scalar) -> Self { + U256::from_be_slice(&scalar.to_be_bytes()) + } +} + +impl From for FieldBytes { + fn from(scalar: Secp256k1Scalar) -> Self { + *FieldBytes::from_slice(&scalar.to_be_bytes()) + } +} diff --git a/guest-libs/k256/tests/lib.rs b/guest-libs/k256/tests/lib.rs new file mode 100644 index 0000000000..d55cdba60c --- /dev/null +++ b/guest-libs/k256/tests/lib.rs @@ -0,0 +1,258 @@ +mod guest_tests { + use ecdsa_config::EcdsaConfig; + use eyre::Result; + use openvm_algebra_transpiler::ModularTranspilerExtension; + use openvm_circuit::{arch::instructions::exe::VmExe, utils::air_test}; + use openvm_ecc_circuit::{Rv32WeierstrassConfig, SECP256K1_CONFIG}; + use openvm_ecc_transpiler::EccTranspilerExtension; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_sha256_transpiler::Sha256TranspilerExtension; + use openvm_stark_sdk::p3_baby_bear::BabyBear; + use openvm_toolchain_tests::{build_example_program_at_path, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + + type F = BabyBear; + + #[test] + fn test_add() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "add", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_mul() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "mul", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_linear_combination() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "linear_combination", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + + mod ecdsa_config { + use eyre::Result; + use openvm_algebra_circuit::{ + ModularExtension, ModularExtensionExecutor, ModularExtensionPeriphery, + }; + use openvm_circuit::{ + arch::{InitFileGenerator, SystemConfig}, + derive::VmConfig, + }; + use openvm_ecc_circuit::{ + CurveConfig, WeierstrassExtension, WeierstrassExtensionExecutor, + WeierstrassExtensionPeriphery, + }; + use openvm_rv32im_circuit::{ + Rv32I, Rv32IExecutor, Rv32IPeriphery, Rv32Io, Rv32IoExecutor, Rv32IoPeriphery, Rv32M, + Rv32MExecutor, Rv32MPeriphery, + }; + use openvm_sha256_circuit::{Sha256, Sha256Executor, Sha256Periphery}; + use openvm_stark_backend::p3_field::PrimeField32; + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, VmConfig, Serialize, Deserialize)] + pub struct EcdsaConfig { + #[system] + pub system: SystemConfig, + #[extension] + pub base: Rv32I, + #[extension] + pub mul: Rv32M, + #[extension] + pub io: Rv32Io, + #[extension] + pub modular: ModularExtension, + #[extension] + pub weierstrass: WeierstrassExtension, + #[extension] + pub sha256: Sha256, + } + + impl EcdsaConfig { + pub fn new(curves: Vec) -> Self { + let primes: Vec<_> = curves + .iter() + .flat_map(|c| [c.modulus.clone(), c.scalar.clone()]) + .collect(); + Self { + system: SystemConfig::default().with_continuations(), + base: Default::default(), + mul: Default::default(), + io: Default::default(), + modular: ModularExtension::new(primes), + weierstrass: WeierstrassExtension::new(curves), + sha256: Default::default(), + } + } + } + + impl InitFileGenerator for EcdsaConfig { + fn generate_init_file_contents(&self) -> Option { + Some(format!( + "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n", + self.modular.generate_moduli_init(), + self.weierstrass.generate_sw_init() + )) + } + } + } + + #[test] + fn test_ecdsa() -> Result<()> { + let config = EcdsaConfig::new(vec![SECP256K1_CONFIG.clone()]); + + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "ecdsa", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Sha256TranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_scalar_sqrt() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "scalar_sqrt", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } +} + +mod host_tests { + use hex_literal::hex; + use k256::{Scalar as Secp256k1Scalar, Secp256k1Coord, Secp256k1Point}; + use openvm_algebra_guest::IntMod; + use openvm_ecc_guest::{msm, weierstrass::WeierstrassPoint, Group}; + + #[test] + fn test_host_secp256k1() { + // Sample points got from https://asecuritysite.com/ecc/ecc_points2 and + // https://learnmeabitcoin.com/technical/cryptography/elliptic-curve/#add + let x1 = Secp256k1Coord::from_u32(1); + let y1 = Secp256k1Coord::from_le_bytes(&hex!( + "EEA7767E580D75BC6FDD7F58D2A84C2614FB22586068DB63B346C6E60AF21842" + )); + let x2 = Secp256k1Coord::from_u32(2); + let y2 = Secp256k1Coord::from_le_bytes(&hex!( + "D1A847A8F879E0AEE32544DA5BA0B3BD1703A1F52867A5601FF6454DD8180499" + )); + // This is the sum of (x1, y1) and (x2, y2). + let x3 = Secp256k1Coord::from_le_bytes(&hex!( + "BE675E31F8AC8200CBCC6B10CECCD6EB93FB07D99BB9E7C99CC9245C862D3AF2" + )); + let y3 = Secp256k1Coord::from_le_bytes(&hex!( + "B44573B48FD3416DD256A8C0E1BAD03E88A78BF176778682589B9CB478FC1D79" + )); + // This is the double of (x2, y2). + let x4 = Secp256k1Coord::from_le_bytes(&hex!( + "3BFFFFFF32333333333333333333333333333333333333333333333333333333" + )); + let y4 = Secp256k1Coord::from_le_bytes(&hex!( + "AC54ECC4254A4EDCAB10CC557A9811ED1EF7CB8AFDC64820C6803D2C5F481639" + )); + + let mut p1 = Secp256k1Point::from_xy(x1, y1).unwrap(); + let mut p2 = Secp256k1Point::from_xy(x2, y2).unwrap(); + + // Generic add can handle equal or unequal points. + #[allow(clippy::op_ref)] + let p3 = &p1 + &p2; + if p3.x() != &x3 || p3.y() != &y3 { + panic!(); + } + #[allow(clippy::op_ref)] + let p4 = &p2 + &p2; + if p4.x() != &x4 || p4.y() != &y4 { + panic!(); + } + + // Add assign and double assign + p1 += &p2; + if p1.x() != &x3 || p1.y() != &y3 { + panic!(); + } + p2.double_assign(); + if p2.x() != &x4 || p2.y() != &y4 { + panic!(); + } + + // Ec Mul + let p1 = Secp256k1Point::from_xy(x1, y1).unwrap(); + let scalar = Secp256k1Scalar::from_u32(12345678); + // Calculated with https://learnmeabitcoin.com/technical/cryptography/elliptic-curve/#ec-multiply-tool + let x5 = Secp256k1Coord::from_le_bytes(&hex!( + "194A93387F790803D972AF9C4A40CB89D106A36F58EE2F31DC48A41768216D6D" + )); + let y5 = Secp256k1Coord::from_le_bytes(&hex!( + "9E272F746DA7BED171E522610212B6AEEAAFDB2AD9F4B530B8E1B27293B19B2C" + )); + let result = msm(&[scalar], &[p1]); + if result.x() != &x5 || result.y() != &y5 { + panic!(); + } + } +} diff --git a/guest-libs/k256/tests/programs/Cargo.toml b/guest-libs/k256/tests/programs/Cargo.toml new file mode 100644 index 0000000000..4e2002af5a --- /dev/null +++ b/guest-libs/k256/tests/programs/Cargo.toml @@ -0,0 +1,27 @@ +[workspace] +[package] +name = "openvm-k256-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-algebra-guest = { path = "../../../../extensions/algebra/guest" } +openvm-algebra-moduli-macros = { path = "../../../../extensions/algebra/moduli-macros/" } +openvm-ecc-guest = { path = "../../../../extensions/ecc/guest" } +openvm-ecc-sw-macros = { path = "../../../../extensions/ecc/sw-macros/" } +openvm-k256 = { path = "../../", package = "k256" } +openvm-sha2 = { path = "../../../sha2/" } + +elliptic-curve = { version = "0.13.8" } +ecdsa = { version = "0.16.9" } +hex-literal = { version = "0.4.1", default-features = false } + +[features] +default = [] +std = ["openvm/std"] + +[profile.release] +panic = "abort" +lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing +# strip = "symbols" diff --git a/guest-libs/k256/tests/programs/examples/add.rs b/guest-libs/k256/tests/programs/examples/add.rs new file mode 100644 index 0000000000..c6989f44f5 --- /dev/null +++ b/guest-libs/k256/tests/programs/examples/add.rs @@ -0,0 +1,31 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use elliptic_curve::{group::Curve, CurveArithmetic, Group}; +use openvm_k256::Secp256k1; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_k256::Secp256k1Point; + +mod test_vectors; +use test_vectors::ADD_TEST_VECTORS; + +openvm::init!("openvm_init_simple.rs"); + +openvm::entry!(main); + +// Taken from https://github.com/RustCrypto/elliptic-curves/blob/32343a78f1522aa5bd856556f114053d4bb938e0/k256/src/arithmetic/projective.rs#L797 +pub fn main() { + let generator = ::ProjectivePoint::generator(); + let mut p = generator; + + for test_vector in ADD_TEST_VECTORS { + let affine = p.to_affine(); + + let (expected_x, expected_y) = test_vector; + assert_eq!(&affine.x_be_bytes(), expected_x); + assert_eq!(&affine.y_be_bytes(), expected_y); + + p += &generator; + } +} diff --git a/guest-libs/k256/tests/programs/examples/ecdsa.rs b/guest-libs/k256/tests/programs/examples/ecdsa.rs new file mode 100644 index 0000000000..d886e80ed0 --- /dev/null +++ b/guest-libs/k256/tests/programs/examples/ecdsa.rs @@ -0,0 +1,58 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use ecdsa::RecoveryId; +use hex_literal::hex; +use openvm_k256::ecdsa::{Signature, VerifyingKey}; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_k256::Secp256k1Point; +use openvm_sha2::sha256; + +openvm::init!("openvm_init_ecdsa.rs"); + +openvm::entry!(main); + +/// Signature recovery test vectors +struct RecoveryTestVector { + pk: [u8; 33], + msg: &'static [u8], + sig: [u8; 64], + recid: RecoveryId, +} + +const RECOVERY_TEST_VECTORS: &[RecoveryTestVector] = &[ + // Recovery ID 0 + RecoveryTestVector { + pk: hex!("021a7a569e91dbf60581509c7fc946d1003b60c7dee85299538db6353538d59574"), + msg: b"example message", + sig: hex!( + "ce53abb3721bafc561408ce8ff99c909f7f0b18a2f788649d6470162ab1aa032 + 3971edc523a6d6453f3fb6128d318d9db1a5ff3386feb1047d9816e780039d52" + ), + recid: RecoveryId::new(false, false), + }, + // Recovery ID 1 + RecoveryTestVector { + pk: hex!("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"), + msg: b"example message", + sig: hex!( + "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51 + 35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb" + ), + recid: RecoveryId::new(true, false), + }, +]; + +// Test public key recovery +fn main() { + for vector in RECOVERY_TEST_VECTORS { + let digest = sha256(vector.msg); + let sig = Signature::try_from(vector.sig.as_slice()).unwrap(); + let recid = vector.recid; + let pk = VerifyingKey::recover_from_prehash(digest.as_slice(), &sig, recid).unwrap(); + assert_eq!(&vector.pk[..], &pk.to_sec1_bytes(true)); + } +} diff --git a/guest-libs/k256/tests/programs/examples/linear_combination.rs b/guest-libs/k256/tests/programs/examples/linear_combination.rs new file mode 100644 index 0000000000..8a492350ec --- /dev/null +++ b/guest-libs/k256/tests/programs/examples/linear_combination.rs @@ -0,0 +1,22 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use elliptic_curve::{ops::LinearCombination, Group, PrimeField}; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_k256::Secp256k1Point; +use openvm_k256::{ProjectivePoint, Scalar}; + +openvm::init!("openvm_init_linear_combination.rs"); + +openvm::entry!(main); + +pub fn main() { + let g = ProjectivePoint::generator(); + let a = ProjectivePoint::lincomb(&g, &Scalar::from_u128(100), &g, &Scalar::from_u128(156)); + let mut b = g; + for _ in 0..8 { + b += b; + } + assert_eq!(a, b); +} diff --git a/guest-libs/k256/tests/programs/examples/mul.rs b/guest-libs/k256/tests/programs/examples/mul.rs new file mode 100644 index 0000000000..65cb74fb22 --- /dev/null +++ b/guest-libs/k256/tests/programs/examples/mul.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use elliptic_curve::{CurveArithmetic, Group, PrimeField}; +use openvm_k256::Secp256k1; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_k256::Secp256k1Point; + +mod test_vectors; +use test_vectors::{ADD_TEST_VECTORS, MUL_TEST_VECTORS}; + +openvm::init!("openvm_init_simple.rs"); + +openvm::entry!(main); + +pub fn main() { + let generator = ::ProjectivePoint::generator(); + + for (k, coords) in ADD_TEST_VECTORS + .iter() + .enumerate() + .map(|(k, coords)| { + ( + ::Scalar::from(k as u64 + 1), + *coords, + ) + }) + .chain(MUL_TEST_VECTORS.iter().cloned().map(|(k, x, y)| { + ( + ::Scalar::from_repr(k.into()).unwrap(), + (x, y), + ) + })) + { + let p = generator * k; + assert_eq!(p.x_be_bytes(), coords.0); + assert_eq!(p.y_be_bytes(), coords.1); + } +} diff --git a/guest-libs/k256/tests/programs/examples/scalar_sqrt.rs b/guest-libs/k256/tests/programs/examples/scalar_sqrt.rs new file mode 100644 index 0000000000..f92506e1ea --- /dev/null +++ b/guest-libs/k256/tests/programs/examples/scalar_sqrt.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use elliptic_curve::{CurveArithmetic, Field, PrimeField}; +use openvm_k256::Secp256k1; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_k256::Secp256k1Point; +openvm::init!("openvm_init_scalar_sqrt.rs"); + +openvm::entry!(main); + +pub fn main() { + type Scalar = ::Scalar; + + let a = Scalar::from_u128(4); + let b = a.sqrt().unwrap(); + assert!(b == Scalar::from_u128(2) || b == -Scalar::from_u128(2)); + + let a = Scalar::from_u128(2); + let b = a.sqrt().unwrap(); + let sqrt_2 = Scalar::from_str_vartime( + "2823942750030662837874242031155578187138543190171473581917399304008038956128", + ) + .unwrap(); + assert!(b == sqrt_2 || b == -sqrt_2); + assert!(b * b == a); + + let a = Scalar::from_u128(5); + let b = a.sqrt(); + assert!(bool::from(b.is_none())); +} diff --git a/guest-libs/k256/tests/programs/examples/test_vectors/mod.rs b/guest-libs/k256/tests/programs/examples/test_vectors/mod.rs new file mode 100644 index 0000000000..0c1682fff5 --- /dev/null +++ b/guest-libs/k256/tests/programs/examples/test_vectors/mod.rs @@ -0,0 +1,224 @@ +// Taken from https://github.com/RustCrypto/elliptic-curves/blob/32343a78f1522aa5bd856556f114053d4bb938e0/k256/src/test_vectors/group.rs#L9 + +use hex_literal::hex; + +/// Repeated addition of the generator. +/// +/// Vectors for secp256k1 are difficult to find. These are the vectors from: +/// +pub const ADD_TEST_VECTORS: &[([u8; 32], [u8; 32])] = &[ + ( + hex!("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"), + hex!("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"), + ), + ( + hex!("C6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5"), + hex!("1AE168FEA63DC339A3C58419466CEAEEF7F632653266D0E1236431A950CFE52A"), + ), + ( + hex!("F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"), + hex!("388F7B0F632DE8140FE337E62A37F3566500A99934C2231B6CB9FD7584B8E672"), + ), + ( + hex!("E493DBF1C10D80F3581E4904930B1404CC6C13900EE0758474FA94ABE8C4CD13"), + hex!("51ED993EA0D455B75642E2098EA51448D967AE33BFBDFE40CFE97BDC47739922"), + ), + ( + hex!("2F8BDE4D1A07209355B4A7250A5C5128E88B84BDDC619AB7CBA8D569B240EFE4"), + hex!("D8AC222636E5E3D6D4DBA9DDA6C9C426F788271BAB0D6840DCA87D3AA6AC62D6"), + ), + ( + hex!("FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A1460297556"), + hex!("AE12777AACFBB620F3BE96017F45C560DE80F0F6518FE4A03C870C36B075F297"), + ), + ( + hex!("5CBDF0646E5DB4EAA398F365F2EA7A0E3D419B7E0330E39CE92BDDEDCAC4F9BC"), + hex!("6AEBCA40BA255960A3178D6D861A54DBA813D0B813FDE7B5A5082628087264DA"), + ), + ( + hex!("2F01E5E15CCA351DAFF3843FB70F3C2F0A1BDD05E5AF888A67784EF3E10A2A01"), + hex!("5C4DA8A741539949293D082A132D13B4C2E213D6BA5B7617B5DA2CB76CBDE904"), + ), + ( + hex!("ACD484E2F0C7F65309AD178A9F559ABDE09796974C57E714C35F110DFC27CCBE"), + hex!("CC338921B0A7D9FD64380971763B61E9ADD888A4375F8E0F05CC262AC64F9C37"), + ), + ( + hex!("A0434D9E47F3C86235477C7B1AE6AE5D3442D49B1943C2B752A68E2A47E247C7"), + hex!("893ABA425419BC27A3B6C7E693A24C696F794C2ED877A1593CBEE53B037368D7"), + ), + ( + hex!("774AE7F858A9411E5EF4246B70C65AAC5649980BE5C17891BBEC17895DA008CB"), + hex!("D984A032EB6B5E190243DD56D7B7B365372DB1E2DFF9D6A8301D74C9C953C61B"), + ), + ( + hex!("D01115D548E7561B15C38F004D734633687CF4419620095BC5B0F47070AFE85A"), + hex!("A9F34FFDC815E0D7A8B64537E17BD81579238C5DD9A86D526B051B13F4062327"), + ), + ( + hex!("F28773C2D975288BC7D1D205C3748651B075FBC6610E58CDDEEDDF8F19405AA8"), + hex!("0AB0902E8D880A89758212EB65CDAF473A1A06DA521FA91F29B5CB52DB03ED81"), + ), + ( + hex!("499FDF9E895E719CFD64E67F07D38E3226AA7B63678949E6E49B241A60E823E4"), + hex!("CAC2F6C4B54E855190F044E4A7B3D464464279C27A3F95BCC65F40D403A13F5B"), + ), + ( + hex!("D7924D4F7D43EA965A465AE3095FF41131E5946F3C85F79E44ADBCF8E27E080E"), + hex!("581E2872A86C72A683842EC228CC6DEFEA40AF2BD896D3A5C504DC9FF6A26B58"), + ), + ( + hex!("E60FCE93B59E9EC53011AABC21C23E97B2A31369B87A5AE9C44EE89E2A6DEC0A"), + hex!("F7E3507399E595929DB99F34F57937101296891E44D23F0BE1F32CCE69616821"), + ), + ( + hex!("DEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34"), + hex!("4211AB0694635168E997B0EAD2A93DAECED1F4A04A95C0F6CFB199F69E56EB77"), + ), + ( + hex!("5601570CB47F238D2B0286DB4A990FA0F3BA28D1A319F5E7CF55C2A2444DA7CC"), + hex!("C136C1DC0CBEB930E9E298043589351D81D8E0BC736AE2A1F5192E5E8B061D58"), + ), + ( + hex!("2B4EA0A797A443D293EF5CFF444F4979F06ACFEBD7E86D277475656138385B6C"), + hex!("85E89BC037945D93B343083B5A1C86131A01F60C50269763B570C854E5C09B7A"), + ), + ( + hex!("4CE119C96E2FA357200B559B2F7DD5A5F02D5290AFF74B03F3E471B273211C97"), + hex!("12BA26DCB10EC1625DA61FA10A844C676162948271D96967450288EE9233DC3A"), + ), +]; + +/// Scalar multiplication with the generator. +/// +/// Vectors for secp256k1 are difficult to find. These are the vectors from: +/// +// clippy thinks this is unused for some reason, but it's used in mul.rs +#[allow(dead_code)] +pub const MUL_TEST_VECTORS: &[([u8; 32], [u8; 32], [u8; 32])] = &[ + ( + hex!("000000000000000000000000000000000000000000000000018EBBB95EED0E13"), + hex!("A90CC3D3F3E146DAADFC74CA1372207CB4B725AE708CEF713A98EDD73D99EF29"), + hex!("5A79D6B289610C68BC3B47F3D72F9788A26A06868B4D8E433E1E2AD76FB7DC76"), + ), + ( + hex!("0000000000000000000000000000000000159D893D4CDD747246CDCA43590E13"), + hex!("E5A2636BCFD412EBF36EC45B19BFB68A1BC5F8632E678132B885F7DF99C5E9B3"), + hex!("736C1CE161AE27B405CAFD2A7520370153C2C861AC51D6C1D5985D9606B45F39"), + ), + ( + hex!("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAEABB739ABD2280EEFF497A3340D9050"), + hex!("A6B594B38FB3E77C6EDF78161FADE2041F4E09FD8497DB776E546C41567FEB3C"), + hex!("71444009192228730CD8237A490FEBA2AFE3D27D7CC1136BC97E439D13330D55"), + ), + ( + hex!("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0"), + hex!("00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C63"), + hex!("3F3979BF72AE8202983DC989AEC7F2FF2ED91BDD69CE02FC0700CA100E59DDF3"), + ), + ( + hex!("BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C0325AD0376782CCFDDC6E99C28B0F0"), + hex!("E24CE4BEEE294AA6350FAA67512B99D388693AE4E7F53D19882A6EA169FC1CE1"), + hex!("8B71E83545FC2B5872589F99D948C03108D36797C4DE363EBD3FF6A9E1A95B10"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036412D"), + hex!("4CE119C96E2FA357200B559B2F7DD5A5F02D5290AFF74B03F3E471B273211C97"), + hex!("ED45D9234EF13E9DA259E05EF57BB3989E9D6B7D8E269698BAFD77106DCC1FF5"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036412E"), + hex!("2B4EA0A797A443D293EF5CFF444F4979F06ACFEBD7E86D277475656138385B6C"), + hex!("7A17643FC86BA26C4CBCF7C4A5E379ECE5FE09F3AFD9689C4A8F37AA1A3F60B5"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036412F"), + hex!("5601570CB47F238D2B0286DB4A990FA0F3BA28D1A319F5E7CF55C2A2444DA7CC"), + hex!("3EC93E23F34146CF161D67FBCA76CAE27E271F438C951D5E0AE6D1A074F9DED7"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364130"), + hex!("DEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34"), + hex!("BDEE54F96B9CAE9716684F152D56C251312E0B5FB56A3F09304E660861A910B8"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364131"), + hex!("E60FCE93B59E9EC53011AABC21C23E97B2A31369B87A5AE9C44EE89E2A6DEC0A"), + hex!("081CAF8C661A6A6D624660CB0A86C8EFED6976E1BB2DC0F41E0CD330969E940E"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364132"), + hex!("D7924D4F7D43EA965A465AE3095FF41131E5946F3C85F79E44ADBCF8E27E080E"), + hex!("A7E1D78D57938D597C7BD13DD733921015BF50D427692C5A3AFB235F095D90D7"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364133"), + hex!("499FDF9E895E719CFD64E67F07D38E3226AA7B63678949E6E49B241A60E823E4"), + hex!("353D093B4AB17AAE6F0FBB1B584C2B9BB9BD863D85C06A4339A0BF2AFC5EBCD4"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364134"), + hex!("F28773C2D975288BC7D1D205C3748651B075FBC6610E58CDDEEDDF8F19405AA8"), + hex!("F54F6FD17277F5768A7DED149A3250B8C5E5F925ADE056E0D64A34AC24FC0EAE"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364135"), + hex!("D01115D548E7561B15C38F004D734633687CF4419620095BC5B0F47070AFE85A"), + hex!("560CB00237EA1F285749BAC81E8427EA86DC73A2265792AD94FAE4EB0BF9D908"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364136"), + hex!("774AE7F858A9411E5EF4246B70C65AAC5649980BE5C17891BBEC17895DA008CB"), + hex!("267B5FCD1494A1E6FDBC22A928484C9AC8D24E1D20062957CFE28B3536AC3614"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364137"), + hex!("A0434D9E47F3C86235477C7B1AE6AE5D3442D49B1943C2B752A68E2A47E247C7"), + hex!("76C545BDABE643D85C4938196C5DB3969086B3D127885EA6C3411AC3FC8C9358"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364138"), + hex!("ACD484E2F0C7F65309AD178A9F559ABDE09796974C57E714C35F110DFC27CCBE"), + hex!("33CC76DE4F5826029BC7F68E89C49E165227775BC8A071F0FA33D9D439B05FF8"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364139"), + hex!("2F01E5E15CCA351DAFF3843FB70F3C2F0A1BDD05E5AF888A67784EF3E10A2A01"), + hex!("A3B25758BEAC66B6D6C2F7D5ECD2EC4B3D1DEC2945A489E84A25D3479342132B"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036413A"), + hex!("5CBDF0646E5DB4EAA398F365F2EA7A0E3D419B7E0330E39CE92BDDEDCAC4F9BC"), + hex!("951435BF45DAA69F5CE8729279E5AB2457EC2F47EC02184A5AF7D9D6F78D9755"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036413B"), + hex!("FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A1460297556"), + hex!("51ED8885530449DF0C4169FE80BA3A9F217F0F09AE701B5FC378F3C84F8A0998"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036413C"), + hex!("2F8BDE4D1A07209355B4A7250A5C5128E88B84BDDC619AB7CBA8D569B240EFE4"), + hex!("2753DDD9C91A1C292B24562259363BD90877D8E454F297BF235782C459539959"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036413D"), + hex!("E493DBF1C10D80F3581E4904930B1404CC6C13900EE0758474FA94ABE8C4CD13"), + hex!("AE1266C15F2BAA48A9BD1DF6715AEBB7269851CC404201BF30168422B88C630D"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036413E"), + hex!("F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"), + hex!("C77084F09CD217EBF01CC819D5C80CA99AFF5666CB3DDCE4934602897B4715BD"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036413F"), + hex!("C6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5"), + hex!("E51E970159C23CC65C3A7BE6B99315110809CD9ACD992F1EDC9BCE55AF301705"), + ), + ( + hex!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140"), + hex!("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"), + hex!("B7C52588D95C3B9AA25B0403F1EEF75702E84BB7597AABE663B82F6F04EF2777"), + ), +]; diff --git a/guest-libs/k256/tests/programs/openvm_init_add.rs b/guest-libs/k256/tests/programs/openvm_init_add.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/guest-libs/k256/tests/programs/openvm_init_add.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/guest-libs/k256/tests/programs/openvm_init_ecdsa.rs b/guest-libs/k256/tests/programs/openvm_init_ecdsa.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/guest-libs/k256/tests/programs/openvm_init_ecdsa.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/guest-libs/k256/tests/programs/openvm_init_linear_combination.rs b/guest-libs/k256/tests/programs/openvm_init_linear_combination.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/guest-libs/k256/tests/programs/openvm_init_linear_combination.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/guest-libs/k256/tests/programs/openvm_init_mul.rs b/guest-libs/k256/tests/programs/openvm_init_mul.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/guest-libs/k256/tests/programs/openvm_init_mul.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/guest-libs/k256/tests/programs/openvm_init_scalar_sqrt.rs b/guest-libs/k256/tests/programs/openvm_init_scalar_sqrt.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/guest-libs/k256/tests/programs/openvm_init_scalar_sqrt.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/guest-libs/k256/tests/programs/openvm_init_simple.rs b/guest-libs/k256/tests/programs/openvm_init_simple.rs new file mode 100644 index 0000000000..bec9f527e9 --- /dev/null +++ b/guest-libs/k256/tests/programs/openvm_init_simple.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_ecc_guest::sw_macros::sw_init! { Secp256k1Point } diff --git a/guest-libs/keccak256/Cargo.toml b/guest-libs/keccak256/Cargo.toml new file mode 100644 index 0000000000..8b0c710ad1 --- /dev/null +++ b/guest-libs/keccak256/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "openvm-keccak256" +description = "OpenVM library for keccak256" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +openvm-keccak256-guest = { workspace = true } + +[dev-dependencies] +openvm-instructions = { workspace = true } +openvm-stark-sdk = { workspace = true } +openvm-circuit = { workspace = true, features = ["test-utils", "parallel"] } +openvm-transpiler = { workspace = true } +openvm-keccak256-transpiler = { workspace = true } +openvm-keccak256-circuit = { workspace = true } +openvm-rv32im-transpiler = { workspace = true } +openvm-toolchain-tests = { workspace = true } +eyre = { workspace = true } + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +tiny-keccak = { workspace = true } diff --git a/guest-libs/keccak256/src/lib.rs b/guest-libs/keccak256/src/lib.rs new file mode 100644 index 0000000000..963bee9d53 --- /dev/null +++ b/guest-libs/keccak256/src/lib.rs @@ -0,0 +1,42 @@ +#![no_std] + +#[cfg(target_os = "zkvm")] +use core::mem::MaybeUninit; + +/// The keccak256 cryptographic hash function. +#[inline(always)] +pub fn keccak256(input: &[u8]) -> [u8; 32] { + #[cfg(not(target_os = "zkvm"))] + { + let mut output = [0u8; 32]; + set_keccak256(input, &mut output); + output + } + #[cfg(target_os = "zkvm")] + { + let mut output = MaybeUninit::<[u8; 32]>::uninit(); + openvm_keccak256_guest::native_keccak256( + input.as_ptr(), + input.len(), + output.as_mut_ptr() as *mut u8, + ); + unsafe { output.assume_init() } + } +} + +/// Sets `output` to the keccak256 hash of `input`. +pub fn set_keccak256(input: &[u8], output: &mut [u8; 32]) { + #[cfg(not(target_os = "zkvm"))] + { + use tiny_keccak::Hasher; + let mut hasher = tiny_keccak::Keccak::v256(); + hasher.update(input); + hasher.finalize(output); + } + #[cfg(target_os = "zkvm")] + openvm_keccak256_guest::native_keccak256( + input.as_ptr(), + input.len(), + output.as_mut_ptr() as *mut u8, + ); +} diff --git a/extensions/keccak256/tests/src/lib.rs b/guest-libs/keccak256/tests/lib.rs similarity index 83% rename from extensions/keccak256/tests/src/lib.rs rename to guest-libs/keccak256/tests/lib.rs index ecb2f524ee..836d158a4c 100644 --- a/extensions/keccak256/tests/src/lib.rs +++ b/guest-libs/keccak256/tests/lib.rs @@ -16,7 +16,9 @@ mod tests { #[test] fn test_keccak256() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "keccak")?; + let config = Keccak256Rv32Config::default(); + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "keccak", &config)?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -25,7 +27,7 @@ mod tests { .with_extension(Rv32MTranspilerExtension) .with_extension(Rv32IoTranspilerExtension), )?; - air_test(Keccak256Rv32Config::default(), openvm_exe); + air_test(config, openvm_exe); Ok(()) } } diff --git a/extensions/keccak256/tests/programs/Cargo.toml b/guest-libs/keccak256/tests/programs/Cargo.toml similarity index 83% rename from extensions/keccak256/tests/programs/Cargo.toml rename to guest-libs/keccak256/tests/programs/Cargo.toml index 8eb24c3af1..60c0f64d24 100644 --- a/extensions/keccak256/tests/programs/Cargo.toml +++ b/guest-libs/keccak256/tests/programs/Cargo.toml @@ -6,15 +6,14 @@ edition = "2021" [dependencies] openvm = { path = "../../../../crates/toolchain/openvm" } -openvm-platform = { path = "../../../../crates/toolchain/platform" } -openvm-keccak256-guest = { path = "../../guest" } +openvm-keccak256 = { path = "../../" } + hex = { version = "0.4.3", default-features = false, features = ["alloc"] } serde = { version = "1.0", default-features = false, features = [ "alloc", "derive", ] } - [features] default = [] std = ["serde/std", "openvm/std"] diff --git a/extensions/keccak256/tests/programs/examples/keccak.rs b/guest-libs/keccak256/tests/programs/examples/keccak.rs similarity index 99% rename from extensions/keccak256/tests/programs/examples/keccak.rs rename to guest-libs/keccak256/tests/programs/examples/keccak.rs index d850bfdab2..37955be114 100644 --- a/extensions/keccak256/tests/programs/examples/keccak.rs +++ b/guest-libs/keccak256/tests/programs/examples/keccak.rs @@ -7,7 +7,7 @@ use alloc::vec::Vec; use core::hint::black_box; use hex::FromHex; -use openvm_keccak256_guest::keccak256; +use openvm_keccak256::keccak256; openvm::entry!(main); diff --git a/guest-libs/p256/Cargo.toml b/guest-libs/p256/Cargo.toml new file mode 100644 index 0000000000..e54a7d22d6 --- /dev/null +++ b/guest-libs/p256/Cargo.toml @@ -0,0 +1,73 @@ +[package] +# for patching purposes, the name must be the same as the original `p256` crate +name = "p256" +# for patching purposes, version must match that of original `p256` crate +version = "0.13.2" +description = "OpenVM fork of p256" +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +openvm = { workspace = true } +openvm-algebra-guest = { workspace = true } +openvm-algebra-moduli-macros = { workspace = true } +openvm-ecc-guest = { workspace = true } +openvm-ecc-sw-macros = { workspace = true } + +elliptic-curve = { workspace = true, features = ["hazmat", "sec1"] } +ecdsa-core = { version = "0.16.9", package = "ecdsa", optional = true, default-features = false, features = [ + "der", +] } +serde = { workspace = true } +hex-literal = { workspace = true } +ff = { workspace = true } + +[dev-dependencies] +openvm-circuit = { workspace = true, features = ["test-utils", "parallel"] } +openvm-transpiler.workspace = true +openvm-algebra-circuit.workspace = true +openvm-algebra-transpiler.workspace = true +openvm-ecc-transpiler.workspace = true +openvm-ecc-circuit.workspace = true +openvm-sha256-circuit.workspace = true +openvm-sha256-transpiler.workspace = true +openvm-rv32im-circuit.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-toolchain-tests.workspace = true + +openvm-stark-backend.workspace = true +openvm-stark-sdk.workspace = true + +serde.workspace = true +eyre.workspace = true +derive_more = { workspace = true, features = ["from"] } + +[features] +default = ["ecdsa"] +alloc = ["ecdsa-core?/alloc", "elliptic-curve/alloc"] +std = ["alloc", "ecdsa-core?/std", "elliptic-curve/std", "openvm-ecc-guest/std"] + +arithmetic = ["elliptic-curve/arithmetic"] +bits = ["arithmetic", "elliptic-curve/bits"] +digest = ["ecdsa-core/digest", "ecdsa-core/hazmat"] +ecdh = ["arithmetic", "elliptic-curve/ecdh"] +ecdsa = ["arithmetic", "ecdsa-core/signing", "ecdsa-core/verifying"] +expose-field = ["arithmetic"] +hash2curve = ["arithmetic", "elliptic-curve/hash2curve"] +jwk = ["elliptic-curve/jwk"] +pem = ["elliptic-curve/pem", "ecdsa-core/pem", "pkcs8"] +pkcs8 = ["ecdsa-core?/pkcs8", "elliptic-curve/pkcs8"] +serde = ["ecdsa-core?/serde", "elliptic-curve/serde"] +sha256 = [] +test-vectors = [] +voprf = ["elliptic-curve/voprf"] + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +num-bigint = { workspace = true } + +[package.metadata.cargo-shear] +ignored = ["openvm", "serde", "num-bigint", "derive_more"] diff --git a/guest-libs/p256/src/coord.rs b/guest-libs/p256/src/coord.rs new file mode 100644 index 0000000000..7475bafdcb --- /dev/null +++ b/guest-libs/p256/src/coord.rs @@ -0,0 +1,38 @@ +use alloc::vec::Vec; + +use elliptic_curve::subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use openvm_algebra_guest::IntMod; + +use crate::internal::P256Coord; + +// --- Implement elliptic_curve traits on P256Coord --- + +impl Copy for P256Coord {} + +impl ConditionallySelectable for P256Coord { + fn conditional_select(a: &P256Coord, b: &P256Coord, choice: Choice) -> P256Coord { + P256Coord::from_le_bytes( + &a.as_le_bytes() + .iter() + .zip(b.as_le_bytes().iter()) + .map(|(a, b)| u8::conditional_select(a, b, choice)) + .collect::>(), + ) + } +} + +impl ConstantTimeEq for P256Coord { + fn ct_eq(&self, other: &P256Coord) -> Choice { + #[cfg(not(target_os = "zkvm"))] + { + // Requires canonical form + self.as_le_bytes().ct_eq(other.as_le_bytes()) + } + #[cfg(target_os = "zkvm")] + { + // The zkVM implementation calls iseqmod opcode so it is constant time, _except_ a check + // of whether the setup opcode has been called already + Choice::from((self == other) as u8) + } + } +} diff --git a/guest-libs/p256/src/ecdsa.rs b/guest-libs/p256/src/ecdsa.rs new file mode 100644 index 0000000000..1f2c90e797 --- /dev/null +++ b/guest-libs/p256/src/ecdsa.rs @@ -0,0 +1,37 @@ +// re-export types that are visible in the p256 crate for API compatibility + +// Use these types instead of unpatched p256::ecdsa::{Signature, VerifyingKey} +// because those are type aliases that use non-zkvm implementations + +pub use ecdsa_core::signature::{self, Error}; +#[cfg(feature = "ecdsa")] +use {super::P256Point, ecdsa_core::hazmat::VerifyPrimitive}; + +use super::NistP256; + +/// ECDSA/secp256k1 signature (fixed-size) +pub type Signature = ecdsa_core::Signature; + +/// ECDSA/secp256k1 signing key +#[cfg(feature = "ecdsa")] +pub type SigningKey = ecdsa_core::SigningKey; + +/// ECDSA/secp256k1 verification key (i.e. public key) +#[cfg(feature = "ecdsa")] +pub type VerifyingKey = openvm_ecc_guest::ecdsa::VerifyingKey; + +#[cfg(feature = "ecdsa")] +impl VerifyPrimitive for P256Point { + fn verify_prehashed( + &self, + z: &crate::point::FieldBytes, + sig: &Signature, + ) -> Result<(), ecdsa_core::Error> { + openvm_ecc_guest::ecdsa::verify_prehashed::( + *self, + z.as_slice(), + sig.to_bytes().as_slice(), + ) + .map_err(|_| ecdsa_core::Error::new()) + } +} diff --git a/extensions/ecc/guest/src/p256.rs b/guest-libs/p256/src/internal.rs similarity index 59% rename from extensions/ecc/guest/src/p256.rs rename to guest-libs/p256/src/internal.rs index 6bbe4ab718..b98c401c8c 100644 --- a/extensions/ecc/guest/src/p256.rs +++ b/guest-libs/p256/src/internal.rs @@ -1,34 +1,23 @@ use core::ops::{Add, Neg}; use hex_literal::hex; -#[cfg(not(target_os = "zkvm"))] -use lazy_static::lazy_static; -#[cfg(not(target_os = "zkvm"))] -use num_bigint::BigUint; -use openvm_algebra_guest::{Field, IntMod}; +use openvm_algebra_guest::IntMod; +use openvm_algebra_moduli_macros::moduli_declare; +use openvm_ecc_guest::{ + weierstrass::{CachedMulTable, IntrinsicCurve, WeierstrassPoint}, + CyclicGroup, Group, +}; +use openvm_ecc_sw_macros::sw_declare; -use super::group::{CyclicGroup, Group}; -use crate::weierstrass::{CachedMulTable, IntrinsicCurve}; +use crate::NistP256; -#[cfg(not(target_os = "zkvm"))] -lazy_static! { - // The constants are taken from: https://neuromancer.sk/std/secg/secp256r1 - pub static ref P256_MODULUS: BigUint = BigUint::from_bytes_be(&hex!( - "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff" - )); - pub static ref P256_ORDER: BigUint = BigUint::from_bytes_be(&hex!( - "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551" - )); -} +// --- Define the OpenVM modular arithmetic and ecc types --- -openvm_algebra_moduli_macros::moduli_declare! { +moduli_declare! { P256Coord { modulus = "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff" }, P256Scalar { modulus = "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551" }, } -pub const P256_NUM_LIMBS: usize = 32; -pub const P256_LIMB_BITS: usize = 8; -pub const P256_BLOCK_SIZE: usize = 32; // from_const_bytes is little endian pub const CURVE_A: P256Coord = P256Coord::from_const_bytes(hex!( "fcffffffffffffffffffffff00000000000000000000000001000000ffffffff" @@ -37,24 +26,11 @@ pub const CURVE_B: P256Coord = P256Coord::from_const_bytes(hex!( "4b60d2273e3cce3bf6b053ccb0061d65bc86987655bdebb3e7933aaad835c65a" )); -openvm_ecc_sw_macros::sw_declare! { +sw_declare! { P256Point { mod_type = P256Coord, a = CURVE_A, b = CURVE_B }, } -impl Field for P256Coord { - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - type SelfRef<'a> = &'a Self; - - fn double_assign(&mut self) { - IntMod::double_assign(self); - } - - fn square_assign(&mut self) { - IntMod::square_assign(self); - } -} +// --- Implement internal traits --- impl CyclicGroup for P256Point { // The constants are taken from: https://neuromancer.sk/std/secg/secp256r1 @@ -77,7 +53,7 @@ impl CyclicGroup for P256Point { }; } -impl IntrinsicCurve for p256::NistP256 { +impl IntrinsicCurve for NistP256 { type Scalar = P256Scalar; type Point = P256Point; @@ -89,7 +65,19 @@ impl IntrinsicCurve for p256::NistP256 { let table = CachedMulTable::::new_with_prime_order(bases, 4); table.windowed_mul(coeffs) } else { - crate::msm(coeffs, bases) + openvm_ecc_guest::msm(coeffs, bases) } } } + +// --- Implement helpful methods mimicking the structs in p256 --- + +impl P256Point { + pub fn x_be_bytes(&self) -> [u8; 32] { + ::x(self).to_be_bytes() + } + + pub fn y_be_bytes(&self) -> [u8; 32] { + ::y(self).to_be_bytes() + } +} diff --git a/guest-libs/p256/src/lib.rs b/guest-libs/p256/src/lib.rs new file mode 100644 index 0000000000..a3492a12f7 --- /dev/null +++ b/guest-libs/p256/src/lib.rs @@ -0,0 +1,60 @@ +// Fork of RustCrypto's p256 crate https://docs.rs/p256/latest/p256/ +// that uses zkvm instructions + +#![no_std] +extern crate alloc; + +use elliptic_curve::{ + bigint::U256, consts::U32, point::PointCompression, Curve, CurveArithmetic, PrimeCurve, +}; + +mod coord; +mod internal; +mod point; +mod scalar; + +#[cfg(feature = "ecdsa-core")] +pub mod ecdsa; + +// Needs to be public so that the `sw_init` macro can access it +pub use internal::{P256Coord, P256Point, P256Scalar}; + +// -- Define the ZST for implementing the elliptic curve traits -- +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct NistP256; + +// --- Implement the Curve trait on P256 --- + +/// Order of the P256 elliptic curve in hexadecimal. +const ORDER_HEX: &str = "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"; + +/// Order of the P256 elliptic curve. +const ORDER: U256 = U256::from_be_hex(ORDER_HEX); + +impl Curve for NistP256 { + /// 32-byte serialized field elements. + type FieldBytesSize = U32; + + // Perf: Use the U256 type from openvm_ruint here + type Uint = U256; + + /// Curve order. + const ORDER: U256 = ORDER; +} + +impl PrimeCurve for NistP256 {} + +impl CurveArithmetic for NistP256 { + type AffinePoint = P256Point; + /// The `ProjectivePoint` type is still internally represented as an affine point. + type ProjectivePoint = P256Point; + type Scalar = P256Scalar; +} + +impl PointCompression for NistP256 { + /// P256 points are typically uncompressed. + const COMPRESS_POINTS: bool = false; +} + +/// SEC1-encoded P256 curve point. +pub type EncodedPoint = elliptic_curve::sec1::EncodedPoint; diff --git a/guest-libs/p256/src/point.rs b/guest-libs/p256/src/point.rs new file mode 100644 index 0000000000..83e061bdb7 --- /dev/null +++ b/guest-libs/p256/src/point.rs @@ -0,0 +1,254 @@ +use core::{ + iter::Sum, + ops::{Mul, MulAssign}, +}; + +use elliptic_curve::{ + bigint::{ArrayEncoding, U256}, + ops::{LinearCombination, MulByGenerator}, + point::{AffineCoordinates, DecompactPoint, DecompressPoint}, + rand_core::RngCore, + sec1::{FromEncodedPoint, ToEncodedPoint}, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, + FieldBytesEncoding, +}; +use openvm_algebra_guest::IntMod; +use openvm_ecc_guest::{ + weierstrass::{IntrinsicCurve, WeierstrassPoint}, + CyclicGroup, +}; + +use crate::{ + internal::{P256Coord, P256Point, P256Scalar}, + EncodedPoint, NistP256, +}; + +// --- Implement elliptic_curve traits on P256Point --- + +/// P256 field element serialized as bytes. +/// +/// Byte array containing a serialized field element value (base field or scalar). +pub type FieldBytes = elliptic_curve::FieldBytes; + +impl FieldBytesEncoding for U256 { + fn decode_field_bytes(field_bytes: &FieldBytes) -> Self { + U256::from_be_byte_array(*field_bytes) + } + + fn encode_field_bytes(&self) -> FieldBytes { + self.to_be_byte_array() + } +} + +impl AffineCoordinates for P256Point { + type FieldRepr = FieldBytes; + + fn x(&self) -> FieldBytes { + *FieldBytes::from_slice(&::x(self).to_be_bytes()) + } + + fn y_is_odd(&self) -> Choice { + (self.y().as_le_bytes()[0] & 1).into() + } +} + +impl Copy for P256Point {} + +impl ConditionallySelectable for P256Point { + fn conditional_select(a: &P256Point, b: &P256Point, choice: Choice) -> P256Point { + P256Point::from_xy_unchecked( + P256Coord::conditional_select( + ::x(a), + ::x(b), + choice, + ), + P256Coord::conditional_select(a.y(), b.y(), choice), + ) + } +} + +impl ConstantTimeEq for P256Point { + fn ct_eq(&self, other: &P256Point) -> Choice { + ::x(self).ct_eq(::x(other)) + & self.y().ct_eq(other.y()) + } +} + +impl Default for P256Point { + fn default() -> Self { + ::IDENTITY + } +} + +impl DefaultIsZeroes for P256Point {} + +impl Sum for P256Point { + fn sum>(iter: I) -> Self { + iter.fold(::IDENTITY, |a, b| a + b) + } +} + +impl<'a> Sum<&'a P256Point> for P256Point { + fn sum>(iter: I) -> Self { + iter.cloned().sum() + } +} + +impl Mul for P256Point { + type Output = P256Point; + + fn mul(self, other: P256Scalar) -> P256Point { + NistP256::msm(&[other], &[self]) + } +} + +impl Mul<&P256Scalar> for &P256Point { + type Output = P256Point; + + fn mul(self, other: &P256Scalar) -> P256Point { + NistP256::msm(&[*other], &[*self]) + } +} + +impl Mul<&P256Scalar> for P256Point { + type Output = P256Point; + + fn mul(self, other: &P256Scalar) -> P256Point { + NistP256::msm(&[*other], &[self]) + } +} + +impl MulAssign for P256Point { + fn mul_assign(&mut self, rhs: P256Scalar) { + *self = NistP256::msm(&[rhs], &[*self]); + } +} + +impl MulAssign<&P256Scalar> for P256Point { + fn mul_assign(&mut self, rhs: &P256Scalar) { + *self = NistP256::msm(&[*rhs], &[*self]); + } +} + +impl elliptic_curve::Group for P256Point { + type Scalar = P256Scalar; + + fn random(mut _rng: impl RngCore) -> Self { + // Self::GENERATOR * Self::Scalar::random(&mut rng) + unimplemented!() + } + + fn identity() -> Self { + ::IDENTITY + } + + fn generator() -> Self { + Self::GENERATOR + } + + fn is_identity(&self) -> Choice { + (::is_identity(self) as u8).into() + } + + #[must_use] + fn double(&self) -> Self { + self + self + } +} + +impl elliptic_curve::group::Curve for P256Point { + type AffineRepr = P256Point; + + fn to_affine(&self) -> P256Point { + *self + } +} + +impl LinearCombination for P256Point { + fn lincomb(x: &Self, k: &Self::Scalar, y: &Self, l: &Self::Scalar) -> Self { + NistP256::msm(&[*k, *l], &[*x, *y]) + } +} + +// default implementation +impl MulByGenerator for P256Point {} + +impl DecompressPoint for P256Point { + /// Note that this is not constant time + fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { + use openvm_ecc_guest::weierstrass::FromCompressed; + + let x = P256Coord::from_be_bytes(x_bytes.as_slice()); + let rec_id = y_is_odd.unwrap_u8(); + let y = >::decompress(x, &rec_id); + match y { + Some(point) => CtOption::new(point, 1.into()), + None => CtOption::new(P256Point::default(), 0.into()), + } + } +} + +impl DecompactPoint for P256Point { + fn decompact(x_bytes: &FieldBytes) -> CtOption { + Self::decompress(x_bytes, Choice::from(0)) + } +} + +impl FromEncodedPoint for P256Point { + /// Attempts to parse the given [`EncodedPoint`] as an SEC1-encoded [`P256Point`]. + /// + /// # Returns + /// + /// `None` value if `encoded_point` is not on the secp256k1 curve. + fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { + match openvm_ecc_guest::ecdsa::VerifyingKey::::from_sec1_bytes( + encoded_point.as_bytes(), + ) { + Ok(verifying_key) => CtOption::new(*verifying_key.as_affine(), 1.into()), + Err(_) => CtOption::new(P256Point::default(), 0.into()), + } + } +} + +impl ToEncodedPoint for P256Point { + fn to_encoded_point(&self, compress: bool) -> EncodedPoint { + EncodedPoint::conditional_select( + &EncodedPoint::from_affine_coordinates( + &::x(self).to_be_bytes().into(), + &::y(self).to_be_bytes().into(), + compress, + ), + &EncodedPoint::identity(), + elliptic_curve::Group::is_identity(self), + ) + } +} + +impl TryFrom for P256Point { + type Error = elliptic_curve::Error; + + fn try_from(point: EncodedPoint) -> elliptic_curve::Result { + P256Point::try_from(&point) + } +} + +impl TryFrom<&EncodedPoint> for P256Point { + type Error = elliptic_curve::Error; + + fn try_from(point: &EncodedPoint) -> elliptic_curve::Result { + Option::from(P256Point::from_encoded_point(point)).ok_or(elliptic_curve::Error) + } +} + +impl From for EncodedPoint { + fn from(affine_point: P256Point) -> EncodedPoint { + EncodedPoint::from(&affine_point) + } +} + +impl From<&P256Point> for EncodedPoint { + fn from(affine_point: &P256Point) -> EncodedPoint { + affine_point.to_encoded_point(true) + } +} diff --git a/guest-libs/p256/src/scalar.rs b/guest-libs/p256/src/scalar.rs new file mode 100644 index 0000000000..1dd42f8246 --- /dev/null +++ b/guest-libs/p256/src/scalar.rs @@ -0,0 +1,237 @@ +use alloc::vec::Vec; +use core::{cmp::Ordering, ops::ShrAssign}; + +use elliptic_curve::{ + bigint::{ArrayEncoding, Encoding, U256}, + ops::{Invert, Reduce}, + rand_core::RngCore, + scalar::{FromUintUnchecked, IsHigh}, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, + Field, PrimeField, ScalarPrimitive, +}; +use hex_literal::hex; +use openvm_algebra_guest::IntMod; + +use crate::{internal::P256Scalar, point::FieldBytes, NistP256, ORDER_HEX}; + +impl P256Scalar { + /// Returns the SEC1 encoding of this scalar. + pub fn to_bytes(&self) -> FieldBytes { + self.to_be_bytes().into() + } +} +// --- Implement elliptic_curve traits on P256Scalar --- + +impl Copy for P256Scalar {} + +impl From for P256Scalar { + fn from(value: u64) -> Self { + Self::from_u64(value) + } +} + +impl Default for P256Scalar { + fn default() -> Self { + ::ZERO + } +} + +// Requires canonical form +impl ConstantTimeEq for P256Scalar { + fn ct_eq(&self, other: &Self) -> Choice { + self.as_le_bytes().ct_eq(other.as_le_bytes()) + } +} + +impl ConditionallySelectable for P256Scalar { + fn conditional_select(a: &P256Scalar, b: &P256Scalar, choice: Choice) -> P256Scalar { + P256Scalar::from_le_bytes( + &a.as_le_bytes() + .iter() + .zip(b.as_le_bytes().iter()) + .map(|(a, b)| u8::conditional_select(a, b, choice)) + .collect::>(), + ) + } +} + +impl Field for P256Scalar { + const ZERO: Self = ::ZERO; + const ONE: Self = ::ONE; + + fn random(mut _rng: impl RngCore) -> Self { + unimplemented!() + } + + #[must_use] + fn square(&self) -> Self { + self * self + } + + #[must_use] + fn double(&self) -> Self { + self + self + } + + fn invert(&self) -> CtOption { + // needs to be in canonical form for ct_eq + self.assert_reduced(); + let is_zero = self.ct_eq(&::ZERO); + CtOption::new( + ::invert(self), + !is_zero, + ) + } + + #[allow(clippy::many_single_char_names)] + fn sqrt(&self) -> CtOption { + match ::sqrt(self) { + Some(sqrt) => CtOption::new(sqrt, 1.into()), + None => CtOption::new(::ZERO, 0.into()), + } + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + ff::helpers::sqrt_ratio_generic(num, div) + } +} + +const fn seven_le() -> [u8; 32] { + let mut buf = [0u8; 32]; + buf[0] = 7; + buf +} + +impl PrimeField for P256Scalar { + type Repr = FieldBytes; + + const MODULUS: &'static str = ORDER_HEX; + const NUM_BITS: u32 = 256; + const CAPACITY: u32 = 255; + const TWO_INV: Self = Self::from_const_bytes(hex!( + "a992317e61e5dc7942cf8bd3567d73deffffffffffffff7f00000080ffffff7f" + )); + const MULTIPLICATIVE_GENERATOR: Self = Self::from_const_bytes(seven_le()); + const S: u32 = 4; + const ROOT_OF_UNITY: Self = Self::from_const_bytes(hex!( + "02661eb4fbd79205af8d3704d0ca4615fc3d2a84ce7a80ba9209772a067fc9ff" + )); + const ROOT_OF_UNITY_INV: Self = Self::from_const_bytes(hex!( + "6437c757067f9c3737414c797c11ace3ae1c135804fa45c62a6fd462556aa6a0" + )); + const DELTA: Self = Self::from_const_bytes(hex!( + "817d05a5391e0000000000000000000000000000000000000000000000000000" + )); + + /// Attempts to parse the given byte array as an SEC1-encoded scalar. + /// + /// Returns None if the byte array does not contain a big-endian integer in the range + /// [0, p). + fn from_repr(bytes: FieldBytes) -> CtOption { + let ret = Self::from_be_bytes(bytes.as_slice()); + CtOption::new(ret, (ret.is_reduced() as u8).into()) + } + + // Endianness should match from_repr + fn to_repr(&self) -> FieldBytes { + *FieldBytes::from_slice(&self.to_be_bytes()) + } + + fn is_odd(&self) -> Choice { + (self.as_le_bytes()[0] & 1).into() + } +} + +impl ShrAssign for P256Scalar { + fn shr_assign(&mut self, _rhs: usize) { + // I don't think this is used anywhere + unimplemented!() + } +} + +impl Reduce for P256Scalar { + type Bytes = FieldBytes; + + fn reduce(w: U256) -> Self { + Self::from_le_bytes(&w.to_le_bytes()) + } + + #[inline] + fn reduce_bytes(bytes: &FieldBytes) -> Self { + Self::reduce(U256::from_be_byte_array(*bytes)) + } +} + +impl PartialOrd for P256Scalar { + // requires self and other to be in canonical form + fn partial_cmp(&self, other: &Self) -> Option { + self.assert_reduced(); + other.assert_reduced(); + Some( + self.to_be_bytes() + .iter() + .zip(other.to_be_bytes().iter()) + .map(|(a, b)| a.cmp(b)) + .find(|ord| *ord != Ordering::Equal) + .unwrap_or(Ordering::Equal), + ) + } +} + +impl IsHigh for P256Scalar { + fn is_high(&self) -> Choice { + // self > n/2 + // iff self + self overflows + // iff self + self < self + ((self + self < *self) as u8).into() + } +} + +impl Invert for P256Scalar { + type Output = CtOption; + + fn invert(&self) -> CtOption { + ::invert(self) + } +} + +impl FromUintUnchecked for P256Scalar { + type Uint = U256; + + fn from_uint_unchecked(uint: Self::Uint) -> Self { + Self::from_le_bytes(&uint.to_le_bytes()) + } +} + +impl From> for P256Scalar { + fn from(scalar: ScalarPrimitive) -> Self { + Self::from_le_bytes(&scalar.as_uint().to_le_bytes()) + } +} + +impl From for ScalarPrimitive { + fn from(scalar: P256Scalar) -> ScalarPrimitive { + ScalarPrimitive::from_slice(&scalar.to_be_bytes()).unwrap() + } +} + +impl DefaultIsZeroes for P256Scalar {} + +impl AsRef for P256Scalar { + fn as_ref(&self) -> &P256Scalar { + self + } +} + +impl From for U256 { + fn from(scalar: P256Scalar) -> Self { + U256::from_be_slice(&scalar.to_be_bytes()) + } +} + +impl From for FieldBytes { + fn from(scalar: P256Scalar) -> Self { + *FieldBytes::from_slice(&scalar.to_be_bytes()) + } +} diff --git a/guest-libs/p256/tests/lib.rs b/guest-libs/p256/tests/lib.rs new file mode 100644 index 0000000000..8be2c34850 --- /dev/null +++ b/guest-libs/p256/tests/lib.rs @@ -0,0 +1,233 @@ +mod guest_tests { + use ecdsa_config::EcdsaConfig; + use eyre::Result; + use openvm_algebra_transpiler::ModularTranspilerExtension; + use openvm_circuit::{arch::instructions::exe::VmExe, utils::air_test}; + use openvm_ecc_circuit::{Rv32WeierstrassConfig, P256_CONFIG}; + use openvm_ecc_transpiler::EccTranspilerExtension; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_sha256_transpiler::Sha256TranspilerExtension; + use openvm_stark_sdk::p3_baby_bear::BabyBear; + use openvm_toolchain_tests::{build_example_program_at_path, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + + type F = BabyBear; + + #[test] + fn test_add() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![P256_CONFIG.clone()]); + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "add", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_mul() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![P256_CONFIG.clone()]); + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "mul", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_linear_combination() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![P256_CONFIG.clone()]); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "linear_combination", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + + mod ecdsa_config { + use eyre::Result; + use openvm_algebra_circuit::{ + ModularExtension, ModularExtensionExecutor, ModularExtensionPeriphery, + }; + use openvm_circuit::{ + arch::{InitFileGenerator, SystemConfig}, + derive::VmConfig, + }; + use openvm_ecc_circuit::{ + CurveConfig, WeierstrassExtension, WeierstrassExtensionExecutor, + WeierstrassExtensionPeriphery, + }; + use openvm_rv32im_circuit::{ + Rv32I, Rv32IExecutor, Rv32IPeriphery, Rv32Io, Rv32IoExecutor, Rv32IoPeriphery, Rv32M, + Rv32MExecutor, Rv32MPeriphery, + }; + use openvm_sha256_circuit::{Sha256, Sha256Executor, Sha256Periphery}; + use openvm_stark_backend::p3_field::PrimeField32; + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, VmConfig, Serialize, Deserialize)] + pub struct EcdsaConfig { + #[system] + pub system: SystemConfig, + #[extension] + pub base: Rv32I, + #[extension] + pub mul: Rv32M, + #[extension] + pub io: Rv32Io, + #[extension] + pub modular: ModularExtension, + #[extension] + pub weierstrass: WeierstrassExtension, + #[extension] + pub sha256: Sha256, + } + + impl EcdsaConfig { + pub fn new(curves: Vec) -> Self { + let primes: Vec<_> = curves + .iter() + .flat_map(|c| [c.modulus.clone(), c.scalar.clone()]) + .collect(); + Self { + system: SystemConfig::default().with_continuations(), + base: Default::default(), + mul: Default::default(), + io: Default::default(), + modular: ModularExtension::new(primes), + weierstrass: WeierstrassExtension::new(curves), + sha256: Default::default(), + } + } + } + + impl InitFileGenerator for EcdsaConfig { + fn generate_init_file_contents(&self) -> Option { + Some(format!( + "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n", + self.modular.generate_moduli_init(), + self.weierstrass.generate_sw_init() + )) + } + } + } + + #[test] + fn test_ecdsa() -> Result<()> { + let config = EcdsaConfig::new(vec![P256_CONFIG.clone()]); + + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "ecdsa", &config)?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Sha256TranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_scalar_sqrt() -> Result<()> { + let config = Rv32WeierstrassConfig::new(vec![P256_CONFIG.clone()]); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "scalar_sqrt", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } +} + +mod host_tests { + use hex_literal::hex; + use openvm_algebra_guest::IntMod; + use openvm_ecc_guest::{msm, weierstrass::WeierstrassPoint, Group}; + use p256::{P256Coord, P256Point, P256Scalar}; + + #[test] + fn test_host_p256() { + // Sample points got from https://asecuritysite.com/ecc/p256p + let x1 = P256Coord::from_u32(5); + let y1 = P256Coord::from_le_bytes(&hex!( + "ccfb4832085c4133c5a3d9643c50ca11de7a8199ce3b91fe061858aab9439245" + )); + let p1 = P256Point::from_xy(x1, y1).unwrap(); + let x2 = P256Coord::from_u32(6); + let y2 = P256Coord::from_le_bytes(&hex!( + "cb23828228510d22e9c0e70fb802d1dc47007233e5856946c20a25542c4cb236" + )); + let p2 = P256Point::from_xy(x2, y2).unwrap(); + + // Generic add can handle equal or unequal points. + #[allow(clippy::op_ref)] + let p3 = &p1 + &p2; + #[allow(clippy::op_ref)] + let p4 = &p2 + &p2; + + // Add assign and double assign + let mut sum = P256Point::from_xy(x1, y1).unwrap(); + sum += &p2; + if sum.x() != p3.x() || sum.y() != p3.y() { + panic!(); + } + let mut double = P256Point::from_xy(x2, y2).unwrap(); + double.double_assign(); + if double.x() != p4.x() || double.y() != p4.y() { + panic!(); + } + + // Ec Mul + let p1 = P256Point::from_xy(x1, y1).unwrap(); + let scalar = P256Scalar::from_u32(3); + #[allow(clippy::op_ref)] + let p2 = &p1.double() + &p1; + let result = msm(&[scalar], &[p1]); + if result.x() != p2.x() || result.y() != p2.y() { + panic!(); + } + } +} diff --git a/guest-libs/p256/tests/programs/Cargo.toml b/guest-libs/p256/tests/programs/Cargo.toml new file mode 100644 index 0000000000..18c52d5ceb --- /dev/null +++ b/guest-libs/p256/tests/programs/Cargo.toml @@ -0,0 +1,27 @@ +[workspace] +[package] +name = "openvm-p256-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-algebra-guest = { path = "../../../../extensions/algebra/guest" } +openvm-algebra-moduli-macros = { path = "../../../../extensions/algebra/moduli-macros/" } +openvm-ecc-guest = { path = "../../../../extensions/ecc/guest" } +openvm-ecc-sw-macros = { path = "../../../../extensions/ecc/sw-macros/" } +openvm-p256 = { path = "../../", package = "p256" } +openvm-sha2 = { path = "../../../sha2/" } + +elliptic-curve = { version = "0.13.8" } +ecdsa = { version = "0.16.9" } +hex-literal = { version = "0.4.1", default-features = false } + +[features] +default = [] +std = ["openvm/std"] + +[profile.release] +panic = "abort" +lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing +# strip = "symbols" diff --git a/guest-libs/p256/tests/programs/examples/add.rs b/guest-libs/p256/tests/programs/examples/add.rs new file mode 100644 index 0000000000..b1c4d62fb4 --- /dev/null +++ b/guest-libs/p256/tests/programs/examples/add.rs @@ -0,0 +1,30 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use elliptic_curve::{group::Curve, CurveArithmetic, Group}; +use openvm_p256::NistP256; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_p256::P256Point; + +openvm::init!("openvm_init_simple.rs"); + +openvm::entry!(main); + +mod test_vectors; +use test_vectors::ADD_TEST_VECTORS; + +pub fn main() { + let generator = ::ProjectivePoint::generator(); + let mut p = generator; + + for test_vector in ADD_TEST_VECTORS { + let affine = p.to_affine(); + + let (expected_x, expected_y) = test_vector; + assert_eq!(&affine.x_be_bytes(), expected_x); + assert_eq!(&affine.y_be_bytes(), expected_y); + + p += &generator; + } +} diff --git a/guest-libs/p256/tests/programs/examples/ecdsa.rs b/guest-libs/p256/tests/programs/examples/ecdsa.rs new file mode 100644 index 0000000000..e56b33dad6 --- /dev/null +++ b/guest-libs/p256/tests/programs/examples/ecdsa.rs @@ -0,0 +1,46 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use ecdsa::signature::hazmat::PrehashVerifier; +use elliptic_curve::{sec1::FromEncodedPoint, CurveArithmetic}; +use hex_literal::hex; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_p256::P256Point; +use openvm_p256::{ + ecdsa::{Signature, VerifyingKey}, + EncodedPoint, NistP256, +}; + +openvm::init!("openvm_init_ecdsa.rs"); + +openvm::entry!(main); + +fn main() { + // The following test vector adapted from the FIPS 186-4 ECDSA test vectors + // (P-256, SHA-384, from `SigGen.txt` in `186-4ecdsatestvectors.zip`) + // + let verifier = VerifyingKey::from_affine( + ::AffinePoint::from_encoded_point( + &EncodedPoint::from_affine_coordinates( + &hex!("e0e7b99bc62d8dd67883e39ed9fa0657789c5ff556cc1fd8dd1e2a55e9e3f243").into(), + &hex!("63fbfd0232b95578075c903a4dbf85ad58f8350516e1ec89b0ee1f5e1362da69").into(), + false, + ), + ) + .unwrap(), + ) + .unwrap(); + let signature = Signature::from_scalars( + hex!("f5087878e212b703578f5c66f434883f3ef414dc23e2e8d8ab6a8d159ed5ad83"), + hex!("306b4c6c20213707982dffbb30fba99b96e792163dd59dbe606e734328dd7c8a"), + ) + .unwrap(); + let result = verifier.verify_prehash( + &hex!("d9c83b92fa0979f4a5ddbd8dd22ab9377801c3c31bf50f932ace0d2146e2574da0d5552dbed4b18836280e9f94558ea6"), + &signature, + ); + assert!(result.is_ok()); +} diff --git a/guest-libs/p256/tests/programs/examples/linear_combination.rs b/guest-libs/p256/tests/programs/examples/linear_combination.rs new file mode 100644 index 0000000000..2ddd13fb98 --- /dev/null +++ b/guest-libs/p256/tests/programs/examples/linear_combination.rs @@ -0,0 +1,21 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use elliptic_curve::{ops::LinearCombination, Group, PrimeField}; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_p256::{P256Point, P256Point as ProjectivePoint, P256Scalar as Scalar}; + +openvm::init!("openvm_init_linear_combination.rs"); + +openvm::entry!(main); + +pub fn main() { + let g = ProjectivePoint::generator(); + let a = ProjectivePoint::lincomb(&g, &Scalar::from_u128(100), &g, &Scalar::from_u128(156)); + let mut b = g; + for _ in 0..8 { + b += b; + } + assert_eq!(a, b); +} diff --git a/guest-libs/p256/tests/programs/examples/mul.rs b/guest-libs/p256/tests/programs/examples/mul.rs new file mode 100644 index 0000000000..3cdf0eab5e --- /dev/null +++ b/guest-libs/p256/tests/programs/examples/mul.rs @@ -0,0 +1,41 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use elliptic_curve::{CurveArithmetic, Group, PrimeField}; +use openvm_p256::NistP256; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_p256::P256Point; + +openvm::init!("openvm_init_mul.rs"); + +openvm::entry!(main); + +mod test_vectors; +use test_vectors::{ADD_TEST_VECTORS, MUL_TEST_VECTORS}; + +// Taken from https://github.com/RustCrypto/elliptic-curves/blob/master/primeorder/src/dev.rs +pub fn main() { + let generator = ::ProjectivePoint::generator(); + + for (k, coords) in ADD_TEST_VECTORS + .iter() + .enumerate() + .map(|(k, coords)| { + ( + ::Scalar::from(k as u64 + 1), + *coords, + ) + }) + .chain(MUL_TEST_VECTORS.iter().cloned().map(|(k, x, y)| { + ( + ::Scalar::from_repr(k.into()).unwrap(), + (x, y), + ) + })) + { + let p = generator * k; + assert_eq!(p.x_be_bytes(), coords.0); + assert_eq!(p.y_be_bytes(), coords.1); + } +} diff --git a/guest-libs/p256/tests/programs/examples/scalar_sqrt.rs b/guest-libs/p256/tests/programs/examples/scalar_sqrt.rs new file mode 100644 index 0000000000..7aa8d44343 --- /dev/null +++ b/guest-libs/p256/tests/programs/examples/scalar_sqrt.rs @@ -0,0 +1,33 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use elliptic_curve::{CurveArithmetic, Field, PrimeField}; +use openvm_p256::NistP256; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_p256::P256Point; + +openvm::init!("openvm_init_scalar_sqrt.rs"); + +openvm::entry!(main); + +pub fn main() { + type Scalar = ::Scalar; + + let a = Scalar::from_u128(4); + let b = a.sqrt().unwrap(); + assert!(b == Scalar::from_u128(2) || b == -Scalar::from_u128(2)); + + let a = Scalar::from_u128(5); + let b = a.sqrt().unwrap(); + let sqrt_5 = Scalar::from_str_vartime( + "37706888570942939511621860890978929712654002332559277021296980149138421130241", + ) + .unwrap(); + assert!(b == sqrt_5 || b == -sqrt_5); + assert!(b * b == a); + + let a = Scalar::from_u128(7); + let b = a.sqrt(); + assert!(bool::from(b.is_none())); +} diff --git a/guest-libs/p256/tests/programs/examples/test_vectors/mod.rs b/guest-libs/p256/tests/programs/examples/test_vectors/mod.rs new file mode 100644 index 0000000000..02778c877f --- /dev/null +++ b/guest-libs/p256/tests/programs/examples/test_vectors/mod.rs @@ -0,0 +1,258 @@ +// Taken from https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/test_vectors/group.rs + +use hex_literal::hex; + +/// Repeated addition of the generator. +/// +/// These are the first 20 test vectors from +pub const ADD_TEST_VECTORS: &[([u8; 32], [u8; 32])] = &[ + ( + hex!("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296"), + hex!("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5"), + ), + ( + hex!("7CF27B188D034F7E8A52380304B51AC3C08969E277F21B35A60B48FC47669978"), + hex!("07775510DB8ED040293D9AC69F7430DBBA7DADE63CE982299E04B79D227873D1"), + ), + ( + hex!("5ECBE4D1A6330A44C8F7EF951D4BF165E6C6B721EFADA985FB41661BC6E7FD6C"), + hex!("8734640C4998FF7E374B06CE1A64A2ECD82AB036384FB83D9A79B127A27D5032"), + ), + ( + hex!("E2534A3532D08FBBA02DDE659EE62BD0031FE2DB785596EF509302446B030852"), + hex!("E0F1575A4C633CC719DFEE5FDA862D764EFC96C3F30EE0055C42C23F184ED8C6"), + ), + ( + hex!("51590B7A515140D2D784C85608668FDFEF8C82FD1F5BE52421554A0DC3D033ED"), + hex!("E0C17DA8904A727D8AE1BF36BF8A79260D012F00D4D80888D1D0BB44FDA16DA4"), + ), + ( + hex!("B01A172A76A4602C92D3242CB897DDE3024C740DEBB215B4C6B0AAE93C2291A9"), + hex!("E85C10743237DAD56FEC0E2DFBA703791C00F7701C7E16BDFD7C48538FC77FE2"), + ), + ( + hex!("8E533B6FA0BF7B4625BB30667C01FB607EF9F8B8A80FEF5B300628703187B2A3"), + hex!("73EB1DBDE03318366D069F83A6F5900053C73633CB041B21C55E1A86C1F400B4"), + ), + ( + hex!("62D9779DBEE9B0534042742D3AB54CADC1D238980FCE97DBB4DD9DC1DB6FB393"), + hex!("AD5ACCBD91E9D8244FF15D771167CEE0A2ED51F6BBE76A78DA540A6A0F09957E"), + ), + ( + hex!("EA68D7B6FEDF0B71878938D51D71F8729E0ACB8C2C6DF8B3D79E8A4B90949EE0"), + hex!("2A2744C972C9FCE787014A964A8EA0C84D714FEAA4DE823FE85A224A4DD048FA"), + ), + ( + hex!("CEF66D6B2A3A993E591214D1EA223FB545CA6C471C48306E4C36069404C5723F"), + hex!("878662A229AAAE906E123CDD9D3B4C10590DED29FE751EEECA34BBAA44AF0773"), + ), + ( + hex!("3ED113B7883B4C590638379DB0C21CDA16742ED0255048BF433391D374BC21D1"), + hex!("9099209ACCC4C8A224C843AFA4F4C68A090D04DA5E9889DAE2F8EEFCE82A3740"), + ), + ( + hex!("741DD5BDA817D95E4626537320E5D55179983028B2F82C99D500C5EE8624E3C4"), + hex!("0770B46A9C385FDC567383554887B1548EEB912C35BA5CA71995FF22CD4481D3"), + ), + ( + hex!("177C837AE0AC495A61805DF2D85EE2FC792E284B65EAD58A98E15D9D46072C01"), + hex!("63BB58CD4EBEA558A24091ADB40F4E7226EE14C3A1FB4DF39C43BBE2EFC7BFD8"), + ), + ( + hex!("54E77A001C3862B97A76647F4336DF3CF126ACBE7A069C5E5709277324D2920B"), + hex!("F599F1BB29F4317542121F8C05A2E7C37171EA77735090081BA7C82F60D0B375"), + ), + ( + hex!("F0454DC6971ABAE7ADFB378999888265AE03AF92DE3A0EF163668C63E59B9D5F"), + hex!("B5B93EE3592E2D1F4E6594E51F9643E62A3B21CE75B5FA3F47E59CDE0D034F36"), + ), + ( + hex!("76A94D138A6B41858B821C629836315FCD28392EFF6CA038A5EB4787E1277C6E"), + hex!("A985FE61341F260E6CB0A1B5E11E87208599A0040FC78BAA0E9DDD724B8C5110"), + ), + ( + hex!("47776904C0F1CC3A9C0984B66F75301A5FA68678F0D64AF8BA1ABCE34738A73E"), + hex!("AA005EE6B5B957286231856577648E8381B2804428D5733F32F787FF71F1FCDC"), + ), + ( + hex!("1057E0AB5780F470DEFC9378D1C7C87437BB4C6F9EA55C63D936266DBD781FDA"), + hex!("F6F1645A15CBE5DC9FA9B7DFD96EE5A7DCC11B5C5EF4F1F78D83B3393C6A45A2"), + ), + ( + hex!("CB6D2861102C0C25CE39B7C17108C507782C452257884895C1FC7B74AB03ED83"), + hex!("58D7614B24D9EF515C35E7100D6D6CE4A496716E30FA3E03E39150752BCECDAA"), + ), + ( + hex!("83A01A9378395BAB9BCD6A0AD03CC56D56E6B19250465A94A234DC4C6B28DA9A"), + hex!("76E49B6DE2F73234AE6A5EB9D612B75C9F2202BB6923F54FF8240AAA86F640B8"), + ), +]; + +/// Scalar multiplication with the generator. +/// +/// These are the test vectors from that are not +/// part of [`ADD_TEST_VECTORS`]. +// clippy thinks this is unused for some reason, but it's used in mul.rs +#[allow(dead_code)] +pub const MUL_TEST_VECTORS: &[([u8; 32], [u8; 32], [u8; 32])] = &[ + ( + hex!("000000000000000000000000000000000000000000000000018EBBB95EED0E13"), + hex!("339150844EC15234807FE862A86BE77977DBFB3AE3D96F4C22795513AEAAB82F"), + hex!("B1C14DDFDC8EC1B2583F51E85A5EB3A155840F2034730E9B5ADA38B674336A21"), + ), + ( + hex!("0000000000000000000000000000000000159D893D4CDD747246CDCA43590E13"), + hex!("1B7E046A076CC25E6D7FA5003F6729F665CC3241B5ADAB12B498CD32F2803264"), + hex!("BFEA79BE2B666B073DB69A2A241ADAB0738FE9D2DD28B5604EB8C8CF097C457B"), + ), + ( + hex!("41FFC1FFFFFE01FFFC0003FFFE0007C001FFF00003FFF07FFE0007C000000003"), + hex!("9EACE8F4B071E677C5350B02F2BB2B384AAE89D58AA72CA97A170572E0FB222F"), + hex!("1BBDAEC2430B09B93F7CB08678636CE12EAAFD58390699B5FD2F6E1188FC2A78"), + ), + ( + hex!("7FFFFFC03FFFC003FFFFFC007FFF00000000070000100000000E00FFFFFFF3FF"), + hex!("878F22CC6DB6048D2B767268F22FFAD8E56AB8E2DC615F7BD89F1E350500DD8D"), + hex!("714A5D7BB901C9C5853400D12341A892EF45D87FC553786756C4F0C9391D763E"), + ), + ( + hex!("0000FFFFF01FFFF8FFFFC00FFFFFFFFFC000000FFFFFC007FFFFFC000FFFE3FF"), + hex!("659A379625AB122F2512B8DADA02C6348D53B54452DFF67AC7ACE4E8856295CA"), + hex!("49D81AB97B648464D0B4A288BD7818FAB41A16426E943527C4FED8736C53D0F6"), + ), + ( + hex!("4000008000FFFFFC000003F00000FFFFFFFF800003800F8000E0000E000000FF"), + hex!("CBCEAAA8A4DD44BBCE58E8DB7740A5510EC2CB7EA8DA8D8F036B3FB04CDA4DE4"), + hex!("4BD7AA301A80D7F59FD983FEDBE59BB7B2863FE46494935E3745B360E32332FA"), + ), + ( + hex!("003FFFFFF0001F80000003F80003FFFFC0000000000FFE0000007FF818000F80"), + hex!("F0C4A0576154FF3A33A3460D42EAED806E854DFA37125221D37935124BA462A4"), + hex!("5B392FA964434D29EEC6C9DBC261CF116796864AA2FAADB984A2DF38D1AEF7A3"), + ), + ( + hex!("000001C000000000001001F803FFFFFF80000000000007FF0000000000000000"), + hex!("5E6C8524B6369530B12C62D31EC53E0288173BD662BDF680B53A41ECBCAD00CC"), + hex!("447FE742C2BFEF4D0DB14B5B83A2682309B5618E0064A94804E9282179FE089F"), + ), + ( + hex!("7FC0007FFFFFFC0003FFFFFFFFFFFFFE00003FFFFF07FFFFFFFFFFFFC007FFFF"), + hex!("03792E541BC209076A3D7920A915021ECD396A6EB5C3960024BE5575F3223484"), + hex!("FC774AE092403101563B712F68170312304F20C80B40C06282063DB25F268DE4"), + ), + ( + hex!("7FFFFC03FF807FFFE0001FFFFF800FFF800001FFFF0001FFFFFE001FFFC00000"), + hex!("2379FF85AB693CDF901D6CE6F2473F39C04A2FE3DCD842CE7AAB0E002095BCF8"), + hex!("F8B476530A634589D5129E46F322B02FBC610A703D80875EE70D7CE1877436A1"), + ), + ( + hex!("00FFFFFFFE03FFFC07FFFC800070000FC0007FFC00000000000FFFE1FBFF81FF"), + hex!("C1E4072C529BF2F44DA769EFC934472848003B3AF2C0F5AA8F8DDBD53E12ED7C"), + hex!("39A6EE77812BB37E8079CD01ED649D3830FCA46F718C1D3993E4A591824ABCDB"), + ), + ( + hex!("01FFF81FC000000000FF801FFFC0F81F01FFF8001FC005FFFFFF800000FFFFFC"), + hex!("34DFBC09404C21E250A9B40FA8772897AC63A094877DB65862B61BD1507B34F3"), + hex!("CF6F8A876C6F99CEAEC87148F18C7E1E0DA6E165FFC8ED82ABB65955215F77D3"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63253D"), + hex!("83A01A9378395BAB9BCD6A0AD03CC56D56E6B19250465A94A234DC4C6B28DA9A"), + hex!("891B64911D08CDCC5195A14629ED48A360DDFD4596DC0AB007DBF5557909BF47"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63253E"), + hex!("CB6D2861102C0C25CE39B7C17108C507782C452257884895C1FC7B74AB03ED83"), + hex!("A7289EB3DB2610AFA3CA18EFF292931B5B698E92CF05C1FC1C6EAF8AD4313255"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63253F"), + hex!("1057E0AB5780F470DEFC9378D1C7C87437BB4C6F9EA55C63D936266DBD781FDA"), + hex!("090E9BA4EA341A246056482026911A58233EE4A4A10B0E08727C4CC6C395BA5D"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632540"), + hex!("47776904C0F1CC3A9C0984B66F75301A5FA68678F0D64AF8BA1ABCE34738A73E"), + hex!("55FFA1184A46A8D89DCE7A9A889B717C7E4D7FBCD72A8CC0CD0878008E0E0323"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632541"), + hex!("76A94D138A6B41858B821C629836315FCD28392EFF6CA038A5EB4787E1277C6E"), + hex!("567A019DCBE0D9F2934F5E4A1EE178DF7A665FFCF0387455F162228DB473AEEF"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632542"), + hex!("F0454DC6971ABAE7ADFB378999888265AE03AF92DE3A0EF163668C63E59B9D5F"), + hex!("4A46C11BA6D1D2E1B19A6B1AE069BC19D5C4DE328A4A05C0B81A6321F2FCB0C9"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632543"), + hex!("54E77A001C3862B97A76647F4336DF3CF126ACBE7A069C5E5709277324D2920B"), + hex!("0A660E43D60BCE8BBDEDE073FA5D183C8E8E15898CAF6FF7E45837D09F2F4C8A"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632544"), + hex!("177C837AE0AC495A61805DF2D85EE2FC792E284B65EAD58A98E15D9D46072C01"), + hex!("9C44A731B1415AA85DBF6E524BF0B18DD911EB3D5E04B20C63BC441D10384027"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632545"), + hex!("741DD5BDA817D95E4626537320E5D55179983028B2F82C99D500C5EE8624E3C4"), + hex!("F88F4B9463C7A024A98C7CAAB7784EAB71146ED4CA45A358E66A00DD32BB7E2C"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632546"), + hex!("3ED113B7883B4C590638379DB0C21CDA16742ED0255048BF433391D374BC21D1"), + hex!("6F66DF64333B375EDB37BC505B0B3975F6F2FB26A16776251D07110317D5C8BF"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632547"), + hex!("CEF66D6B2A3A993E591214D1EA223FB545CA6C471C48306E4C36069404C5723F"), + hex!("78799D5CD655517091EDC32262C4B3EFA6F212D7018AE11135CB4455BB50F88C"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632548"), + hex!("EA68D7B6FEDF0B71878938D51D71F8729E0ACB8C2C6DF8B3D79E8A4B90949EE0"), + hex!("D5D8BB358D36031978FEB569B5715F37B28EB0165B217DC017A5DDB5B22FB705"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632549"), + hex!("62D9779DBEE9B0534042742D3AB54CADC1D238980FCE97DBB4DD9DC1DB6FB393"), + hex!("52A533416E1627DCB00EA288EE98311F5D12AE0A4418958725ABF595F0F66A81"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254A"), + hex!("8E533B6FA0BF7B4625BB30667C01FB607EF9F8B8A80FEF5B300628703187B2A3"), + hex!("8C14E2411FCCE7CA92F9607C590A6FFFAC38C9CD34FBE4DE3AA1E5793E0BFF4B"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254B"), + hex!("B01A172A76A4602C92D3242CB897DDE3024C740DEBB215B4C6B0AAE93C2291A9"), + hex!("17A3EF8ACDC8252B9013F1D20458FC86E3FF0890E381E9420283B7AC7038801D"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254C"), + hex!("51590B7A515140D2D784C85608668FDFEF8C82FD1F5BE52421554A0DC3D033ED"), + hex!("1F3E82566FB58D83751E40C9407586D9F2FED1002B27F7772E2F44BB025E925B"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254D"), + hex!("E2534A3532D08FBBA02DDE659EE62BD0031FE2DB785596EF509302446B030852"), + hex!("1F0EA8A4B39CC339E62011A02579D289B103693D0CF11FFAA3BD3DC0E7B12739"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254E"), + hex!("5ECBE4D1A6330A44C8F7EF951D4BF165E6C6B721EFADA985FB41661BC6E7FD6C"), + hex!("78CB9BF2B6670082C8B4F931E59B5D1327D54FCAC7B047C265864ED85D82AFCD"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F"), + hex!("7CF27B188D034F7E8A52380304B51AC3C08969E277F21B35A60B48FC47669978"), + hex!("F888AAEE24712FC0D6C26539608BCF244582521AC3167DD661FB4862DD878C2E"), + ), + ( + hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632550"), + hex!("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296"), + hex!("B01CBD1C01E58065711814B583F061E9D431CCA994CEA1313449BF97C840AE0A"), + ), +]; diff --git a/guest-libs/p256/tests/programs/openvm_init_add.rs b/guest-libs/p256/tests/programs/openvm_init_add.rs new file mode 100644 index 0000000000..02f8b5c05d --- /dev/null +++ b/guest-libs/p256/tests/programs/openvm_init_add.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } +openvm_ecc_guest::sw_macros::sw_init! { P256Point } diff --git a/guest-libs/p256/tests/programs/openvm_init_ecdsa.rs b/guest-libs/p256/tests/programs/openvm_init_ecdsa.rs new file mode 100644 index 0000000000..02f8b5c05d --- /dev/null +++ b/guest-libs/p256/tests/programs/openvm_init_ecdsa.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } +openvm_ecc_guest::sw_macros::sw_init! { P256Point } diff --git a/guest-libs/p256/tests/programs/openvm_init_linear_combination.rs b/guest-libs/p256/tests/programs/openvm_init_linear_combination.rs new file mode 100644 index 0000000000..02f8b5c05d --- /dev/null +++ b/guest-libs/p256/tests/programs/openvm_init_linear_combination.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } +openvm_ecc_guest::sw_macros::sw_init! { P256Point } diff --git a/guest-libs/p256/tests/programs/openvm_init_mul.rs b/guest-libs/p256/tests/programs/openvm_init_mul.rs new file mode 100644 index 0000000000..02f8b5c05d --- /dev/null +++ b/guest-libs/p256/tests/programs/openvm_init_mul.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } +openvm_ecc_guest::sw_macros::sw_init! { P256Point } diff --git a/guest-libs/p256/tests/programs/openvm_init_scalar_sqrt.rs b/guest-libs/p256/tests/programs/openvm_init_scalar_sqrt.rs new file mode 100644 index 0000000000..02f8b5c05d --- /dev/null +++ b/guest-libs/p256/tests/programs/openvm_init_scalar_sqrt.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } +openvm_ecc_guest::sw_macros::sw_init! { P256Point } diff --git a/guest-libs/p256/tests/programs/openvm_init_simple.rs b/guest-libs/p256/tests/programs/openvm_init_simple.rs new file mode 100644 index 0000000000..02f8b5c05d --- /dev/null +++ b/guest-libs/p256/tests/programs/openvm_init_simple.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } +openvm_ecc_guest::sw_macros::sw_init! { P256Point } diff --git a/guest-libs/pairing/Cargo.toml b/guest-libs/pairing/Cargo.toml new file mode 100644 index 0000000000..1e0bcbc80b --- /dev/null +++ b/guest-libs/pairing/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "openvm-pairing" +description = "OpenVM library for elliptic curve pairing" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +openvm = { workspace = true } +openvm-platform = { workspace = true } +serde = { workspace = true } +itertools = { workspace = true, features = ["use_alloc"] } +rand.workspace = true +hex-literal = { workspace = true } +openvm-algebra-guest = { workspace = true } +openvm-algebra-moduli-macros = { workspace = true } +openvm-ecc-guest = { workspace = true } +openvm-ecc-sw-macros = { workspace = true } +openvm-algebra-complex-macros = { workspace = true } +openvm-custom-insn = { workspace = true } +openvm-rv32im-guest = { workspace = true } +openvm-pairing-guest = { workspace = true } + +# Used for `halo2curves` feature +halo2curves-axiom = { workspace = true, optional = true } +group = "0.13.0" + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +num-bigint.workspace = true +num-traits.workspace = true +openvm-ecc-guest = { workspace = true } + +[dev-dependencies] +openvm-instructions = { workspace = true } +openvm-stark-sdk.workspace = true +openvm-circuit = { workspace = true, features = ["test-utils", "parallel"] } +openvm-transpiler.workspace = true +openvm-algebra-circuit.workspace = true +openvm-algebra-transpiler.workspace = true +openvm-pairing-circuit.workspace = true +openvm-pairing-transpiler.workspace = true +openvm-pairing-guest.workspace = true +openvm-ecc-circuit.workspace = true +openvm-ecc-guest.workspace = true +openvm-ecc-transpiler.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm = { workspace = true } +openvm-toolchain-tests = { workspace = true } +eyre.workspace = true +rand.workspace = true +num-bigint.workspace = true +num-traits.workspace = true +halo2curves-axiom = { workspace = true } + +[features] +default = [] +halo2curves = ["bls12_381", "bn254", "dep:halo2curves-axiom"] +# features to enable specific curves in guest programs +# only enable for the curves you use as it affects the init! macro +bn254 = ["openvm-pairing-guest/bn254"] +bls12_381 = ["openvm-pairing-guest/bls12_381"] + +[package.metadata.cargo-shear] +ignored = ["openvm", "openvm-custom-insn"] diff --git a/extensions/pairing/README.md b/guest-libs/pairing/README.md similarity index 100% rename from extensions/pairing/README.md rename to guest-libs/pairing/README.md diff --git a/extensions/pairing/guest/src/bls12_381/fp12.rs b/guest-libs/pairing/src/bls12_381/fp12.rs similarity index 94% rename from extensions/pairing/guest/src/bls12_381/fp12.rs rename to guest-libs/pairing/src/bls12_381/fp12.rs index 413f9377af..0269c7b864 100644 --- a/extensions/pairing/guest/src/bls12_381/fp12.rs +++ b/guest-libs/pairing/src/bls12_381/fp12.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use alloc::vec::Vec; use core::ops::{Mul, MulAssign, Neg}; @@ -5,9 +7,10 @@ use openvm_algebra_guest::{ field::{ComplexConjugate, FieldExtension}, DivAssignUnsafe, DivUnsafe, Field, }; +use openvm_pairing_guest::pairing::PairingIntrinsics; use super::{Bls12_381, Fp, Fp2}; -use crate::pairing::{fp12_invert_assign, PairingIntrinsics, SexticExtField}; +use crate::operations::{fp12_invert_assign, SexticExtField}; pub type Fp12 = SexticExtField; @@ -126,7 +129,7 @@ impl ComplexConjugate for Fp12 { impl<'a> MulAssign<&'a Fp12> for Fp12 { #[inline(always)] fn mul_assign(&mut self, other: &'a Fp12) { - *self = crate::pairing::sextic_tower_mul(self, other, &Bls12_381::XI); + *self = crate::operations::sextic_tower_mul(self, other, &Bls12_381::XI); } } @@ -134,7 +137,7 @@ impl<'a> Mul<&'a Fp12> for &'a Fp12 { type Output = Fp12; #[inline(always)] fn mul(self, other: &'a Fp12) -> Self::Output { - crate::pairing::sextic_tower_mul(self, other, &Bls12_381::XI) + crate::operations::sextic_tower_mul(self, other, &Bls12_381::XI) } } diff --git a/extensions/pairing/guest/src/bls12_381/fp2.rs b/guest-libs/pairing/src/bls12_381/fp2.rs similarity index 98% rename from extensions/pairing/guest/src/bls12_381/fp2.rs rename to guest-libs/pairing/src/bls12_381/fp2.rs index 6e8672680e..2e1512878b 100644 --- a/extensions/pairing/guest/src/bls12_381/fp2.rs +++ b/guest-libs/pairing/src/bls12_381/fp2.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use alloc::vec::Vec; use core::ops::Neg; diff --git a/guest-libs/pairing/src/bls12_381/mod.rs b/guest-libs/pairing/src/bls12_381/mod.rs new file mode 100644 index 0000000000..0a7c150e1c --- /dev/null +++ b/guest-libs/pairing/src/bls12_381/mod.rs @@ -0,0 +1,622 @@ +extern crate alloc; + +use core::ops::Neg; + +use openvm_algebra_guest::IntMod; +use openvm_algebra_moduli_macros::moduli_declare; +use openvm_ecc_guest::{weierstrass::IntrinsicCurve, CyclicGroup, Group}; + +mod fp12; +mod fp2; +mod pairing; +#[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] +pub(crate) mod utils; + +pub use fp12::*; +pub use fp2::*; +use hex_literal::hex; +use openvm_ecc_sw_macros::sw_declare; +use openvm_pairing_guest::pairing::PairingIntrinsics; + +#[cfg(all(test, feature = "halo2curves", not(target_os = "zkvm")))] +mod tests; + +moduli_declare! { + Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" }, + Bls12_381Scalar { modulus = "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" }, +} + +const CURVE_B: Bls12_381Fp = Bls12_381Fp::from_const_u8(4); + +sw_declare! { + Bls12_381G1Affine { mod_type = Bls12_381Fp, b = CURVE_B }, +} + +pub type Fp = Bls12_381Fp; +pub type Scalar = Bls12_381Scalar; +/// Affine point representation of `Fp` points of BLS12-381. +/// **Note**: an instance of this type may be constructed that lies +/// on the curve but not necessarily in the prime order subgroup +/// because the group has cofactors. +pub type G1Affine = Bls12_381G1Affine; +pub use g2::G2Affine; + +// https://hackmd.io/@benjaminion/bls12-381#Cofactor +// BLS12-381: The from_xy function will allow constructing elements that lie on the curve +// but aren't actually in the cyclic subgroup of prime order that is usually called G1. +impl CyclicGroup for G1Affine { + // https://github.com/zcash/librustzcash/blob/6e0364cd42a2b3d2b958a54771ef51a8db79dd29/pairing/src/bls12_381/README.md#generators + const GENERATOR: Self = G1Affine { + x: Bls12_381Fp::from_const_bytes(hex!( + "BBC622DB0AF03AFBEF1A7AF93FE8556C58AC1B173F3A4EA105B974974F8C68C30FACA94F8C63952694D79731A7D3F117" + )), + y: Bls12_381Fp::from_const_bytes(hex!( + "E1E7C5462923AA0CE48A88A244C73CD0EDB3042CCB18DB00F60AD0D595E0F5FCE48A1D74ED309EA0F1A0AAE381F4B308" + )), + }; + const NEG_GENERATOR: Self = G1Affine { + x: Bls12_381Fp::from_const_bytes(hex!( + "BBC622DB0AF03AFBEF1A7AF93FE8556C58AC1B173F3A4EA105B974974F8C68C30FACA94F8C63952694D79731A7D3F117" + )), + y: Bls12_381Fp::from_const_bytes(hex!( + "CAC239B9D6DC54AD1B75CB0EBA386F4E3642ACCAD5B95566C907B51DEF6A8167F2212ECFC8767DAAA845D555681D4D11" + )), + }; +} + +pub struct Bls12_381; + +impl IntrinsicCurve for Bls12_381 { + type Scalar = Scalar; + type Point = G1Affine; + + fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point { + openvm_ecc_guest::msm(coeffs, bases) + } +} + +// Define a G2Affine struct that implements curve operations using `Fp2` intrinsics +// but not special E(Fp2) intrinsics. +mod g2 { + use openvm_algebra_guest::Field; + use openvm_ecc_guest::{ + impl_sw_affine, impl_sw_group_ops, weierstrass::WeierstrassPoint, AffinePoint, Group, + }; + + use super::{Fp, Fp2}; + + const THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::ZERO); + const B: Fp2 = Fp2::new(Fp::from_const_u8(4), Fp::from_const_u8(4)); + impl_sw_affine!(G2Affine, Fp2, THREE, B); + impl_sw_group_ops!(G2Affine, Fp2); +} + +impl PairingIntrinsics for Bls12_381 { + type Fp = Fp; + type Fp2 = Fp2; + type Fp12 = Fp12; + + const PAIRING_IDX: usize = 1; + // The sextic extension `Fp12` is `Fp2[X] / (X^6 - \xi)`, where `\xi` is a non-residue. + const XI: Fp2 = Fp2::new(Fp::from_const_u8(1), Fp::from_const_u8(1)); + const FP2_TWO: Fp2 = Fp2::new(Fp::from_const_u8(2), Fp::from_const_u8(0)); + const FP2_THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::from_const_u8(0)); + + // Multiplication constants for the Frobenius map for coefficients in Fp2 c1..=c5 for powers + // 0..12 FROBENIUS_COEFFS\[i\]\[j\] = \xi^{(j + 1) * (p^i - 1)/6} when p = 1 (mod 6) + // These are validated against `halo2curves::bls12_381::FROBENIUS_COEFF_FQ12_C1` in tests.rs + const FROBENIUS_COEFFS: [[Self::Fp2; 5]; 12] = [ + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + ")), + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + ")), + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + ")), + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + ")), + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + ")), + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "b85f2392ed75078d3d81e7633da57ef6c4b9ba84d743247b4f5fbd3cfd03d60f1f0d2c20b4be31c26706bb02bfd30419" + )), + c1: Bls12_381Fp(hex!( + "f34adc6d128af72cc27e6c4dc15a2d285f3cf671c98e0cec6fb3c7b68747a154b89f1f2302e9e98832e0c4362b3efc00" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )), + c1: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "adaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "16810780e9fa189b32877f256e3e3ac666059c8e4ddfea8bee8f0b0c241698f345e0b1486bfa47dfd85f3a01d9cfb205" + )), + c1: Bls12_381Fp(hex!( + "9529f87f1605e61ecd78d48b90c17158bdf0146853f345dbd08279e76035df7091cc99fa4aadd36bc186453811424e14" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "fffffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "adaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )), + c1: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )), + c1: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )), + c1: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "9529f87f1605e61ecd78d48b90c17158bdf0146853f345dbd08279e76035df7091cc99fa4aadd36bc186453811424e14" + )), + c1: Bls12_381Fp(hex!( + "16810780e9fa189b32877f256e3e3ac666059c8e4ddfea8bee8f0b0c241698f345e0b1486bfa47dfd85f3a01d9cfb205" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )), + c1: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "fffffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "f34adc6d128af72cc27e6c4dc15a2d285f3cf671c98e0cec6fb3c7b68747a154b89f1f2302e9e98832e0c4362b3efc00" + )), + c1: Bls12_381Fp(hex!( + "b85f2392ed75078d3d81e7633da57ef6c4b9ba84d743247b4f5fbd3cfd03d60f1f0d2c20b4be31c26706bb02bfd30419" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "f34adc6d128af72cc27e6c4dc15a2d285f3cf671c98e0cec6fb3c7b68747a154b89f1f2302e9e98832e0c4362b3efc00" + )), + c1: Bls12_381Fp(hex!( + "b85f2392ed75078d3d81e7633da57ef6c4b9ba84d743247b4f5fbd3cfd03d60f1f0d2c20b4be31c26706bb02bfd30419" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )), + c1: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "adaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "9529f87f1605e61ecd78d48b90c17158bdf0146853f345dbd08279e76035df7091cc99fa4aadd36bc186453811424e14" + )), + c1: Bls12_381Fp(hex!( + "16810780e9fa189b32877f256e3e3ac666059c8e4ddfea8bee8f0b0c241698f345e0b1486bfa47dfd85f3a01d9cfb205" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )), + c1: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )), + c1: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )), + c1: Bls12_381Fp(hex!( + "09cce3edfb8410c8f405ec722f9967eec5419200176ef7775e43d3c2ab5d3948fe7fd16b6de331680b40ff37040eaf06" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "adaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "acaa00000000fd8bfdff494feb2794409b5fb80f65297d89d49a75897d850daa85ded463864002ec99e67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "aaaafffffffffeb9ffff53b1feffab1e24f6b0f6a0d23067bf1285f3844b7764d7ac4b43b6a71b4b9ae67f39ea11011a" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "fffffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + ], + [ + Fp2 { + c0: Bls12_381Fp(hex!( + "16810780e9fa189b32877f256e3e3ac666059c8e4ddfea8bee8f0b0c241698f345e0b1486bfa47dfd85f3a01d9cfb205" + )), + c1: Bls12_381Fp(hex!( + "9529f87f1605e61ecd78d48b90c17158bdf0146853f345dbd08279e76035df7091cc99fa4aadd36bc186453811424e14" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bls12_381Fp(hex!( + "fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )), + c1: Bls12_381Fp(hex!( + "a2de1b12047beef10afa673ecf6644305eb41ef6896439ef60cfb130d9ed3d1cd92c7ad748c4e9e28ea68001e6035213" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "fffffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000" + )), + c1: Bls12_381Fp(hex!( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + )) + }, + Fp2 { + c0: Bls12_381Fp(hex!( + "b85f2392ed75078d3d81e7633da57ef6c4b9ba84d743247b4f5fbd3cfd03d60f1f0d2c20b4be31c26706bb02bfd30419" + )), + c1: Bls12_381Fp(hex!( + "f34adc6d128af72cc27e6c4dc15a2d285f3cf671c98e0cec6fb3c7b68747a154b89f1f2302e9e98832e0c4362b3efc00" + )) + }, + ], + ]; +} + +impl Bls12_381 { + // FINAL_EXPONENT = (p^12 - 1) / r in big-endian + // Validated by a test in test.rs + pub const FINAL_EXPONENT: [u8; 540] = hex!( + "02ee1db5dcc825b7e1bda9c0496a1c0a89ee0193d4977b3f7d4507d07363baa13f8d14a917848517badc3a43d1073776ab353f2c30698e8cc7deada9c0aadff5e9cfee9a074e43b9a660835cc872ee83ff3a0f0f1c0ad0d6106feaf4e347aa68ad49466fa927e7bb9375331807a0dce2630d9aa4b113f414386b0e8819328148978e2b0dd39099b86e1ab656d2670d93e4d7acdd350da5359bc73ab61a0c5bf24c374693c49f570bcd2b01f3077ffb10bf24dde41064837f27611212596bc293c8d4c01f25118790f4684d0b9c40a68eb74bb22a40ee7169cdc1041296532fef459f12438dfc8e2886ef965e61a474c5c85b0129127a1b5ad0463434724538411d1676a53b5a62eb34c05739334f46c02c3f0bd0c55d3109cd15948d0a1fad20044ce6ad4c6bec3ec03ef19592004cedd556952c6d8823b19dadd7c2498345c6e5308f1c511291097db60b1749bf9b71a9f9e0100418a3ef0bc627751bbd81367066bca6a4c1b6dcfc5cceb73fc56947a403577dfa9e13c24ea820b09c1d9f7c31759c3635de3f7a3639991708e88adce88177456c49637fd7961be1a4c7e79fb02faa732e2f3ec2bea83d196283313492caa9d4aff1c910e9622d2a73f62537f2701aaef6539314043f7bbce5b78c7869aeb2181a67e49eeed2161daf3f881bd88592d767f67c4717489119226c2f011d4cab803e9d71650a6f80698e2f8491d12191a04406fbc8fbd5f48925f98630e68bfb24c0bcb9b55df57510" + ); +} diff --git a/extensions/pairing/guest/src/bls12_381/pairing.rs b/guest-libs/pairing/src/bls12_381/pairing.rs similarity index 94% rename from extensions/pairing/guest/src/bls12_381/pairing.rs rename to guest-libs/pairing/src/bls12_381/pairing.rs index 9cd7ade4a5..db13c785e1 100644 --- a/extensions/pairing/guest/src/bls12_381/pairing.rs +++ b/guest-libs/pairing/src/bls12_381/pairing.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use alloc::vec::Vec; use itertools::izip; @@ -6,28 +8,31 @@ use openvm_algebra_guest::{ DivUnsafe, Field, }; use openvm_ecc_guest::AffinePoint; +use openvm_pairing_guest::{ + bls12_381::{BLS12_381_PSEUDO_BINARY_ENCODING, BLS12_381_SEED_ABS}, + pairing::{ + exp_check_fallback, Evaluatable, EvaluatedLine, FromLineMType, LineMulMType, MillerStep, + MultiMillerLoop, PairingCheck, PairingCheckError, PairingIntrinsics, UnevaluatedLine, + }, +}; +#[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] +use openvm_pairing_guest::{ + halo2curves_shims::bls12_381::Bls12_381 as Halo2CurvesBls12_381, pairing::FinalExp, +}; #[cfg(target_os = "zkvm")] use { - crate::{PairingBaseFunct7, OPCODE, PAIRING_FUNCT3}, core::mem::MaybeUninit, + openvm_pairing_guest::{PairingBaseFunct7, OPCODE, PAIRING_FUNCT3}, openvm_platform::custom_insn_r, openvm_rv32im_guest, openvm_rv32im_guest::hint_buffer_u32, }; -use super::{Bls12_381, Fp, Fp12, Fp2, BLS12_381_PSEUDO_BINARY_ENCODING, BLS12_381_SEED_ABS}; -use crate::pairing::{ - exp_check_fallback, Evaluatable, EvaluatedLine, FromLineMType, LineMulMType, MillerStep, - MultiMillerLoop, PairingCheck, PairingCheckError, PairingIntrinsics, UnevaluatedLine, -}; +use super::{Bls12_381, Fp, Fp12, Fp2}; #[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] -use crate::{ - bls12_381::utils::{ - convert_bls12381_fp2_to_halo2_fq2, convert_bls12381_fp_to_halo2_fq, - convert_bls12381_halo2_fq12_to_fp12, - }, - halo2curves_shims::bls12_381::Bls12_381 as Halo2CurvesBls12_381, - pairing::FinalExp, +use crate::bls12_381::utils::{ + convert_bls12381_fp2_to_halo2_fq2, convert_bls12381_fp_to_halo2_fq, + convert_bls12381_halo2_fq12_to_fp12, }; impl Evaluatable for UnevaluatedLine { diff --git a/guest-libs/pairing/src/bls12_381/tests.rs b/guest-libs/pairing/src/bls12_381/tests.rs new file mode 100644 index 0000000000..dc2873be47 --- /dev/null +++ b/guest-libs/pairing/src/bls12_381/tests.rs @@ -0,0 +1,309 @@ +use group::ff::Field; +use halo2curves_axiom::bls12_381::{ + Fq, Fq12, Fq2, Fq6, G1Affine, G2Affine, G2Prepared, MillerLoopResult, FROBENIUS_COEFF_FQ12_C1, +}; +use num_bigint::BigUint; +use num_traits::One; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; +use openvm_ecc_guest::{weierstrass::WeierstrassPoint, AffinePoint}; +use openvm_pairing_guest::{ + bls12_381::{BLS12_381_MODULUS, BLS12_381_ORDER}, + pairing::{FinalExp, MultiMillerLoop, PairingCheck, PairingIntrinsics}, +}; +use rand::{rngs::StdRng, SeedableRng}; + +use super::{Fp, Fp12, Fp2}; +use crate::{ + bls12_381::{ + utils::{ + convert_bls12381_fp12_to_halo2_fq12, convert_bls12381_halo2_fq12_to_fp12, + convert_bls12381_halo2_fq2_to_fp2, convert_bls12381_halo2_fq_to_fp, + convert_g2_affine_halo2_to_openvm, + }, + Bls12_381, G2Affine as OpenVmG2Affine, + }, + operations::{fp2_invert_assign, fp6_invert_assign, fp6_square_assign}, +}; + +#[test] +fn test_bls12381_frobenius_coeffs() { + #[allow(clippy::needless_range_loop)] + for i in 0..12 { + for j in 0..5 { + assert_eq!( + Bls12_381::FROBENIUS_COEFFS[i][j], + convert_bls12381_halo2_fq2_to_fp2(FROBENIUS_COEFF_FQ12_C1[i].pow([j as u64 + 1])), + "FROBENIUS_COEFFS[{}][{}] failed", + i, + j + ) + } + } +} + +#[test] +fn test_bls12381_frobenius() { + let mut rng = StdRng::seed_from_u64(15); + for pow in 0..12 { + let fq = Fq12::random(&mut rng); + let mut fq_frob = fq; + for _ in 0..pow { + fq_frob = fq_frob.frobenius_map(); + } + + let fp = convert_bls12381_halo2_fq12_to_fp12(fq); + let fp_frob = fp.frobenius_map(pow); + + assert_eq!(fp_frob, convert_bls12381_halo2_fq12_to_fp12(fq_frob)); + } +} + +#[test] +fn test_fp12_invert() { + let mut rng = StdRng::seed_from_u64(15); + let fq = Fq12::random(&mut rng); + let fq_inv = fq.invert().unwrap(); + + let fp = convert_bls12381_halo2_fq12_to_fp12(fq); + let fp_inv = fp.invert(); + assert_eq!(fp_inv, convert_bls12381_halo2_fq12_to_fp12(fq_inv)); +} + +#[test] +fn test_fp6_invert() { + let mut rng = StdRng::seed_from_u64(20); + let fq6 = Fq6 { + c0: Fq2::random(&mut rng), + c1: Fq2::random(&mut rng), + c2: Fq2::random(&mut rng), + }; + let fq6_inv = fq6.invert().unwrap(); + + let fp6c0 = convert_bls12381_halo2_fq2_to_fp2(fq6.c0); + let fp6c1 = convert_bls12381_halo2_fq2_to_fp2(fq6.c1); + let fp6c2 = convert_bls12381_halo2_fq2_to_fp2(fq6.c2); + let mut fp6 = [fp6c0, fp6c1, fp6c2]; + fp6_invert_assign::(&mut fp6, &Bls12_381::XI); + + let fq6_invc0 = convert_bls12381_halo2_fq2_to_fp2(fq6_inv.c0); + let fq6_invc1 = convert_bls12381_halo2_fq2_to_fp2(fq6_inv.c1); + let fq6_invc2 = convert_bls12381_halo2_fq2_to_fp2(fq6_inv.c2); + let fq6_inv = [fq6_invc0, fq6_invc1, fq6_invc2]; + assert_eq!(fp6, fq6_inv); +} + +#[test] +fn test_fp2_invert() { + let mut rng = StdRng::seed_from_u64(25); + let fq2 = Fq2::random(&mut rng); + let fq2_inv = fq2.invert().unwrap(); + + let mut fp2 = convert_bls12381_halo2_fq2_to_fp2(fq2).to_coeffs(); + fp2_invert_assign::(&mut fp2); + assert_eq!(fp2, convert_bls12381_halo2_fq2_to_fp2(fq2_inv).to_coeffs()); +} + +#[test] +fn test_fp6_square() { + let mut rng = StdRng::seed_from_u64(45); + let fq6 = Fq6 { + c0: Fq2::random(&mut rng), + c1: Fq2::random(&mut rng), + c2: Fq2::random(&mut rng), + }; + let fq6_sq = fq6.square(); + + let fp6c0 = convert_bls12381_halo2_fq2_to_fp2(fq6.c0); + let fp6c1 = convert_bls12381_halo2_fq2_to_fp2(fq6.c1); + let fp6c2 = convert_bls12381_halo2_fq2_to_fp2(fq6.c2); + let mut fp6 = [fp6c0, fp6c1, fp6c2]; + fp6_square_assign::(&mut fp6, &Bls12_381::XI); + + let fq6_sqc0 = convert_bls12381_halo2_fq2_to_fp2(fq6_sq.c0); + let fq6_sqc1 = convert_bls12381_halo2_fq2_to_fp2(fq6_sq.c1); + let fq6_sqc2 = convert_bls12381_halo2_fq2_to_fp2(fq6_sq.c2); + let fq6_sq = [fq6_sqc0, fq6_sqc1, fq6_sqc2]; + assert_eq!(fp6, fq6_sq); +} + +#[test] +fn test_fp2_square() { + let mut rng = StdRng::seed_from_u64(55); + let fq2 = Fq2::random(&mut rng); + let fq2_sq = fq2.square(); + + let fp2 = convert_bls12381_halo2_fq2_to_fp2(fq2); + let fp2_sq = &fp2 * &fp2; + assert_eq!(fp2_sq, convert_bls12381_halo2_fq2_to_fp2(fq2_sq)); +} + +#[test] +fn test_fp_add() { + let mut rng = StdRng::seed_from_u64(65); + let fq = Fq::random(&mut rng); + let fq_res = fq + Fq::one(); + + let fp = convert_bls12381_halo2_fq_to_fp(fq); + let fp_res = fp + Fp::ONE; + assert_eq!(fp_res, convert_bls12381_halo2_fq_to_fp(fq_res)); +} + +#[test] +fn test_fp_one() { + let fp_one = Fp::ONE; + let fq_one = Fq::ONE; + assert_eq!(fp_one, convert_bls12381_halo2_fq_to_fp(fq_one)); +} + +// Gt(Fq12) is not public +fn assert_miller_results_eq(a: MillerLoopResult, b: Fp12) { + let b = convert_bls12381_fp12_to_halo2_fq12(b); + openvm_pairing_guest::halo2curves_shims::bls12_381::test_utils::assert_miller_results_eq(a, b); +} + +#[test] +fn test_bls12381_miller_loop() { + let mut rng = StdRng::seed_from_u64(65); + let h2c_p = G1Affine::random(&mut rng); + let h2c_q = G2Affine::random(&mut rng); + + let p = AffinePoint { + x: convert_bls12381_halo2_fq_to_fp(h2c_p.x), + y: convert_bls12381_halo2_fq_to_fp(h2c_p.y), + }; + let q = AffinePoint { + x: convert_bls12381_halo2_fq2_to_fp2(h2c_q.x), + y: convert_bls12381_halo2_fq2_to_fp2(h2c_q.y), + }; + + // Compare against halo2curves implementation + let h2c_q_prepared = G2Prepared::from(h2c_q); + let compare_miller = + halo2curves_axiom::bls12_381::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); + let f = Bls12_381::multi_miller_loop(&[p], &[q]); + assert_miller_results_eq(compare_miller, f); +} + +#[test] +fn test_bls12381_miller_loop_identity() { + let mut rng = StdRng::seed_from_u64(33); + let h2c_p = G1Affine::identity(); + let h2c_q = G2Affine::random(&mut rng); + + let p = AffinePoint { + x: convert_bls12381_halo2_fq_to_fp(Fq::ZERO), + y: convert_bls12381_halo2_fq_to_fp(Fq::ZERO), + }; + let q = AffinePoint { + x: convert_bls12381_halo2_fq2_to_fp2(h2c_q.x), + y: convert_bls12381_halo2_fq2_to_fp2(h2c_q.y), + }; + + let f = Bls12_381::multi_miller_loop(&[p], &[q]); + // halo2curves implementation + let h2c_q_prepared = G2Prepared::from(h2c_q); + let compare_miller = + halo2curves_axiom::bls12_381::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); + assert_miller_results_eq(compare_miller, f); +} + +#[test] +fn test_bls12381_miller_loop_identity_2() { + let mut rng = StdRng::seed_from_u64(33); + let h2c_p = G1Affine::random(&mut rng); + let h2c_q = G2Affine::identity(); + let p = AffinePoint { + x: convert_bls12381_halo2_fq_to_fp(h2c_p.x), + y: convert_bls12381_halo2_fq_to_fp(h2c_p.y), + }; + let q = AffinePoint { + x: convert_bls12381_halo2_fq2_to_fp2(Fq2::ZERO), + y: convert_bls12381_halo2_fq2_to_fp2(Fq2::ZERO), + }; + + let f = Bls12_381::multi_miller_loop(&[p], &[q]); + // halo2curves implementation + let h2c_q_prepared = G2Prepared::from(h2c_q); + let compare_miller = + halo2curves_axiom::bls12_381::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); + assert_miller_results_eq(compare_miller, f); +} + +// test on host is enough since we are testing the curve formulas and not anything +// about intrinsic functions +#[test] +fn test_bls12381_g2_affine() { + let mut rng = StdRng::seed_from_u64(34); + for _ in 0..10 { + let p = G2Affine::random(&mut rng); + let q = G2Affine::random(&mut rng); + let expected_add = G2Affine::from(p + q); + let expected_sub = G2Affine::from(p - q); + let expected_neg = -p; + let expected_double = G2Affine::from(p + p); + let [p, q] = [p, q].map(|p| { + let x = convert_bls12381_halo2_fq2_to_fp2(p.x); + let y = convert_bls12381_halo2_fq2_to_fp2(p.y); + // check on curve + OpenVmG2Affine::from_xy(x, y).unwrap() + }); + let r_add = &p + &q; + let r_sub = &p - &q; + let r_neg = -&p; + let r_double = &p + &p; + + for (expected, actual) in [ + (expected_add, r_add), + (expected_sub, r_sub), + (expected_neg, r_neg), + (expected_double, r_double), + ] { + assert_eq!(convert_g2_affine_halo2_to_openvm(expected), actual); + } + } +} + +#[test] +fn test_bls12381_pairing_check_hint_host() { + let mut rng = StdRng::seed_from_u64(83); + let h2c_p = G1Affine::random(&mut rng); + let h2c_q = G2Affine::random(&mut rng); + + let p = AffinePoint { + x: convert_bls12381_halo2_fq_to_fp(h2c_p.x), + y: convert_bls12381_halo2_fq_to_fp(h2c_p.y), + }; + let q = AffinePoint { + x: convert_bls12381_halo2_fq2_to_fp2(h2c_q.x), + y: convert_bls12381_halo2_fq2_to_fp2(h2c_q.y), + }; + + let (c, s) = Bls12_381::pairing_check_hint(&[p], &[q]); + + let p_cmp = AffinePoint { + x: h2c_p.x, + y: h2c_p.y, + }; + let q_cmp = AffinePoint { + x: h2c_q.x, + y: h2c_q.y, + }; + + let f_cmp = openvm_pairing_guest::halo2curves_shims::bls12_381::Bls12_381::multi_miller_loop( + &[p_cmp], + &[q_cmp], + ); + let (c_cmp, s_cmp) = + openvm_pairing_guest::halo2curves_shims::bls12_381::Bls12_381::final_exp_hint(&f_cmp); + let c_cmp = convert_bls12381_halo2_fq12_to_fp12(c_cmp); + let s_cmp = convert_bls12381_halo2_fq12_to_fp12(s_cmp); + + assert_eq!(c, c_cmp); + assert_eq!(s, s_cmp); +} + +#[test] +fn test_bls12381_final_exponent() { + let final_exp = (BLS12_381_MODULUS.pow(12) - BigUint::one()) / BLS12_381_ORDER.clone(); + assert_eq!(Bls12_381::FINAL_EXPONENT.to_vec(), final_exp.to_bytes_be()); +} diff --git a/extensions/pairing/guest/src/bls12_381/utils.rs b/guest-libs/pairing/src/bls12_381/utils.rs similarity index 100% rename from extensions/pairing/guest/src/bls12_381/utils.rs rename to guest-libs/pairing/src/bls12_381/utils.rs diff --git a/extensions/pairing/guest/src/bn254/fp12.rs b/guest-libs/pairing/src/bn254/fp12.rs similarity index 95% rename from extensions/pairing/guest/src/bn254/fp12.rs rename to guest-libs/pairing/src/bn254/fp12.rs index d8b9b07415..66fb188421 100644 --- a/extensions/pairing/guest/src/bn254/fp12.rs +++ b/guest-libs/pairing/src/bn254/fp12.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use alloc::vec::Vec; use core::ops::{Mul, MulAssign, Neg}; @@ -5,9 +7,10 @@ use openvm_algebra_guest::{ field::{ComplexConjugate, FieldExtension}, DivAssignUnsafe, DivUnsafe, Field, }; +use openvm_pairing_guest::pairing::PairingIntrinsics; use super::{Bn254, Fp, Fp2}; -use crate::pairing::{fp12_invert_assign, PairingIntrinsics, SexticExtField}; +use crate::operations::{fp12_invert_assign, SexticExtField}; pub type Fp12 = SexticExtField; @@ -127,7 +130,7 @@ impl ComplexConjugate for Fp12 { impl<'a> MulAssign<&'a Fp12> for Fp12 { #[inline(always)] fn mul_assign(&mut self, other: &'a Fp12) { - *self = crate::pairing::sextic_tower_mul(self, other, &Bn254::XI); + *self = crate::operations::sextic_tower_mul(self, other, &Bn254::XI); } } @@ -135,7 +138,7 @@ impl<'a> Mul<&'a Fp12> for &'a Fp12 { type Output = Fp12; #[inline(always)] fn mul(self, other: &'a Fp12) -> Self::Output { - crate::pairing::sextic_tower_mul(self, other, &Bn254::XI) + crate::operations::sextic_tower_mul(self, other, &Bn254::XI) } } diff --git a/extensions/pairing/guest/src/bn254/fp2.rs b/guest-libs/pairing/src/bn254/fp2.rs similarity index 98% rename from extensions/pairing/guest/src/bn254/fp2.rs rename to guest-libs/pairing/src/bn254/fp2.rs index e05f6a0d9a..0df1f9525c 100644 --- a/extensions/pairing/guest/src/bn254/fp2.rs +++ b/guest-libs/pairing/src/bn254/fp2.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use alloc::vec::Vec; use core::ops::Neg; diff --git a/guest-libs/pairing/src/bn254/mod.rs b/guest-libs/pairing/src/bn254/mod.rs new file mode 100644 index 0000000000..8384b8b3e8 --- /dev/null +++ b/guest-libs/pairing/src/bn254/mod.rs @@ -0,0 +1,683 @@ +extern crate alloc; + +use core::ops::{Add, Neg}; + +use hex_literal::hex; +use openvm_algebra_guest::IntMod; +use openvm_algebra_moduli_macros::moduli_declare; +use openvm_ecc_guest::{ + weierstrass::{CachedMulTable, IntrinsicCurve}, + CyclicGroup, Group, +}; +use openvm_ecc_sw_macros::sw_declare; +use openvm_pairing_guest::pairing::PairingIntrinsics; + +mod fp12; +mod fp2; +pub mod pairing; +#[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] +pub(crate) mod utils; + +pub use fp12::*; +pub use fp2::*; + +#[cfg(all(test, feature = "halo2curves", not(target_os = "zkvm")))] +pub mod tests; + +moduli_declare! { + Bn254Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" }, + Bn254Scalar { modulus = "21888242871839275222246405745257275088548364400416034343698204186575808495617" }, +} + +const CURVE_B: Bn254Fp = Bn254Fp::from_const_bytes(hex!( + "0300000000000000000000000000000000000000000000000000000000000000" +)); + +sw_declare! { + Bn254G1Affine { mod_type = Bn254Fp, b = CURVE_B }, +} + +pub type Fp = Bn254Fp; +pub type Scalar = Bn254Scalar; +pub type G1Affine = Bn254G1Affine; +pub use g2::G2Affine; + +impl CyclicGroup for G1Affine { + // https://eips.ethereum.org/EIPS/eip-197 + const GENERATOR: Self = G1Affine { + x: Bn254Fp::from_const_u8(1), + y: Bn254Fp::from_const_u8(2), + }; + const NEG_GENERATOR: Self = G1Affine { + x: Bn254Fp::from_const_u8(1), + y: Bn254Fp::from_const_bytes(hex!( + "45FD7CD8168C203C8DCA7168916A81975D588181B64550B829A031E1724E6430" + )), + }; +} + +// Define a G2Affine struct that implements curve operations using `Fp2` intrinsics +// but not special E(Fp2) intrinsics. +mod g2 { + use hex_literal::hex; + use openvm_algebra_guest::Field; + use openvm_ecc_guest::{ + impl_sw_affine, impl_sw_group_ops, weierstrass::WeierstrassPoint, AffinePoint, Group, + }; + + use super::{Fp, Fp2}; + + const THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::ZERO); + // 3 / (9 + u) + // validated by a test below + const B: Fp2 = Fp2::new( + Fp::from_const_bytes(hex!( + "e538a124dce66732a3efdb59e5c5b4b5c36ae01b9918be81aeaab8ce409d142b" + )), + Fp::from_const_bytes(hex!( + "d215c38506bda2e452182de584a04fa7f4fdd8eeadaf2ccdd4fef03ab0139700" + )), + ); + impl_sw_affine!(G2Affine, Fp2, THREE, B); + impl_sw_group_ops!(G2Affine, Fp2); + + #[test] + fn test_g2_curve_equation_b() { + use openvm_algebra_guest::DivUnsafe; + let b = Fp2::new(Fp::from_const_u8(3), Fp::ZERO) + .div_unsafe(Fp2::new(Fp::from_const_u8(9), Fp::ONE)); + assert_eq!(b, B); + } +} + +pub struct Bn254; + +impl Bn254 { + // Same as the values from halo2curves_shims + // Validated by a test in tests.rs + pub const FROBENIUS_COEFF_FQ6_C1: [Fp2; 3] = [ + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "3d556f175795e3990c33c3c210c38cb743b159f53cec0b4cf711794f9847b32f" + )), + c1: Bn254Fp(hex!( + "a2cb0f641cd56516ce9d7c0b1d2aae3294075ad78bcca44b20aeeb6150e5c916" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ]; + + // Same as the values from halo2curves_shims + // Validated by a test in tests.rs + pub const XI_TO_Q_MINUS_1_OVER_2: Fp2 = Fp2 { + c0: Bn254Fp(hex!( + "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" + )), + c1: Bn254Fp(hex!( + "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" + )), + }; + + // FINAL_EXPONENT = (p^12 - 1) / r in big-endian + // Validated by a test in test.rs + pub const FINAL_EXPONENT: [u8; 349] = hex!( + "2f4b6dc97020fddadf107d20bc842d43bf6369b1ff6a1c71015f3f7be2e1e30a73bb94fec0daf15466b2383a5d3ec3d15ad524d8f70c54efee1bd8c3b21377e563a09a1b705887e72eceaddea3790364a61f676baaf977870e88d5c6c8fef0781361e443ae77f5b63a2a2264487f2940a8b1ddb3d15062cd0fb2015dfc6668449aed3cc48a82d0d602d268c7daab6a41294c0cc4ebe5664568dfc50e1648a45a4a1e3a5195846a3ed011a337a02088ec80e0ebae8755cfe107acf3aafb40494e406f804216bb10cf430b0f37856b42db8dc5514724ee93dfb10826f0dd4a0364b9580291d2cd65664814fde37ca80bb4ea44eacc5e641bbadf423f9a2cbf813b8d145da90029baee7ddadda71c7f3811c4105262945bba1668c3be69a3c230974d83561841d766f9c9d570bb7fbe04c7e8a6c3c760c0de81def35692da361102b6b9b2b918837fa97896e84abb40a4efb7e54523a486964b64ca86f120" + ); +} + +impl IntrinsicCurve for Bn254 { + type Scalar = Scalar; + type Point = G1Affine; + + fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point + where + for<'a> &'a Self::Point: Add<&'a Self::Point, Output = Self::Point>, + { + // heuristic + if coeffs.len() < 25 { + // BN254(Fp) is of prime order by Weil conjecture: + // + let table = CachedMulTable::::new_with_prime_order(bases, 4); + table.windowed_mul(coeffs) + } else { + openvm_ecc_guest::msm(coeffs, bases) + } + } +} + +impl PairingIntrinsics for Bn254 { + type Fp = Fp; + type Fp2 = Fp2; + type Fp12 = Fp12; + + const PAIRING_IDX: usize = 0; + // The sextic extension `Fp12` is `Fp2[X] / (X^6 - \xi)`, where `\xi` is a non-residue. + const XI: Fp2 = Fp2::new(Fp::from_const_u8(9), Fp::from_const_u8(1)); + const FP2_TWO: Fp2 = Fp2::new(Fp::from_const_u8(2), Fp::from_const_u8(0)); + const FP2_THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::from_const_u8(0)); + // Multiplication constants for the Frobenius map for coefficients in Fp2 c1..=c5 for powers + // 0..12 FROBENIUS_COEFFS\[i\]\[j\] = \xi^{(j + 1) * (p^i - 1)/6} when p = 1 (mod 6) + // These are validated against `halo2curves::bn256::FROBENIUS_COEFF_FQ12_C1` in tests.rs + // (Note that bn256 here is another name for bn254) + const FROBENIUS_COEFFS: [[Self::Fp2; 5]; 12] = [ + [ + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "70e4c9dcda350bd676212f29081e525c608be676dd9fb9e8dfa765281cb78412" + )), + c1: Bn254Fp(hex!( + "ac62f3805ff05ccae5c7ee8e779279748e0b1512fe7c32a6e6e7fab4f3966924" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "3d556f175795e3990c33c3c210c38cb743b159f53cec0b4cf711794f9847b32f" + )), + c1: Bn254Fp(hex!( + "a2cb0f641cd56516ce9d7c0b1d2aae3294075ad78bcca44b20aeeb6150e5c916" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" + )), + c1: Bn254Fp(hex!( + "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "62a71e92551f8a8472ec94bef76533d3841e185ab7c0f38001a8ee645e4fb505" + )), + c1: Bn254Fp(hex!( + "26812bcd11473bc163c7de1bead28536921c0b3bb0803a9fee8afde7db5e142c" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "2f69b7ea10c8a22ed31baa559b455c42f43f35a461363ae94986794fe7c18301" + )), + c1: Bn254Fp(hex!( + "4b2c0c6eeeb8c624c02a8e6799cb80b07d9f72c746b27fa27506fd76caf2ac12" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "49fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "ffffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "7fa6d41e397d6fe84ad255be8db34c8990aaacd08c60e9efbbe482cccf81dc19" + )), + c1: Bn254Fp(hex!( + "01c1c0f42baa9476ec39d497e3a5037f9d137635e3eecb06737de70bb6f8ab00" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "6dfbdc7be86e747bd342695d3dfd5f80ac259f95771cffba0aef55b778e05608" + )), + c1: Bn254Fp(hex!( + "de86a5aa2bab0c383126ff98bf31df0f4f0926ec6d0ef3a96f76d1b341def104" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "ede9dc66d08acc5ff470a8bea389d6bba35e9eca1d7ff1db4caa96986d5b272a" + )), + c1: Bn254Fp(hex!( + "644c59b2b30c4db9ba6ecfd8c7ec007632e907950e904bb18f9bf034b611a428" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "66f0cb3cbc921a0ecb6bb075450933e64e44b2b5f7e0be19ab8dc011668cc50b" + )), + c1: Bn254Fp(hex!( + "9f230c739dede35fe5967f73089e4aa4041dd20ceff6b0fe120a91e199e9d523" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "431b26767084deeba5847c969880d62e693f4d3bfa99167105092c954490c413" + )), + c1: Bn254Fp(hex!( + "992428841304251f21800220eada2d3e3d63482a28b2b19f0bddb1596a36db16" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "0fc20a425e476412d4b026958595fa2c301fc659afc02f07dc3c1da4b3ca5707" + )), + c1: Bn254Fp(hex!( + "9c5b4a4ce34558e8933c5771fd7d0ba26c60e2a49bb7e918b6351e3835b0a60c" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "e4a9ad1dee13e9623a1fb7b0d41416f7cad90978b8829569513f94bbd474be28" + )), + c1: Bn254Fp(hex!( + "c7aac7c9ce0baeed8d06f6c3b40ef4547a4701bebc6ab8c2997b74cbe08aa814" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" + )), + c1: Bn254Fp(hex!( + "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "7f65920905da7ba94f722c3454fb1ade89f5b67107a49d1d7d6a826aae72e91e" + )), + c1: Bn254Fp(hex!( + "c955c2707ee32157d136854130643254247725bbcd13b5d251abd4f86f54de10" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "14b26e8b5fbc3bbdd268d240fd3a7aec74ff17979863dc87bb82b2455dce4012" + )), + c1: Bn254Fp(hex!( + "4ef81b16254b5efa605574b8500fad8dbfc3d562e1ff31fd95d6b4e29f432e04" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "d718b3fb3b56156616a9423f894c2f3bfdcc9a0ad9a596cf49f8cbb85697df1d" + )), + c1: Bn254Fp(hex!( + "9b9a8957b79bc371a70283d919d80723cf4c6c6fb8c81d1243b8362c7fb7fa0b" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "3d556f175795e3990c33c3c210c38cb743b159f53cec0b4cf711794f9847b32f" + )), + c1: Bn254Fp(hex!( + "a2cb0f641cd56516ce9d7c0b1d2aae3294075ad78bcca44b20aeeb6150e5c916" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "ede9dc66d08acc5ff470a8bea389d6bba35e9eca1d7ff1db4caa96986d5b272a" + )), + c1: Bn254Fp(hex!( + "644c59b2b30c4db9ba6ecfd8c7ec007632e907950e904bb18f9bf034b611a428" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "62a71e92551f8a8472ec94bef76533d3841e185ab7c0f38001a8ee645e4fb505" + )), + c1: Bn254Fp(hex!( + "26812bcd11473bc163c7de1bead28536921c0b3bb0803a9fee8afde7db5e142c" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "1894c5ed05c47d0dbaaec712f624255569184cdd540f16cfdf19b8918b8ce02e" + )), + c1: Bn254Fp(hex!( + "fcd0706a28d35917cd9fe300f89e00e7dfb80eba6f93d015b499346aa85bb71d" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "0100000000000000000000000000000000000000000000000000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "c856a8b9dd0eb15342f81baa03b7340ecdadd4b029e566c86dbbae14a3cc8716" + )), + c1: Bn254Fp(hex!( + "463cbce3eae18bc5a0909dd0adc47d18c0440b4cd35684b1b6224ad5bc55b82f" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "6dfbdc7be86e747bd342695d3dfd5f80ac259f95771cffba0aef55b778e05608" + )), + c1: Bn254Fp(hex!( + "de86a5aa2bab0c383126ff98bf31df0f4f0926ec6d0ef3a96f76d1b341def104" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" + )), + c1: Bn254Fp(hex!( + "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "66f0cb3cbc921a0ecb6bb075450933e64e44b2b5f7e0be19ab8dc011668cc50b" + )), + c1: Bn254Fp(hex!( + "9f230c739dede35fe5967f73089e4aa4041dd20ceff6b0fe120a91e199e9d523" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "04e25662a6074250e745f5d1f8e9aa68f4183446bcab39472497054c2ebe9f1c" + )), + c1: Bn254Fp(hex!( + "aed854540388fb1c6c4a6f48a78f535920f538578e939e181ec37f8708188919" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "ffffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "feffff77314763574f5cdbacf163f2d4ac8bd4a0ce6be2590000000000000000" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "46fd7cd8168c203c8dca7168916a81975d588181b64550b829a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "49fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ], + [ + Fp2 { + c0: Bn254Fp(hex!( + "383b7296b844bc29b9194bd30bd5866a2d39bb27078520b14d63143dbf830c29" + )), + c1: Bn254Fp(hex!( + "aba1328c3346c853f98d1af793ec75f5f0f79edc1a8e669f736a13a93d9ebd23" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "e4a9ad1dee13e9623a1fb7b0d41416f7cad90978b8829569513f94bbd474be28" + )), + c1: Bn254Fp(hex!( + "c7aac7c9ce0baeed8d06f6c3b40ef4547a4701bebc6ab8c2997b74cbe08aa814" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "ede9dc66d08acc5ff470a8bea389d6bba35e9eca1d7ff1db4caa96986d5b272a" + )), + c1: Bn254Fp(hex!( + "644c59b2b30c4db9ba6ecfd8c7ec007632e907950e904bb18f9bf034b611a428" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "7f65920905da7ba94f722c3454fb1ade89f5b67107a49d1d7d6a826aae72e91e" + )), + c1: Bn254Fp(hex!( + "c955c2707ee32157d136854130643254247725bbcd13b5d251abd4f86f54de10" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "334b0e4db7cfe47eba619f27942f07abe85869ea1de273306e1d7f9b1580231e" + )), + c1: Bn254Fp(hex!( + "f90461c2f140c2412c75fdaf405bd4099e94ab1ed5451ebb93c97cfed20a362c" + )), + }, + ], + ]; +} diff --git a/extensions/pairing/guest/src/bn254/pairing.rs b/guest-libs/pairing/src/bn254/pairing.rs similarity index 94% rename from extensions/pairing/guest/src/bn254/pairing.rs rename to guest-libs/pairing/src/bn254/pairing.rs index 25a0d6b7fe..c0f1cc35f2 100644 --- a/extensions/pairing/guest/src/bn254/pairing.rs +++ b/guest-libs/pairing/src/bn254/pairing.rs @@ -1,29 +1,33 @@ +extern crate alloc; + use alloc::vec::Vec; use itertools::izip; use openvm_algebra_guest::{field::FieldExtension, DivUnsafe, Field}; use openvm_ecc_guest::AffinePoint; +use openvm_pairing_guest::{ + bn254::{BN254_PSEUDO_BINARY_ENCODING, BN254_SEED}, + pairing::{ + exp_check_fallback, Evaluatable, EvaluatedLine, FromLineDType, LineMulDType, MillerStep, + MultiMillerLoop, PairingCheck, PairingCheckError, PairingIntrinsics, UnevaluatedLine, + }, +}; +#[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] +use openvm_pairing_guest::{ + halo2curves_shims::bn254::Bn254 as Halo2CurvesBn254, pairing::FinalExp, +}; #[cfg(target_os = "zkvm")] use { - crate::{PairingBaseFunct7, OPCODE, PAIRING_FUNCT3}, core::mem::MaybeUninit, + openvm_pairing_guest::{PairingBaseFunct7, OPCODE, PAIRING_FUNCT3}, openvm_platform::custom_insn_r, openvm_rv32im_guest::hint_buffer_u32, }; -use super::{Bn254, Fp, Fp12, Fp2, BN254_PSEUDO_BINARY_ENCODING, BN254_SEED}; -use crate::pairing::{ - exp_check_fallback, Evaluatable, EvaluatedLine, FromLineDType, LineMulDType, MillerStep, - MultiMillerLoop, PairingCheck, PairingCheckError, PairingIntrinsics, UnevaluatedLine, -}; +use super::{Bn254, Fp, Fp12, Fp2}; #[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] -use crate::{ - bn254::utils::{ - convert_bn254_fp2_to_halo2_fq2, convert_bn254_fp_to_halo2_fq, - convert_bn254_halo2_fq12_to_fp12, - }, - halo2curves_shims::bn254::Bn254 as Halo2CurvesBn254, - pairing::FinalExp, +use crate::bn254::utils::{ + convert_bn254_fp2_to_halo2_fq2, convert_bn254_fp_to_halo2_fq, convert_bn254_halo2_fq12_to_fp12, }; impl Evaluatable for UnevaluatedLine { diff --git a/guest-libs/pairing/src/bn254/tests.rs b/guest-libs/pairing/src/bn254/tests.rs new file mode 100644 index 0000000000..e2baf942e3 --- /dev/null +++ b/guest-libs/pairing/src/bn254/tests.rs @@ -0,0 +1,318 @@ +use group::{ff::Field, prime::PrimeCurveAffine}; +use halo2curves_axiom::bn256::{ + Fq, Fq12, Fq2, Fq6, G1Affine, G2Affine, G2Prepared, Gt, FROBENIUS_COEFF_FQ12_C1, + FROBENIUS_COEFF_FQ6_C1, XI_TO_Q_MINUS_1_OVER_2, +}; +use num_bigint::BigUint; +use num_traits::One; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; +use openvm_ecc_guest::{weierstrass::WeierstrassPoint, AffinePoint}; +use openvm_pairing_guest::{ + bn254::{BN254_MODULUS, BN254_ORDER}, + pairing::{FinalExp, MultiMillerLoop, PairingCheck, PairingIntrinsics}, +}; +use rand::{rngs::StdRng, SeedableRng}; + +use super::{Fp, Fp12, Fp2}; +use crate::{ + bn254::{ + utils::{ + convert_bn254_fp12_to_halo2_fq12, convert_bn254_halo2_fq12_to_fp12, + convert_bn254_halo2_fq2_to_fp2, convert_bn254_halo2_fq_to_fp, + convert_g2_affine_halo2_to_openvm, + }, + Bn254, G2Affine as OpenVmG2Affine, + }, + operations::{fp2_invert_assign, fp6_invert_assign, fp6_square_assign}, +}; + +#[test] +fn test_bn254_frobenius_coeffs() { + #[allow(clippy::needless_range_loop)] + for i in 0..12 { + for j in 0..5 { + assert_eq!( + Bn254::FROBENIUS_COEFFS[i][j], + convert_bn254_halo2_fq2_to_fp2(FROBENIUS_COEFF_FQ12_C1[i].pow([j as u64 + 1])), + "FROBENIUS_COEFFS[{}][{}] failed", + i, + j + ) + } + } +} + +#[test] +fn test_bn254_frobenius() { + let mut rng = StdRng::seed_from_u64(15); + for pow in 0..12 { + let fq = Fq12::random(&mut rng); + let fq_frob = fq.frobenius_map(pow); + + let fp = convert_bn254_halo2_fq12_to_fp12(fq); + let fp_frob = fp.frobenius_map(pow); + + assert_eq!(fp_frob, convert_bn254_halo2_fq12_to_fp12(fq_frob)); + } +} + +#[test] +fn test_fp12_invert() { + let mut rng = StdRng::seed_from_u64(15); + let fq = Fq12::random(&mut rng); + let fq_inv = fq.invert().unwrap(); + + let fp = convert_bn254_halo2_fq12_to_fp12(fq); + let fp_inv = fp.invert(); + assert_eq!(fp_inv, convert_bn254_halo2_fq12_to_fp12(fq_inv)); +} + +#[test] +fn test_fp6_invert() { + let mut rng = StdRng::seed_from_u64(20); + let fq6 = Fq6::random(&mut rng); + let fq6_inv = fq6.invert().unwrap(); + + let fp6c0 = convert_bn254_halo2_fq2_to_fp2(fq6.c0); + let fp6c1 = convert_bn254_halo2_fq2_to_fp2(fq6.c1); + let fp6c2 = convert_bn254_halo2_fq2_to_fp2(fq6.c2); + let mut fp6 = [fp6c0, fp6c1, fp6c2]; + fp6_invert_assign::(&mut fp6, &Bn254::XI); + + let fq6_invc0 = convert_bn254_halo2_fq2_to_fp2(fq6_inv.c0); + let fq6_invc1 = convert_bn254_halo2_fq2_to_fp2(fq6_inv.c1); + let fq6_invc2 = convert_bn254_halo2_fq2_to_fp2(fq6_inv.c2); + let fq6_inv = [fq6_invc0, fq6_invc1, fq6_invc2]; + assert_eq!(fp6, fq6_inv); +} + +#[test] +fn test_fp2_invert() { + let mut rng = StdRng::seed_from_u64(25); + let fq2 = Fq2::random(&mut rng); + let fq2_inv = fq2.invert().unwrap(); + + let mut fp2 = convert_bn254_halo2_fq2_to_fp2(fq2).to_coeffs(); + fp2_invert_assign::(&mut fp2); + assert_eq!(fp2, convert_bn254_halo2_fq2_to_fp2(fq2_inv).to_coeffs()); +} + +#[test] +fn test_fp6_square() { + let mut rng = StdRng::seed_from_u64(45); + let fq6 = Fq6::random(&mut rng); + let fq6_sq = fq6.square(); + + let fp6c0 = convert_bn254_halo2_fq2_to_fp2(fq6.c0); + let fp6c1 = convert_bn254_halo2_fq2_to_fp2(fq6.c1); + let fp6c2 = convert_bn254_halo2_fq2_to_fp2(fq6.c2); + let mut fp6 = [fp6c0, fp6c1, fp6c2]; + fp6_square_assign::(&mut fp6, &Bn254::XI); + + let fq6_sqc0 = convert_bn254_halo2_fq2_to_fp2(fq6_sq.c0); + let fq6_sqc1 = convert_bn254_halo2_fq2_to_fp2(fq6_sq.c1); + let fq6_sqc2 = convert_bn254_halo2_fq2_to_fp2(fq6_sq.c2); + let fq6_sq = [fq6_sqc0, fq6_sqc1, fq6_sqc2]; + assert_eq!(fp6, fq6_sq); +} + +#[test] +fn test_fp2_square() { + let mut rng = StdRng::seed_from_u64(55); + let fq2 = Fq2::random(&mut rng); + let fq2_sq = fq2.square(); + + let fp2 = convert_bn254_halo2_fq2_to_fp2(fq2); + let fp2_sq = &fp2 * &fp2; + assert_eq!(fp2_sq, convert_bn254_halo2_fq2_to_fp2(fq2_sq)); +} + +#[test] +fn test_fp_add() { + let mut rng = StdRng::seed_from_u64(65); + let fq = Fq::random(&mut rng); + let fq_res = fq + Fq::one(); + + let fp = convert_bn254_halo2_fq_to_fp(fq); + let fp_res = fp + Fp::ONE; + assert_eq!(fp_res, convert_bn254_halo2_fq_to_fp(fq_res)); +} + +#[test] +fn test_fp_one() { + let fp_one = Fp::ONE; + let fq_one = Fq::ONE; + assert_eq!(fp_one, convert_bn254_halo2_fq_to_fp(fq_one)); +} + +// Gt(Fq12) is not public +fn assert_miller_results_eq(a: Gt, b: Fp12) { + let b = convert_bn254_fp12_to_halo2_fq12(b); + openvm_pairing_guest::halo2curves_shims::bn254::test_utils::assert_miller_results_eq(a, b); +} + +#[test] +fn test_bn254_miller_loop() { + let mut rng = StdRng::seed_from_u64(53); + let h2c_p = G1Affine::random(&mut rng); + let h2c_q = G2Affine::random(&mut rng); + + let p = AffinePoint { + x: convert_bn254_halo2_fq_to_fp(h2c_p.x), + y: convert_bn254_halo2_fq_to_fp(h2c_p.y), + }; + let q = AffinePoint { + x: convert_bn254_halo2_fq2_to_fp2(h2c_q.x), + y: convert_bn254_halo2_fq2_to_fp2(h2c_q.y), + }; + + // Compare against halo2curves implementation + let h2c_q_prepared = G2Prepared::from(h2c_q); + let compare_miller = halo2curves_axiom::bn256::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); + let f = Bn254::multi_miller_loop(&[p], &[q]); + assert_miller_results_eq(compare_miller, f); +} + +#[test] +fn test_bn254_miller_loop_identity() { + let mut rng = StdRng::seed_from_u64(33); + let h2c_p = G1Affine::identity(); + let h2c_q = G2Affine::random(&mut rng); + + let p = AffinePoint { + x: convert_bn254_halo2_fq_to_fp(Fq::ZERO), + y: convert_bn254_halo2_fq_to_fp(Fq::ZERO), + }; + let q = AffinePoint { + x: convert_bn254_halo2_fq2_to_fp2(h2c_q.x), + y: convert_bn254_halo2_fq2_to_fp2(h2c_q.y), + }; + + let f = Bn254::multi_miller_loop(&[p], &[q]); + // halo2curves implementation + let h2c_q_prepared = G2Prepared::from(h2c_q); + let compare_miller = halo2curves_axiom::bn256::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); + assert_miller_results_eq(compare_miller, f); +} + +#[test] +fn test_bn254_miller_loop_identity_2() { + let mut rng = StdRng::seed_from_u64(33); + let h2c_p = G1Affine::random(&mut rng); + let h2c_q = G2Affine::identity(); + let p = AffinePoint { + x: convert_bn254_halo2_fq_to_fp(h2c_p.x), + y: convert_bn254_halo2_fq_to_fp(h2c_p.y), + }; + let q = AffinePoint { + x: convert_bn254_halo2_fq2_to_fp2(Fq2::ZERO), + y: convert_bn254_halo2_fq2_to_fp2(Fq2::ZERO), + }; + + let f = Bn254::multi_miller_loop(&[p], &[q]); + // halo2curves implementation + let h2c_q_prepared = G2Prepared::from(h2c_q); + let compare_miller = halo2curves_axiom::bn256::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); + assert_miller_results_eq(compare_miller, f); +} + +// test on host is enough since we are testing the curve formulas and not anything +// about intrinsic functions +#[test] +fn test_bn254_g2_affine() { + let mut rng = StdRng::seed_from_u64(34); + for _ in 0..10 { + let p = G2Affine::random(&mut rng); + let q = G2Affine::random(&mut rng); + let expected_add = G2Affine::from(p + q); + let expected_sub = G2Affine::from(p - q); + let expected_neg = -p; + let expected_double = G2Affine::from(p + p); + let [p, q] = [p, q].map(|p| { + let x = convert_bn254_halo2_fq2_to_fp2(p.x); + let y = convert_bn254_halo2_fq2_to_fp2(p.y); + // check on curve + OpenVmG2Affine::from_xy(x, y).unwrap() + }); + let r_add = &p + &q; + let r_sub = &p - &q; + let r_neg = -&p; + let r_double = &p + &p; + + for (expected, actual) in [ + (expected_add, r_add), + (expected_sub, r_sub), + (expected_neg, r_neg), + (expected_double, r_double), + ] { + assert_eq!(convert_g2_affine_halo2_to_openvm(expected), actual); + } + } +} + +#[test] +fn test_bn254_pairing_check_hint_host() { + let mut rng = StdRng::seed_from_u64(83); + let h2c_p = G1Affine::random(&mut rng); + let h2c_q = G2Affine::random(&mut rng); + + let p = AffinePoint { + x: convert_bn254_halo2_fq_to_fp(h2c_p.x), + y: convert_bn254_halo2_fq_to_fp(h2c_p.y), + }; + let q = AffinePoint { + x: convert_bn254_halo2_fq2_to_fp2(h2c_q.x), + y: convert_bn254_halo2_fq2_to_fp2(h2c_q.y), + }; + + let (c, u) = Bn254::pairing_check_hint(&[p], &[q]); + + let p_cmp = AffinePoint { + x: h2c_p.x, + y: h2c_p.y, + }; + let q_cmp = AffinePoint { + x: h2c_q.x, + y: h2c_q.y, + }; + + let f_cmp = openvm_pairing_guest::halo2curves_shims::bn254::Bn254::multi_miller_loop( + &[p_cmp], + &[q_cmp], + ); + let (c_cmp, u_cmp) = + openvm_pairing_guest::halo2curves_shims::bn254::Bn254::final_exp_hint(&f_cmp); + let c_cmp = convert_bn254_halo2_fq12_to_fp12(c_cmp); + let u_cmp = convert_bn254_halo2_fq12_to_fp12(u_cmp); + + assert_eq!(c, c_cmp); + assert_eq!(u, u_cmp); +} + +#[test] +fn test_bn254_final_exponent() { + let final_exp = (BN254_MODULUS.pow(12) - BigUint::one()) / BN254_ORDER.clone(); + assert_eq!(Bn254::FINAL_EXPONENT.to_vec(), final_exp.to_bytes_be()); +} + +#[test] +fn test_bn254_frobenius_coeffs_fq6() { + #[allow(clippy::needless_range_loop)] + for i in 0..3 { + assert_eq!( + Bn254::FROBENIUS_COEFF_FQ6_C1[i], + convert_bn254_halo2_fq2_to_fp2(FROBENIUS_COEFF_FQ6_C1[i]), + "FROBENIUS_COEFFS_FQ6_C1[{}] failed", + i, + ) + } +} + +#[test] +fn test_bn254_xi_to_q_minus_1_over_2() { + assert_eq!( + Bn254::XI_TO_Q_MINUS_1_OVER_2, + convert_bn254_halo2_fq2_to_fp2(XI_TO_Q_MINUS_1_OVER_2), + "XI_TO_Q_MINUS_1_OVER_2 failed", + ) +} diff --git a/extensions/pairing/guest/src/bn254/utils.rs b/guest-libs/pairing/src/bn254/utils.rs similarity index 100% rename from extensions/pairing/guest/src/bn254/utils.rs rename to guest-libs/pairing/src/bn254/utils.rs diff --git a/guest-libs/pairing/src/lib.rs b/guest-libs/pairing/src/lib.rs new file mode 100644 index 0000000000..e2f800e547 --- /dev/null +++ b/guest-libs/pairing/src/lib.rs @@ -0,0 +1,17 @@ +#![no_std] + +#[cfg(any(feature = "bn254", feature = "bls12_381"))] +mod operations; + +#[allow(unused_imports)] +#[cfg(any(feature = "bn254", feature = "bls12_381"))] +pub(crate) use operations::*; + +/// Types for BLS12-381 curve with intrinsic functions. +#[cfg(feature = "bls12_381")] +pub mod bls12_381; +/// Types for BN254 curve with intrinsic functions. +#[cfg(feature = "bn254")] +pub mod bn254; + +pub use openvm_pairing_guest::pairing::PairingCheck; diff --git a/extensions/pairing/guest/src/pairing/operations/fp12.rs b/guest-libs/pairing/src/operations/fp12.rs similarity index 100% rename from extensions/pairing/guest/src/pairing/operations/fp12.rs rename to guest-libs/pairing/src/operations/fp12.rs diff --git a/extensions/pairing/guest/src/pairing/operations/fp2.rs b/guest-libs/pairing/src/operations/fp2.rs similarity index 100% rename from extensions/pairing/guest/src/pairing/operations/fp2.rs rename to guest-libs/pairing/src/operations/fp2.rs diff --git a/extensions/pairing/guest/src/pairing/operations/fp6.rs b/guest-libs/pairing/src/operations/fp6.rs similarity index 100% rename from extensions/pairing/guest/src/pairing/operations/fp6.rs rename to guest-libs/pairing/src/operations/fp6.rs diff --git a/extensions/pairing/guest/src/pairing/operations/mod.rs b/guest-libs/pairing/src/operations/mod.rs similarity index 63% rename from extensions/pairing/guest/src/pairing/operations/mod.rs rename to guest-libs/pairing/src/operations/mod.rs index 4a75c8a9d7..eafc6be657 100644 --- a/extensions/pairing/guest/src/pairing/operations/mod.rs +++ b/guest-libs/pairing/src/operations/mod.rs @@ -1,9 +1,9 @@ -#![allow(dead_code)] mod fp12; mod fp2; mod fp6; +mod sextic_ext_field; -#[allow(unused_imports)] pub(crate) use fp12::*; pub(crate) use fp2::*; pub(crate) use fp6::*; +pub(crate) use sextic_ext_field::*; diff --git a/extensions/pairing/guest/src/pairing/sextic_ext_field.rs b/guest-libs/pairing/src/operations/sextic_ext_field.rs similarity index 100% rename from extensions/pairing/guest/src/pairing/sextic_ext_field.rs rename to guest-libs/pairing/src/operations/sextic_ext_field.rs diff --git a/extensions/pairing/tests/src/lib.rs b/guest-libs/pairing/tests/lib.rs similarity index 83% rename from extensions/pairing/tests/src/lib.rs rename to guest-libs/pairing/tests/lib.rs index 33890ecb8a..e7c189b550 100644 --- a/extensions/pairing/tests/src/lib.rs +++ b/guest-libs/pairing/tests/lib.rs @@ -1,29 +1,30 @@ #![allow(non_snake_case)] -#[cfg(test)] +#[cfg(feature = "bn254")] mod bn254 { use std::iter; use eyre::Result; + use halo2curves_axiom::{ + bn256::{Fq12, Fq2, Fr, G1Affine, G2Affine}, + ff::Field, + }; use openvm_algebra_circuit::{Fp2Extension, ModularExtension}; use openvm_algebra_transpiler::{Fp2TranspilerExtension, ModularTranspilerExtension}; use openvm_circuit::{ arch::SystemConfig, - utils::{air_test_impl, air_test_with_min_segments}, + utils::{air_test, air_test_impl, air_test_with_min_segments}, }; - use openvm_ecc_circuit::WeierstrassExtension; + use openvm_ecc_circuit::{Rv32WeierstrassConfig, WeierstrassExtension}; use openvm_ecc_guest::{ algebra::{field::FieldExtension, IntMod}, - halo2curves::{ - bn256::{Fq12, Fq2, Fr, G1Affine, G2Affine}, - ff::Field, - }, AffinePoint, }; + use openvm_ecc_transpiler::EccTranspilerExtension; use openvm_instructions::exe::VmExe; use openvm_pairing_circuit::{PairingCurve, PairingExtension, Rv32PairingConfig}; use openvm_pairing_guest::{ - bn254::BN254_MODULUS, + bn254::{BN254_COMPLEX_STRUCT_NAME, BN254_MODULUS}, halo2curves_shims::bn254::Bn254, pairing::{EvaluatedLine, FinalExp, LineMulDType, MillerStep, MultiMillerLoop}, }; @@ -38,26 +39,57 @@ mod bn254 { type F = BabyBear; + #[cfg(test)] pub fn get_testing_config() -> Rv32PairingConfig { let primes = [BN254_MODULUS.clone()]; + let complex_struct_names = [BN254_COMPLEX_STRUCT_NAME.to_string()]; + let primes_with_names = complex_struct_names + .into_iter() + .zip(primes.clone()) + .collect::>(); Rv32PairingConfig { system: SystemConfig::default().with_continuations(), base: Default::default(), mul: Default::default(), io: Default::default(), modular: ModularExtension::new(primes.to_vec()), - fp2: Fp2Extension::new(primes.to_vec()), + fp2: Fp2Extension::new(primes_with_names), weierstrass: WeierstrassExtension::new(vec![]), pairing: PairingExtension::new(vec![PairingCurve::Bn254]), } } + #[test] + fn test_bn_ec() -> Result<()> { + let curve = PairingCurve::Bn254.curve_config(); + let config = Rv32WeierstrassConfig::new(vec![curve]); + let elf = build_example_program_at_path_with_features( + get_programs_dir!("tests/programs"), + "bn_ec", + ["bn254"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } + #[test] fn test_bn254_fp12_mul() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "fp12_mul", ["bn254"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -82,16 +114,18 @@ mod bn254 { .map(FieldAlgebra::from_canonical_u8) .collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1); + air_test_with_min_segments(config, openvm_exe, vec![io], 1); Ok(()) } #[test] fn test_bn254_line_functions() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_line", ["bn254"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -138,16 +172,18 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); + air_test_with_min_segments(config, openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bn254_miller_step() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_miller_step", ["bn254"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -185,16 +221,18 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); + air_test_with_min_segments(config, openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bn254_miller_loop() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_miller_loop", ["bn254"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -236,16 +274,18 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); + air_test_with_min_segments(config, openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bn254_pairing_check() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_check", ["bn254"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -291,16 +331,18 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); + air_test_with_min_segments(config, openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bn254_pairing_check_fallback() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_check_fallback", ["bn254"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -352,10 +394,12 @@ mod bn254 { #[test] fn test_bn254_final_exp_hint() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "bn_final_exp_hint", ["bn254"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -377,20 +421,20 @@ mod bn254 { let ps = ps .into_iter() .map(|pt| { - let [x, y] = [pt.x, pt.y] - .map(|x| openvm_pairing_guest::bn254::Fp::from_le_bytes(&x.to_bytes())); + let [x, y] = + [pt.x, pt.y].map(|x| openvm_pairing::bn254::Fp::from_le_bytes(&x.to_bytes())); AffinePoint::new(x, y) }) .collect::>(); let qs = qs .into_iter() .map(|pt| { - let [x, y] = [pt.x, pt.y] - .map(|x| openvm_pairing_guest::bn254::Fp2::from_bytes(&x.to_bytes())); + let [x, y] = + [pt.x, pt.y].map(|x| openvm_pairing::bn254::Fp2::from_bytes(&x.to_bytes())); AffinePoint::new(x, y) }) .collect::>(); - let [c, s] = [c, s].map(|x| openvm_pairing_guest::bn254::Fp12::from_bytes(&x.to_bytes())); + let [c, s] = [c, s].map(|x| openvm_pairing::bn254::Fp12::from_bytes(&x.to_bytes())); let io = (ps, qs, (c, s)); let io = openvm::serde::to_vec(&io).unwrap(); let io = io @@ -398,14 +442,18 @@ mod bn254 { .flat_map(|w| w.to_le_bytes()) .map(F::from_canonical_u8) .collect(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1); + air_test_with_min_segments(config, openvm_exe, vec![io], 1); Ok(()) } } -#[cfg(test)] +#[cfg(feature = "bls12_381")] mod bls12_381 { use eyre::Result; + use halo2curves_axiom::{ + bls12_381::{Fq12, Fq2, Fr, G1Affine, G2Affine}, + ff::Field, + }; use num_bigint::BigUint; use num_traits::{self, FromPrimitive}; use openvm_algebra_circuit::{Fp2Extension, ModularExtension}; @@ -417,16 +465,15 @@ mod bls12_381 { use openvm_ecc_circuit::{CurveConfig, Rv32WeierstrassConfig, WeierstrassExtension}; use openvm_ecc_guest::{ algebra::{field::FieldExtension, IntMod}, - halo2curves::{ - bls12_381::{Fq12, Fq2, Fr, G1Affine, G2Affine}, - ff::Field, - }, AffinePoint, }; use openvm_ecc_transpiler::EccTranspilerExtension; use openvm_pairing_circuit::{PairingCurve, PairingExtension, Rv32PairingConfig}; use openvm_pairing_guest::{ - bls12_381::{BLS12_381_MODULUS, BLS12_381_ORDER}, + bls12_381::{ + BLS12_381_COMPLEX_STRUCT_NAME, BLS12_381_ECC_STRUCT_NAME, BLS12_381_MODULUS, + BLS12_381_ORDER, + }, halo2curves_shims::bls12_381::Bls12_381, pairing::{EvaluatedLine, FinalExp, LineMulMType, MillerStep, MultiMillerLoop}, }; @@ -441,15 +488,21 @@ mod bls12_381 { type F = BabyBear; + #[cfg(test)] pub fn get_testing_config() -> Rv32PairingConfig { let primes = [BLS12_381_MODULUS.clone()]; + let complex_struct_names = [BLS12_381_COMPLEX_STRUCT_NAME.to_string()]; + let primes_with_names = complex_struct_names + .into_iter() + .zip(primes.clone()) + .collect::>(); Rv32PairingConfig { system: SystemConfig::default().with_continuations(), base: Default::default(), mul: Default::default(), io: Default::default(), modular: ModularExtension::new(primes.to_vec()), - fp2: Fp2Extension::new(primes.to_vec()), + fp2: Fp2Extension::new(primes_with_names), weierstrass: WeierstrassExtension::new(vec![]), pairing: PairingExtension::new(vec![PairingCurve::Bls12_381]), } @@ -457,17 +510,20 @@ mod bls12_381 { #[test] fn test_bls_ec() -> Result<()> { - let elf = build_example_program_at_path_with_features( - get_programs_dir!(), - "bls_ec", - ["bls12_381"], - )?; let curve = CurveConfig { + struct_name: BLS12_381_ECC_STRUCT_NAME.to_string(), modulus: BLS12_381_MODULUS.clone(), scalar: BLS12_381_ORDER.clone(), a: BigUint::ZERO, b: BigUint::from_u8(4).unwrap(), }; + let config = Rv32WeierstrassConfig::new(vec![curve]); + let elf = build_example_program_at_path_with_features( + get_programs_dir!("tests/programs"), + "bls_ec", + ["bls12_381"], + &config, + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -477,17 +533,18 @@ mod bls12_381 { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - let config = Rv32WeierstrassConfig::new(vec![curve]); air_test(config, openvm_exe); Ok(()) } #[test] fn test_bls12_381_fp12_mul() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "fp12_mul", ["bls12_381"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -512,16 +569,18 @@ mod bls12_381 { .map(FieldAlgebra::from_canonical_u8) .collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1); + air_test_with_min_segments(config, openvm_exe, vec![io], 1); Ok(()) } #[test] fn test_bls12_381_line_functions() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_line", ["bls12_381"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -569,16 +628,18 @@ mod bls12_381 { let io_all = io0.into_iter().chain(io1).collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); + air_test_with_min_segments(config, openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bls12_381_miller_step() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_miller_step", ["bls12_381"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -616,16 +677,18 @@ mod bls12_381 { let io_all = io0.into_iter().chain(io1).collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); + air_test_with_min_segments(config, openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bls12_381_miller_loop() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_miller_loop", ["bls12_381"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -673,16 +736,18 @@ mod bls12_381 { let io_all = io0.into_iter().chain(io1).collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); + air_test_with_min_segments(config, openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bls12_381_pairing_check() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_check", ["bls12_381"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -727,17 +792,19 @@ mod bls12_381 { let io_all = io0.into_iter().chain(io1).collect::>(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); + air_test_with_min_segments(config, openvm_exe, vec![io_all], 1); Ok(()) } #[ignore] #[test] fn test_bls12_381_pairing_check_fallback() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "pairing_check_fallback", ["bls12_381"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -788,10 +855,12 @@ mod bls12_381 { #[test] fn test_bls12_381_final_exp_hint() -> Result<()> { + let config = get_testing_config(); let elf = build_example_program_at_path_with_features( - get_programs_dir!(), + get_programs_dir!("tests/programs"), "bls_final_exp_hint", ["bls12_381"], + &config, )?; let openvm_exe = VmExe::from_elf( elf, @@ -814,20 +883,19 @@ mod bls12_381 { .into_iter() .map(|pt| { let [x, y] = [pt.x, pt.y] - .map(|x| openvm_pairing_guest::bls12_381::Fp::from_le_bytes(&x.to_bytes())); + .map(|x| openvm_pairing::bls12_381::Fp::from_le_bytes(&x.to_bytes())); AffinePoint::new(x, y) }) .collect::>(); let qs = qs .into_iter() .map(|pt| { - let [x, y] = [pt.x, pt.y] - .map(|x| openvm_pairing_guest::bls12_381::Fp2::from_bytes(&x.to_bytes())); + let [x, y] = + [pt.x, pt.y].map(|x| openvm_pairing::bls12_381::Fp2::from_bytes(&x.to_bytes())); AffinePoint::new(x, y) }) .collect::>(); - let [c, s] = - [c, s].map(|x| openvm_pairing_guest::bls12_381::Fp12::from_bytes(&x.to_bytes())); + let [c, s] = [c, s].map(|x| openvm_pairing::bls12_381::Fp12::from_bytes(&x.to_bytes())); let io = (ps, qs, (c, s)); let io = openvm::serde::to_vec(&io).unwrap(); let io = io @@ -835,7 +903,7 @@ mod bls12_381 { .flat_map(|w| w.to_le_bytes()) .map(F::from_canonical_u8) .collect(); - air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1); + air_test_with_min_segments(config, openvm_exe, vec![io], 1); Ok(()) } } diff --git a/extensions/pairing/tests/programs/Cargo.toml b/guest-libs/pairing/tests/programs/Cargo.toml similarity index 74% rename from extensions/pairing/tests/programs/Cargo.toml rename to guest-libs/pairing/tests/programs/Cargo.toml index 6e5cd66984..ce4229af68 100644 --- a/extensions/pairing/tests/programs/Cargo.toml +++ b/guest-libs/pairing/tests/programs/Cargo.toml @@ -6,14 +6,13 @@ edition = "2021" [dependencies] openvm = { path = "../../../../crates/toolchain/openvm" } -openvm-platform = { path = "../../../../crates/toolchain/platform" } - -openvm-algebra-guest = { path = "../../../../extensions/algebra/guest", default-features = false } -openvm-algebra-moduli-macros = { path = "../../../../extensions/algebra/moduli-macros", default-features = false } -openvm-algebra-complex-macros = { path = "../../../../extensions/algebra/complex-macros", default-features = false } -openvm-ecc-guest = { path = "../../../../extensions/ecc/guest", default-features = false } -openvm-ecc-sw-macros = { path = "../../../../extensions/ecc/sw-macros", default-features = false } -openvm-pairing-guest = { path = "../../../../extensions/pairing/guest", default-features = false } +openvm-algebra-guest = { path = "../../../../extensions/algebra/guest" } +openvm-algebra-moduli-macros = { path = "../../../../extensions/algebra/moduli-macros/" } +openvm-algebra-complex-macros = { path = "../../../../extensions/algebra/complex-macros/" } +openvm-ecc-guest = { path = "../../../../extensions/ecc/guest" } +openvm-ecc-sw-macros = { path = "../../../../extensions/ecc/sw-macros/" } +openvm-pairing-guest = { path = "../../../../extensions/pairing/guest" } +openvm-pairing = { path = "../../" } serde = { version = "1.0", default-features = false, features = [ "alloc", @@ -30,8 +29,8 @@ k256 = { version = "0.13.3", default-features = false, features = [ default = [] std = ["serde/std", "openvm/std"] -bn254 = ["openvm-pairing-guest/bn254"] -bls12_381 = ["openvm-pairing-guest/bls12_381"] +bn254 = ["openvm-pairing/bn254"] +bls12_381 = ["openvm-pairing/bls12_381"] [profile.release] panic = "abort" diff --git a/guest-libs/pairing/tests/programs/examples/bls_ec.rs b/guest-libs/pairing/tests/programs/examples/bls_ec.rs new file mode 100644 index 0000000000..a79cc05961 --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/bls_ec.rs @@ -0,0 +1,11 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[allow(unused_imports)] +use openvm_pairing::bls12_381::Bls12_381G1Affine; + +openvm::init!("openvm_init_bls_ec_bls12_381.rs"); + +openvm::entry!(main); + +pub fn main() {} diff --git a/extensions/pairing/tests/programs/examples/bls_final_exp_hint.rs b/guest-libs/pairing/tests/programs/examples/bls_final_exp_hint.rs similarity index 64% rename from extensions/pairing/tests/programs/examples/bls_final_exp_hint.rs rename to guest-libs/pairing/tests/programs/examples/bls_final_exp_hint.rs index dade1cd161..25945a0780 100644 --- a/extensions/pairing/tests/programs/examples/bls_final_exp_hint.rs +++ b/guest-libs/pairing/tests/programs/examples/bls_final_exp_hint.rs @@ -7,17 +7,14 @@ use alloc::vec::Vec; use openvm::io::read; use openvm_ecc_guest::AffinePoint; -use openvm_pairing_guest::{ +use openvm_pairing::{ bls12_381::{Bls12_381, Fp, Fp12, Fp2}, - pairing::PairingCheck, + PairingCheck, }; openvm::entry!(main); -openvm_algebra_moduli_macros::moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" -} +openvm::init!("openvm_init_bls_final_exp_hint_bls12_381.rs"); pub fn main() { #[allow(clippy::type_complexity)] diff --git a/guest-libs/pairing/tests/programs/examples/bn_ec.rs b/guest-libs/pairing/tests/programs/examples/bn_ec.rs new file mode 100644 index 0000000000..6bd19dd416 --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/bn_ec.rs @@ -0,0 +1,12 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[allow(unused_imports)] +#[cfg(feature = "bn254")] +use openvm_pairing::bn254::Bn254G1Affine; + +openvm::init!("openvm_init_bn_ec_bn254.rs"); + +openvm::entry!(main); + +pub fn main() {} diff --git a/extensions/pairing/tests/programs/examples/bn_final_exp_hint.rs b/guest-libs/pairing/tests/programs/examples/bn_final_exp_hint.rs similarity index 64% rename from extensions/pairing/tests/programs/examples/bn_final_exp_hint.rs rename to guest-libs/pairing/tests/programs/examples/bn_final_exp_hint.rs index 3b71253b1e..0a59242b09 100644 --- a/extensions/pairing/tests/programs/examples/bn_final_exp_hint.rs +++ b/guest-libs/pairing/tests/programs/examples/bn_final_exp_hint.rs @@ -7,17 +7,14 @@ use alloc::vec::Vec; use openvm::io::read; use openvm_ecc_guest::AffinePoint; -use openvm_pairing_guest::{ +use openvm_pairing::{ bn254::{Bn254, Fp, Fp12, Fp2}, - pairing::PairingCheck, + PairingCheck, }; openvm::entry!(main); -openvm_algebra_moduli_macros::moduli_init! { - "21888242871839275222246405745257275088696311157297823662689037894645226208583", - "21888242871839275222246405745257275088548364400416034343698204186575808495617", -} +openvm::init!("openvm_init_bn_final_exp_hint_bn254.rs"); pub fn main() { #[allow(clippy::type_complexity)] diff --git a/extensions/pairing/tests/programs/examples/fp12_mul.rs b/guest-libs/pairing/tests/programs/examples/fp12_mul.rs similarity index 68% rename from extensions/pairing/tests/programs/examples/fp12_mul.rs rename to guest-libs/pairing/tests/programs/examples/fp12_mul.rs index fddc5d9415..ad58cc7f72 100644 --- a/extensions/pairing/tests/programs/examples/fp12_mul.rs +++ b/guest-libs/pairing/tests/programs/examples/fp12_mul.rs @@ -9,22 +9,13 @@ openvm::entry!(main); #[cfg(feature = "bn254")] mod bn254 { - use openvm_pairing_guest::bn254::{Fp, Fp12}; + use openvm_pairing::bn254::{Fp, Fp12}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bn254Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_fp12_mul_bn254.rs"); pub fn test_fp12_mul(io: &[u8]) { - setup_0(); - setup_all_complex_extensions(); assert_eq!(io.len(), 32 * 36); let f0 = &io[0..32 * 12]; @@ -48,22 +39,13 @@ mod bn254 { #[cfg(feature = "bls12_381")] mod bls12_381 { - use openvm_pairing_guest::bls12_381::{Fp, Fp12}; + use openvm_pairing::bls12_381::{Fp, Fp12}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bls12_381Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_fp12_mul_bls12_381.rs"); pub fn test_fp12_mul(io: &[u8]) { - setup_0(); - setup_all_complex_extensions(); assert_eq!(io.len(), 48 * 36); let f0 = &io[0..48 * 12]; diff --git a/extensions/pairing/tests/programs/examples/pairing_check.rs b/guest-libs/pairing/tests/programs/examples/pairing_check.rs similarity index 72% rename from extensions/pairing/tests/programs/examples/pairing_check.rs rename to guest-libs/pairing/tests/programs/examples/pairing_check.rs index 88f17ceacb..5f77128fd6 100644 --- a/extensions/pairing/tests/programs/examples/pairing_check.rs +++ b/guest-libs/pairing/tests/programs/examples/pairing_check.rs @@ -6,7 +6,7 @@ extern crate alloc; use openvm::io::read_vec; use openvm_ecc_guest::AffinePoint; -use openvm_pairing_guest::pairing::PairingCheck; +use openvm_pairing::PairingCheck; openvm::entry!(main); @@ -15,22 +15,13 @@ mod bn254 { use alloc::format; use openvm_algebra_guest::{field::FieldExtension, IntMod}; - use openvm_pairing_guest::bn254::{Bn254, Fp, Fp2}; + use openvm_pairing::bn254::{Bn254, Fp, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bn254Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_check_bn254.rs"); pub fn test_pairing_check(io: &[u8]) { - setup_0(); - setup_all_complex_extensions(); let s0 = &io[0..32 * 2]; let s1 = &io[32 * 2..32 * 4]; let q0 = &io[32 * 4..32 * 8]; @@ -57,22 +48,13 @@ mod bls12_381 { use alloc::format; use openvm_algebra_guest::{field::FieldExtension, IntMod}; - use openvm_pairing_guest::bls12_381::{Bls12_381, Fp, Fp2}; + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bls12_381Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_check_bls12_381.rs"); pub fn test_pairing_check(io: &[u8]) { - setup_0(); - setup_all_complex_extensions(); let s0 = &io[0..48 * 2]; let s1 = &io[48 * 2..48 * 4]; let q0 = &io[48 * 4..48 * 8]; diff --git a/extensions/pairing/tests/programs/examples/pairing_check_fallback.rs b/guest-libs/pairing/tests/programs/examples/pairing_check_fallback.rs similarity index 88% rename from extensions/pairing/tests/programs/examples/pairing_check_fallback.rs rename to guest-libs/pairing/tests/programs/examples/pairing_check_fallback.rs index 7f47709097..7c09388306 100644 --- a/extensions/pairing/tests/programs/examples/pairing_check_fallback.rs +++ b/guest-libs/pairing/tests/programs/examples/pairing_check_fallback.rs @@ -10,9 +10,8 @@ use openvm_algebra_guest::{ DivUnsafe, Field, IntMod, }; use openvm_ecc_guest::AffinePoint; -use openvm_pairing_guest::pairing::{ - exp_check_fallback, MultiMillerLoop, PairingCheck, PairingCheckError, -}; +use openvm_pairing::PairingCheck; +use openvm_pairing_guest::pairing::{exp_check_fallback, MultiMillerLoop, PairingCheckError}; openvm::entry!(main); @@ -20,18 +19,11 @@ openvm::entry!(main); mod bn254 { use alloc::format; - use openvm_pairing_guest::bn254::{Bn254, Fp, Fp12, Fp2}; + use openvm_pairing::bn254::{Bn254, Fp, Fp12, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bn254Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_check_fallback_bn254.rs"); // Wrapper so that we can override `pairing_check_hint` struct Bn254Wrapper(Bn254); @@ -101,8 +93,6 @@ mod bn254 { } pub fn test_pairing_check(io: &[u8]) { - setup_0(); - setup_all_complex_extensions(); let s0 = &io[0..32 * 2]; let s1 = &io[32 * 2..32 * 4]; let q0 = &io[32 * 4..32 * 8]; @@ -134,18 +124,11 @@ mod bls12_381 { use alloc::format; - use openvm_pairing_guest::bls12_381::{Bls12_381, Fp, Fp12, Fp2}; + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp12, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bls12_381Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_check_fallback_bls12_381.rs"); // Wrapper so that we can override `pairing_check_hint` struct Bls12_381Wrapper(Bls12_381); @@ -216,8 +199,6 @@ mod bls12_381 { } pub fn test_pairing_check(io: &[u8]) { - setup_0(); - setup_all_complex_extensions(); let s0 = &io[0..48 * 2]; let s1 = &io[48 * 2..48 * 4]; let q0 = &io[48 * 4..48 * 8]; diff --git a/extensions/pairing/tests/programs/examples/pairing_line.rs b/guest-libs/pairing/tests/programs/examples/pairing_line.rs similarity index 82% rename from extensions/pairing/tests/programs/examples/pairing_line.rs rename to guest-libs/pairing/tests/programs/examples/pairing_line.rs index 27969e15ea..4b64f0f081 100644 --- a/extensions/pairing/tests/programs/examples/pairing_line.rs +++ b/guest-libs/pairing/tests/programs/examples/pairing_line.rs @@ -10,18 +10,11 @@ openvm::entry!(main); #[cfg(feature = "bn254")] mod bn254 { - use openvm_pairing_guest::bn254::{Bn254, Fp, Fp12, Fp2}; + use openvm_pairing::bn254::{Bn254, Fp, Fp12, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bn254Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_line_bn254.rs"); pub fn test_mul_013_by_013(io: &[u8]) { assert_eq!(io.len(), 32 * 18); @@ -78,18 +71,11 @@ mod bn254 { #[cfg(feature = "bls12_381")] mod bls12_381 { - use openvm_pairing_guest::bls12_381::{Bls12_381, Fp, Fp12, Fp2}; + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp12, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bls12_381Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_line_bls12_381.rs"); pub fn test_mul_023_by_023(io: &[u8]) { assert_eq!(io.len(), 48 * 18); @@ -150,15 +136,11 @@ pub fn main() { #[cfg(feature = "bn254")] { - bn254::setup_0(); - bn254::setup_all_complex_extensions(); bn254::test_mul_013_by_013(&io[..32 * 18]); bn254::test_mul_by_01234(&io[32 * 18..32 * 52]); } #[cfg(feature = "bls12_381")] { - bls12_381::setup_0(); - bls12_381::setup_all_complex_extensions(); bls12_381::test_mul_023_by_023(&io[..48 * 18]); bls12_381::test_mul_by_02345(&io[48 * 18..48 * 52]); } diff --git a/extensions/pairing/tests/programs/examples/pairing_miller_loop.rs b/guest-libs/pairing/tests/programs/examples/pairing_miller_loop.rs similarity index 76% rename from extensions/pairing/tests/programs/examples/pairing_miller_loop.rs rename to guest-libs/pairing/tests/programs/examples/pairing_miller_loop.rs index eb732f68a2..faa2efad4a 100644 --- a/extensions/pairing/tests/programs/examples/pairing_miller_loop.rs +++ b/guest-libs/pairing/tests/programs/examples/pairing_miller_loop.rs @@ -13,22 +13,13 @@ openvm::entry!(main); #[cfg(feature = "bn254")] mod bn254 { - use openvm_pairing_guest::bn254::{Bn254, Fp, Fp2}; + use openvm_pairing::bn254::{Bn254, Fp, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bn254Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_miller_loop_bn254.rs"); pub fn test_miller_loop(io: &[u8]) { - setup_0(); - setup_all_complex_extensions(); let s0 = &io[0..32 * 2]; let s1 = &io[32 * 2..32 * 4]; let q0 = &io[32 * 4..32 * 8]; @@ -56,22 +47,13 @@ mod bn254 { #[cfg(feature = "bls12_381")] mod bls12_381 { - use openvm_pairing_guest::bls12_381::{Bls12_381, Fp, Fp2}; + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bls12_381Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_miller_loop_bls12_381.rs"); pub fn test_miller_loop(io: &[u8]) { - setup_0(); - setup_all_complex_extensions(); let s0 = &io[0..48 * 2]; let s1 = &io[48 * 2..48 * 4]; let q0 = &io[48 * 4..48 * 8]; diff --git a/extensions/pairing/tests/programs/examples/pairing_miller_step.rs b/guest-libs/pairing/tests/programs/examples/pairing_miller_step.rs similarity index 88% rename from extensions/pairing/tests/programs/examples/pairing_miller_step.rs rename to guest-libs/pairing/tests/programs/examples/pairing_miller_step.rs index c32aefc7c1..e726406aff 100644 --- a/extensions/pairing/tests/programs/examples/pairing_miller_step.rs +++ b/guest-libs/pairing/tests/programs/examples/pairing_miller_step.rs @@ -12,18 +12,11 @@ openvm::entry!(main); #[cfg(feature = "bn254")] mod bn254 { use openvm_algebra_guest::field::FieldExtension; - use openvm_pairing_guest::bn254::{Bn254, Fp, Fp2}; + use openvm_pairing::bn254::{Bn254, Fp, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bn254Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_miller_step_bn254.rs"); pub fn test_miller_step(io: &[u8]) { assert_eq!(io.len(), 32 * 12); @@ -91,18 +84,11 @@ mod bn254 { #[cfg(feature = "bls12_381")] mod bls12_381 { use openvm_algebra_guest::field::FieldExtension; - use openvm_pairing_guest::bls12_381::{Bls12_381, Fp, Fp2}; + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp2}; use super::*; - openvm_algebra_moduli_macros::moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" - } - - openvm_algebra_complex_macros::complex_init! { - Bls12_381Fp2 { mod_idx = 0 }, - } + openvm::init!("openvm_init_pairing_miller_step_bls12_381.rs"); pub fn test_miller_step(io: &[u8]) { assert_eq!(io.len(), 48 * 12); @@ -169,15 +155,11 @@ pub fn main() { #[cfg(feature = "bn254")] { - bn254::setup_0(); - bn254::setup_all_complex_extensions(); bn254::test_miller_step(&io[..32 * 12]); bn254::test_miller_double_and_add_step(&io[32 * 12..]); } #[cfg(feature = "bls12_381")] { - bls12_381::setup_0(); - bls12_381::setup_all_complex_extensions(); bls12_381::test_miller_step(&io[..48 * 12]); bls12_381::test_miller_double_and_add_step(&io[48 * 12..]); } diff --git a/guest-libs/pairing/tests/programs/openvm_init_bls_ec_bls12_381.rs b/guest-libs/pairing/tests/programs/openvm_init_bls_ec_bls12_381.rs new file mode 100644 index 0000000000..95a4e46fd3 --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_bls_ec_bls12_381.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", "52435875175126190479447740508185965837690552500527637822603658699938581184513" } +openvm_ecc_guest::sw_macros::sw_init! { Bls12_381G1Affine } diff --git a/guest-libs/pairing/tests/programs/openvm_init_bls_final_exp_hint_bls12_381.rs b/guest-libs/pairing/tests/programs/openvm_init_bls_final_exp_hint_bls12_381.rs new file mode 100644 index 0000000000..00181b03ef --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_bls_final_exp_hint_bls12_381.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" } +openvm_algebra_guest::complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_bn_ec_bn254.rs b/guest-libs/pairing/tests/programs/openvm_init_bn_ec_bn254.rs new file mode 100644 index 0000000000..64de28e83a --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_bn_ec_bn254.rs @@ -0,0 +1,3 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583", "21888242871839275222246405745257275088548364400416034343698204186575808495617" } +openvm_ecc_guest::sw_macros::sw_init! { Bn254G1Affine } diff --git a/guest-libs/pairing/tests/programs/openvm_init_bn_final_exp_hint_bn254.rs b/guest-libs/pairing/tests/programs/openvm_init_bn_final_exp_hint_bn254.rs new file mode 100644 index 0000000000..c130859ad8 --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_bn_final_exp_hint_bn254.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_fp12_mul_bls12_381.rs b/guest-libs/pairing/tests/programs/openvm_init_fp12_mul_bls12_381.rs new file mode 100644 index 0000000000..00181b03ef --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_fp12_mul_bls12_381.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" } +openvm_algebra_guest::complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_fp12_mul_bn254.rs b/guest-libs/pairing/tests/programs/openvm_init_fp12_mul_bn254.rs new file mode 100644 index 0000000000..c130859ad8 --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_fp12_mul_bn254.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_check_bls12_381.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_check_bls12_381.rs new file mode 100644 index 0000000000..00181b03ef --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_check_bls12_381.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" } +openvm_algebra_guest::complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_check_bn254.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_check_bn254.rs new file mode 100644 index 0000000000..c130859ad8 --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_check_bn254.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_check_fallback_bls12_381.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_check_fallback_bls12_381.rs new file mode 100644 index 0000000000..00181b03ef --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_check_fallback_bls12_381.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" } +openvm_algebra_guest::complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_check_fallback_bn254.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_check_fallback_bn254.rs new file mode 100644 index 0000000000..c130859ad8 --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_check_fallback_bn254.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_line_bls12_381.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_line_bls12_381.rs new file mode 100644 index 0000000000..00181b03ef --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_line_bls12_381.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" } +openvm_algebra_guest::complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_line_bn254.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_line_bn254.rs new file mode 100644 index 0000000000..c130859ad8 --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_line_bn254.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_loop_bls12_381.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_loop_bls12_381.rs new file mode 100644 index 0000000000..00181b03ef --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_loop_bls12_381.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" } +openvm_algebra_guest::complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_loop_bn254.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_loop_bn254.rs new file mode 100644 index 0000000000..c130859ad8 --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_loop_bn254.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_step_bls12_381.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_step_bls12_381.rs new file mode 100644 index 0000000000..00181b03ef --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_step_bls12_381.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" } +openvm_algebra_guest::complex_macros::complex_init! { Bls12_381Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_step_bn254.rs b/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_step_bn254.rs new file mode 100644 index 0000000000..c130859ad8 --- /dev/null +++ b/guest-libs/pairing/tests/programs/openvm_init_pairing_miller_step_bn254.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "21888242871839275222246405745257275088696311157297823662689037894645226208583" } +openvm_algebra_guest::complex_macros::complex_init! { Bn254Fp2 { mod_idx = 0 } } +openvm_ecc_guest::sw_macros::sw_init! { } diff --git a/guest-libs/ruint/.gitignore b/guest-libs/ruint/.gitignore new file mode 100644 index 0000000000..a6821f469e --- /dev/null +++ b/guest-libs/ruint/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock +.vscode +.idea diff --git a/guest-libs/ruint/Cargo.toml b/guest-libs/ruint/Cargo.toml new file mode 100644 index 0000000000..d2f37f0030 --- /dev/null +++ b/guest-libs/ruint/Cargo.toml @@ -0,0 +1,190 @@ +[package] +# for patching purposes, the name must be the same as the original `ruint` crate +name = "ruint" +description = "OpenVM fork of ruint" +# for patching purposes, the version must be the same as the original `ruint` crate +version = "1.14.0" +keywords = ["uint"] +categories = ["mathematics"] +include = [".cargo/", "src/", "README.md"] +readme = "README.md" + +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = [ + "--cfg", + "docsrs", + "--html-in-header", + ".cargo/katex-header.html", +] + +[dependencies] +ruint-macro = { version = "1.2.1", path = "ruint-macro" } + +thiserror = { version = "2.0", optional = true } + +# support +alloy-rlp = { version = "0.3", optional = true, default-features = false } +arbitrary = { version = "1", optional = true, default-features = false } +ark-ff-03 = { version = "0.3.0", package = "ark-ff", optional = true, default-features = false } +ark-ff-04 = { version = "0.4.0", package = "ark-ff", optional = true, default-features = false } +bn-rs = { version = "0.2", optional = true, default-features = true } +fastrlp-03 = { version = "0.3", package = "fastrlp", optional = true, default-features = false, features = [ + "alloc", +] } +fastrlp-04 = { version = "0.4", package = "fastrlp", optional = true, default-features = false, features = [ + "alloc", +] } +num-bigint = { version = "0.4", optional = true, default-features = false } +num-integer = { version = "0.1", optional = true, default-features = false } +num-traits = { version = "0.2.16", optional = true, default-features = false } +parity-scale-codec = { version = "3", optional = true, default-features = false, features = [ + "derive", + "max-encoded-len", +] } +primitive-types = { version = "0.12", optional = true, default-features = false } +proptest = { version = "1", optional = true, default-features = false, features = [ + "no_std", +] } +pyo3 = { version = "0.25", optional = true, default-features = false } +quickcheck = { version = "1", optional = true, default-features = false } +rand-08 = { version = "0.8", package = "rand", optional = true, default-features = false } +rand-09 = { version = "0.9", package = "rand", optional = true, default-features = false } +rlp = { version = "0.5", optional = true, default-features = false } +serde = { version = "1", optional = true, default-features = false } +valuable = { version = "0.1", optional = true, default-features = false } +zeroize = { version = "1.6", optional = true, default-features = false } +bytemuck = { version = "1.13.1", optional = true, default-features = false } +ethereum_ssz = { version = "0.5.3", optional = true, default-features = false } +der = { version = "0.7", optional = true, default-features = false, features = [ + "alloc", +] } +subtle = { version = "2.6.1", optional = true, default-features = false } + +# postgres +bytes = { version = "1.4", optional = true } +postgres-types = { version = "0.2", optional = true } + +# diesel +diesel = { version = "2.2", optional = true } + +# sqlx +sqlx-core = { version = "0.8.2", optional = true } + +# borsh +borsh = { version = "1.5", optional = true, default-features = false } + +[target.'cfg(target_os = "zkvm")'.dependencies] +openvm-bigint-guest = { workspace = true, default-features = false, features = [ + "export-intrinsics", +] } + +[dev-dependencies] +ruint = { path = ".", features = ["arbitrary", "proptest"] } + +ark-bn254-03 = { version = "0.3.0", package = "ark-bn254" } +ark-bn254-04 = { version = "0.4.0", package = "ark-bn254" } + +criterion = "0.5" +rand-09 = { version = "0.9", package = "rand" } + +approx = "0.5" +bincode = "1.3" +hex = "0.4" +hex-literal = "1.0" +postgres = "0.19" +proptest = "1" +serde_json = "1.0" + +# borsh +borsh = { version = "1.5", features = ["derive"] } + +# Dev-dependencies for integration tests +openvm-instructions = { workspace = true } +openvm-stark-sdk.workspace = true +openvm-circuit = { workspace = true, features = ["test-utils", "parallel"] } +openvm-transpiler.workspace = true +openvm-bigint-transpiler.workspace = true +openvm-bigint-circuit.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-toolchain-tests = { workspace = true } +eyre.workspace = true + +[features] +default = ["std"] +std = [ + "alloc", + "alloy-rlp?/std", + "ark-ff-03?/std", + "ark-ff-04?/std", + "bytes?/std", + "fastrlp-03?/std", + "fastrlp-04?/std", + "num-bigint?/std", + "num-integer?/std", + "num-traits?/std", + "parity-scale-codec?/std", + "primitive-types?/std", + "proptest?/std", + "rand-08?/std", + "rand-08?/std_rng", + "rand-09?/std", + "rand-09?/thread_rng", + "rlp?/std", + "serde?/std", + "valuable?/std", + "zeroize?/std", +] +ssz = ["std", "dep:ethereum_ssz"] +alloc = [ + "proptest?/alloc", + "rand-08?/alloc", + "rand-09?/alloc", + "serde?/alloc", + "valuable?/alloc", + "zeroize?/alloc", +] + +# nightly-only features +nightly = [] +generic_const_exprs = ["nightly"] + +# support +alloy-rlp = ["dep:alloy-rlp", "alloc"] +arbitrary = ["dep:arbitrary", "std"] +ark-ff = ["dep:ark-ff-03"] +ark-ff-04 = ["dep:ark-ff-04"] +bn-rs = ["dep:bn-rs", "std"] +borsh = ["dep:borsh"] +bytemuck = ["dep:bytemuck"] +der = ["dep:der", "alloc"] # TODO: also have alloc free der impls. +diesel = ["dep:diesel", "std", "dep:thiserror"] +fastrlp = ["dep:fastrlp-03", "alloc"] +fastrlp-04 = ["dep:fastrlp-04", "alloc"] +num-bigint = ["dep:num-bigint", "alloc"] +num-integer = ["dep:num-integer", "num-traits", "alloc"] +num-traits = ["dep:num-traits", "alloc"] +parity-scale-codec = ["dep:parity-scale-codec", "alloc"] +postgres = ["dep:postgres-types", "dep:bytes", "dep:thiserror", "std"] +primitive-types = ["dep:primitive-types"] +proptest = ["dep:proptest", "alloc"] +pyo3 = ["dep:pyo3", "std"] +quickcheck = ["dep:quickcheck", "std"] +rand = ["dep:rand-08"] +rand-09 = ["dep:rand-09"] +rlp = ["dep:rlp", "alloc"] +serde = ["dep:serde", "alloc"] # TODO: try to avoid alloc in serde impls +sqlx = ["dep:sqlx-core", "std", "dep:thiserror"] +subtle = ["dep:subtle"] +valuable = ["dep:valuable"] +zeroize = ["dep:zeroize"] + +[package.metadata.cargo-shear] +ignored = ["criterion", "hex"] diff --git a/guest-libs/ruint/README.md b/guest-libs/ruint/README.md new file mode 100644 index 0000000000..c58f3611dc --- /dev/null +++ b/guest-libs/ruint/README.md @@ -0,0 +1 @@ +# OpenVM Uint Fork diff --git a/guest-libs/ruint/cspell.json b/guest-libs/ruint/cspell.json new file mode 100644 index 0000000000..20d7c32ed1 --- /dev/null +++ b/guest-libs/ruint/cspell.json @@ -0,0 +1,50 @@ +{ + "version": "0.2", + "ignorePaths": [], + "dictionaryDefinitions": [], + "dictionaries": [], + "words": [ + "aarch", + "adcs", + "archs", + "BYTEA", + "borsh", + "clippy", + "codecov", + "conv", + "cpython", + "cset", + "divq", + "divrem", + "docsrs", + "Encodable", + "fastrlp", + "freethreaded", + "froms", + "itertools", + "izip", + "mathtt", + "nlimbs", + "nocapture", + "nomem", + "nostack", + "proptest", + "punct", + "pypy", + "quickcheck", + "REDC", + "rposition", + "ruint", + "RUSTDOCFLAGS", + "serde", + "sqlx", + "Stackoverflow", + "Structable", + "thiserror", + "trunc", + "unnormalize", + "zeroize" + ], + "ignoreWords": [], + "import": [] +} diff --git a/guest-libs/ruint/deny.toml b/guest-libs/ruint/deny.toml new file mode 100644 index 0000000000..be873bab08 --- /dev/null +++ b/guest-libs/ruint/deny.toml @@ -0,0 +1,35 @@ +[advisories] +version = 2 +ignore = [ + { id = "RUSTSEC-2024-0388", reason = "this is an unmaintained transitive subdep of a dev dep" }, + { id = "RUSTSEC-2024-0436", reason = "this is an unmaintained transitive subdep of a dep" }, +] + +[bans] +multiple-versions = "warn" +wildcards = "warn" +highlight = "all" + +[licenses] +version = 2 +confidence-threshold = 0.9 +# copyleft = "deny" +allow = [ + "MIT", + "MIT-0", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Unicode-DFS-2016", + "Unlicense", + "MPL-2.0", + "CC0-1.0", + "Unicode-3.0", + "Zlib", +] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" diff --git a/guest-libs/ruint/ruint-macro/.cargo b/guest-libs/ruint/ruint-macro/.cargo new file mode 120000 index 0000000000..ca32a98568 --- /dev/null +++ b/guest-libs/ruint/ruint-macro/.cargo @@ -0,0 +1 @@ +../.cargo \ No newline at end of file diff --git a/guest-libs/ruint/ruint-macro/Cargo.toml b/guest-libs/ruint/ruint-macro/Cargo.toml new file mode 100644 index 0000000000..158b684141 --- /dev/null +++ b/guest-libs/ruint/ruint-macro/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ruint-macro" +description = "The `uint!` macro for `Uint` and `Bits` literals" +version = "1.2.1" +keywords = ["uint", "macro"] +categories = ["mathematics"] +readme = "README.md" + +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lib] +proc-macro = true + +[package.metadata.release] +tag = false + +[dev-dependencies] +ruint = { path = ".." } diff --git a/guest-libs/ruint/ruint-macro/README.md b/guest-libs/ruint/ruint-macro/README.md new file mode 100644 index 0000000000..e23474f599 --- /dev/null +++ b/guest-libs/ruint/ruint-macro/README.md @@ -0,0 +1,68 @@ +# The `uint!` macro for `Uint` and `Bits` literals + +Within the [`uint!`] macro arguments, you can write [`Uint`] and [`Bits`] literals using the [same syntax][rust-syntax] as Rust integer literals, but using a capital `U` or `B` suffix respectively. Note that there is ambiguity for hexadecimals with a `B` suffix, to lessen the impact an underscore is required in this case. + + + +[`Uint`]: ../uint/struct.Uint.html +[`Bits`]: ../uint/struct.Bits.html +[rust-syntax]: https://doc.rust-lang.org/stable/reference/tokens.html#integer-literals + +To use it simply import it in scope: + +```rust +use ruint::uint; +``` + +Now constants can be created in decimal, hex, binary and even octal: + +```rust +# use ruint::uint; +let avogadro = uint!(602_214_076_000_000_000_000_000_U256); +let cow_key = uint!(0xee79b5f6e221356af78cf4c36f4f7885a11b67dfcc81c34d80249947330c0f82_U256); +let bender = uint!(0b1010011010_U10); +``` + +The [`uint!`] macro recurses through the parse tree, so the above can equivalently be written + +```rust +# use ruint::uint; +uint! { +let avogadro = 602_214_076_000_000_000_000_000_U256; +let cow_key = 0xee79b5f6e221356af78cf4c36f4f7885a11b67dfcc81c34d80249947330c0f82_U256; +let bender = 0b1010011010_U10; +} +``` + +This latter form is particularly useful for lookup tables: + +```rust +# use ruint::{Uint, uint}; +const PRIMES: [Uint<128, 2>; 3] = uint!([ + 170141183460469231731687303715884105757_U128, + 170141183460469231731687303715884105773_U128, + 170141183460469231731687303715884105793_U128, +]); +``` + +The macro will throw a compile time error if you try to create a constant that +does not fit the type: + +```rust,compile_fail +# use ruint::uint; +# uint! { +let sparta = 300_U8; +# } +``` + +```text,ignore +error: Value too large for Uint<8>: 300 + --> src/example.rs:1:14 + | +1 | let sparta = 300_U8; + | ^^^^^^ +``` + +## References + +* Rust [integer literals syntax](https://doc.rust-lang.org/stable/reference/tokens.html#integer-literals). diff --git a/guest-libs/ruint/ruint-macro/src/lib.rs b/guest-libs/ruint/ruint-macro/src/lib.rs new file mode 100644 index 0000000000..86dc5afcf0 --- /dev/null +++ b/guest-libs/ruint/ruint-macro/src/lib.rs @@ -0,0 +1,299 @@ +#![doc = include_str!("../README.md")] +#![warn(clippy::all, clippy::pedantic, clippy::nursery)] + +use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; +use std::fmt::{self, Write}; + +// Repeat the crate doc. +#[doc = include_str!("../README.md")] +#[proc_macro] +pub fn uint(stream: TokenStream) -> TokenStream { + Transformer::new(None).transform_stream(stream) +} + +/// Same as [`uint`], but with the first token always being a +/// [group](proc_macro::Group) containing the `ruint` crate path. +/// +/// This allows the macro to be used in a crates that don't on `ruint` through a +/// wrapper `macro_rules!` that passes `$crate` as the path. +/// +/// This is an implementation detail and should not be used directly. +#[proc_macro] +#[doc(hidden)] +pub fn uint_with_path(stream: TokenStream) -> TokenStream { + let mut stream_iter = stream.into_iter(); + let Some(TokenTree::Group(group)) = stream_iter.next() else { + return error( + Span::call_site(), + "Expected a group containing the `ruint` crate path", + ) + .into(); + }; + Transformer::new(Some(group.stream())).transform_stream(stream_iter.collect()) +} + +#[derive(Copy, Clone, PartialEq, Debug)] +enum LiteralBaseType { + Uint, + Bits, +} + +impl LiteralBaseType { + const PATTERN: &'static [char] = &['U', 'B']; +} + +impl fmt::Display for LiteralBaseType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Uint => f.write_str("Uint"), + Self::Bits => f.write_str("Bits"), + } + } +} + +impl std::str::FromStr for LiteralBaseType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "U" => Ok(Self::Uint), + "B" => Ok(Self::Bits), + _ => Err(()), + } + } +} + +/// Construct a compiler error message. +// FEATURE: (BLOCKED) Replace with Diagnostic API when stable. +// See +fn error(span: Span, message: &str) -> TokenTree { + // See: https://docs.rs/syn/1.0.70/src/syn/error.rs.html#243 + let tokens = TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("compile_error", span)), + TokenTree::Punct(Punct::new('!', Spacing::Alone)), + TokenTree::Group({ + let mut group = Group::new( + Delimiter::Brace, + TokenStream::from_iter(vec![TokenTree::Literal(Literal::string(message))]), + ); + group.set_span(span); + group + }), + ]); + TokenTree::Group(Group::new(Delimiter::None, tokens)) +} + +fn parse_digits(value: &str) -> Result, String> { + // Parse base + let (base, digits) = if value.len() >= 2 { + let (prefix, remainder) = value.split_at(2); + match prefix { + "0x" => (16_u8, remainder), + "0o" => (8, remainder), + "0b" => (2, remainder), + _ => (10, value), + } + } else { + (10, value) + }; + + // Parse digits in base + let mut limbs = vec![0_u64]; + for c in digits.chars() { + // Read next digit + let digit = match c { + '0'..='9' => c as u64 - '0' as u64, + 'a'..='f' => c as u64 - 'a' as u64 + 10, + 'A'..='F' => c as u64 - 'A' as u64 + 10, + '_' => continue, + _ => return Err(format!("Invalid character '{c}'")), + }; + #[allow(clippy::cast_lossless)] + if digit > base as u64 { + return Err(format!( + "Invalid digit {c} in base {base} (did you forget the `0x` prefix?)" + )); + } + + // Multiply result by base and add digit + let mut carry = digit; + #[allow(clippy::cast_lossless)] + #[allow(clippy::cast_possible_truncation)] + for limb in &mut limbs { + let product = (*limb as u128) * (base as u128) + (carry as u128); + *limb = product as u64; + carry = (product >> 64) as u64; + } + if carry > 0 { + limbs.push(carry); + } + } + Ok(limbs) +} + +fn pad_limbs(bits: usize, mut limbs: Vec) -> Option> { + // Get limb count and mask + let num_limbs = (bits + 63) / 64; + let mask = if bits == 0 { + 0 + } else { + let bits = bits % 64; + if bits == 0 { + u64::MAX + } else { + (1 << bits) - 1 + } + }; + + // Remove trailing zeros, pad with zeros + while limbs.len() > num_limbs && limbs.last() == Some(&0) { + limbs.pop(); + } + while limbs.len() < num_limbs { + limbs.push(0); + } + + // Validate length + if limbs.len() > num_limbs || limbs.last().copied().unwrap_or(0) > mask { + return None; + } + Some(limbs) +} + +fn parse_suffix(source: &str) -> Option<(LiteralBaseType, usize, &str)> { + // Parse into value, bits, and base type. + let suffix_index = source.rfind(LiteralBaseType::PATTERN)?; + let (value, suffix) = source.split_at(suffix_index); + let (base_type, bits) = suffix.split_at(1); + let base_type = base_type.parse::().ok()?; + let bits = bits.parse::().ok()?; + + // Ignore hexadecimal Bits literals without `_` before the suffix. + if base_type == LiteralBaseType::Bits && value.starts_with("0x") && !value.ends_with('_') { + return None; + } + Some((base_type, bits, value)) +} + +struct Transformer { + /// The `ruint` crate path. + /// Note that this stream's span must be used in order for the `$crate` to + /// work. + ruint_crate: TokenStream, +} + +impl Transformer { + fn new(ruint_crate: Option) -> Self { + Self { + ruint_crate: ruint_crate.unwrap_or_else(|| "::ruint".parse().unwrap()), + } + } + + /// Construct a `<{base_type}><{bits}>` literal from `limbs`. + fn construct(&self, base_type: LiteralBaseType, bits: usize, limbs: &[u64]) -> TokenStream { + let mut limbs_str = String::new(); + for limb in limbs { + write!(&mut limbs_str, "0x{limb:016x}_u64, ").unwrap(); + } + let limbs_str = limbs_str.trim_end_matches(", "); + let limbs = (bits + 63) / 64; + let source = format!("::{base_type}::<{bits}, {limbs}>::from_limbs([{limbs_str}])"); + + let mut tokens = self.ruint_crate.clone(); + tokens.extend(source.parse::().unwrap()); + tokens + } + + /// Transforms a [`Literal`] and returns the substitute [`TokenStream`]. + fn transform_literal(&self, source: &str) -> Result, String> { + // Check if literal has a suffix we accept. + let Some((base_type, bits, value)) = parse_suffix(source) else { + return Ok(None); + }; + + // Parse `value` into limbs. + // At this point we are confident the literal was for us, so we throw errors. + let limbs = parse_digits(value)?; + + // Pad limbs to the correct length. + let Some(limbs) = pad_limbs(bits, limbs) else { + let value = value.trim_end_matches('_'); + return Err(format!("Value too large for {base_type}<{bits}>: {value}")); + }; + + Ok(Some(self.construct(base_type, bits, &limbs))) + } + + /// Recurse down tree and transform all literals. + fn transform_tree(&self, tree: TokenTree) -> TokenTree { + match tree { + TokenTree::Group(group) => { + let delimiter = group.delimiter(); + let span = group.span(); + let stream = self.transform_stream(group.stream()); + let mut transformed = Group::new(delimiter, stream); + transformed.set_span(span); + TokenTree::Group(transformed) + } + TokenTree::Literal(a) => { + let span = a.span(); + let source = a.to_string(); + let mut tree = match self.transform_literal(&source) { + Ok(Some(stream)) => TokenTree::Group({ + let mut group = Group::new(Delimiter::None, stream); + group.set_span(span); + group + }), + Ok(None) => TokenTree::Literal(a), + Err(message) => error(span, &message), + }; + tree.set_span(span); + tree + } + tree => tree, + } + } + + /// Iterate over a [`TokenStream`] and transform all [`TokenTree`]s. + fn transform_stream(&self, stream: TokenStream) -> TokenStream { + stream + .into_iter() + .map(|tree| self.transform_tree(tree)) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_zero_size() { + assert_eq!(parse_digits("0"), Ok(vec![0])); + assert_eq!(parse_digits("00000"), Ok(vec![0])); + assert_eq!(parse_digits("0x00"), Ok(vec![0])); + assert_eq!(parse_digits("0b0000"), Ok(vec![0])); + assert_eq!(parse_digits("0b0000000"), Ok(vec![0])); + + assert_eq!(parse_digits("0"), Ok(vec![0])); + assert_eq!(parse_digits("00000"), Ok(vec![0])); + assert_eq!(parse_digits("0x00"), Ok(vec![0])); + assert_eq!(parse_digits("0b0000"), Ok(vec![0])); + assert_eq!(parse_digits("0b0000000"), Ok(vec![0])); + } + + #[test] + fn test_bases() { + assert_eq!(parse_digits("10"), Ok(vec![10])); + assert_eq!(parse_digits("0x10"), Ok(vec![16])); + assert_eq!(parse_digits("0b10"), Ok(vec![2])); + assert_eq!(parse_digits("0o10"), Ok(vec![8])); + } + + #[test] + #[allow(clippy::unreadable_literal)] + fn test_overflow_during_parsing() { + assert_eq!(parse_digits("258664426012969093929703085429980814127835149614277183275038967946009968870203535512256352201271898244626862047232"), Ok(vec![0, 15125697203588300800, 6414901478162127871, 13296924585243691235, 13584922160258634318, 121098312706494698])); + assert_eq!(parse_digits("2135987035920910082395021706169552114602704522356652769947041607822219725780640550022962086936576"), Ok(vec![0, 0, 0, 0, 0, 1])); + } +} diff --git a/guest-libs/ruint/ruint-macro/tests/parse_mixed.rs b/guest-libs/ruint/ruint-macro/tests/parse_mixed.rs new file mode 100644 index 0000000000..01556e8e49 --- /dev/null +++ b/guest-libs/ruint/ruint-macro/tests/parse_mixed.rs @@ -0,0 +1,31 @@ +use ruint::aliases::{B256, U256}; +use ruint_macro::uint; + +#[test] +fn test_non_literal() { + uint! { + assert_eq!(0xBBBB_B432_B245_B323_u64, 13527604035569693475); + }; +} + +#[test] +fn test_parse_mixed() { + uint! { + assert_eq!(0x10U256, "0x10".parse::().unwrap()); + assert_eq!(0o10U256, "0o10".parse::().unwrap()); + assert_eq!(0b10U256, "0b10".parse::().unwrap()); + + assert_eq!(0x10_U256, "0x10".parse::().unwrap()); + assert_eq!(0o10_U256, "0o10".parse::().unwrap()); + assert_eq!(0b10_U256, "0b10".parse::().unwrap()); + + assert_eq!(0x10_B256, "0x10".parse::().unwrap()); + assert_eq!(0o10B256, "0o10".parse::().unwrap()); + assert_eq!(0b10B256, "0b10".parse::().unwrap()); + + assert_eq!(0o10_B256, "0o10".parse::().unwrap()); + assert_eq!(0b10_B256, "0b10".parse::().unwrap()); + + assert_eq!(2, 2); + } +} diff --git a/guest-libs/ruint/src/add.rs b/guest-libs/ruint/src/add.rs new file mode 100644 index 0000000000..a4674080ea --- /dev/null +++ b/guest-libs/ruint/src/add.rs @@ -0,0 +1,304 @@ +use crate::{ + algorithms::{borrowing_sub, carrying_add}, + Uint, +}; +use core::{ + iter::Sum, + ops::{Add, AddAssign, Neg, Sub, SubAssign}, +}; + +impl Uint { + /// Computes the absolute difference between `self` and `other`. + /// + /// Returns $\left\vert \mathtt{self} - \mathtt{other} \right\vert$. + #[inline(always)] + #[must_use] + pub fn abs_diff(self, other: Self) -> Self { + if self < other { + other.wrapping_sub(self) + } else { + self.wrapping_sub(other) + } + } + + /// Computes `self + rhs`, returning [`None`] if overflow occurred. + #[inline(always)] + #[must_use] + pub const fn checked_add(self, rhs: Self) -> Option { + match self.overflowing_add(rhs) { + (value, false) => Some(value), + _ => None, + } + } + + /// Computes `-self`, returning [`None`] unless `self == 0`. + #[inline(always)] + #[must_use] + pub const fn checked_neg(self) -> Option { + match self.overflowing_neg() { + (value, false) => Some(value), + _ => None, + } + } + + /// Computes `self - rhs`, returning [`None`] if overflow occurred. + #[inline(always)] + #[must_use] + pub const fn checked_sub(self, rhs: Self) -> Option { + match self.overflowing_sub(rhs) { + (value, false) => Some(value), + _ => None, + } + } + + /// Calculates $\mod{\mathtt{self} + \mathtt{rhs}}_{2^{BITS}}$. + /// + /// Returns a tuple of the addition along with a boolean indicating whether + /// an arithmetic overflow would occur. If an overflow would have occurred + /// then the wrapped value is returned. + #[inline] + #[must_use] + pub const fn overflowing_add(mut self, rhs: Self) -> (Self, bool) { + if BITS == 0 { + return (Self::ZERO, false); + } + let mut carry = false; + let mut i = 0; + while i < LIMBS { + (self.limbs[i], carry) = carrying_add(self.limbs[i], rhs.limbs[i], carry); + i += 1; + } + let overflow = carry | (self.limbs[LIMBS - 1] > Self::MASK); + (self.masked(), overflow) + } + + /// Calculates $\mod{-\mathtt{self}}_{2^{BITS}}$. + /// + /// Returns `!self + 1` using wrapping operations to return the value that + /// represents the negation of this unsigned value. Note that for positive + /// unsigned values overflow always occurs, but negating 0 does not + /// overflow. + #[inline(always)] + #[must_use] + pub const fn overflowing_neg(self) -> (Self, bool) { + Self::ZERO.overflowing_sub(self) + } + + /// Calculates $\mod{\mathtt{self} - \mathtt{rhs}}_{2^{BITS}}$. + /// + /// Returns a tuple of the subtraction along with a boolean indicating + /// whether an arithmetic overflow would occur. If an overflow would have + /// occurred then the wrapped value is returned. + #[inline] + #[must_use] + pub const fn overflowing_sub(mut self, rhs: Self) -> (Self, bool) { + if BITS == 0 { + return (Self::ZERO, false); + } + let mut borrow = false; + let mut i = 0; + while i < LIMBS { + (self.limbs[i], borrow) = borrowing_sub(self.limbs[i], rhs.limbs[i], borrow); + i += 1; + } + let overflow = borrow | (self.limbs[LIMBS - 1] > Self::MASK); + (self.masked(), overflow) + } + + /// Computes `self + rhs`, saturating at the numeric bounds instead of + /// overflowing. + #[inline(always)] + #[must_use] + pub const fn saturating_add(self, rhs: Self) -> Self { + match self.overflowing_add(rhs) { + (value, false) => value, + _ => Self::MAX, + } + } + + /// Computes `self - rhs`, saturating at the numeric bounds instead of + /// overflowing + #[inline(always)] + #[must_use] + pub const fn saturating_sub(self, rhs: Self) -> Self { + match self.overflowing_sub(rhs) { + (value, false) => value, + _ => Self::ZERO, + } + } + + /// Computes `self + rhs`, wrapping around at the boundary of the type. + #[cfg(not(target_os = "zkvm"))] + #[inline(always)] + #[must_use] + pub const fn wrapping_add(self, rhs: Self) -> Self { + self.overflowing_add(rhs).0 + } + + /// Computes `self + rhs`, wrapping around at the boundary of the type. + #[cfg(target_os = "zkvm")] + #[inline(always)] + #[must_use] + pub fn wrapping_add(mut self, rhs: Self) -> Self { + use crate::support::zkvm::zkvm_u256_wrapping_add_impl; + if BITS == 256 { + unsafe { + zkvm_u256_wrapping_add_impl( + self.limbs.as_mut_ptr() as *mut u8, + self.limbs.as_ptr() as *const u8, + rhs.limbs.as_ptr() as *const u8, + ); + } + return self; + } + self.overflowing_add(rhs).0 + } + + /// Computes `-self`, wrapping around at the boundary of the type. + #[cfg(not(target_os = "zkvm"))] + #[inline(always)] + #[must_use] + pub const fn wrapping_neg(self) -> Self { + self.overflowing_neg().0 + } + + /// Computes `-self`, wrapping around at the boundary of the type. + #[cfg(target_os = "zkvm")] + #[inline(always)] + #[must_use] + pub fn wrapping_neg(self) -> Self { + Self::ZERO.wrapping_sub(self) + } + + /// Computes `self - rhs`, wrapping around at the boundary of the type. + #[cfg(not(target_os = "zkvm"))] + #[inline(always)] + #[must_use] + pub const fn wrapping_sub(self, rhs: Self) -> Self { + self.overflowing_sub(rhs).0 + } + + /// Computes `self - rhs`, wrapping around at the boundary of the type. + #[cfg(target_os = "zkvm")] + #[inline(always)] + #[must_use] + pub fn wrapping_sub(mut self, rhs: Self) -> Self { + use crate::support::zkvm::zkvm_u256_wrapping_sub_impl; + if BITS == 256 { + unsafe { + zkvm_u256_wrapping_sub_impl( + self.limbs.as_mut_ptr() as *mut u8, + self.limbs.as_ptr() as *const u8, + rhs.limbs.as_ptr() as *const u8, + ); + } + return self; + } + self.overflowing_sub(rhs).0 + } +} + +impl Neg for Uint { + type Output = Self; + + #[inline(always)] + fn neg(self) -> Self::Output { + self.wrapping_neg() + } +} + +impl Neg for &Uint { + type Output = Uint; + + #[inline(always)] + fn neg(self) -> Self::Output { + self.wrapping_neg() + } +} + +impl Sum for Uint { + #[inline] + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Self::ZERO, Self::wrapping_add) + } +} + +impl<'a, const BITS: usize, const LIMBS: usize> Sum<&'a Self> for Uint { + #[inline] + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.copied().fold(Self::ZERO, Self::wrapping_add) + } +} + +impl_bin_op!(Add, add, AddAssign, add_assign, wrapping_add); +impl_bin_op!(Sub, sub, SubAssign, sub_assign, wrapping_sub); + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::proptest; + + #[test] + fn test_neg_one() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + assert_eq!(-U::ONE, !U::ZERO); + }); + } + + #[test] + fn test_commutative() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U)| { + assert_eq!(a + b, b + a); + assert_eq!(a - b, -(b - a)); + }); + }); + } + + #[test] + fn test_associative() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U, c: U)| { + assert_eq!(a + (b + c), (a + b) + c); + }); + }); + } + + #[test] + fn test_identity() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + assert_eq!(value + U::ZERO, value); + assert_eq!(value - U::ZERO, value); + }); + }); + } + + #[test] + fn test_inverse() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U)| { + assert_eq!(a + (-a), U::ZERO); + assert_eq!(a - a, U::ZERO); + assert_eq!(-(-a), a); + }); + }); + } +} diff --git a/guest-libs/ruint/src/algorithms/add.rs b/guest-libs/ruint/src/algorithms/add.rs new file mode 100644 index 0000000000..92db3414c3 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/add.rs @@ -0,0 +1,19 @@ +use super::ops::{adc, sbb}; + +/// `lhs += rhs + carry` +#[inline(always)] +pub fn adc_n(lhs: &mut [u64], rhs: &[u64], mut carry: u64) -> u64 { + for i in 0..lhs.len() { + (lhs[i], carry) = adc(lhs[i], rhs[i], carry); + } + carry +} + +/// `lhs -= rhs - borrow` +#[inline(always)] +pub fn sbb_n(lhs: &mut [u64], rhs: &[u64], mut borrow: u64) -> u64 { + for i in 0..lhs.len() { + (lhs[i], borrow) = sbb(lhs[i], rhs[i], borrow); + } + borrow +} diff --git a/guest-libs/ruint/src/algorithms/div/knuth.rs b/guest-libs/ruint/src/algorithms/div/knuth.rs new file mode 100644 index 0000000000..9fc3a7d72d --- /dev/null +++ b/guest-libs/ruint/src/algorithms/div/knuth.rs @@ -0,0 +1,515 @@ +//! Knuth division + +use super::{reciprocal::reciprocal_2, small::div_3x2, DoubleWord}; +use crate::{ + algorithms::{add::adc_n, mul::submul_nx1}, + utils::{likely, unlikely}, +}; + +/// ⚠️ In-place Knuth normalized long division with reciprocals. +/// +/// # Conditions of Use +/// +/// * The highest (most-significant) bit of the divisor MUST be set. +/// * The `divisor` and `numerator` MUST each be at least two limbs. +/// * `numerator` MUST contain at at least as many elements as `divisor`. +/// +/// # Panics +/// +/// May panic if any condition of use is violated. +#[inline] +#[allow(clippy::many_single_char_names)] +pub fn div_nxm_normalized(numerator: &mut [u64], divisor: &[u64]) { + debug_assert!(divisor.len() >= 2); + debug_assert!(numerator.len() >= divisor.len()); + debug_assert!(*divisor.last().unwrap() >= (1 << 63)); + + let n = divisor.len(); + let m = numerator.len() - n - 1; + + // Compute the divisor double limb and reciprocal + let d = u128::join(divisor[n - 1], divisor[n - 2]); + let v = reciprocal_2(d); + + // Compute the quotient one limb at a time. + for j in (0..=m).rev() { + // Fetch the first three limbs of the numerator. + let n21 = u128::join(numerator[j + n], numerator[j + n - 1]); + let n0 = numerator[j + n - 2]; + debug_assert!(n21 <= d); + + // Overflow case + if unlikely(n21 == d) { + let q = u64::MAX; + let _carry = submul_nx1(&mut numerator[j..j + n], divisor, q); + numerator[j + n] = q; + continue; + } + + // Calculate 3x2 approximate quotient word. + // By using 3x2 limbs we get a quotient that is very likely correct + // and at most one too large. In the process we also get the first + // two remainder limbs. + let (mut q, r) = div_3x2(n21, n0, d, v); + + // Subtract the quotient times the divisor from the remainder. + // We already have the highest two limbs, so we can reduce the + // computation. We still need to carry propagate into these limbs. + let borrow = submul_nx1(&mut numerator[j..j + n - 2], &divisor[..n - 2], q); + let (r, borrow) = r.overflowing_sub(u128::from(borrow)); + numerator[j + n - 2] = r.low(); + numerator[j + n - 1] = r.high(); + + // If we have a carry then the quotient was one too large. + // We correct by decrementing the quotient and adding one divisor back. + if unlikely(borrow) { + q = q.wrapping_sub(1); + let carry = adc_n(&mut numerator[j..j + n], &divisor[..n], 0); + // Expect carry because we flip sign back to positive. + debug_assert_eq!(carry, 1); + } + + // Store quotient in the unused bits of numerator + numerator[j + n] = q; + } +} + +/// ⚠️ In-place Knuth long division with implicit normalization and reciprocals. +/// +/// # Conditions of use: +/// +/// * `divisor` MUST NOT be empty. +/// * The highest (most-significant) limb of `divisor` MUST be non-zero. +/// * `divisor` and `numerator` MUST contain at least three limbs. +/// * `numerator` MUST contain at at least as many elements as `divisor`. +/// +/// # Panics +/// +/// May panic if any condition of use is violated. +#[inline] +#[allow(clippy::many_single_char_names)] +pub fn div_nxm(numerator: &mut [u64], divisor: &mut [u64]) { + debug_assert!(divisor.len() >= 3); + debug_assert!(numerator.len() >= divisor.len()); + debug_assert!(*divisor.last().unwrap() >= 1); + + let n = divisor.len(); + let m = numerator.len() - n; + + // Compute normalized divisor double-word and reciprocal. + // TODO: Delegate to div_nxm_normalized if normalized. + let (d, shift) = { + let d = u128::join(divisor[n - 1], divisor[n - 2]); + let shift = d.high().leading_zeros(); + ( + if shift == 0 { + d + } else { + (d << shift) | u128::from(divisor[n - 3] >> (64 - shift)) + }, + shift, + ) + }; + debug_assert!(d >= 1 << 127); + let v = reciprocal_2(d); + + // Compute the quotient one limb at a time. + let mut q_high = 0; + for j in (0..=m).rev() { + // Fetch the first three limbs of the shifted numerator starting at `j + n`. + let (n21, n0) = { + let n2 = numerator.get(j + n).copied().unwrap_or_default(); + let n21 = u128::join(n2, numerator[j + n - 1]); + let n0 = numerator[j + n - 2]; + if shift == 0 { + (n21, n0) + } else { + ( + (n21 << shift) | u128::from(n0 >> (64 - shift)), + (n0 << shift) | (numerator[j + n - 3] >> (64 - shift)), + ) + } + }; + debug_assert!(n21 <= d); + + // Compute the quotient + let q = if likely(n21 < d) { + // Calculate 3x2 approximate quotient word. + // By using 3x2 limbs we get a quotient that is very likely correct + // and at most one too large. In the process we also get the first + // two remainder limbs. + let (mut q, r) = div_3x2(n21, n0, d, v); + + if q != 0 { + // Subtract the quotient times the divisor from the remainder. + // We already have the highest 128 bit, so we can reduce the + // computation. We still need to carry propagate into these limbs. + let borrow = if shift == 0 { + let borrow = submul_nx1(&mut numerator[j..j + n - 2], &divisor[..n - 2], q); + let (r, borrow) = r.overflowing_sub(u128::from(borrow)); + numerator[j + n - 2] = r.low(); + numerator[j + n - 1] = r.high(); + borrow + } else { + // OPT: Can we re-use `r` here somehow? The problem is we can not just + // shift the `r` or `borrow` because we need to accurately reproduce + // the remainder and carry in the middle of a limb. + let borrow = submul_nx1(&mut numerator[j..j + n], divisor, q); + let n2 = numerator.get(j + n).copied().unwrap_or_default(); + borrow != n2 + }; + + // If we have a carry then the quotient was one too large. + // We correct by decrementing the quotient and adding one divisor back. + if unlikely(borrow) { + q = q.wrapping_sub(1); + let carry = adc_n(&mut numerator[j..j + n], &divisor[..n], 0); + // Expect carry because we flip sign back to positive. + debug_assert_eq!(carry, 1); + } + } + q + } else { + // Overflow case + let q = u64::MAX; + let _carry = submul_nx1(&mut numerator[j..j + n], divisor, q); + q + }; + + // Store the quotient in the processed bits of numerator plus `q_high`. + if j + n < numerator.len() { + numerator[j + n] = q; + } else { + q_high = q; + } + } + + // Copy remainder to `divisor` and `quotient` to numerator. + divisor.copy_from_slice(&numerator[..n]); + numerator.copy_within(n.., 0); + numerator[m] = q_high; + numerator[m + 1..].fill(0); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::algorithms::{addmul, cmp, sbb_n}; + use core::cmp::Ordering; + use proptest::{ + collection, num, proptest, + strategy::{Just, Strategy}, + }; + + #[allow(unused_imports)] + use alloc::vec::Vec; + + // Basic test without exceptional paths + #[test] + fn test_divrem_8by4() { + let mut numerator = [ + 0x3000000000000000, + 0xd4e15e75fd4e6516, + 0x593a70aa5daf127b, + 0xff0a216ae9c215f1, + 0xa78c7ad6fea10429, + 0x18276b093f5d1dac, + 0xfe2e0bccb9e6d8b3, + 0x1bebfb3bc05d9347, + ]; + let divisor = [ + 0x800000000000000, + 0x580c0c40583c55b5, + 0x6b16b3fb5bd85ed3, + 0xcc958c207ce3c96f, + ]; + let expected_quotient = [ + 0x9128464e61d6b5b3_u64, + 0xd9eea4fc30c5ac6c_u64, + 0x944a2d832d5a6a08_u64, + 0x22f06722e8d883b1_u64, + ]; + let expected_remainder = [ + 0x9800000000000000, + 0x70efd2d3f528c8d9, + 0x6dad759fcd6af14a, + 0x5fe38801c609f277, + ]; + div_nxm_normalized(&mut numerator, &divisor); + let (remainder, quotient) = numerator.split_at(divisor.len()); + assert_eq!(remainder, expected_remainder); + assert_eq!(quotient, expected_quotient); + } + + // Test case that forces the `unlikely(borrow)` branch. + #[test] + fn test_div_rollback() { + let mut numerator = [ + 0x1656178c14142000, + 0x821415dfe9e81612, + 0x1616561616161616, + 0x96000016820016, + ]; + let divisor = [0x1415dfe9e8161414, 0x1656161616161682, 0x9600001682001616]; + let expected_quotient = [0xffffffffffffff]; + let expected_remainder = [0x166bf775fc2a3414, 0x1656161616161680, 0x9600001682001616]; + div_nxm_normalized(&mut numerator, &divisor); + let (remainder, quotient) = numerator.split_at(divisor.len()); + assert_eq!(remainder, expected_remainder); + assert_eq!(quotient, expected_quotient); + } + + // Test case that forces the `unlikely(borrow)` branch. + #[test] + fn test_div_rollback_2() { + let mut numerator = [ + 0x100100000, + 0x81000, + 0x1000000000000000, + 0x0, + 0x0, + 0xfffff00000000000, + 0xffffffffffffffff, + 0xdfffffffffffff, + ]; + let divisor = [ + 0xfffffffffff00000, + 0xffffffffffffffff, + 0xfffffffffffff3ff, + 0xffffffffffffffff, + 0xdfffffffffffffff, + ]; + let expected_quotient = [0xffffedb6db6db6e9, 0xffffffffffffffff, 0xffffffffffffff]; + let expected_remainder = [ + 0xdb6db6dc6ea00000, + 0x80ffe, + 0xf2492492492ec00, + 0x1000, + 0x2000000000000000, + ]; + div_nxm_normalized(&mut numerator, &divisor); + let (remainder, quotient) = numerator.split_at(divisor.len()); + assert_eq!(quotient, expected_quotient); + assert_eq!(remainder, expected_remainder); + } + + #[test] + fn test_div_overflow() { + let mut numerator = [0xb200000000000002, 0x1, 0x0, 0xfdffffff00000000]; + let divisor = [0x10002, 0x0, 0xfdffffff00000000]; + let expected_quotient = [0xffffffffffffffff]; + let expected_remainder = [0xb200000000010004, 0xfffffffffffeffff, 0xfdfffffeffffffff]; + div_nxm_normalized(&mut numerator, &divisor); + let (remainder, quotient) = numerator.split_at(divisor.len()); + assert_eq!(quotient, expected_quotient); + assert_eq!(remainder, expected_remainder); + } + + // Proptest without forced exceptional paths + #[test] + fn test_div_nxm_normalized() { + let quotient = collection::vec(num::u64::ANY, 1..10); + let divisor = collection::vec(num::u64::ANY, 2..10).prop_map(|mut vec| { + *vec.last_mut().unwrap() |= 1 << 63; + vec + }); + let dr = divisor.prop_flat_map(|divisor| { + let d = divisor.clone(); + let remainder = + collection::vec(num::u64::ANY, divisor.len()).prop_map(move |mut vec| { + if cmp(&vec, &d) != Ordering::Less { + let carry = sbb_n(&mut vec, &d, 0); + assert_eq!(carry, 0); + } + vec + }); + (Just(divisor), remainder) + }); + proptest!(|(quotient in quotient, (divisor, remainder) in dr)| { + let mut numerator: Vec = vec![0; divisor.len() + quotient.len()]; + numerator[..remainder.len()].copy_from_slice(&remainder); + addmul(&mut numerator, quotient.as_slice(), divisor.as_slice()); + + div_nxm_normalized(numerator.as_mut_slice(), divisor.as_slice()); + let (r, q) = numerator.split_at(divisor.len()); + assert_eq!(r, remainder); + assert_eq!(q, quotient); + }); + } + + // Basic test without exceptional paths (with shift == 0) + #[test] + fn test_div_nxm_8by4_noshift() { + let mut numerator = [ + 0x3000000000000000, + 0xd4e15e75fd4e6516, + 0x593a70aa5daf127b, + 0xff0a216ae9c215f1, + 0xa78c7ad6fea10429, + 0x18276b093f5d1dac, + 0xfe2e0bccb9e6d8b3, + 0x1bebfb3bc05d9347, + ]; + let mut divisor = [ + 0x800000000000000, + 0x580c0c40583c55b5, + 0x6b16b3fb5bd85ed3, + 0xcc958c207ce3c96f, + ]; + let quotient = [ + 0x9128464e61d6b5b3, + 0xd9eea4fc30c5ac6c, + 0x944a2d832d5a6a08, + 0x22f06722e8d883b1, + ]; + let remainder = [ + 0x9800000000000000, + 0x70efd2d3f528c8d9, + 0x6dad759fcd6af14a, + 0x5fe38801c609f277, + ]; + div_nxm(&mut numerator, &mut divisor); + assert_eq!(&numerator[..quotient.len()], quotient); + assert_eq!(divisor, remainder); + } + + // Basic test without exceptional paths (with shift > 0) + #[test] + fn test_div_nxm_8by4_shift() { + let mut numerator = [ + 0xc59c28364a491d22, + 0x1ab240e2a2a91050, + 0xe497baaf4e4b16cb, + 0xd21643d231c590d6, + 0xda918cd26803c7f1, + 0xb445074f770b5261, + 0x37aff2aa32059516, + 0x3cf254c1, + ]; + let mut divisor = [ + 0xc91e935375a97723, + 0x86a9ded3044ec12b, + 0xc7d2c4d3b53bff51, + 0x6ef0530d, + ]; + let quotient = [ + 0x456b1581ef1a759a, + 0x88702c90bbe2ef3c, + 0xff8492ee85dec642, + 0x8ca39da4ca785f36, + ]; + let remainder = [ + 0x82c3522848567314, + 0xeaba6edb18db568e, + 0x18f16cfde22dcefe, + 0x11296685, + ]; + div_nxm(&mut numerator, &mut divisor); + assert_eq!(&numerator[..quotient.len()], quotient); + assert_eq!(divisor, remainder); + } + + // Basic test without exceptional paths (with q_high > 0) + #[test] + fn test_div_nxm_8by4_q_high() { + let mut numerator = [ + 0x39ea76324ed952cc, + 0x89b7a0d30e2df1be, + 0x7011596e8b3f301f, + 0x11930a89eca68640, + 0x36a34eca4f73d0e4, + 0x86d53c52b1108c90, + 0x6093338b7b667e03, + ]; + let mut divisor = [ + 0x439702d44a8f62a4, + 0x8dfa6ea7fc41f689, + 0xc79723ff4dd060e0, + 0x7d13e204, + ]; + let quotient = [ + 0x181cecbb64efa48b, + 0x1e97056793a15125, + 0xe8145d63cd312d02, + 0xc5a9aced, + ]; + let remainder = [ + 0x682e10f8d0b1b3c0, + 0xbf46c8b0e5ac8a62, + 0x5abe292d53acf085, + 0x699fc911, + ]; + div_nxm(&mut numerator, &mut divisor); + assert_eq!(&numerator[..quotient.len()], quotient); + assert_eq!(divisor, remainder); + } + + // Basic test with numerator and divisor the same size. + #[test] + fn test_div_nxm_4by4() { + let mut numerator = [ + 0x55a8f128f187ecee, + 0xe059a1f3fe52e559, + 0x570ab3b2aac5c5d9, + 0xf7ea0c73b80ddca1, + ]; + let mut divisor = [ + 0x6c8cd670adcae7da, + 0x458d6428c7fd36d3, + 0x4a73ad64cc703a1d, + 0x33bf790f92ed51fe, + ]; + let quotient = [0x4]; + let remainder = [ + 0xa37597663a5c4d86, + 0xca241150de5e0a0b, + 0x2d3bfe1f7904dd64, + 0x28ec28356c5894a8, + ]; + div_nxm(&mut numerator, &mut divisor); + assert_eq!(&numerator[..quotient.len()], quotient); + assert_eq!(divisor, remainder); + } + + #[test] + fn test_div_nxm_4by3() { + let mut numerator = [ + 0x8000000000000000, + 0x8000000000000000, + 0x8000000000000000, + 0x8000000000000001, + ]; + let mut divisor = [0x8000000000000000, 0x8000000000000000, 0x8000000000000000]; + let quotient = [0x1, 0x1]; + let remainder = [0x0, 0x8000000000000000, 0x7fffffffffffffff]; + div_nxm(&mut numerator, &mut divisor); + assert_eq!(&numerator[..quotient.len()], quotient); + assert_eq!(divisor, remainder); + } + + // Proptest without forced exceptional paths + #[test] + fn test_div_nxm() { + let quotient = collection::vec(num::u64::ANY, 1..10); + let divisor = collection::vec(num::u64::ANY, 3..10); + let dr = divisor.prop_flat_map(|divisor| { + let d = divisor.clone(); + let remainder = + collection::vec(num::u64::ANY, divisor.len()).prop_map(move |mut vec| { + *vec.last_mut().unwrap() %= d.last().unwrap(); + vec + }); + (Just(divisor), remainder) + }); + proptest!(|(quotient in quotient, (mut divisor, remainder) in dr)| { + let mut numerator: Vec = vec![0; divisor.len() + quotient.len()]; + numerator[..remainder.len()].copy_from_slice(&remainder); + addmul(&mut numerator, quotient.as_slice(), divisor.as_slice()); + + div_nxm(numerator.as_mut_slice(), divisor.as_mut_slice()); + assert_eq!(&numerator[..quotient.len()], quotient); + assert_eq!(divisor, remainder); + assert!(numerator[quotient.len()..].iter().all(|&x| x == 0)); + }); + } +} diff --git a/guest-libs/ruint/src/algorithms/div/mod.rs b/guest-libs/ruint/src/algorithms/div/mod.rs new file mode 100644 index 0000000000..45f3e307c0 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/div/mod.rs @@ -0,0 +1,461 @@ +//! ⚠️ Collection of division algorithms. +//! +//! **Warning.** Most functions in this module are currently not considered part +//! of the stable API and may be changed or removed in future minor releases. +//! +//! All division algorithms also compute the remainder. There is no benefit +//! to splitting the division and remainder into separate functions, since +//! the remainder is always computed as part of the division algorithm. +//! +//! These functions are adapted from algorithms in [MG10] and [K97]. +//! +//! [K97]: https://cs.stanford.edu/~knuth/taocp.html +//! [MG10]: https://gmplib.org/~tege/division-paper.pdf + +#![allow(clippy::similar_names)] // TODO + +mod knuth; +mod reciprocal; +mod small; + +pub use self::{ + knuth::{div_nxm, div_nxm_normalized}, + reciprocal::{reciprocal, reciprocal_2, reciprocal_2_mg10, reciprocal_mg10, reciprocal_ref}, + small::{ + div_2x1, div_2x1_mg10, div_2x1_ref, div_3x2, div_3x2_mg10, div_3x2_ref, div_nx1, + div_nx1_normalized, div_nx2, div_nx2_normalized, + }, +}; +use crate::algorithms::DoubleWord; + +/// ⚠️ Division with remainder. +/// +/// **Warning.** This function is not part of the stable API. +/// +/// The quotient is stored in the `numerator` and the remainder is stored +/// in the `divisor`. +/// +/// # Algorithm +/// +/// It trims zeros from the numerator and divisor then solves the trivial cases +/// directly, or dispatches to the [`div_nx1`], [`div_nx2`] or [`div_nxm`] +/// functions. +/// +/// # Panics +/// +/// Panics if `divisor` is zero. +#[inline] +pub fn div(numerator: &mut [u64], divisor: &mut [u64]) { + // Trim most significant zeros from divisor. + let i = divisor + .iter() + .rposition(|&x| x != 0) + .expect("Divisor is zero"); + let divisor = &mut divisor[..=i]; + debug_assert!(!divisor.is_empty()); + debug_assert!(divisor.last() != Some(&0)); + + // Trim zeros from numerator + let numerator = if let Some(i) = numerator.iter().rposition(|&n| n != 0) { + &mut numerator[..=i] + } else { + // Empty numerator (q, r) = (0,0) + divisor.fill(0); + return; + }; + debug_assert!(!numerator.is_empty()); + debug_assert!(*numerator.last().unwrap() != 0); + + // If numerator is smaller than divisor (q, r) = (0, numerator) + if numerator.len() < divisor.len() { + let (remainder, padding) = divisor.split_at_mut(numerator.len()); + remainder.copy_from_slice(numerator); + padding.fill(0); + numerator.fill(0); + return; + } + debug_assert!(numerator.len() >= divisor.len()); + + // Compute quotient and remainder, branching out to different algorithms. + if divisor.len() <= 2 { + if divisor.len() == 1 { + if numerator.len() == 1 { + let q = numerator[0] / divisor[0]; + let r = numerator[0] % divisor[0]; + numerator[0] = q; + divisor[0] = r; + } else { + divisor[0] = div_nx1(numerator, divisor[0]); + } + } else { + let d = u128::join(divisor[1], divisor[0]); + let remainder = div_nx2(numerator, d); + divisor[0] = remainder.low(); + divisor[1] = remainder.high(); + } + } else { + div_nxm(numerator, divisor); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::aliases::U512; + + // Test vectors from + // [[numerator, divisor, quotient, remainder]; _] + const INTX_TESTS: [[U512; 4]; 45] = uint!([ + [2_U512, 1_U512, 2_U512, 0_U512], + [ + 0x10000000000000000_U512, + 2_U512, + 0x8000000000000000_U512, + 0_U512, + ], + [ + 0x7000000000000000_U512, + 0x8000000000000000_U512, + 0_U512, + 0x7000000000000000_U512, + ], + [ + 0x8000000000000000_U512, + 0x8000000000000000_U512, + 1_U512, + 0_U512, + ], + [ + 0x8000000000000001_U512, + 0x8000000000000000_U512, + 1_U512, + 1_U512, + ], + [ + 0x80000000000000010000000000000000_U512, + 0x80000000000000000000000000000000_U512, + 1_U512, + 0x10000000000000000_U512, + ], + [ + 0x80000000000000000000000000000000_U512, + 0x80000000000000000000000000000001_U512, + 0_U512, + 0x80000000000000000000000000000000_U512, + ], + [ + 0x478392145435897052_U512, + 0x111_U512, + 0x430f89ebadad0baa_U512, + 8_U512, + ], + [ + 0x400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_U512, + 0x800000000000000000000000000000000000000000000000_U512, + 0x800000000000000000000000000000000000000000000000_U512, + 0_U512, + ], + [ + 0x80000000000000000000000000000000000000000000000000000000000000000000000000000000_U512, + 0x800000000000000000000000000000000000000000000000_U512, + 0x100000000000000000000000000000000_U512, + 0_U512, + ], + [ + 0x1e00000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000_U512, + 0xa_U512, + 0x30000000000000000000000e6666666666666666666666666666666666666666666666666666666666666666666666666666666674ccccccccccccccccc_U512, + 8_U512, + ], + [ + 0x767676767676767676000000767676767676_U512, + 0x2900760076761e00020076760000000076767676000000_U512, + 0_U512, + 0x767676767676767676000000767676767676_U512, + ], + [ + 0x12121212121212121212121212121212_U512, + 0x232323232323232323_U512, + 0x83a83a83a83a83_U512, + 0x171729292929292929_U512, + ], + [ + 0xabc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0abc0_U512, + 0x1c01c01c01c01c01c01c01c01c_U512, + 0x621ed21ed21ed21ed21ed21ed224f40bf40bf40bf40bf40bf40bf46e12de12de12de12de12de12de1900000000000000000_U512, + 0xabc0abc0abc0abc0_U512, + ], + [ + 0xfffff716b61616160b0b0b2b0b0b0becf4bef50a0df4f48b090b2b0bc60a0a00_U512, + 0xfffff716b61616160b0b0b2b0b230b000008010d0a2b00_U512, + 0xffffffffffffffffff_U512, + 0xfffff7169e17030ac1ff082ed51796090b330cd3143500_U512, + ], + [ + 0x50beb1c60141a0000dc2b0b0b0b0b0b410a0a0df4f40b090b2b0bc60a0a00_U512, + 0x2000110000000d0a300e750a000000090a0a_U512, + 0x285f437064cd09ff8bc5b7857d_U512, + 0x1fda1c384d86199e14bb4edfc6693042f11e_U512, + ], + [ + 0x4b00000b41000b0b0b2b0b0b0b0b0b410a0aeff4f40b090b2b0bc60a0a1000_U512, + 0x4b00000b41000b0b0b2b0b0b0b0b0b410a0aeff4f40b0a0a_U512, + 0xffffffffffffff_U512, + 0x4b00000b41000b0b0b2b0b0b0b0b0b400b35fbbafe151a0a_U512, + ], + [ + 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee_U512, + 7_U512, + 0x22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222_U512, + 0_U512, + ], + [ + 0xf6376770abd3a36b20394c5664afef1194c801c3f05e42566f085ed24d002bb0_U512, + 0xb368d219438b7f3f_U512, + 0x15f53bce87e9fb63c7c3ab03f6c0ba30d3ecf982fa97cdf0a_U512, + 0x4bfd94dbec31523a_U512, + ], + [ + 0x0_U512, + 0x10900000000000000000000000000000000000000000000000000_U512, + 0x0_U512, + 0x0_U512, + ], + [ + 0x77676767676760000000000000001002e000000000000040000000e000000000000007f0000000000000000000000000000000000000000f7000000000000_U512, + 0xfffc000000000000767676240000000000002b05760476000000000000000000767676767600000000000000000000000000000000_U512, + 0x7769450c7b994e65025_U512, + 0x241cb1aa4f67c22ae65c9920bf3bb7ad8280311a887aee8be4054a3e242a5ea9ab35d800f2000000000000000000f7000000000000_U512, + ], + [ + 0xdffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000001000000000000000000000000008100000000001001_U512, + 0xdffffffffffffffffffffffffffffffffffffffffffff3fffffffffffffffffffffffffffff_U512, + 0xffffffffffffffffffffffffffffffffffedb6db6db6e9_U512, + 0x200000000000000000000000000010000f2492492492ec000000000000080ffedb6db6dc6ea_U512, + ], + [ + 0xff000000000000000000000000000000000000000400000092767600000000000000000000000081000000000000000000000001020000000000eeffffffffff_U512, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000005000000000000000000ffffffffff100000000000000000_U512, + 0x0_U512, + 0xff000000000000000000000000000000000000000400000092767600000000000000000000000081000000000000000000000001020000000000eeffffffffff_U512, + ], + [ + 0xfffffffffffffffffffffffffffffffffffffffbffffff6d8989ffffffffffffffffffffffff7efffffffffffffffffffffffefdffffffffff110000000001_U512, + 0xfffffffffffffffffffffffaffffffffffffffffff0000000000f00000000000000000_U512, + 0x1000000000000000000000004fffffffffffffffc00ffff8689890fff_U512, + 0xffffffec09fffda0afa81efafc00ffff868d481fff71de0d8100efffff110000000001_U512, + ], + [ + 0x767676767676000000000076000000000000005600000000000000000000_U512, + 0x767676767676000000000076000000760000_U512, + 0xffffffffffffffffffffffff_U512, + 0x767676007676005600000076000000760000_U512, + ], + [ + 0x8200000000000000000000000000000000000000000000000000000000000000_U512, + 0x8200000000000000fe000004000000ffff000000fffff700_U512, + 0xfffffffffffffffe_U512, + 0x5fffffbffffff01fd00000700000afffe000001ffffee00_U512, + ], + [ + 0xdac7fff9ffd9e1322626262626262600_U512, + 0xd021262626262626_U512, + 0x10d1a094108c5da55_U512, + 0x6f386ccc73c11f62_U512, + ], + [ + 0x8000000000000001800000000000000080000000000000008000000000000000_U512, + 0x800000000000000080000000000000008000000000000000_U512, + 0x10000000000000001_U512, + 0x7fffffffffffffff80000000000000000000000000000000_U512, + ], + [ + 0x00e8e8e8e2000100000009ea02000000000000ff3ffffff800000010002200000000000000000000000000000000000000000000000000000000000000000000_U512, + 0x00e8e8e8e2000100000009ea02000000000000ff3ffffff800000010002280ff0000000000000000000000000000000000000000000000000000000000000000_U512, + 0_U512, + 0x00e8e8e8e2000100000009ea02000000000000ff3ffffff800000010002200000000000000000000000000000000000000000000000000000000000000000000_U512, + ], + [ + 0x000000c9700000000000000000023f00c00014ff0000000000000000223008050000000000000000000000000000000000000000000000000000000000000000_U512, + 0x00000000c9700000000000000000023f00c00014ff002c0000000000002231080000000000000000000000000000000000000000000000000000000000000000_U512, + 0xff_U512, + 0x00000000c9700000000000000000023f00c00014fed42c00000000000021310d0000000000000000000000000000000000000000000000000000000000000000_U512, + ], + [ + 0x40000000fd000000db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001_U512, + 0x40000000fd000000db0000000000000000000040000000fd000000db000001_U512, + 0xfffffffffffffffffffffffffffffffffffffeffffffffffffffff_U512, + 0x3ffffffffd000000db000040000000fd0000011b000001fd000000db000002_U512, + ], + [ + 0x40000000fd000000db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001_U512, + 0x40000000fd000000db0000000000000000000040000000fd000000db0000d3_U512, + 0xfffffffffffffffffffffffffffffffffffffeffffffffffffffff_U512, + 0x3fffff2dfd000000db000040000000fd0000011b0000d3fd000000db0000d4_U512, + ], + [ + 0x001f000000000000000000000000000000200000000100000000000000000000_U512, + 0x0000000000000000000100000000ffffffffffffffff0000000000002e000000_U512, + 0x1effffffe10000001f_U512, + 0xfffa6e20000591fffffa6e000000_U512, + ], + [ + 0x7effffff80000000000000000000000000020000440000000000000000000001_U512, + 0x7effffff800000007effffff800000008000ff0000010000_U512, + 0xfffffffffffffffe_U512, + 0x7effffff800000007e0100ff43ff00010001fe0000020001_U512, + ], + [ + 0x5fd8fffffffffffffffffffffffffffffc090000ce700004d0c9ffffff000001_U512, + 0x2ffffffffffffffffffffffffffffffffff000000030000_U512, + 0x1ff2ffffffffffffff_U512, + 0x2fffffffffffffffc28f300ce102704d0c8ffffff030001_U512, + ], + [ + 0x62d8fffffffffffffffffffffffffffffc18000000000000000000ca00000001_U512, + 0x2ffffffffffffffffffffffffffffffffff200000000000_U512, + 0x20f2ffffffffffffff_U512, + 0x2fffffffffffffffc34d49fffffffffffff20ca00000001_U512, + ], + [ + 0x7effffff8000000000000000000000000000000000000000d900000000000001_U512, + 0x7effffff8000000000000000000000000000000000008001_U512, + 0xffffffffffffffff_U512, + 0x7effffff7fffffffffffffffffff7fffd900000000008002_U512, + ], + [ + 0x0000000000000006400aff20ff00200004e7fd1eff08ffca0afd1eff08ffca0a_U512, + 0x00000000000000210000000000000022_U512, + 0x307c7456554d945ce57749fd52bfdb7f_U512, + 0x1491254b5a0b84a32c_U512, + ], + [ + 0x7effffff8000000000000000000000000000000000150000d900000000000001_U512, + 0x7effffff8000000000000000000000000000000000f9e101_U512, + 0xffffffffffffffff_U512, + 0x7effffff7fffffffffffffffff1b1effd900000000f9e102_U512, + ], + [ + 0xffffffff0100000000000000000000000000ffff0000ffffffff0100000000_U512, + 0xffffffff010000000000000000000000ffff0000ffffff_U512, + 0xffffffffffffffff_U512, + 0xffffffff00ffffff0001fffe00010100fffe0100ffffff_U512, + ], + [ + 0xabfffff0000ffffffffff36363636363636363636d00500000000ffffffffffffe90000ff00000000000000000000ffff0000000000_U512, + 0xabfffff0000ffffffffff36363636363636363636d00500000000ffffffffffffe9ff001f_U512, + 0xffffffffffffffffffffffffffffffffff_U512, + 0xabfffff0000ffffffffff36363636363537371636d00500000001000000fffeffe9ff001f_U512, + ], + [ + 0xff00ffffffffffffffcaffffffff0100_U512, + 0x0100000000000000ff800000000000ff_U512, + 0xff_U512, + 0xffffffffff017f4afffffffe02ff_U512, + ], + [ + 0x9000ffffffffffffffcaffffffff0100_U512, + 0x800000000000007fc000000000007f80_U512, + 1_U512, + 0x1000ffffffffff803fcafffffffe8180_U512, + ], + [ + // Very special case for reciprocal_3by2(). + 170141183460488574554024512018559533058_U512, + 170141183460488574554024512018559533057_U512, + 1_U512, + 1_U512, + ], + [ + 0x6e2d23924d38f0ab643864e9b2a328a54914f48533114fae3475168bfd74a61ae91e676b4a4f33a5b3b6cc189536ccb4afc46d02b061d6daaf0298c993376ab4_U512, + 170141183460488574554024512018559533057_U512, + 0xdc5a47249a56560d078334729ffb61da211f5d2ec622c22f88bc3b4ebae1abdac6b03621554ef71070bc1e0dc5c301bc_U512, + 0x6dc100ea02272bdcf68a4a5b95f468f8_U512, + ] + ]); + + macro_rules! test_cases { + ($n:ty, $d:ty) => { + for [numerator, divisor, quotient, remainder] in INTX_TESTS { + if numerator.bit_len() > <$n>::BITS || divisor.bit_len() > <$d>::BITS { + continue; + } + let mut numerator = <$n>::from(numerator).into_limbs(); + let mut divisor = <$d>::from(divisor).into_limbs(); + let quotient = <$n>::from(quotient).into_limbs(); + let remainder = <$d>::from(remainder).into_limbs(); + div(&mut numerator, &mut divisor); + assert_eq!(numerator, quotient); + assert_eq!(divisor, remainder); + } + }; + } + + #[test] + fn test_div_8x8() { + use crate::aliases::U512; + test_cases!(U512, U512); + } + + #[test] + fn test_div_6x6() { + use crate::aliases::U384; + test_cases!(U384, U384); + } + + #[test] + fn test_div_4x4() { + use crate::aliases::U256; + test_cases!(U256, U256); + } + + #[test] + fn test_div_5x4() { + use crate::aliases::{U256, U320}; + test_cases!(U320, U256); + } + + #[test] + fn test_div_8x4() { + use crate::aliases::{U256, U512}; + test_cases!(U512, U256); + } + + #[test] + #[allow(clippy::unreadable_literal)] + fn test_div_8by4_one() { + let mut numerator = [ + 0x9c2bcebfa9cca2c6_u64, + 0x274e154bb5e24f7a_u64, + 0xe1442d5d3842be2b_u64, + 0xf18f5adfd420853f_u64, + 0x04ed6127eba3b594_u64, + 0xc5c179973cdb1663_u64, + 0x7d7f67780bb268ff_u64, + 0x0000000000000003_u64, + 0x0000000000000000_u64, + ]; + let mut divisor = [ + 0x0181880b078ab6a1_u64, + 0x62d67f6b7b0bda6b_u64, + 0x92b1840f9c792ded_u64, + 0x0000000000000019_u64, + ]; + let expected_quotient = [ + 0x9128464e61d6b5b3_u64, + 0xd9eea4fc30c5ac6c_u64, + 0x944a2d832d5a6a08_u64, + 0x22f06722e8d883b1_u64, + 0x0000000000000000_u64, + ]; + let expected_remainder = [ + 0x1dfa5a7ea5191b33_u64, + 0xb5aeb3f9ad5e294e_u64, + 0xfc710038c13e4eed_u64, + 0x000000000000000b_u64, + ]; + div(&mut numerator, &mut divisor); + assert_eq!(numerator[..5], expected_quotient); + assert_eq!(divisor, expected_remainder); + } +} diff --git a/guest-libs/ruint/src/algorithms/div/reciprocal.rs b/guest-libs/ruint/src/algorithms/div/reciprocal.rs new file mode 100644 index 0000000000..8dca569ac0 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/div/reciprocal.rs @@ -0,0 +1,190 @@ +//! Reciprocals and division using reciprocals +//! See [MG10]. +//! +//! [MG10]: https://gmplib.org/~tege/division-paper.pdf +//! [GM94]: https://gmplib.org/~tege/divcnst-pldi94.pdf +//! [new]: https://gmplib.org/list-archives/gmp-devel/2019-October/005590.html +#![allow(dead_code, clippy::cast_possible_truncation, clippy::cast_lossless)] + +use core::num::Wrapping; + +pub use self::{reciprocal_2_mg10 as reciprocal_2, reciprocal_mg10 as reciprocal}; + +/// ⚠️ Computes $\floor{\frac{2^{128} - 1}{\mathtt{d}}} - 2^{64}$. +/// +/// Requires $\mathtt{d} ≥ 2^{127}$, i.e. the highest bit of $\mathtt{d}$ must +/// be set. +#[inline(always)] +#[must_use] +pub fn reciprocal_ref(d: u64) -> u64 { + debug_assert!(d >= (1 << 63)); + let r = u128::MAX / u128::from(d); + debug_assert!(r >= (1 << 64)); + debug_assert!(r < (1 << 65)); + r as u64 +} + +/// ⚠️ Computes $\floor{\frac{2^{128} - 1}{\mathsf{d}}} - 2^{64}$. +/// +/// Requires $\mathsf{d} ∈ [2^{63}, 2^{64})$, i.e. the highest bit of +/// $\mathsf{d}$ must be set. +/// +/// Using [MG10] algorithm 3. See also the [intx] implementation. Here is a +/// direct translation of the algorithm to Python for reference: +/// +/// ```python +/// d0 = d % 2 +/// d9 = d // 2**55 +/// d40 = d // 2**24 + 1 +/// d63 = (d + 1) // 2 +/// v0 = (2**19 - 3 * 2**8) // d9 +/// v1 = 2**11 * v0 - v0**2 * d40 // 2**40 - 1 +/// v2 = 2**13 * v1 + v1 * (2**60 - v1 * d40) // 2**47 +/// e = 2**96 - v2 * d63 + (v2 // 2) * d0 +/// v3 = (2**31 * v2 +v2 * e // 2**65) % 2**64 +/// v4 = (v3 - (v3 + 2**64 + 1) * d // 2**64) % 2**64 +/// ``` +/// +/// [MG10]: https://gmplib.org/~tege/division-paper.pdf +/// [intx]: https://github.com/chfast/intx/blob/8b5f4748a7386a9530769893dae26b3273e0ffe2/include/intx/intx.hpp#L683 +#[inline] +#[must_use] +pub fn reciprocal_mg10(d: u64) -> u64 { + const ZERO: Wrapping = Wrapping(0); + const ONE: Wrapping = Wrapping(1); + + // Lookup table for $\floor{\frac{2^{19} -3 ⋅ 2^8}{d_9 - 256}}$ + static TABLE: [u16; 256] = [ + 2045, 2037, 2029, 2021, 2013, 2005, 1998, 1990, 1983, 1975, 1968, 1960, 1953, 1946, 1938, + 1931, 1924, 1917, 1910, 1903, 1896, 1889, 1883, 1876, 1869, 1863, 1856, 1849, 1843, 1836, + 1830, 1824, 1817, 1811, 1805, 1799, 1792, 1786, 1780, 1774, 1768, 1762, 1756, 1750, 1745, + 1739, 1733, 1727, 1722, 1716, 1710, 1705, 1699, 1694, 1688, 1683, 1677, 1672, 1667, 1661, + 1656, 1651, 1646, 1641, 1636, 1630, 1625, 1620, 1615, 1610, 1605, 1600, 1596, 1591, 1586, + 1581, 1576, 1572, 1567, 1562, 1558, 1553, 1548, 1544, 1539, 1535, 1530, 1526, 1521, 1517, + 1513, 1508, 1504, 1500, 1495, 1491, 1487, 1483, 1478, 1474, 1470, 1466, 1462, 1458, 1454, + 1450, 1446, 1442, 1438, 1434, 1430, 1426, 1422, 1418, 1414, 1411, 1407, 1403, 1399, 1396, + 1392, 1388, 1384, 1381, 1377, 1374, 1370, 1366, 1363, 1359, 1356, 1352, 1349, 1345, 1342, + 1338, 1335, 1332, 1328, 1325, 1322, 1318, 1315, 1312, 1308, 1305, 1302, 1299, 1295, 1292, + 1289, 1286, 1283, 1280, 1276, 1273, 1270, 1267, 1264, 1261, 1258, 1255, 1252, 1249, 1246, + 1243, 1240, 1237, 1234, 1231, 1228, 1226, 1223, 1220, 1217, 1214, 1211, 1209, 1206, 1203, + 1200, 1197, 1195, 1192, 1189, 1187, 1184, 1181, 1179, 1176, 1173, 1171, 1168, 1165, 1163, + 1160, 1158, 1155, 1153, 1150, 1148, 1145, 1143, 1140, 1138, 1135, 1133, 1130, 1128, 1125, + 1123, 1121, 1118, 1116, 1113, 1111, 1109, 1106, 1104, 1102, 1099, 1097, 1095, 1092, 1090, + 1088, 1086, 1083, 1081, 1079, 1077, 1074, 1072, 1070, 1068, 1066, 1064, 1061, 1059, 1057, + 1055, 1053, 1051, 1049, 1047, 1044, 1042, 1040, 1038, 1036, 1034, 1032, 1030, 1028, 1026, + 1024, + ]; + + debug_assert!(d >= (1 << 63)); + let d = Wrapping(d); + + let d0 = d & ONE; + let d9 = d >> 55; + let d40 = ONE + (d >> 24); + let d63 = (d + ONE) >> 1; + // let v0 = Wrapping(TABLE[(d9.0 - 256) as usize] as u64); + let v0 = Wrapping(*unsafe { TABLE.get_unchecked((d9.0 - 256) as usize) } as u64); + let v1 = (v0 << 11) - ((v0 * v0 * d40) >> 40) - ONE; + let v2 = (v1 << 13) + ((v1 * ((ONE << 60) - v1 * d40)) >> 47); + let e = ((v2 >> 1) & (ZERO - d0)) - v2 * d63; + let v3 = (mul_hi(v2, e) >> 1) + (v2 << 31); + let v4 = v3 - muladd_hi(v3, d, d) - d; + + v4.0 +} + +/// ⚠️ Computes $\floor{\frac{2^{192} - 1}{\mathsf{d}}} - 2^{64}$. +/// +/// Requires $\mathsf{d} ∈ [2^{127}, 2^{128})$, i.e. the most significant bit +/// of $\mathsf{d}$ must be set. +/// +/// Implements [MG10] algorithm 6. +/// +/// [MG10]: https://gmplib.org/~tege/division-paper.pdf +#[inline] +#[must_use] +pub fn reciprocal_2_mg10(d: u128) -> u64 { + debug_assert!(d >= (1 << 127)); + let d1 = (d >> 64) as u64; + let d0 = d as u64; + + let mut v = reciprocal(d1); + let mut p = d1.wrapping_mul(v).wrapping_add(d0); + // OPT: This is checking the carry flag + if p < d0 { + v = v.wrapping_sub(1); + if p >= d1 { + v = v.wrapping_sub(1); + p = p.wrapping_sub(d1); + } + p = p.wrapping_sub(d1); + } + let t = u128::from(v) * u128::from(d0); + let t1 = (t >> 64) as u64; + let t0 = t as u64; + + let p = p.wrapping_add(t1); + // OPT: This is checking the carry flag + if p < t1 { + v = v.wrapping_sub(1); + if (u128::from(p) << 64) | u128::from(t0) >= d { + v = v.wrapping_sub(1); + } + } + v +} + +#[allow(clippy::missing_const_for_fn)] // False positive +#[inline] +#[must_use] +fn mul_hi(a: Wrapping, b: Wrapping) -> Wrapping { + let a = u128::from(a.0); + let b = u128::from(b.0); + let r = a * b; + Wrapping((r >> 64) as u64) +} + +#[allow(clippy::missing_const_for_fn)] // False positive +#[inline] +#[must_use] +fn muladd_hi(a: Wrapping, b: Wrapping, c: Wrapping) -> Wrapping { + let a = u128::from(a.0); + let b = u128::from(b.0); + let c = u128::from(c.0); + let r = a * b + c; + Wrapping((r >> 64) as u64) +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::proptest; + + #[test] + fn test_reciprocal() { + proptest!(|(n: u64)| { + let n = n | (1 << 63); + let expected = reciprocal_ref(n); + let actual = reciprocal_mg10(n); + assert_eq!(expected, actual); + }); + } + + #[test] + fn test_reciprocal_2() { + assert_eq!(reciprocal_2_mg10(1 << 127), u64::MAX); + assert_eq!(reciprocal_2_mg10(u128::MAX), 0); + assert_eq!( + reciprocal_2_mg10(0xd555_5555_5555_5555_5555_5555_5555_5555), + 0x3333_3333_3333_3333 + ); + assert_eq!( + reciprocal_2_mg10(0xd0e7_57b0_2171_5fbe_cba4_ad0e_825a_e500), + 0x39b6_c5af_970f_86b3 + ); + assert_eq!( + reciprocal_2_mg10(0xae5d_6551_8a51_3208_a850_5491_9637_eb17), + 0x77db_09d1_5c3b_970b + ); + } +} diff --git a/guest-libs/ruint/src/algorithms/div/small.rs b/guest-libs/ruint/src/algorithms/div/small.rs new file mode 100644 index 0000000000..8f52c16bd1 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/div/small.rs @@ -0,0 +1,424 @@ +//! Small division using reciprocals from [MG10]. +//! +//! [MG10]: https://gmplib.org/~tege/division-paper.pdf + +// Following naming from paper. +#![allow(clippy::many_single_char_names, clippy::similar_names)] +// Truncation is intentional +#![allow(clippy::cast_possible_truncation)] + +use super::reciprocal::{reciprocal, reciprocal_2}; +use crate::{algorithms::DoubleWord, utils::unlikely}; + +// The running time is 2.7 ns for [`div_2x1_mg10`] versus 18 ns for +// [`div_2x1_ref`]. +pub use self::{div_2x1_mg10 as div_2x1, div_3x2_mg10 as div_3x2}; + +/// ⚠️ Compute single limb normalized division. +/// +/// The divisor must be normalized. See algorithm 7 from [MG10]. +/// +/// [MG10]: https://gmplib.org/~tege/division-paper.pdf +#[inline] +pub fn div_nx1_normalized(u: &mut [u64], d: u64) -> u64 { + // OPT: Version with in-place shifting of `u` + debug_assert!(d >= (1 << 63)); + + let v = reciprocal(d); + let mut r: u64 = 0; + for u in u.iter_mut().rev() { + let n = u128::join(r, *u); + let (q, r0) = div_2x1(n, d, v); + *u = q; + r = r0; + } + r +} + +/// ⚠️ Compute single limb division. +/// +/// The highest limb of `numerator` and `divisor` must be nonzero. +/// The divisor does not need normalization. +/// See algorithm 7 from [MG10]. +/// +/// [MG10]: https://gmplib.org/~tege/division-paper.pdf +/// +/// # Panics +/// +/// May panics if the above requirements are not met. +// TODO: Rewrite in a way that avoids bounds-checks without unsafe. +#[inline] +pub fn div_nx1(limbs: &mut [u64], divisor: u64) -> u64 { + debug_assert!(divisor != 0); + debug_assert!(!limbs.is_empty()); + debug_assert!(*limbs.last().unwrap() != 0); + + // Normalize and compute reciprocal + let shift = divisor.leading_zeros(); + if shift == 0 { + return div_nx1_normalized(limbs, divisor); + } + let divisor = divisor << shift; + let reciprocal = reciprocal(divisor); + + let last = unsafe { limbs.get_unchecked(limbs.len() - 1) }; + let mut remainder = last >> (64 - shift); + for i in (1..limbs.len()).rev() { + // Shift limbs + let upper = unsafe { limbs.get_unchecked(i) }; + let lower = unsafe { limbs.get_unchecked(i - 1) }; + let u = (upper << shift) | (lower >> (64 - shift)); + + // Compute quotient + let n = u128::join(remainder, u); + let (q, r) = div_2x1(n, divisor, reciprocal); + + // Store quotient + *unsafe { limbs.get_unchecked_mut(i) } = q; + remainder = r; + } + // Compute last quotient + let first = unsafe { limbs.get_unchecked_mut(0) }; + let n = u128::join(remainder, *first << shift); + let (q, remainder) = div_2x1(n, divisor, reciprocal); + *first = q; + + // Un-normalize remainder + remainder >> shift +} + +/// ⚠️ Compute double limb normalized division. +/// +/// Requires `divisor` to be in the range $[2^{127}, 2^{128})$ (i.e. +/// normalized). Same as [`div_nx1`] but using [`div_3x2`] internally. +#[inline] +pub fn div_nx2_normalized(u: &mut [u64], d: u128) -> u128 { + // OPT: Version with in-place shifting of `u` + debug_assert!(d >= (1 << 127)); + + let v = reciprocal_2(d); + let mut remainder: u128 = 0; + for u in u.iter_mut().rev() { + let (q, r) = div_3x2(remainder, *u, d, v); + *u = q; + remainder = r; + } + remainder +} + +/// ⚠️ Compute double limb division. +/// +/// Requires `divisor` to be in the range $[2^{64}, 2^{128})$. Same as +/// [`div_nx2_normalized`] but does the shifting of the numerator inline. +/// +/// # Panics +/// +/// May panics if the above requirements are not met. +// TODO: Rewrite in a way that avoids bounds-checks without unsafe. +#[inline] +pub fn div_nx2(limbs: &mut [u64], divisor: u128) -> u128 { + debug_assert!(divisor >= 1 << 64); + debug_assert!(!limbs.is_empty()); + debug_assert!(*limbs.last().unwrap() != 0); + + // Normalize and compute reciprocal + let shift = divisor.high().leading_zeros(); + if shift == 0 { + return div_nx2_normalized(limbs, divisor); + } + let divisor = divisor << shift; + let reciprocal = reciprocal_2(divisor); + + let last = unsafe { limbs.get_unchecked(limbs.len() - 1) }; + let mut remainder: u128 = u128::from(last >> (64 - shift)); + for i in (1..limbs.len()).rev() { + // Shift limbs + let upper = unsafe { limbs.get_unchecked(i) }; + let lower = unsafe { limbs.get_unchecked(i - 1) }; + let u = (upper << shift) | (lower >> (64 - shift)); + + // Compute quotient + let (q, r) = div_3x2(remainder, u, divisor, reciprocal); + + // Store quotient + *unsafe { limbs.get_unchecked_mut(i) } = q; + remainder = r; + } + // Compute last quotient + let first = unsafe { limbs.get_unchecked_mut(0) }; + let (q, remainder) = div_3x2(remainder, *first << shift, divisor, reciprocal); + *first = q; + + // Un-normalize remainder + remainder >> shift +} + +#[inline] +#[must_use] +pub fn div_2x1_ref(u: u128, d: u64) -> (u64, u64) { + debug_assert!(d >= (1 << 63)); + debug_assert!((u >> 64) < u128::from(d)); + let d = u128::from(d); + let q = (u / d) as u64; + let r = (u % d) as u64; + (q, r) +} + +/// ⚠️ Computes the quotient and remainder of a `u128` divided by a `u64`. +/// +/// Requires +/// * `u < d * 2**64`, +/// * `d >= 2**63`, and +/// * `v = reciprocal(d)`. +/// +/// Implements algorithm 4 from [MG10]. +/// +/// [MG10]: https://gmplib.org/~tege/division-paper.pdf +#[inline] +#[must_use] +pub fn div_2x1_mg10(u: u128, d: u64, v: u64) -> (u64, u64) { + debug_assert!(d >= (1 << 63)); + debug_assert!((u >> 64) < u128::from(d)); + debug_assert_eq!(v, reciprocal(d)); + + let q = u + (u >> 64) * u128::from(v); + let q0 = q as u64; + let q1 = ((q >> 64) as u64).wrapping_add(1); + let r = (u as u64).wrapping_sub(q1.wrapping_mul(d)); + let (q1, r) = if r > q0 { + (q1.wrapping_sub(1), r.wrapping_add(d)) + } else { + (q1, r) + }; + let (q1, r) = if unlikely(r >= d) { + (q1.wrapping_add(1), r.wrapping_sub(d)) + } else { + (q1, r) + }; + (q1, r) +} + +/// TODO: This implementation is off by one. +#[inline] +#[must_use] +pub fn div_3x2_ref(n21: u128, n0: u64, d: u128) -> u64 { + debug_assert!(d >= (1 << 127)); + debug_assert!(n21 < d); + + let n2 = (n21 >> 64) as u64; + let n1 = n21 as u64; + let d1 = (d >> 64) as u64; + let d0 = d as u64; + + if unlikely(n2 == d1) { + // From [n2 n1] < [d1 d0] and n2 = d1 it follows that n[1] < d[0]. + debug_assert!(n1 < d0); + // We start by subtracting 2^64 times the divisor, resulting in a + // negative remainder. Depending on the result, we need to add back + // in one or two times the divisor to make the remainder positive. + // (It can not be more since the divisor is > 2^127 and the negated + // remainder is < 2^128.) + let neg_remainder = u128::from(d0).wrapping_sub((u128::from(n1) << 64) | u128::from(n0)); + if neg_remainder > d { + 0xffff_ffff_ffff_fffe_u64 + } else { + 0xffff_ffff_ffff_ffff_u64 + } + } else { + // Compute quotient and remainder + let (mut q, mut r) = div_2x1_ref(n21, d1); + + let t1 = u128::from(q) * u128::from(d0); + let t2 = (u128::from(n0) << 64) | u128::from(r); + if t1 > t2 { + q -= 1; + r = r.wrapping_add(d1); + let overflow = r < d1; + if !overflow { + let t1 = u128::from(q) * u128::from(d0); + let t2 = (u128::from(n0) << 64) | u128::from(r); + if t1 > t2 { + q -= 1; + // UNUSED: r += d[1]; + } + } + } + q + } +} + +/// ⚠️ Computes the quotient of a 192 bits divided by a normalized u128. +/// +/// Implements [MG10] algorithm 5. +/// +/// [MG10]: https://gmplib.org/~tege/division-paper.pdf +#[inline] +#[must_use] +pub fn div_3x2_mg10(u21: u128, u0: u64, d: u128, v: u64) -> (u64, u128) { + debug_assert!(d >= (1 << 127)); + debug_assert!(u21 < d); + debug_assert_eq!(v, reciprocal_2(d)); + + let q = u128::mul(u21.high(), v) + u21; + let r1 = u21.low().wrapping_sub(q.high().wrapping_mul(d.high())); + let t = u128::mul(d.low(), q.high()); + let mut r = u128::join(r1, u0).wrapping_sub(t).wrapping_sub(d); + let mut q1 = q.high().wrapping_add(1); + if r.high() >= q.low() { + q1 = q1.wrapping_sub(1); + r = r.wrapping_add(d); + } + if unlikely(r >= d) { + q1 = q1.wrapping_add(1); + r = r.wrapping_sub(d); + } + (q1, r) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::algorithms::addmul; + use proptest::{ + collection, + num::{u128, u64}, + prop_assume, proptest, + strategy::Strategy, + }; + + #[test] + fn test_div_2x1_mg10() { + proptest!(|(q: u64, r: u64, mut d: u64)| { + let d = d | (1 << 63); + let r = r % d; + let n = u128::from(q) * u128::from(d) + u128::from(r); + let v = reciprocal(d); + assert_eq!(div_2x1_mg10(n, d, v), (q,r)); + }); + } + + #[ignore] // TODO + #[test] + fn test_div_3x2_ref() { + proptest!(|(q: u64, r: u128, mut d: u128)| { + let d = d | (1 << 127); + let r = r % d; + let (n21, n0) = { + let d1 = (d >> 64) as u64; + let d0 = d as u64; + let r1 = (r >> 64) as u64; + let r0 = r as u64; + // n = q * d + r + let n10 = u128::from(q) * u128::from(d0) + u128::from(r0); + let n0 = n10 as u64; + let n21 = (n10 >> 64) + u128::from(q) * u128::from(d1) + u128::from(r1); + (n21, n0) + }; + assert_eq!(div_3x2_ref(n21, n0, d), q); + }); + } + + #[test] + fn test_div_3x2_mg10() { + proptest!(|(q: u64, r: u128, mut d: u128)| { + let d = d | (1 << 127); + let r = r % d; + let (n21, n0) = { + let d1 = (d >> 64) as u64; + let d0 = d as u64; + let r1 = (r >> 64) as u64; + let r0 = r as u64; + // n = q * d + r + let n10 = u128::from(q) * u128::from(d0) + u128::from(r0); + let n0 = n10 as u64; + let n21 = (n10 >> 64) + u128::from(q) * u128::from(d1) + u128::from(r1); + (n21, n0) + }; + let v = reciprocal_2(d); + assert_eq!(div_3x2_mg10(n21, n0, d, v), (q, r)); + }); + } + + #[test] + fn test_div_nx1_normalized() { + let any_vec = collection::vec(u64::ANY, ..10); + proptest!(|(quotient in any_vec, mut divisor: u64, mut remainder: u64)| { + // Construct problem + divisor |= 1 << 63; + remainder %= divisor; + let mut numerator = vec![0; quotient.len() + 1]; + numerator[0] = remainder; + addmul(&mut numerator, "ient, &[divisor]); + + // Test + let r = div_nx1_normalized(&mut numerator, divisor); + assert_eq!(&numerator[..quotient.len()], "ient); + assert_eq!(r, remainder); + }); + } + + #[test] + fn test_div_nx1() { + let any_vec = collection::vec(u64::ANY, 1..10); + let divrem = (1..u64::MAX, u64::ANY).prop_map(|(d, r)| (d, r % d)); + proptest!(|(quotient in any_vec,(divisor, remainder) in divrem)| { + // Construct problem + let mut numerator = vec![0; quotient.len() + 1]; + numerator[0] = remainder; + addmul( &mut numerator, "ient, &[divisor]); + + // Trim numerator + while numerator.last() == Some(&0) { + numerator.pop(); + } + prop_assume!(!numerator.is_empty()); + + // Test + let r = div_nx1(&mut numerator, divisor); + assert_eq!(&numerator[..quotient.len()], "ient); + assert_eq!(r, remainder); + }); + } + + #[test] + fn test_div_nx2_normalized() { + let any_vec = collection::vec(u64::ANY, ..10); + let divrem = (1_u128 << 127.., u128::ANY).prop_map(|(d, r)| (d, r % d)); + proptest!(|(quotient in any_vec, (divisor, remainder) in divrem)| { + // Construct problem + let mut numerator = vec![0; quotient.len() + 2]; + numerator[0] = remainder.low(); + numerator[1] = remainder.high(); + addmul(&mut numerator, "ient, &[divisor.low(), divisor.high()]); + + // Test + let r = div_nx2_normalized(&mut numerator, divisor); + assert_eq!(&numerator[..quotient.len()], "ient); + assert_eq!(r, remainder); + }); + } + + #[test] + fn test_div_nx2() { + let any_vec = collection::vec(u64::ANY, 2..10); + let divrem = (1..u128::MAX, u128::ANY).prop_map(|(d, r)| (d, r % d)); + proptest!(|(quotient in any_vec,(divisor, remainder) in divrem)| { + // Construct problem + let mut numerator = vec![0; quotient.len() + 2]; + numerator[0] = remainder.low(); + numerator[1] = remainder.high(); + addmul(&mut numerator, "ient, &[divisor.low(), divisor.high()]); + + // Trim numerator + while numerator.last() == Some(&0) { + numerator.pop(); + } + prop_assume!(!numerator.is_empty()); + + // Test + let r = div_nx2(&mut numerator, divisor); + assert_eq!(&numerator[..quotient.len()], "ient); + assert_eq!(r, remainder); + }); + } +} diff --git a/guest-libs/ruint/src/algorithms/gcd/gcd_old.rs b/guest-libs/ruint/src/algorithms/gcd/gcd_old.rs new file mode 100644 index 0000000000..634bae6ffc --- /dev/null +++ b/guest-libs/ruint/src/algorithms/gcd/gcd_old.rs @@ -0,0 +1,734 @@ +/// Lehmer update matrix +/// +/// Signs are implicit, the boolean `.4` encodes which of two sign +/// patterns applies. The signs and layout of the matrix are: +/// +/// ```text +/// true false +/// [ .0 -.1] [-.0 .1] +/// [-.2 .3] [ .2 -.3] +/// ``` +#[derive(PartialEq, Eq, Clone, Debug)] +struct Matrix(u64, u64, u64, u64, bool); + +impl Matrix { + const IDENTITY: Self = Self(1, 0, 0, 1, true); +} + +/// Computes a double linear combination efficiently in place. +/// +/// Simultaneously computes +/// +/// ```text +/// a' = q00 a - q01 b +/// b' = q11 b - q10 a +/// ``` +// We want to keep spaces to align arguments +#[rustfmt::skip] +// We shadow variables for readability. +#[allow(clippy::shadow_unrelated)] +fn mat_mul(a: &mut U256, b: &mut U256, (q00, q01, q10, q11): (u64, u64, u64, u64)) { + use crate::algorithms::limb_operations::{mac, msb}; + let (ai, ac) = mac( 0, q00, a.limb(0), 0); + let (ai, ab) = msb(ai, q01, b.limb(0), 0); + let (bi, bc) = mac( 0, q11, b.limb(0), 0); + let (bi, bb) = msb(bi, q10, a.limb(0), 0); + a.set_limb(0, ai); + b.set_limb(0, bi); + let (ai, ac) = mac( 0, q00, a.limb(1), ac); + let (ai, ab) = msb(ai, q01, b.limb(1), ab); + let (bi, bc) = mac( 0, q11, b.limb(1), bc); + let (bi, bb) = msb(bi, q10, a.limb(1), bb); + a.set_limb(1, ai); + b.set_limb(1, bi); + let (ai, ac) = mac( 0, q00, a.limb(2), ac); + let (ai, ab) = msb(ai, q01, b.limb(2), ab); + let (bi, bc) = mac( 0, q11, b.limb(2), bc); + let (bi, bb) = msb(bi, q10, a.limb(2), bb); + a.set_limb(2, ai); + b.set_limb(2, bi); + let (ai, _) = mac( 0, q00, a.limb(3), ac); + let (ai, _) = msb(ai, q01, b.limb(3), ab); + let (bi, _) = mac( 0, q11, b.limb(3), bc); + let (bi, _) = msb(bi, q10, a.limb(3), bb); + a.set_limb(3, ai); + b.set_limb(3, bi); +} + +/// Applies the Lehmer update matrix to the variable pair in place. +fn lehmer_update(a0: &mut U256, a1: &mut U256, Matrix(q00, q01, q10, q11, even): &Matrix) { + if *even { + mat_mul(a0, a1, (*q00, *q01, *q10, *q11)); + } else { + mat_mul(a0, a1, (*q10, *q11, *q00, *q01)); + core::mem::swap(a0, a1); + } +} + +/// Division optimized for small values +/// +/// Requires a >= b > 0. +/// Returns a / b. +/// +/// See also `div1` in GMPs Lehmer implementation. +/// +#[allow(clippy::cognitive_complexity)] +fn div1(mut a: u64, b: u64) -> u64 { + debug_assert!(a >= b); + debug_assert!(b > 0); + unroll! { + for i in 1..20 { + a -= b; + if a < b { + return i as u64 + } + } + } + 19 + a / b +} + +/// Single step of the extended Euclid's algorithm for u64. +/// +/// Equivalent to the following, but faster for small `q`: +/// +/// ```text +/// let q = *a3 / a2; +/// *a3 -= q * a2; +/// *k3 += q * k2; +/// ``` +/// +/// NOTE: This routine is critical for the performance of +/// Lehmer GCD computations. +// Performance is 40% better with forced inlining. +#[inline(always)] +// Clippy operates on the unrolled code, giving a false positive. +#[allow(clippy::cognitive_complexity)] +fn lehmer_unroll(a2: u64, a3: &mut u64, k2: u64, k3: &mut u64) { + debug_assert!(a2 < *a3); + debug_assert!(a2 > 0); + unroll! { + for i in 1..17 { + *a3 -= a2; + *k3 += k2; + if *a3 < a2 { + return; + } + } + } + let q = *a3 / a2; + *a3 -= q * a2; + *k3 += q * k2; +} + +/// Compute the Lehmer update matrix for small values. +/// +/// This is essentially Euclids extended GCD algorithm for 64 bits. +// OPT: Would this be faster using extended binary gcd? +// We shadow q for readability. +#[allow(clippy::shadow_unrelated)] +fn lehmer_small(mut r0: u64, mut r1: u64) -> Matrix { + debug_assert!(r0 >= r1); + if r1 == 0_u64 { + return Matrix::IDENTITY; + } + let mut q00 = 1_u64; + let mut q01 = 0_u64; + let mut q10 = 0_u64; + let mut q11 = 1_u64; + loop { + // Loop is unrolled once to avoid swapping variables and tracking parity. + let q = div1(r0, r1); + r0 -= q * r1; + q00 += q * q10; + q01 += q * q11; + if r0 == 0_u64 { + return Matrix(q10, q11, q00, q01, false); + } + let q = div1(r1, r0); + r1 -= q * r0; + q10 += q * q00; + q11 += q * q01; + if r1 == 0_u64 { + return Matrix(q00, q01, q10, q11, true); + } + } +} + +/// Compute the largest valid Lehmer update matrix for a prefix. +/// +/// Compute the Lehmer update matrix for a0 and a1 such that the matrix is valid +/// for any two large integers starting with the bits of a0 and a1. +/// +/// See also `mpn_hgcd2` in GMP, but ours handles the double precision bit +/// separately in `lehmer_double`. +/// +// We shadow q for readability. +#[allow(clippy::shadow_unrelated)] +fn lehmer_loop(a0: u64, mut a1: u64) -> Matrix { + const LIMIT: u64 = 1_u64 << 32; + debug_assert!(a0 >= 1_u64 << 63); + debug_assert!(a0 >= a1); + + // Here we do something original: The cofactors undergo identical + // operations which makes them a candidate for SIMD instructions. + // They are also never exceed 32 bit, so we can SWAR them in a single u64. + let mut k0 = 1_u64 << 32; // u0 = 1, v0 = 0 + let mut k1 = 1_u64; // u1 = 0, v1 = 1 + let mut even = true; + if a1 < LIMIT { + return Matrix::IDENTITY; + } + + // Compute a2 + let q = div1(a0, a1); + let mut a2 = a0 - q * a1; + let mut k2 = k0 + q * k1; + if a2 < LIMIT { + let u2 = k2 >> 32; + let v2 = k2 % LIMIT; + + // Test i + 1 (odd) + if a2 >= v2 && a1 - a2 >= u2 { + return Matrix(0, 1, u2, v2, false); + } else { + return Matrix::IDENTITY; + } + } + + // Compute a3 + let q = div1(a1, a2); + let mut a3 = a1 - q * a2; + let mut k3 = k1 + q * k2; + + // Loop until a3 < LIMIT, maintaining the last three values + // of a and the last four values of k. + while a3 >= LIMIT { + a1 = a2; + a2 = a3; + a3 = a1; + k0 = k1; + k1 = k2; + k2 = k3; + k3 = k1; + lehmer_unroll(a2, &mut a3, k2, &mut k3); + if a3 < LIMIT { + even = false; + break; + } + a1 = a2; + a2 = a3; + a3 = a1; + k0 = k1; + k1 = k2; + k2 = k3; + k3 = k1; + lehmer_unroll(a2, &mut a3, k2, &mut k3); + } + // Unpack k into cofactors u and v + let u0 = k0 >> 32; + let u1 = k1 >> 32; + let u2 = k2 >> 32; + let u3 = k3 >> 32; + let v0 = k0 % LIMIT; + let v1 = k1 % LIMIT; + let v2 = k2 % LIMIT; + let v3 = k3 % LIMIT; + debug_assert!(a2 >= LIMIT); + debug_assert!(a3 < LIMIT); + + // Use Jebelean's exact condition to determine which outputs are correct. + // Statistically, i + 2 should be correct about two-thirds of the time. + if even { + // Test i + 1 (odd) + debug_assert!(a2 >= v2); + if a1 - a2 >= u2 + u1 { + // Test i + 2 (even) + if a3 >= u3 && a2 - a3 >= v3 + v2 { + // Correct value is i + 2 + Matrix(u2, v2, u3, v3, true) + } else { + // Correct value is i + 1 + Matrix(u1, v1, u2, v2, false) + } + } else { + // Correct value is i + Matrix(u0, v0, u1, v1, true) + } + } else { + // Test i + 1 (even) + debug_assert!(a2 >= u2); + if a1 - a2 >= v2 + v1 { + // Test i + 2 (odd) + if a3 >= v3 && a2 - a3 >= u3 + u2 { + // Correct value is i + 2 + Matrix(u2, v2, u3, v3, false) + } else { + // Correct value is i + 1 + Matrix(u1, v1, u2, v2, true) + } + } else { + // Correct value is i + Matrix(u0, v0, u1, v1, false) + } + } +} + +/// Compute the Lehmer update matrix in full 64 bit precision. +/// +/// Jebelean solves this by starting in double-precision followed +/// by single precision once values are small enough. +/// Cohen instead runs a single precision round, refreshes the r0 and r1 +/// values and continues with another single precision round on top. +/// Our approach is similar to Cohen, but instead doing the second round +/// on the same matrix, we start we a fresh matrix and multiply both in the +/// end. This requires 8 additional multiplications, but allows us to use +/// the tighter stopping conditions from Jebelean. It also seems the simplest +/// out of these solutions. +// OPT: We can update r0 and r1 in place. This won't remove the partially +// redundant call to lehmer_update, but it reduces memory usage. +// We shadow s for readability. +#[allow(clippy::shadow_unrelated)] +fn lehmer_double(mut r0: U256, mut r1: U256) -> Matrix { + debug_assert!(r0 >= r1); + if r0.leading_zeros() >= 192 { + // OPT: Rewrite using to_u64 -> Option + debug_assert!(r1.leading_zeros() >= 192); + debug_assert!(r0.limb(0) >= r1.limb(0)); + return lehmer_small(r0.limb(0), r1.limb(0)); + } + let s = r0.leading_zeros(); + let r0s = r0.clone() << s; + let r1s = r1.clone() << s; + let q = lehmer_loop(r0s.limb(3), r1s.limb(3)); + if q == Matrix::IDENTITY { + return q; + } + // We can return q here and have a perfectly valid single-word Lehmer GCD. + // return q; + + // Recompute r0 and r1 and take the high bits. + // OPT: This does not need full precision. + // OPT: Can we reuse the shifted variables here? + lehmer_update(&mut r0, &mut r1, &q); + let s = r0.leading_zeros(); + let r0s = r0 << s; + let r1s = r1 << s; + let qn = lehmer_loop(r0s.limb(3), r1s.limb(3)); + + // Multiply matrices qn * q + Matrix( + qn.0 * q.0 + qn.1 * q.2, + qn.0 * q.1 + qn.1 * q.3, + qn.2 * q.0 + qn.3 * q.2, + qn.2 * q.1 + qn.3 * q.3, + qn.4 ^ !q.4, + ) +} + +//// Lehmer's GCD algorithms. +/// See `gcd_extended` for documentation. This version maintains +/// full precision cofactors. +pub(crate) fn gcd(mut r0: U256, mut r1: U256) -> U256 { + if r1 > r0 { + core::mem::swap(&mut r0, &mut r1); + } + debug_assert!(r0 >= r1); + while r1 != U256::ZERO { + let q = lehmer_double(r0.clone(), r1.clone()); + if q == Matrix::IDENTITY { + // Do a full precision Euclid step. q is at least a halfword. + // This should happen zero or one time, seldom more. + // OPT: use single limb version when q is small enough? + let q = &r0 / &r1; + let t = r0 - &q * &r1; + r0 = r1; + r1 = t; + } else { + lehmer_update(&mut r0, &mut r1, &q); + } + } + r0 +} + +/// Lehmer's extended GCD. +/// +/// A variation of Euclids algorithm where repeated 64-bit approximations are +/// used to make rapid progress on. +/// +/// See Jebelean (1994) "A Double-Digit Lehmer-Euclid Algorithm for Finding the +/// GCD of Long Integers". +/// +/// The function `lehmer_double` takes two `U256`'s and returns a 64-bit matrix. +/// +/// The function `lehmer_update` updates state variables using this matrix. If +/// the matrix makes no progress (because 64 bit precision is not enough) a full +/// precision Euclid step is done, but this happens rarely. +/// +/// See also `mpn_gcdext_lehmer_n` in GMP. +/// +// Importing as `gcd_extended` is more readable than `gcd::extended`. +#[allow(clippy::module_name_repetitions)] +pub(crate) fn gcd_extended(mut r0: U256, mut r1: U256) -> (U256, U256, U256, bool) { + let swapped = r1 > r0; + if swapped { + core::mem::swap(&mut r0, &mut r1); + } + debug_assert!(r0 >= r1); + let mut s0 = U256::ONE; + let mut s1 = U256::ZERO; + let mut t0 = U256::ZERO; + let mut t1 = U256::ONE; + let mut even = true; + while r1 != U256::ZERO { + let q = lehmer_double(r0.clone(), r1.clone()); + if q == Matrix::IDENTITY { + // Do a full precision Euclid step. q is at least a halfword. + // This should happen zero or one time, seldom more. + // OPT: use single limb version when q is small enough? + let q = &r0 / &r1; + let t = r0 - &q * &r1; + r0 = r1; + r1 = t; + let t = s0 - &q * &s1; + s0 = s1; + s1 = t; + let t = t0 - q * &t1; + t0 = t1; + t1 = t; + even = !even; + } else { + lehmer_update(&mut r0, &mut r1, &q); + lehmer_update(&mut s0, &mut s1, &q); + lehmer_update(&mut t0, &mut t1, &q); + even ^= !q.4; + } + } + // TODO: Compute using absolute value instead of patching sign. + if even { + // t negative + t0 = U256::ZERO - t0; + } else { + // s negative + s0 = U256::ZERO - s0; + } + if swapped { + core::mem::swap(&mut s0, &mut t0); + even = !even; + } + (r0, s0, t0, even) +} + +/// Modular inversion using extended GCD. +/// +/// It uses the Bezout identity +/// +/// ```text +/// a * modulus + b * num = gcd(modulus, num) +/// ```` +/// +/// where `a` and `b` are the cofactors from the extended Euclidean algorithm. +/// A modular inverse only exists if `modulus` and `num` are coprime, in which +/// case `gcd(modulus, num)` is one. Reducing both sides by the modulus then +/// results in the equation `b * num = 1 (mod modulus)`. In other words, the +/// cofactor `b` is the modular inverse of `num`. +/// +/// It differs from `gcd_extended` in that it only computes the required +/// cofactor, and returns `None` if the GCD is not one (i.e. when `num` does +/// not have an inverse). +pub(crate) fn inv_mod(modulus: &U256, num: &U256) -> Option { + let mut r0 = modulus.clone(); + let mut r1 = num.clone(); + if r1 >= r0 { + r1 %= &r0; + } + let mut t0 = U256::ZERO; + let mut t1 = U256::ONE; + let mut even = true; + while r1 != U256::ZERO { + let q = lehmer_double(r0.clone(), r1.clone()); + if q == Matrix::IDENTITY { + // Do a full precision Euclid step. q is at least a halfword. + // This should happen zero or one time, seldom more. + let q = &r0 / &r1; + let t = r0 - &q * &r1; + r0 = r1; + r1 = t; + let t = t0 - q * &t1; + t0 = t1; + t1 = t; + even = !even; + } else { + lehmer_update(&mut r0, &mut r1, &q); + lehmer_update(&mut t0, &mut t1, &q); + even ^= !q.4; + } + } + if r0 == U256::ONE { + // When `even` t0 is negative and in twos-complement form + Some(if even { modulus + t0 } else { t0 }) + } else { + None + } +} + +// We don't mind large number literals here. +#[allow(clippy::unreadable_literal)] +#[cfg(test)] +mod tests { + use super::*; + use num_traits::identities::{One, Zero}; + use proptest::prelude::*; + use zkp_macros_decl::u256h; + + #[test] + fn test_lehmer_small() { + assert_eq!(lehmer_small(0, 0), Matrix::IDENTITY); + assert_eq!( + lehmer_small(14535145444257436950, 5818365597666026993), + Matrix( + 379355176803460069, + 947685836737753349, + 831195085380860999, + 2076449349179633850, + false + ) + ); + assert_eq!( + lehmer_small(15507080595343815048, 10841422679839906593), + Matrix( + 40154122160696118, + 57434639988632077, + 3613807559946635531, + 5169026865114605016, + true + ) + ); + } + + #[test] + fn test_issue() { + // This triggers div_3by2 to go into an edge case division. + let a = u256h!("0000000000000054000000000000004f000000000000001f0000000000000028"); + let b = u256h!("0000000000000054000000000000005b000000000000002b000000000000005d"); + let _ = gcd(a, b); + } + + #[test] + fn test_lehmer_loop() { + assert_eq!(lehmer_loop(1_u64 << 63, 0), Matrix::IDENTITY); + assert_eq!( + // Accumulates the first 18 quotients + lehmer_loop(16194659139127649777, 14535145444257436950), + Matrix(320831736, 357461893, 1018828859, 1135151083, true) + ); + assert_eq!( + // Accumulates the first 27 coefficients + lehmer_loop(15267531864828975732, 6325623274722585764,), + Matrix(88810257, 214352542, 774927313, 1870365485, false) + ); + } + + proptest!( + #[test] + // We shadow t for readability. + #[allow(clippy::shadow_unrelated)] + fn test_lehmer_loop_match_gcd(mut a: u64, mut b: u64) { + const LIMIT: u64 = 1_u64 << 32; + + // Prepare valid inputs + a |= 1_u64 << 63; + if b > a { + core::mem::swap(&mut a, &mut b) + } + + // Call the function under test + let update_matrix = lehmer_loop(a, b); + + // Verify outputs + assert!(update_matrix.0 < LIMIT); + assert!(update_matrix.1 < LIMIT); + assert!(update_matrix.2 < LIMIT); + assert!(update_matrix.3 < LIMIT); + prop_assume!(update_matrix != Matrix::IDENTITY); + + assert!(update_matrix.0 <= update_matrix.2); + assert!(update_matrix.2 <= update_matrix.3); + assert!(update_matrix.1 <= update_matrix.3); + + // Compare with simple GCD + let mut a0 = a; + let mut a1 = b; + let mut s0 = 1; + let mut s1 = 0; + let mut t0 = 0; + let mut t1 = 1; + let mut even = true; + let mut result = false; + while a1 > 0 { + let r = a0 / a1; + let t = a0 - r * a1; + a0 = a1; + a1 = t; + let t = s0 + r * s1; + s0 = s1; + s1 = t; + let t = t0 + r * t1; + t0 = t1; + t1 = t; + even = !even; + if update_matrix == Matrix(s0, t0, s1, t1, even) { + result = true; + break; + } + } + prop_assert!(result) + } + + #[test] + fn test_mat_mul_match_formula(a: U256, b: U256, q00: u64, q01: u64, q10: u64, q11: u64) { + let a_expected = q00 * a.clone() - q01 * b.clone(); + let b_expected = q11 * b.clone() - q10 * a.clone(); + let mut a_result = a; + let mut b_result = b; + mat_mul(&mut a_result, &mut b_result, (q00, q01, q10, q11)); + prop_assert_eq!(a_result, a_expected); + prop_assert_eq!(b_result, b_expected); + } + ); + + #[test] + fn test_lehmer_double() { + assert_eq!(lehmer_double(U256::ZERO, U256::ZERO), Matrix::IDENTITY); + assert_eq!( + // Aggegrates the first 34 quotients + lehmer_double( + u256h!("518a5cc4c55ac5b050a0831b65e827e5e39fd4515e4e094961c61509e7870814"), + u256h!("018a5cc4c55ac5b050a0831b65e827e5e39fd4515e4e094961c61509e7870814") + ), + Matrix( + 2927556694930003, + 154961230633081597, + 3017020641586254, + 159696730135159213, + true + ) + ); + } + + #[test] + fn test_gcd_lehmer() { + assert_eq!( + gcd_extended(U256::ZERO, U256::ZERO), + (U256::ZERO, U256::ONE, U256::ZERO, true) + ); + assert_eq!( + gcd_extended( + u256h!("fea5a792d0a17b24827908e5524bcceec3ec6a92a7a42eac3b93e2bb351cf4f2"), + u256h!("00028735553c6c798ed1ffb8b694f8f37b672b1bab7f80c4e6f4c0e710c79fb4") + ), + ( + u256h!("0000000000000000000000000000000000000000000000000000000000000002"), + u256h!("00000b5a5ecb4dfc4ea08773d0593986592959a646b2f97655ed839928274ebb"), + u256h!("0477865490d3994853934bf7eae7dad9afac55ccbf412a60c18fc9bea58ec8ba"), + false + ) + ); + assert_eq!( + gcd_extended( + u256h!("518a5cc4c55ac5b050a0831b65e827e5e39fd4515e4e094961c61509e7870814"), + u256h!("018a5cc4c55ac5b050a0831b65e827e5e39fd4515e4e094961c61509e7870814") + ), + ( + U256::from(4), + u256h!("002c851a0dddfaa03b9db2e39d48067d9b57fa0d238b70c7feddf8d267accc41"), + u256h!("0934869c752ae9c7d2ed8aa55e7754e5492aaac49f8c9f3416156313a16c1174"), + true + ) + ); + assert_eq!( + gcd_extended( + u256h!("7dfd26515f3cd365ea32e1a43dbac87a25d0326fd834a889cb1e4c6c3c8d368c"), + u256h!("3d341ef315cbe5b9f0ab79255f9684e153deaf5f460a8425819c84ec1e80a2f3") + ), + ( + u256h!("0000000000000000000000000000000000000000000000000000000000000001"), + u256h!("0bbc35a0c1fd8f1ae85377ead5a901d4fbf0345fa303a87a4b4b68429cd69293"), + u256h!("18283a24821b7de14cf22afb0e1a7efb4212b7f373988f5a0d75f6ee0b936347"), + false + ) + ); + assert_eq!( + gcd_extended( + u256h!("836fab5d425345751b3425e733e8150a17fdab2d5fb840ede5e0879f41497a4f"), + u256h!("196e875b381eb95d9b5c6c3f198c5092b3ccc21279a7e68bc42cb6bca2d2644d") + ), + ( + u256h!("000000000000000000000000000000000000000000000000c59f8490536754fd"), + u256h!("000000000000000006865401d85836d50a2bd608f152186fb24072a122d0dc5d"), + u256h!("000000000000000021b8940f60792f546cbeb17f8b852d33a00b14b323d6de70"), + false + ) + ); + assert_eq!( + gcd_extended( + u256h!("00253222ed7b612113dbea0be0e1a0b88f2c0c16250f54bf1ec35d62671bf83a"), + u256h!("0000000000025d4e064960ef2964b2170f1cd63ab931968621dde8a867079fd4") + ), + ( + u256h!("000000000000000000000000000505b22b0a9fd5a6e2166e3486f0109e6f60b2"), + u256h!("0000000000000000000000000000000000000000000000001f16d40433587ae9"), + u256h!("0000000000000000000000000000000000000001e91177fbec66b1233e79662e"), + true + ) + ); + assert_eq!( + gcd_extended( + u256h!("0000000000025d4e064960ef2964b2170f1cd63ab931968621dde8a867079fd4"), + u256h!("00253222ed7b612113dbea0be0e1a0b88f2c0c16250f54bf1ec35d62671bf83a") + ), + ( + u256h!("000000000000000000000000000505b22b0a9fd5a6e2166e3486f0109e6f60b2"), + u256h!("0000000000000000000000000000000000000001e91177fbec66b1233e79662e"), + u256h!("0000000000000000000000000000000000000000000000001f16d40433587ae9"), + false + ) + ); + } + + #[test] + fn test_gcd_lehmer_extended_equal_inputs() { + let a = U256::from(10); + let b = U256::from(10); + let (gcd, u, v, even) = gcd_extended(a.clone(), b.clone()); + assert_eq!(&a % &gcd, U256::ZERO); + assert_eq!(&b % &gcd, U256::ZERO); + assert!(!even); + assert_eq!(gcd, v * b - u * a); + } + + proptest!( + #[test] + fn test_gcd_lehmer_extended(a: U256, b: U256) { + let (gcd, u, v, even) = gcd_extended(a.clone(), b.clone()); + prop_assert!((&a % &gcd).is_zero()); + prop_assert!((&b % &gcd).is_zero()); + + if even { + prop_assert_eq!(gcd, u * a - v * b); + } else { + prop_assert_eq!(gcd, v * b - u * a); + } + } + + #[test] + fn test_inv_lehmer(mut a: U256) { + const MODULUS: U256 = + u256h!("0800000000000011000000000000000000000000000000000000000000000001"); + a %= MODULUS; + match inv_mod(&MODULUS, &a) { + None => prop_assert!(a.is_zero()), + Some(a_inv) => prop_assert!(a.mulmod(&a_inv, &MODULUS).is_one()), + } + } + ); +} diff --git a/guest-libs/ruint/src/algorithms/gcd/matrix.rs b/guest-libs/ruint/src/algorithms/gcd/matrix.rs new file mode 100644 index 0000000000..34d42d098a --- /dev/null +++ b/guest-libs/ruint/src/algorithms/gcd/matrix.rs @@ -0,0 +1,450 @@ +#![allow(clippy::use_self)] + +use crate::Uint; + +/// ⚠️ Lehmer update matrix +/// +/// **Warning.** This struct is not part of the stable API. +/// +/// Signs are implicit, the boolean `.4` encodes which of two sign +/// patterns applies. The signs and layout of the matrix are: +/// +/// ```text +/// true false +/// [ .0 -.1] [-.0 .1] +/// [-.2 .3] [ .2 -.3] +/// ``` +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Matrix(pub u64, pub u64, pub u64, pub u64, pub bool); + +impl Matrix { + pub const IDENTITY: Self = Self(1, 0, 0, 1, true); + + /// Returns the matrix product `self * other`. + #[inline] + #[allow(clippy::suspicious_operation_groupings)] + #[must_use] + pub const fn compose(self, other: Self) -> Self { + Self( + self.0 * other.0 + self.1 * other.2, + self.0 * other.1 + self.1 * other.3, + self.2 * other.0 + self.3 * other.2, + self.2 * other.1 + self.3 * other.3, + self.4 ^ !other.4, + ) + } + + /// Applies the matrix to a `Uint`. + #[inline] + pub fn apply( + &self, + a: &mut Uint, + b: &mut Uint, + ) { + if BITS == 0 { + return; + } + // OPT: We can avoid the temporary if we implement a dedicated matrix + // multiplication. + let (c, d) = if self.4 { + ( + Uint::from(self.0) * *a - Uint::from(self.1) * *b, + Uint::from(self.3) * *b - Uint::from(self.2) * *a, + ) + } else { + ( + Uint::from(self.1) * *b - Uint::from(self.0) * *a, + Uint::from(self.2) * *a - Uint::from(self.3) * *b, + ) + }; + *a = c; + *b = d; + } + + /// Applies the matrix to a `u128`. + #[inline] + #[must_use] + pub const fn apply_u128(&self, a: u128, b: u128) -> (u128, u128) { + // Intermediate values can overflow but the final result will fit, so we + // compute mod 2^128. + if self.4 { + ( + (self.0 as u128) + .wrapping_mul(a) + .wrapping_sub((self.1 as u128).wrapping_mul(b)), + (self.3 as u128) + .wrapping_mul(b) + .wrapping_sub((self.2 as u128).wrapping_mul(a)), + ) + } else { + ( + (self.1 as u128) + .wrapping_mul(b) + .wrapping_sub((self.0 as u128).wrapping_mul(a)), + (self.2 as u128) + .wrapping_mul(a) + .wrapping_sub((self.3 as u128).wrapping_mul(b)), + ) + } + } + + /// Compute a Lehmer update matrix from two `Uint`s. + /// + /// # Panics + /// + /// Panics if `b > a`. + #[inline] + #[must_use] + pub fn from( + a: Uint, + b: Uint, + ) -> Self { + assert!(a >= b); + + // Grab the first 128 bits. + let s = a.bit_len(); + if s <= 64 { + Self::from_u64(a.try_into().unwrap(), b.try_into().unwrap()) + } else if s <= 128 { + Self::from_u128_prefix(a.try_into().unwrap(), b.try_into().unwrap()) + } else { + let a = a >> (s - 128); + let b = b >> (s - 128); + Self::from_u128_prefix(a.try_into().unwrap(), b.try_into().unwrap()) + } + } + + /// Compute the Lehmer update matrix for small values. + /// + /// This is essentially Euclids extended GCD algorithm for 64 bits. + /// + /// # Panics + /// + /// Panics if `r0 < r1`. + // OPT: Would this be faster using extended binary gcd? + // See + #[inline] + #[must_use] + pub fn from_u64(mut r0: u64, mut r1: u64) -> Self { + debug_assert!(r0 >= r1); + if r1 == 0_u64 { + return Matrix::IDENTITY; + } + let mut q00 = 1_u64; + let mut q01 = 0_u64; + let mut q10 = 0_u64; + let mut q11 = 1_u64; + loop { + // Loop is unrolled once to avoid swapping variables and tracking parity. + let q = r0 / r1; + r0 -= q * r1; + q00 += q * q10; + q01 += q * q11; + if r0 == 0_u64 { + return Matrix(q10, q11, q00, q01, false); + } + let q = r1 / r0; + r1 -= q * r0; + q10 += q * q00; + q11 += q * q01; + if r1 == 0_u64 { + return Matrix(q00, q01, q10, q11, true); + } + } + } + + /// Compute the largest valid Lehmer update matrix for a prefix. + /// + /// Compute the Lehmer update matrix for a0 and a1 such that the matrix is + /// valid for any two large integers starting with the bits of a0 and + /// a1. + /// + /// See also `mpn_hgcd2` in GMP, but ours handles the double precision bit + /// separately in `lehmer_double`. + /// + /// + /// # Panics + /// + /// Panics if `a0` does not have the highest bit set. + /// Panics if `a0 < a1`. + #[inline] + #[must_use] + #[allow(clippy::redundant_else)] + #[allow(clippy::cognitive_complexity)] // REFACTOR: Improve + pub fn from_u64_prefix(a0: u64, mut a1: u64) -> Self { + const LIMIT: u64 = 1_u64 << 32; + debug_assert!(a0 >= 1_u64 << 63); + debug_assert!(a0 >= a1); + + // Here we do something original: The cofactors undergo identical + // operations which makes them a candidate for SIMD instructions. + // They also never exceed 32 bit, so we can SWAR them in a single u64. + let mut k0 = 1_u64 << 32; // u0 = 1, v0 = 0 + let mut k1 = 1_u64; // u1 = 0, v1 = 1 + let mut even = true; + if a1 < LIMIT { + return Matrix::IDENTITY; + } + + // Compute a2 + let q = a0 / a1; + let mut a2 = a0 - q * a1; + let mut k2 = k0 + q * k1; + if a2 < LIMIT { + let u2 = k2 >> 32; + let v2 = k2 % LIMIT; + + // Test i + 1 (odd) + if a2 >= v2 && a1 - a2 >= u2 { + return Matrix(0, 1, u2, v2, false); + } else { + return Matrix::IDENTITY; + } + } + + // Compute a3 + let q = a1 / a2; + let mut a3 = a1 - q * a2; + let mut k3 = k1 + q * k2; + + // Loop until a3 < LIMIT, maintaining the last three values + // of a and the last four values of k. + while a3 >= LIMIT { + a1 = a2; + a2 = a3; + a3 = a1; + k0 = k1; + k1 = k2; + k2 = k3; + k3 = k1; + debug_assert!(a2 < a3); + debug_assert!(a2 > 0); + let q = a3 / a2; + a3 -= q * a2; + k3 += q * k2; + if a3 < LIMIT { + even = false; + break; + } + a1 = a2; + a2 = a3; + a3 = a1; + k0 = k1; + k1 = k2; + k2 = k3; + k3 = k1; + debug_assert!(a2 < a3); + debug_assert!(a2 > 0); + let q = a3 / a2; + a3 -= q * a2; + k3 += q * k2; + } + // Unpack k into cofactors u and v + let u0 = k0 >> 32; + let u1 = k1 >> 32; + let u2 = k2 >> 32; + let u3 = k3 >> 32; + let v0 = k0 % LIMIT; + let v1 = k1 % LIMIT; + let v2 = k2 % LIMIT; + let v3 = k3 % LIMIT; + debug_assert!(a2 >= LIMIT); + debug_assert!(a3 < LIMIT); + + // Use Jebelean's exact condition to determine which outputs are correct. + // Statistically, i + 2 should be correct about two-thirds of the time. + if even { + // Test i + 1 (odd) + debug_assert!(a2 >= v2); + if a1 - a2 >= u2 + u1 { + // Test i + 2 (even) + if a3 >= u3 && a2 - a3 >= v3 + v2 { + // Correct value is i + 2 + Matrix(u2, v2, u3, v3, true) + } else { + // Correct value is i + 1 + Matrix(u1, v1, u2, v2, false) + } + } else { + // Correct value is i + Matrix(u0, v0, u1, v1, true) + } + } else { + // Test i + 1 (even) + debug_assert!(a2 >= u2); + if a1 - a2 >= v2 + v1 { + // Test i + 2 (odd) + if a3 >= v3 && a2 - a3 >= u3 + u2 { + // Correct value is i + 2 + Matrix(u2, v2, u3, v3, false) + } else { + // Correct value is i + 1 + Matrix(u1, v1, u2, v2, true) + } + } else { + // Correct value is i + Matrix(u0, v0, u1, v1, false) + } + } + } + + /// Compute the Lehmer update matrix in full 64 bit precision. + /// + /// Jebelean solves this by starting in double-precission followed + /// by single precision once values are small enough. + /// Cohen instead runs a single precision round, refreshes the r0 and r1 + /// values and continues with another single precision round on top. + /// Our approach is similar to Cohen, but instead doing the second round + /// on the same matrix, we start we a fresh matrix and multiply both in the + /// end. This requires 8 additional multiplications, but allows us to use + /// the tighter stopping conditions from Jebelean. It also seems the + /// simplest out of these solutions. + // OPT: We can update r0 and r1 in place. This won't remove the partially + // redundant call to lehmer_update, but it reduces memory usage. + #[inline] + #[must_use] + pub fn from_u128_prefix(r0: u128, r1: u128) -> Self { + debug_assert!(r0 >= r1); + let s = r0.leading_zeros(); + let r0s = r0 << s; + let r1s = r1 << s; + let q = Self::from_u64_prefix((r0s >> 64) as u64, (r1s >> 64) as u64); + if q == Matrix::IDENTITY { + return q; + } + // We can return q here and have a perfectly valid single-word Lehmer GCD. + q + // OPT: Fix the below method to get double-word Lehmer GCD. + + // Recompute r0 and r1 and take the high bits. + // TODO: Is it safe to do this based on just the u128 prefix? + // let (r0, r1) = q.apply_u128(r0, r1); + // let s = r0.leading_zeros(); + // let r0s = r0 << s; + // let r1s = r1 << s; + // let qn = Self::from_u64_prefix((r0s >> 64) as u64, (r1s >> 64) as + // u64); + + // // Multiply matrices qn * q + // qn.compose(q) + } +} + +#[cfg(test)] +#[allow(clippy::cast_lossless)] +#[allow(clippy::many_single_char_names)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use core::{ + cmp::{max, min}, + mem::swap, + str::FromStr, + }; + use proptest::{proptest, test_runner::Config}; + + fn gcd(mut a: u128, mut b: u128) -> u128 { + while b != 0 { + a %= b; + swap(&mut a, &mut b); + } + a + } + + fn gcd_uint( + mut a: Uint, + mut b: Uint, + ) -> Uint { + while b != Uint::ZERO { + a %= b; + swap(&mut a, &mut b); + } + a + } + + #[test] + fn test_from_u64_example() { + let (a, b) = (252, 105); + let m = Matrix::from_u64(a, b); + assert_eq!(m, Matrix(2, 5, 5, 12, false)); + let (a, b) = m.apply_u128(a as u128, b as u128); + assert_eq!(a, 21); + assert_eq!(b, 0); + } + + #[test] + fn test_from_u64() { + proptest!(|(a: u64, b: u64)| { + let (a, b) = (max(a,b), min(a,b)); + let m = Matrix::from_u64(a, b); + let (c, d) = m.apply_u128(a as u128, b as u128); + assert!(c >= d); + assert_eq!(c, gcd(a as u128, b as u128)); + assert_eq!(d, 0); + }); + } + + #[test] + fn test_from_u64_prefix() { + proptest!(|(a: u128, b: u128)| { + // Prepare input + let (a, b) = (max(a,b), min(a,b)); + let s = a.leading_zeros(); + let (sa, sb) = (a << s, b << s); + + let m = Matrix::from_u64_prefix((sa >> 64) as u64, (sb >> 64) as u64); + let (c, d) = m.apply_u128(a, b); + assert!(c >= d); + if m == Matrix::IDENTITY { + assert_eq!(c, a); + assert_eq!(d, b); + } else { + assert!(c <= a); + assert!(d < b); + assert_eq!(gcd(a, b), gcd(c, d)); + } + }); + } + + fn test_form_uint_one( + a: Uint, + b: Uint, + ) { + let (a, b) = (max(a, b), min(a, b)); + let m = Matrix::from(a, b); + let (mut c, mut d) = (a, b); + m.apply(&mut c, &mut d); + assert!(c >= d); + if m == Matrix::IDENTITY { + assert_eq!(c, a); + assert_eq!(d, b); + } else { + assert!(c <= a); + assert!(d < b); + assert_eq!(gcd_uint(a, b), gcd_uint(c, d)); + } + } + + #[test] + fn test_from_uint_cases() { + // This case fails with the double-word version above. + type U129 = Uint<129, 3>; + test_form_uint_one( + U129::from_str("0x01de6ef6f3caa963a548d7a411b05b9988").unwrap(), + U129::from_str("0x006d7c4641f88b729a97889164dd8d07db").unwrap(), + ); + } + + #[test] + #[allow(clippy::absurd_extreme_comparisons)] // Generated code + fn test_from_uint_proptest() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + let config = Config { cases: 10, ..Default::default() }; + proptest!(config, |(a: U, b: U)| { + test_form_uint_one(a, b); + }); + }); + } +} diff --git a/guest-libs/ruint/src/algorithms/gcd/mod.rs b/guest-libs/ruint/src/algorithms/gcd/mod.rs new file mode 100644 index 0000000000..6a962cc390 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/gcd/mod.rs @@ -0,0 +1,251 @@ +#![allow(clippy::module_name_repetitions)] + +// TODO: https://github.com/bitcoin-core/secp256k1/blob/master/doc/safegcd_implementation.md + +// TODO: Make these algorithms work on limb slices. +mod matrix; + +pub use self::matrix::Matrix as LehmerMatrix; +use crate::Uint; +use core::mem::swap; + +/// ⚠️ Lehmer's GCD algorithms. +/// +/// **Warning.** This struct is not part of the stable API. +/// +/// See [`gcd_extended`] for documentation. +#[inline] +#[must_use] +pub fn gcd( + mut a: Uint, + mut b: Uint, +) -> Uint { + if b > a { + swap(&mut a, &mut b); + } + while b != Uint::ZERO { + debug_assert!(a >= b); + let m = LehmerMatrix::from(a, b); + if m == LehmerMatrix::IDENTITY { + // Lehmer step failed to find a factor, which happens when + // the factor is very large. We do a regular Euclidean step, which + // will make a lot of progress since `q` will be large. + a %= b; + swap(&mut a, &mut b); + } else { + m.apply(&mut a, &mut b); + } + } + a +} + +/// ⚠️ Lehmer's extended GCD. +/// +/// **Warning.** This struct is not part of the stable API. +/// +/// Returns `(gcd, x, y, sign)` such that `gcd = a * x + b * y`. +/// +/// # Algorithm +/// +/// A variation of Euclids algorithm where repeated 64-bit approximations are +/// used to make rapid progress on. +/// +/// See Jebelean (1994) "A Double-Digit Lehmer-Euclid Algorithm for Finding the +/// GCD of Long Integers". +/// +/// The function `lehmer_double` takes two `U256`'s and returns a 64-bit matrix. +/// +/// The function `lehmer_update` updates state variables using this matrix. If +/// the matrix makes no progress (because 64 bit precision is not enough) a full +/// precision Euclid step is done, but this happens rarely. +/// +/// See also `mpn_gcdext_lehmer_n` in GMP. +/// +#[inline] +#[must_use] +pub fn gcd_extended( + mut a: Uint, + mut b: Uint, +) -> ( + Uint, + Uint, + Uint, + bool, +) { + if BITS == 0 { + return (Uint::ZERO, Uint::ZERO, Uint::ZERO, false); + } + let swapped = a < b; + if swapped { + swap(&mut a, &mut b); + } + + // Initialize state matrix to identity. + let mut s0 = Uint::ONE; + let mut s1 = Uint::ZERO; + let mut t0 = Uint::ZERO; + let mut t1 = Uint::ONE; + let mut even = true; + while b != Uint::ZERO { + debug_assert!(a >= b); + let m = LehmerMatrix::from(a, b); + if m == LehmerMatrix::IDENTITY { + // Lehmer step failed to find a factor, which happens when + // the factor is very large. We do a regular Euclidean step, which + // will make a lot of progress since `q` will be large. + let q = a / b; + a -= q * b; + swap(&mut a, &mut b); + s0 -= q * s1; + swap(&mut s0, &mut s1); + t0 -= q * t1; + swap(&mut t0, &mut t1); + even = !even; + } else { + m.apply(&mut a, &mut b); + m.apply(&mut s0, &mut s1); + m.apply(&mut t0, &mut t1); + even ^= !m.4; + } + } + // TODO: Compute using absolute value instead of patching sign. + if even { + // t negative + t0 = Uint::ZERO - t0; + } else { + // s negative + s0 = Uint::ZERO - s0; + } + if swapped { + swap(&mut s0, &mut t0); + even = !even; + } + (a, s0, t0, even) +} + +/// ⚠️ Modular inversion using extended GCD. +/// +/// It uses the Bezout identity +/// +/// ```text +/// a * modulus + b * num = gcd(modulus, num) +/// ```` +/// +/// where `a` and `b` are the cofactors from the extended Euclidean algorithm. +/// A modular inverse only exists if `modulus` and `num` are coprime, in which +/// case `gcd(modulus, num)` is one. Reducing both sides by the modulus then +/// results in the equation `b * num = 1 (mod modulus)`. In other words, the +/// cofactor `b` is the modular inverse of `num`. +/// +/// It differs from `gcd_extended` in that it only computes the required +/// cofactor, and returns `None` if the GCD is not one (i.e. when `num` does +/// not have an inverse). +#[inline] +#[must_use] +pub fn inv_mod( + num: Uint, + modulus: Uint, +) -> Option> { + if BITS == 0 || modulus.is_zero() { + return None; + } + let mut a = modulus; + let mut b = num; + if b >= a { + b %= a; + } + if b.is_zero() { + return None; + } + + let mut t0 = Uint::ZERO; + let mut t1 = Uint::ONE; + let mut even = true; + while b != Uint::ZERO { + debug_assert!(a >= b); + let m = LehmerMatrix::from(a, b); + if m == LehmerMatrix::IDENTITY { + // Lehmer step failed to find a factor, which happens when + // the factor is very large. We do a regular Euclidean step, which + // will make a lot of progress since `q` will be large. + let q = a / b; + a -= q * b; + swap(&mut a, &mut b); + t0 -= q * t1; + swap(&mut t0, &mut t1); + even = !even; + } else { + m.apply(&mut a, &mut b); + m.apply(&mut t0, &mut t1); + even ^= !m.4; + } + } + if a == Uint::ONE { + // When `even` t0 is negative and in twos-complement form + Some(if even { modulus + t0 } else { t0 }) + } else { + None + } +} + +#[cfg(test)] +#[allow(clippy::cast_lossless)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::{proptest, test_runner::Config}; + + #[test] + fn test_gcd_one() { + use core::str::FromStr; + const BITS: usize = 129; + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + let a = U::from_str("0x006d7c4641f88b729a97889164dd8d07db").unwrap(); + let b = U::from_str("0x01de6ef6f3caa963a548d7a411b05b9988").unwrap(); + assert_eq!(gcd(a, b), gcd_ref(a, b)); + } + + // Reference implementation + fn gcd_ref( + mut a: Uint, + mut b: Uint, + ) -> Uint { + while b != Uint::ZERO { + a %= b; + swap(&mut a, &mut b); + } + a + } + + #[test] + #[allow(clippy::absurd_extreme_comparisons)] // Generated code + fn test_gcd() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + let config = Config { cases: 10, ..Default::default()}; + proptest!(config, |(a: U, b: U)| { + assert_eq!(gcd(a, b), gcd_ref(a, b)); + }); + }); + } + + #[test] + fn test_gcd_extended() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + let config = Config { cases: 5, ..Default::default() }; + proptest!(config, |(a: U, b: U)| { + let (g, x, y, sign) = gcd_extended(a, b); + assert_eq!(g, gcd_ref(a, b)); + if sign { + assert_eq!(a * x - b * y, g); + } else { + assert_eq!(b * y - a * x, g); + } + }); + }); + } +} diff --git a/guest-libs/ruint/src/algorithms/mod.rs b/guest-libs/ruint/src/algorithms/mod.rs new file mode 100644 index 0000000000..487daf713b --- /dev/null +++ b/guest-libs/ruint/src/algorithms/mod.rs @@ -0,0 +1,134 @@ +//! ⚠️ Collection of bignum algorithms. +//! +//! **Warning.** Most functions in this module are currently not considered part +//! of the stable API and may be changed or removed in future minor releases. + +#![allow(missing_docs)] // TODO: document algorithms + +use core::cmp::Ordering; + +mod add; +pub mod div; +mod gcd; +mod mul; +mod mul_redc; +mod ops; +mod shift; + +pub use self::{ + add::{adc_n, sbb_n}, + div::div, + gcd::{gcd, gcd_extended, inv_mod, LehmerMatrix}, + mul::{add_nx1, addmul, addmul_n, addmul_nx1, mul_nx1, submul_nx1}, + mul_redc::{mul_redc, square_redc}, + ops::{adc, sbb}, + shift::{shift_left_small, shift_right_small}, +}; + +trait DoubleWord: Sized + Copy { + fn join(high: T, low: T) -> Self; + fn add(a: T, b: T) -> Self; + fn mul(a: T, b: T) -> Self; + fn muladd(a: T, b: T, c: T) -> Self; + fn muladd2(a: T, b: T, c: T, d: T) -> Self; + + fn high(self) -> T; + fn low(self) -> T; + fn split(self) -> (T, T); +} + +impl DoubleWord for u128 { + #[inline(always)] + fn join(high: u64, low: u64) -> Self { + (Self::from(high) << 64) | Self::from(low) + } + + /// Computes `a + b` as a 128-bit value. + #[inline(always)] + fn add(a: u64, b: u64) -> Self { + Self::from(a) + Self::from(b) + } + + /// Computes `a * b` as a 128-bit value. + #[inline(always)] + fn mul(a: u64, b: u64) -> Self { + Self::from(a) * Self::from(b) + } + + /// Computes `a * b + c` as a 128-bit value. Note that this can not + /// overflow. + #[inline(always)] + fn muladd(a: u64, b: u64, c: u64) -> Self { + Self::from(a) * Self::from(b) + Self::from(c) + } + + /// Computes `a * b + c + d` as a 128-bit value. Note that this can not + /// overflow. + #[inline(always)] + fn muladd2(a: u64, b: u64, c: u64, d: u64) -> Self { + Self::from(a) * Self::from(b) + Self::from(c) + Self::from(d) + } + + #[inline(always)] + fn high(self) -> u64 { + (self >> 64) as u64 + } + + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] + fn low(self) -> u64 { + self as u64 + } + + #[inline(always)] + fn split(self) -> (u64, u64) { + (self.low(), self.high()) + } +} + +/// Compare two `u64` slices in reverse order. +#[inline(always)] +#[must_use] +pub fn cmp(left: &[u64], right: &[u64]) -> Ordering { + let l = core::cmp::min(left.len(), right.len()); + + // Slice to the loop iteration range to enable bound check + // elimination in the compiler + let lhs = &left[..l]; + let rhs = &right[..l]; + + for i in (0..l).rev() { + match i8::from(lhs[i] > rhs[i]) - i8::from(lhs[i] < rhs[i]) { + -1 => return Ordering::Less, + 0 => {} + 1 => return Ordering::Greater, + _ => unsafe { core::hint::unreachable_unchecked() }, + } + + // Equivalent to: + // match lhs[i].cmp(&rhs[i]) { + // Ordering::Equal => {} + // non_eq => return non_eq, + // } + } + + left.len().cmp(&right.len()) +} + +// Helper while [Rust#85532](https://github.com/rust-lang/rust/issues/85532) stabilizes. +#[inline] +#[must_use] +pub const fn carrying_add(lhs: u64, rhs: u64, carry: bool) -> (u64, bool) { + let (result, carry_1) = lhs.overflowing_add(rhs); + let (result, carry_2) = result.overflowing_add(carry as u64); + (result, carry_1 | carry_2) +} + +// Helper while [Rust#85532](https://github.com/rust-lang/rust/issues/85532) stabilizes. +#[inline] +#[must_use] +pub const fn borrowing_sub(lhs: u64, rhs: u64, borrow: bool) -> (u64, bool) { + let (result, borrow_1) = lhs.overflowing_sub(rhs); + let (result, borrow_2) = result.overflowing_sub(borrow as u64); + (result, borrow_1 | borrow_2) +} diff --git a/guest-libs/ruint/src/algorithms/mul.rs b/guest-libs/ruint/src/algorithms/mul.rs new file mode 100644 index 0000000000..eff10c6d09 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/mul.rs @@ -0,0 +1,339 @@ +#![allow(clippy::module_name_repetitions)] + +use crate::algorithms::{ops::sbb, DoubleWord}; + +/// ⚠️ Computes `result += a * b` and checks for overflow. +/// +/// **Warning.** This function is not part of the stable API. +/// +/// Arrays are in little-endian order. All arrays can be arbitrary sized. +/// +/// # Algorithm +/// +/// Trims zeros from inputs, then uses the schoolbook multiplication algorithm. +/// It takes the shortest input as the outer loop. +/// +/// # Examples +/// +/// ``` +/// # use ruint::algorithms::addmul; +/// let mut result = [0]; +/// let overflow = addmul(&mut result, &[3], &[4]); +/// assert_eq!(overflow, false); +/// assert_eq!(result, [12]); +/// ``` +#[inline(always)] +pub fn addmul(mut lhs: &mut [u64], mut a: &[u64], mut b: &[u64]) -> bool { + // Trim zeros from `a` + while let [0, rest @ ..] = a { + a = rest; + if let [_, rest @ ..] = lhs { + lhs = rest; + } + } + while let [rest @ .., 0] = a { + a = rest; + } + + // Trim zeros from `b` + while let [0, rest @ ..] = b { + b = rest; + if let [_, rest @ ..] = lhs { + lhs = rest; + } + } + while let [rest @ .., 0] = b { + b = rest; + } + + if a.is_empty() || b.is_empty() { + return false; + } + if lhs.is_empty() { + return true; + } + + let (a, b) = if b.len() > a.len() { (b, a) } else { (a, b) }; + + // Iterate over limbs of `b` and add partial products to `lhs`. + let mut overflow = false; + for &b in b { + if lhs.len() >= a.len() { + let (target, rest) = lhs.split_at_mut(a.len()); + let carry = addmul_nx1(target, a, b); + let carry = add_nx1(rest, carry); + overflow |= carry != 0; + } else { + overflow = true; + if lhs.is_empty() { + break; + } + addmul_nx1(lhs, &a[..lhs.len()], b); + } + lhs = &mut lhs[1..]; + } + overflow +} + +/// Computes `lhs += a` and returns the carry. +#[inline(always)] +pub fn add_nx1(lhs: &mut [u64], mut a: u64) -> u64 { + if a == 0 { + return 0; + } + for lhs in lhs { + (*lhs, a) = u128::add(*lhs, a).split(); + if a == 0 { + return 0; + } + } + a +} + +/// Computes wrapping `lhs += a * b` when all arguments are the same length. +/// +/// # Panics +/// +/// Panics if the lengts are not the same. +#[inline(always)] +pub fn addmul_n(lhs: &mut [u64], a: &[u64], b: &[u64]) { + assert_eq!(lhs.len(), a.len()); + assert_eq!(lhs.len(), b.len()); + match lhs.len() { + 0 => {} + 1 => addmul_1(lhs, a, b), + 2 => addmul_2(lhs, a, b), + 3 => addmul_3(lhs, a, b), + 4 => addmul_4(lhs, a, b), + _ => _ = addmul(lhs, a, b), + } +} + +/// Computes `lhs += a * b` for 1 limb. +#[inline(always)] +fn addmul_1(lhs: &mut [u64], a: &[u64], b: &[u64]) { + assume!(lhs.len() == 1); + assume!(a.len() == 1); + assume!(b.len() == 1); + + mac(&mut lhs[0], a[0], b[0], 0); +} + +/// Computes `lhs += a * b` for 2 limbs. +#[inline(always)] +fn addmul_2(lhs: &mut [u64], a: &[u64], b: &[u64]) { + assume!(lhs.len() == 2); + assume!(a.len() == 2); + assume!(b.len() == 2); + + let carry = mac(&mut lhs[0], a[0], b[0], 0); + mac(&mut lhs[1], a[0], b[1], carry); + + mac(&mut lhs[1], a[1], b[0], 0); +} + +/// Computes `lhs += a * b` for 3 limbs. +#[inline(always)] +fn addmul_3(lhs: &mut [u64], a: &[u64], b: &[u64]) { + assume!(lhs.len() == 3); + assume!(a.len() == 3); + assume!(b.len() == 3); + + let carry = mac(&mut lhs[0], a[0], b[0], 0); + let carry = mac(&mut lhs[1], a[0], b[1], carry); + mac(&mut lhs[2], a[0], b[2], carry); + + let carry = mac(&mut lhs[1], a[1], b[0], 0); + mac(&mut lhs[2], a[1], b[1], carry); + + mac(&mut lhs[2], a[2], b[0], 0); +} + +/// Computes `lhs += a * b` for 4 limbs. +#[inline(always)] +fn addmul_4(lhs: &mut [u64], a: &[u64], b: &[u64]) { + assume!(lhs.len() == 4); + assume!(a.len() == 4); + assume!(b.len() == 4); + + let carry = mac(&mut lhs[0], a[0], b[0], 0); + let carry = mac(&mut lhs[1], a[0], b[1], carry); + let carry = mac(&mut lhs[2], a[0], b[2], carry); + mac(&mut lhs[3], a[0], b[3], carry); + + let carry = mac(&mut lhs[1], a[1], b[0], 0); + let carry = mac(&mut lhs[2], a[1], b[1], carry); + mac(&mut lhs[3], a[1], b[2], carry); + + let carry = mac(&mut lhs[2], a[2], b[0], 0); + mac(&mut lhs[3], a[2], b[1], carry); + + mac(&mut lhs[3], a[3], b[0], 0); +} + +#[inline(always)] +fn mac(lhs: &mut u64, a: u64, b: u64, c: u64) -> u64 { + let prod = u128::muladd2(a, b, c, *lhs); + *lhs = prod.low(); + prod.high() +} + +/// Computes `lhs *= a` and returns the carry. +#[inline(always)] +pub fn mul_nx1(lhs: &mut [u64], a: u64) -> u64 { + let mut carry = 0; + for lhs in lhs { + (*lhs, carry) = u128::muladd(*lhs, a, carry).split(); + } + carry +} + +/// Computes `lhs += a * b` and returns the carry. +/// +/// Requires `lhs.len() == a.len()`. +/// +/// $$ +/// \begin{aligned} +/// \mathsf{lhs'} &= \mod{\mathsf{lhs} + \mathsf{a} ⋅ \mathsf{b}}_{2^{64⋅N}} +/// \\\\ \mathsf{carry} &= \floor{\frac{\mathsf{lhs} + \mathsf{a} ⋅ \mathsf{b} +/// }{2^{64⋅N}}} \end{aligned} +/// $$ +#[inline(always)] +pub fn addmul_nx1(lhs: &mut [u64], a: &[u64], b: u64) -> u64 { + assume!(lhs.len() == a.len()); + let mut carry = 0; + for i in 0..a.len() { + (lhs[i], carry) = u128::muladd2(a[i], b, carry, lhs[i]).split(); + } + carry +} + +/// Computes `lhs -= a * b` and returns the borrow. +/// +/// Requires `lhs.len() == a.len()`. +/// +/// $$ +/// \begin{aligned} +/// \mathsf{lhs'} &= \mod{\mathsf{lhs} - \mathsf{a} ⋅ \mathsf{b}}_{2^{64⋅N}} +/// \\\\ \mathsf{borrow} &= \floor{\frac{\mathsf{a} ⋅ \mathsf{b} - +/// \mathsf{lhs}}{2^{64⋅N}}} \end{aligned} +/// $$ +// OPT: `carry` and `borrow` can probably be merged into a single var. +#[inline(always)] +pub fn submul_nx1(lhs: &mut [u64], a: &[u64], b: u64) -> u64 { + assume!(lhs.len() == a.len()); + let mut carry = 0; + let mut borrow = 0; + for i in 0..a.len() { + // Compute product limbs + let limb; + (limb, carry) = u128::muladd(a[i], b, carry).split(); + + // Subtract + (lhs[i], borrow) = sbb(lhs[i], limb, borrow); + } + borrow + carry +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::{collection, num::u64, proptest}; + + #[allow(clippy::cast_possible_truncation)] // Intentional truncation. + fn addmul_ref(result: &mut [u64], a: &[u64], b: &[u64]) -> bool { + let mut overflow = 0; + for (i, a) in a.iter().copied().enumerate() { + let mut result = result.iter_mut().skip(i); + let mut b = b.iter().copied(); + let mut carry = 0_u128; + loop { + match (result.next(), b.next()) { + // Partial product. + (Some(result), Some(b)) => { + carry += u128::from(*result) + u128::from(a) * u128::from(b); + *result = carry as u64; + carry >>= 64; + } + // Carry propagation. + (Some(result), None) => { + carry += u128::from(*result); + *result = carry as u64; + carry >>= 64; + } + // Excess product. + (None, Some(b)) => { + carry += u128::from(a) * u128::from(b); + overflow |= carry as u64; + carry >>= 64; + } + // Fin. + (None, None) => { + break; + } + } + } + overflow |= carry as u64; + } + overflow != 0 + } + + #[test] + fn test_addmul() { + let any_vec = collection::vec(u64::ANY, 0..10); + proptest!(|(mut lhs in &any_vec, a in &any_vec, b in &any_vec)| { + // Reference + let mut ref_lhs = lhs.clone(); + let ref_overflow = addmul_ref(&mut ref_lhs, &a, &b); + + // Test + let overflow = addmul(&mut lhs, &a, &b); + assert_eq!(lhs, ref_lhs); + assert_eq!(overflow, ref_overflow); + }); + } + + fn test_vals(lhs: &[u64], rhs: &[u64], expected: &[u64], expected_overflow: bool) { + let mut result = vec![0; expected.len()]; + let overflow = addmul(&mut result, lhs, rhs); + assert_eq!(overflow, expected_overflow); + assert_eq!(result, expected); + } + + #[test] + fn test_empty() { + test_vals(&[], &[], &[], false); + test_vals(&[], &[1], &[], false); + test_vals(&[1], &[], &[], false); + test_vals(&[1], &[1], &[], true); + test_vals(&[], &[], &[0], false); + test_vals(&[], &[1], &[0], false); + test_vals(&[1], &[], &[0], false); + test_vals(&[1], &[1], &[1], false); + } + + #[test] + fn test_submul_nx1() { + let mut lhs = [ + 15520854688669198950, + 13760048731709406392, + 14363314282014368551, + 13263184899940581802, + ]; + let a = [ + 7955980792890017645, + 6297379555503105007, + 2473663400150304794, + 18362433840513668572, + ]; + let b = 17275533833223164845; + let borrow = submul_nx1(&mut lhs, &a, b); + assert_eq!(lhs, [ + 2427453526388035261, + 7389014268281543265, + 6670181329660292018, + 8411211985208067428 + ]); + assert_eq!(borrow, 17196576577663999042); + } +} diff --git a/guest-libs/ruint/src/algorithms/mul_redc.rs b/guest-libs/ruint/src/algorithms/mul_redc.rs new file mode 100644 index 0000000000..3781a7f008 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/mul_redc.rs @@ -0,0 +1,260 @@ +// TODO: https://baincapitalcrypto.com/optimizing-montgomery-multiplication-in-webassembly/ + +use super::{borrowing_sub, carrying_add, cmp}; +use core::{cmp::Ordering, iter::zip}; + +/// Computes a * b * 2^(-BITS) mod modulus +/// +/// Requires that `inv` is the inverse of `-modulus[0]` modulo `2^64`. +/// Requires that `a` and `b` are less than `modulus`. +#[inline] +#[must_use] +pub fn mul_redc(a: [u64; N], b: [u64; N], modulus: [u64; N], inv: u64) -> [u64; N] { + debug_assert_eq!(inv.wrapping_mul(modulus[0]), u64::MAX); + debug_assert_eq!(cmp(&a, &modulus), Ordering::Less); + debug_assert_eq!(cmp(&b, &modulus), Ordering::Less); + + // Coarsely Integrated Operand Scanning (CIOS) + // See + // See + // See + let mut result = [0; N]; + let mut carry = false; + for b in b { + let mut m = 0; + let mut carry_1 = 0; + let mut carry_2 = 0; + for i in 0..N { + // Add limb product + let (value, next_carry) = carrying_mul_add(a[i], b, result[i], carry_1); + carry_1 = next_carry; + + if i == 0 { + // Compute reduction factor + m = value.wrapping_mul(inv); + } + + // Add m * modulus to acc to clear next_result[0] + let (value, next_carry) = carrying_mul_add(modulus[i], m, value, carry_2); + carry_2 = next_carry; + + // Shift result + if i > 0 { + result[i - 1] = value; + } else { + debug_assert_eq!(value, 0); + } + } + + // Add carries + let (value, next_carry) = carrying_add(carry_1, carry_2, carry); + result[N - 1] = value; + if modulus[N - 1] >= 0x7fff_ffff_ffff_ffff { + carry = next_carry; + } else { + debug_assert!(!next_carry); + } + } + + // Compute reduced product. + reduce1_carry(result, modulus, carry) +} + +/// Computes a^2 * 2^(-BITS) mod modulus +/// +/// Requires that `inv` is the inverse of `-modulus[0]` modulo `2^64`. +/// Requires that `a` is less than `modulus`. +#[inline] +#[must_use] +#[allow(clippy::cast_possible_truncation)] +pub fn square_redc(a: [u64; N], modulus: [u64; N], inv: u64) -> [u64; N] { + debug_assert_eq!(inv.wrapping_mul(modulus[0]), u64::MAX); + debug_assert_eq!(cmp(&a, &modulus), Ordering::Less); + + let mut result = [0; N]; + let mut carry_outer = 0; + for i in 0..N { + // Add limb product + let (value, mut carry_lo) = carrying_mul_add(a[i], a[i], result[i], 0); + let mut carry_hi = false; + result[i] = value; + for j in (i + 1)..N { + let (value, next_carry_lo, next_carry_hi) = + carrying_double_mul_add(a[i], a[j], result[j], carry_lo, carry_hi); + result[j] = value; + carry_lo = next_carry_lo; + carry_hi = next_carry_hi; + } + + // Add m times modulus to result and shift one limb + let m = result[0].wrapping_mul(inv); + let (value, mut carry) = carrying_mul_add(m, modulus[0], result[0], 0); + debug_assert_eq!(value, 0); + for j in 1..N { + let (value, next_carry) = carrying_mul_add(modulus[j], m, result[j], carry); + result[j - 1] = value; + carry = next_carry; + } + + // Add carries + if modulus[N - 1] >= 0x3fff_ffff_ffff_ffff { + let wide = (carry_outer as u128) + .wrapping_add(carry_lo as u128) + .wrapping_add((carry_hi as u128) << 64) + .wrapping_add(carry as u128); + result[N - 1] = wide as u64; + + // Note carry_outer can be {0, 1, 2}. + carry_outer = (wide >> 64) as u64; + debug_assert!(carry_outer <= 2); + } else { + // `carry_outer` and `carry_hi` are always zero. + debug_assert!(!carry_hi); + debug_assert_eq!(carry_outer, 0); + let (value, carry) = carry_lo.overflowing_add(carry); + debug_assert!(!carry); + result[N - 1] = value; + } + } + + // Compute reduced product. + debug_assert!(carry_outer <= 1); + reduce1_carry(result, modulus, carry_outer > 0) +} + +#[inline] +#[must_use] +#[allow(clippy::needless_bitwise_bool)] +fn reduce1_carry(value: [u64; N], modulus: [u64; N], carry: bool) -> [u64; N] { + let (reduced, borrow) = sub(value, modulus); + // TODO: Ideally this turns into a cmov, which makes the whole mul_redc constant + // time. + if carry | !borrow { + reduced + } else { + value + } +} + +#[inline] +#[must_use] +fn sub(lhs: [u64; N], rhs: [u64; N]) -> ([u64; N], bool) { + let mut result = [0; N]; + let mut borrow = false; + for (result, (lhs, rhs)) in zip(&mut result, zip(lhs, rhs)) { + let (value, next_borrow) = borrowing_sub(lhs, rhs, borrow); + *result = value; + borrow = next_borrow; + } + (result, borrow) +} + +/// Compute `lhs * rhs + add + carry`. +/// The output can not overflow for any input values. +#[inline] +#[must_use] +#[allow(clippy::cast_possible_truncation)] +const fn carrying_mul_add(lhs: u64, rhs: u64, add: u64, carry: u64) -> (u64, u64) { + let wide = (lhs as u128) + .wrapping_mul(rhs as u128) + .wrapping_add(add as u128) + .wrapping_add(carry as u128); + (wide as u64, (wide >> 64) as u64) +} + +/// Compute `2 * lhs * rhs + add + carry_lo + 2^64 * carry_hi`. +/// The output can not overflow for any input values. +#[inline] +#[must_use] +#[allow(clippy::cast_possible_truncation)] +const fn carrying_double_mul_add( + lhs: u64, + rhs: u64, + add: u64, + carry_lo: u64, + carry_hi: bool, +) -> (u64, u64, bool) { + let wide = (lhs as u128).wrapping_mul(rhs as u128); + let (wide, carry_1) = wide.overflowing_add(wide); + let carries = (add as u128) + .wrapping_add(carry_lo as u128) + .wrapping_add((carry_hi as u128) << 64); + let (wide, carry_2) = wide.overflowing_add(carries); + (wide as u64, (wide >> 64) as u64, carry_1 | carry_2) +} + +#[cfg(test)] +mod test { + use core::ops::Neg; + + use super::{ + super::{addmul, div}, + *, + }; + use crate::{aliases::U64, const_for, nlimbs, Uint}; + use proptest::{prop_assert_eq, proptest}; + + fn modmul(a: [u64; N], b: [u64; N], modulus: [u64; N]) -> [u64; N] { + // Compute a * b + let mut product = vec![0; 2 * N]; + addmul(&mut product, &a, &b); + + // Compute product mod modulus + let mut reduced = modulus; + div(&mut product, &mut reduced); + reduced + } + + fn mul_base(a: [u64; N], modulus: [u64; N]) -> [u64; N] { + // Compute a * 2^(N * 64) + let mut product = vec![0; 2 * N]; + product[N..].copy_from_slice(&a); + + // Compute product mod modulus + let mut reduced = modulus; + div(&mut product, &mut reduced); + reduced + } + + #[test] + fn test_mul_redc() { + const_for!(BITS in NON_ZERO if (BITS >= 16) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(mut a: U, mut b: U, mut m: U)| { + m |= U::from(1_u64); // Make sure m is odd. + a %= m; // Make sure a is less than m. + b %= m; // Make sure b is less than m. + let a = *a.as_limbs(); + let b = *b.as_limbs(); + let m = *m.as_limbs(); + let inv = U64::from(m[0]).inv_ring().unwrap().neg().as_limbs()[0]; + + let result = mul_base(mul_redc(a, b, m, inv), m); + let expected = modmul(a, b, m); + + prop_assert_eq!(result, expected); + }); + }); + } + + #[test] + fn test_square_redc() { + const_for!(BITS in NON_ZERO if (BITS >= 16) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(mut a: U, mut m: U)| { + m |= U::from(1_u64); // Make sure m is odd. + a %= m; // Make sure a is less than m. + let a = *a.as_limbs(); + let m = *m.as_limbs(); + let inv = U64::from(m[0]).inv_ring().unwrap().neg().as_limbs()[0]; + + let result = mul_base(square_redc(a, m, inv), m); + let expected = modmul(a, a, m); + + prop_assert_eq!(result, expected); + }); + }); + } +} diff --git a/guest-libs/ruint/src/algorithms/ops.rs b/guest-libs/ruint/src/algorithms/ops.rs new file mode 100644 index 0000000000..f9b1287b6a --- /dev/null +++ b/guest-libs/ruint/src/algorithms/ops.rs @@ -0,0 +1,17 @@ +use super::DoubleWord; + +#[inline(always)] +#[must_use] +pub fn adc(lhs: u64, rhs: u64, carry: u64) -> (u64, u64) { + let result = u128::from(lhs) + u128::from(rhs) + u128::from(carry); + result.split() +} + +#[inline(always)] +#[must_use] +pub fn sbb(lhs: u64, rhs: u64, borrow: u64) -> (u64, u64) { + let result = u128::from(lhs) + .wrapping_sub(u128::from(rhs)) + .wrapping_sub(u128::from(borrow)); + (result.low(), result.high().wrapping_neg()) +} diff --git a/guest-libs/ruint/src/algorithms/primaility.rs b/guest-libs/ruint/src/algorithms/primaility.rs new file mode 100644 index 0000000000..0fe03ae1ce --- /dev/null +++ b/guest-libs/ruint/src/algorithms/primaility.rs @@ -0,0 +1,25 @@ +// Product of primes up to and including 47. +const SMALL_PRIMES: u64 = 614889782588491410; + +/// Miller-Rabin primality test +/// +/// See +pub fn miller_rabin(n: u64, base: u64) -> bool { + todo!{} +} + +/// Exact 64 bit primality test +pub fn is_prime(n: u64) -> bool { + // Sufficient set of bases for `u64` + // See + // See + // OPT: This method ? + // OPT: Combined basis srp + miller_rabin(n, 2) && + miller_rabin(n, 325) && + miller_rabin(n, 9375) && + miller_rabin(n, 28178) && + miller_rabin(n, 450775) && + miller_rabin(n, 9780504) && + miller_rabin(n, 1795265022) +} diff --git a/guest-libs/ruint/src/algorithms/shift.rs b/guest-libs/ruint/src/algorithms/shift.rs new file mode 100644 index 0000000000..ec6832aede --- /dev/null +++ b/guest-libs/ruint/src/algorithms/shift.rs @@ -0,0 +1,45 @@ +#[inline(always)] +pub fn shift_left_small(limbs: &mut [u64], amount: usize) -> u64 { + debug_assert!(amount < 64); + let mut overflow = 0; + for limb in limbs { + let value = (*limb << amount) | overflow; + overflow = *limb >> (64 - amount); + *limb = value; + } + overflow +} + +#[inline(always)] +pub fn shift_right_small(limbs: &mut [u64], amount: usize) -> u64 { + debug_assert!(amount < 64); + + let mut overflow = 0; + for limb in limbs.iter_mut().rev() { + let value = (*limb >> amount) | overflow; + overflow = *limb << (64 - amount); + *limb = value; + } + overflow +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_shift_left_small() { + let mut limbs = [0x1234_5678_9abc_def0, 0x1234_5678_9abc_def0]; + let overflow = shift_left_small(&mut limbs, 4); + assert_eq!(limbs, [0x2345_6789_abcd_ef00, 0x2345_6789_abcd_ef01]); + assert_eq!(overflow, 0x1); + } + + #[test] + fn test_shift_right_small() { + let mut limbs = [0x1234_5678_9abc_deff, 0x1234_5678_9abc_def0]; + let overflow = shift_right_small(&mut limbs, 4); + assert_eq!(limbs, [0x0123_4567_89ab_cdef, 0x0123_4567_89ab_cdef]); + assert_eq!(overflow, 0xf << 60); + } +} diff --git a/guest-libs/ruint/src/aliases.rs b/guest-libs/ruint/src/aliases.rs new file mode 100644 index 0000000000..3a30d90364 --- /dev/null +++ b/guest-libs/ruint/src/aliases.rs @@ -0,0 +1,92 @@ +//! Type aliases for common bit sizes of [`Uint`] and [`Bits`]. +use crate::{Bits, Uint}; + +/// [`Uint`] for `0` bits. Always zero. Similar to `()`. +pub type U0 = Uint<0, 0>; + +/// [`Uint`] for `1` bit. Similar to [`bool`]. +pub type U1 = Uint<1, 1>; + +/// [`Uint`] for `8` bits. Similar to [`u8`]. +pub type U8 = Uint<8, 1>; + +/// [`Uint`] for `16` bits. Similar to [`u16`]. +pub type U16 = Uint<16, 1>; + +/// [`Uint`] for `32` bits. Similar to [`u32`]. +pub type U32 = Uint<32, 1>; + +/// [`Uint`] for `64` bits. Similar to [`u64`]. +pub type U64 = Uint<64, 1>; + +/// [`Uint`] for `128` bits. Similar to [`u128`]. +pub type U128 = Uint<128, 2>; + +macro_rules! bit_alias { + ($($name:ident($bits:expr, $limbs:expr);)*) => {$( + #[doc = concat!("[`Bits`] for `", stringify!($bits),"` bits.")] + pub type $name = Bits<$bits, $limbs>; + )*}; +} + +bit_alias! { + B0(0, 0); + B1(1, 1); + B8(8, 1); + B16(16, 1); + B32(32, 1); + B64(64, 1); + B128(128, 2); +} + +macro_rules! alias { + ($($uname:ident $bname:ident ($bits:expr, $limbs:expr);)*) => {$( + #[doc = concat!("[`Uint`] for `", stringify!($bits),"` bits.")] + pub type $uname = Uint<$bits, $limbs>; + #[doc = concat!("[`Bits`] for `", stringify!($bits),"` bits.")] + pub type $bname = Bits<$bits, $limbs>; + )*}; +} + +alias! { + U160 B160 (160, 3); + U192 B192 (192, 3); + U256 B256 (256, 4); + U320 B320 (320, 5); + U384 B384 (384, 6); + U448 B448 (448, 7); + U512 B512 (512, 8); + U768 B768 (768, 12); + U1024 B1024 (1024, 16); + U2048 B2048 (2048, 32); + U4096 B4096 (4096, 64); +} + +// TODO: I0, I1, I8, ... I4096 + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + const fn instantiate_consts() { + let _ = (U0::ZERO, U0::MAX, B0::ZERO); + let _ = (U1::ZERO, U1::MAX, B1::ZERO); + let _ = (U8::ZERO, U8::MAX, B8::ZERO); + let _ = (U16::ZERO, U16::MAX, B16::ZERO); + let _ = (U32::ZERO, U32::MAX, B32::ZERO); + let _ = (U64::ZERO, U64::MAX, B64::ZERO); + let _ = (U128::ZERO, U128::MAX, B128::ZERO); + let _ = (U160::ZERO, U160::MAX, B160::ZERO); + let _ = (U192::ZERO, U192::MAX, B192::ZERO); + let _ = (U256::ZERO, U256::MAX, B256::ZERO); + let _ = (U320::ZERO, U320::MAX, B320::ZERO); + let _ = (U384::ZERO, U384::MAX, B384::ZERO); + let _ = (U448::ZERO, U448::MAX, B448::ZERO); + let _ = (U512::ZERO, U512::MAX, B512::ZERO); + let _ = (U768::ZERO, U768::MAX, B768::ZERO); + let _ = (U1024::ZERO, U1024::MAX, B1024::ZERO); + let _ = (U2048::ZERO, U2048::MAX, B2048::ZERO); + let _ = (U4096::ZERO, U4096::MAX, B4096::ZERO); + } +} diff --git a/guest-libs/ruint/src/base_convert.rs b/guest-libs/ruint/src/base_convert.rs new file mode 100644 index 0000000000..53f7d544ef --- /dev/null +++ b/guest-libs/ruint/src/base_convert.rs @@ -0,0 +1,334 @@ +use crate::{ + algorithms::{addmul_nx1, mul_nx1}, + Uint, +}; +use core::fmt; + +/// Error for [`from_base_le`][Uint::from_base_le] and +/// [`from_base_be`][Uint::from_base_be]. +#[allow(clippy::module_name_repetitions)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BaseConvertError { + /// The value is too large to fit the target type. + Overflow, + + /// The requested number base `.0` is less than two. + InvalidBase(u64), + + /// The provided digit `.0` is out of range for requested base `.1`. + InvalidDigit(u64, u64), +} + +#[cfg(feature = "std")] +impl std::error::Error for BaseConvertError {} + +impl fmt::Display for BaseConvertError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Overflow => f.write_str("the value is too large to fit the target type"), + Self::InvalidBase(base) => { + write!(f, "the requested number base {base} is less than two") + } + Self::InvalidDigit(digit, base) => { + write!(f, "digit {digit} is out of range for base {base}") + } + } + } +} + +impl Uint { + /// Returns an iterator over the base `base` digits of the number in + /// little-endian order. + /// + /// Pro tip: instead of setting `base = 10`, set it to the highest + /// power of `10` that still fits `u64`. This way much fewer iterations + /// are required to extract all the digits. + // OPT: Internalize this trick so the user won't have to worry about it. + /// # Panics + /// + /// Panics if the base is less than 2. + #[inline] + pub fn to_base_le(&self, base: u64) -> impl Iterator { + assert!(base > 1); + SpigotLittle { + base, + limbs: self.limbs, + } + } + + /// Returns an iterator over the base `base` digits of the number in + /// big-endian order. + /// + /// Pro tip: instead of setting `base = 10`, set it to the highest + /// power of `10` that still fits `u64`. This way much fewer iterations + /// are required to extract all the digits. + /// + /// # Panics + /// + /// Panics if the base is less than 2. + #[inline] + #[cfg(feature = "alloc")] // OPT: Find an allocation free method. Maybe extract from the top? + pub fn to_base_be(&self, base: u64) -> impl Iterator { + struct OwnedVecIterator { + vec: alloc::vec::Vec, + } + + impl Iterator for OwnedVecIterator { + type Item = u64; + + #[inline] + fn next(&mut self) -> Option { + self.vec.pop() + } + } + + assert!(base > 1); + OwnedVecIterator { + vec: self.to_base_le(base).collect(), + } + } + + /// Constructs the [`Uint`] from digits in the base `base` in little-endian. + /// + /// # Errors + /// + /// * [`BaseConvertError::InvalidBase`] if the base is less than 2. + /// * [`BaseConvertError::InvalidDigit`] if a digit is out of range. + /// * [`BaseConvertError::Overflow`] if the number is too large to fit. + #[inline] + pub fn from_base_le(base: u64, digits: I) -> Result + where + I: IntoIterator, + { + if base < 2 { + return Err(BaseConvertError::InvalidBase(base)); + } + if BITS == 0 { + for digit in digits { + if digit >= base { + return Err(BaseConvertError::InvalidDigit(digit, base)); + } + if digit != 0 { + return Err(BaseConvertError::Overflow); + } + } + return Ok(Self::ZERO); + } + + let mut iter = digits.into_iter(); + let mut result = Self::ZERO; + let mut power = Self::ONE; + for digit in iter.by_ref() { + if digit >= base { + return Err(BaseConvertError::InvalidDigit(digit, base)); + } + + // Add digit to result + let overflow = addmul_nx1(&mut result.limbs, power.as_limbs(), digit); + if overflow != 0 || result.limbs[LIMBS - 1] > Self::MASK { + return Err(BaseConvertError::Overflow); + } + + // Update power + let overflow = mul_nx1(&mut power.limbs, base); + if overflow != 0 || power.limbs[LIMBS - 1] > Self::MASK { + // Following digits must be zero + break; + } + } + for digit in iter { + if digit >= base { + return Err(BaseConvertError::InvalidDigit(digit, base)); + } + if digit != 0 { + return Err(BaseConvertError::Overflow); + } + } + Ok(result) + } + + /// Constructs the [`Uint`] from digits in the base `base` in big-endian. + /// + /// # Errors + /// + /// * [`BaseConvertError::InvalidBase`] if the base is less than 2. + /// * [`BaseConvertError::InvalidDigit`] if a digit is out of range. + /// * [`BaseConvertError::Overflow`] if the number is too large to fit. + #[inline] + pub fn from_base_be>( + base: u64, + digits: I, + ) -> Result { + // OPT: Special handling of bases that divide 2^64, and bases that are + // powers of 2. + // OPT: Same trick as with `to_base_le`, find the largest power of base + // that fits `u64` and accumulate there first. + if base < 2 { + return Err(BaseConvertError::InvalidBase(base)); + } + + let mut result = Self::ZERO; + for digit in digits { + if digit >= base { + return Err(BaseConvertError::InvalidDigit(digit, base)); + } + // Multiply by base. + // OPT: keep track of non-zero limbs and mul the minimum. + let mut carry: u128 = u128::from(digit); + #[allow(clippy::cast_possible_truncation)] + for limb in &mut result.limbs { + carry += u128::from(*limb) * u128::from(base); + *limb = carry as u64; + carry >>= 64; + } + if carry > 0 || (LIMBS != 0 && result.limbs[LIMBS - 1] > Self::MASK) { + return Err(BaseConvertError::Overflow); + } + } + + Ok(result) + } +} + +struct SpigotLittle { + base: u64, + limbs: [u64; LIMBS], +} + +impl Iterator for SpigotLittle { + type Item = u64; + + #[inline] + #[allow(clippy::cast_possible_truncation)] // Doesn't truncate + fn next(&mut self) -> Option { + // Knuth Algorithm S. + let mut zero: u64 = 0_u64; + let mut remainder = 0_u128; + // OPT: If we keep track of leading zero limbs we can half iterations. + for limb in self.limbs.iter_mut().rev() { + zero |= *limb; + remainder = (remainder << 64) | u128::from(*limb); + *limb = (remainder / u128::from(self.base)) as u64; + remainder %= u128::from(self.base); + } + if zero == 0 { + None + } else { + Some(remainder as u64) + } + } +} + +#[cfg(test)] +#[allow(clippy::unreadable_literal)] +#[allow(clippy::zero_prefixed_literal)] +mod tests { + use super::*; + + // 90630363884335538722706632492458228784305343302099024356772372330524102404852 + const N: Uint<256, 4> = Uint::from_limbs([ + 0xa8ec92344438aaf4_u64, + 0x9819ebdbd1faaab1_u64, + 0x573b1a7064c19c1a_u64, + 0xc85ef7d79691fe79_u64, + ]); + + #[test] + fn test_to_base_le() { + assert_eq!( + Uint::<64, 1>::from(123456789) + .to_base_le(10) + .collect::>(), + vec![9, 8, 7, 6, 5, 4, 3, 2, 1] + ); + assert_eq!( + N.to_base_le(10000000000000000000_u64).collect::>(), + vec![ + 2372330524102404852, + 0534330209902435677, + 7066324924582287843, + 0630363884335538722, + 9 + ] + ); + } + + #[test] + fn test_from_base_le() { + assert_eq!( + Uint::<64, 1>::from_base_le(10, [9, 8, 7, 6, 5, 4, 3, 2, 1]), + Ok(Uint::<64, 1>::from(123456789)) + ); + assert_eq!( + Uint::<256, 4>::from_base_le(10000000000000000000_u64, [ + 2372330524102404852, + 0534330209902435677, + 7066324924582287843, + 0630363884335538722, + 9 + ]) + .unwrap(), + N + ); + } + + #[test] + fn test_to_base_be() { + assert_eq!( + Uint::<64, 1>::from(123456789) + .to_base_be(10) + .collect::>(), + vec![1, 2, 3, 4, 5, 6, 7, 8, 9] + ); + assert_eq!( + N.to_base_be(10000000000000000000_u64).collect::>(), + vec![ + 9, + 0630363884335538722, + 7066324924582287843, + 0534330209902435677, + 2372330524102404852 + ] + ); + } + + #[test] + fn test_from_base_be() { + assert_eq!( + Uint::<64, 1>::from_base_be(10, [1, 2, 3, 4, 5, 6, 7, 8, 9]), + Ok(Uint::<64, 1>::from(123456789)) + ); + assert_eq!( + Uint::<256, 4>::from_base_be(10000000000000000000_u64, [ + 9, + 0630363884335538722, + 7066324924582287843, + 0534330209902435677, + 2372330524102404852 + ]) + .unwrap(), + N + ); + } + + #[test] + fn test_from_base_be_overflow() { + assert_eq!( + Uint::<0, 0>::from_base_be(10, core::iter::empty()), + Ok(Uint::<0, 0>::ZERO) + ); + assert_eq!( + Uint::<0, 0>::from_base_be(10, core::iter::once(0)), + Ok(Uint::<0, 0>::ZERO) + ); + assert_eq!( + Uint::<0, 0>::from_base_be(10, core::iter::once(1)), + Err(BaseConvertError::Overflow) + ); + assert_eq!( + Uint::<1, 1>::from_base_be(10, [1, 0, 0].into_iter()), + Err(BaseConvertError::Overflow) + ); + } +} diff --git a/guest-libs/ruint/src/bit_arr.rs b/guest-libs/ruint/src/bit_arr.rs new file mode 100644 index 0000000000..48ba2f9733 --- /dev/null +++ b/guest-libs/ruint/src/bit_arr.rs @@ -0,0 +1,418 @@ +use crate::{ParseError, Uint}; +use core::{ + ops::{ + BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Index, Not, Shl, ShlAssign, + Shr, ShrAssign, + }, + str::FromStr, +}; + +#[cfg(feature = "alloc")] +#[allow(unused_imports)] +use alloc::{borrow::Cow, vec::Vec}; + +/// A newtype wrapper around [`Uint`] that restricts operations to those +/// relevant for bit arrays. +#[derive(Clone, Copy, Default, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "alloc", derive(Debug))] +pub struct Bits(Uint); + +impl From> for Bits { + #[inline] + fn from(value: Uint) -> Self { + Self(value) + } +} + +impl From> for Uint { + #[inline] + fn from(value: Bits) -> Self { + value.0 + } +} + +impl FromStr for Bits { + type Err = as FromStr>::Err; + + #[inline] + fn from_str(src: &str) -> Result { + src.parse().map(Self) + } +} + +impl Bits { + /// The size of this integer type in 64-bit limbs. + pub const LIMBS: usize = Uint::::LIMBS; + + /// The size of this integer type in bits. + pub const BITS: usize = Uint::::BITS; + + /// The size of this integer type in bits. + pub const BYTES: usize = Uint::::BYTES; + + /// The value zero. This is the only value that exists in all [`Uint`] + /// types. + pub const ZERO: Self = Self(Uint::::ZERO); + + /// Returns the inner [Uint]. + #[must_use] + #[inline(always)] + pub const fn into_inner(self) -> Uint { + self.0 + } + + /// Returns a reference to the inner [Uint]. + #[must_use] + #[inline(always)] + pub const fn as_uint(&self) -> &Uint { + &self.0 + } + + /// Returns a mutable reference to the inner [Uint]. + #[must_use] + #[inline(always)] + pub fn as_uint_mut(&mut self) -> &mut Uint { + &mut self.0 + } +} + +macro_rules! forward_attributes { + ($fnname:ident, $item:item $(,must_use: true)?) => { + #[doc = concat!("See [`Uint::", stringify!($fnname),"`] for documentation.")] + #[inline(always)] + #[must_use] + $item + }; + ($fnname:ident, $item:item,must_use: false) => { + #[doc = concat!("See [`Uint::", stringify!($fnname),"`] for documentation.")] + #[inline(always)] + $item + }; +} + +// Limitations of declarative macro matching force us to break down on argument +// patterns. +macro_rules! forward { + ($(fn $fnname:ident(self) -> $res:ty;)*) => { + $( + forward_attributes!( + $fnname, + pub fn $fnname(self) -> $res { + Uint::$fnname(self.0).into() + } + ); + )* + }; + ($(fn $fnname:ident$(<$(const $generic_arg:ident: $generic_ty:ty),+>)?(&self) -> $res:ty;)*) => { + $( + forward_attributes!( + $fnname, + pub fn $fnname$(<$(const $generic_arg: $generic_ty),+>)?(&self) -> $res { + Uint::$fnname(&self.0).into() + } + ); + )* + }; + ($(unsafe fn $fnname:ident(&mut self) -> $res:ty;)*) => { + $( + forward_attributes!( + $fnname, + pub unsafe fn $fnname(&mut self) -> $res { + Uint::$fnname(&mut self.0).into() + } + ); + )* + }; + ($(fn $fnname:ident(self, $arg:ident: $arg_ty:ty) -> Option;)*) => { + $( + forward_attributes!( + $fnname, + pub fn $fnname(self, $arg: $arg_ty) -> Option { + Uint::$fnname(self.0, $arg).map(Bits::from) + } + ); + )* + }; + ($(fn $fnname:ident(self, $arg:ident: $arg_ty:ty) -> (Self, bool);)*) => { + $( + forward_attributes!( + $fnname, + pub fn $fnname(self, $arg: $arg_ty) -> (Self, bool) { + let (value, flag) = Uint::$fnname(self.0, $arg); + (value.into(), flag) + } + ); + )* + }; + ($(fn $fnname:ident(self, $arg:ident: $arg_ty:ty) -> $res:ty;)*) => { + $( + forward_attributes!( + $fnname, + pub fn $fnname(self, $arg: $arg_ty) -> $res { + Uint::$fnname(self.0, $arg).into() + } + ); + )* + }; + ($(fn $fnname:ident$(<$(const $generic_arg:ident: $generic_ty:ty),+>)?($($arg:ident: $arg_ty:ty),+) -> Option;)*) => { + $( + forward_attributes!( + $fnname, + pub fn $fnname$(<$(const $generic_arg: $generic_ty),+>)?($($arg: $arg_ty),+) -> Option { + Uint::$fnname($($arg),+).map(Bits::from) + } + ); + )* + }; + ($(fn $fnname:ident$(<$(const $generic_arg:ident: $generic_ty:ty),+>)?($($arg:ident: $arg_ty:ty),+) -> Result;)*) => { + $( + forward_attributes!( + $fnname, + pub fn $fnname$(<$(const $generic_arg: $generic_ty),+>)?($($arg: $arg_ty),+) -> Result { + Uint::$fnname($($arg),+).map(Bits::from) + }, + must_use: false + ); + )* + }; + ($(fn $fnname:ident$(<$(const $generic_arg:ident: $generic_ty:ty),+>)?($($arg:ident: $arg_ty:ty),+) -> $res:ty;)*) => { + $( + forward_attributes!( + $fnname, + pub fn $fnname$(<$(const $generic_arg: $generic_ty),+>)?($($arg: $arg_ty),+) -> $res { + Uint::$fnname($($arg),+).into() + } + ); + )* + }; + ($(const fn $fnname:ident$(<$(const $generic_arg:ident: $generic_ty:ty),+>)?($($arg:ident: $arg_ty:ty),+) -> Self;)*) => { + $( + forward_attributes!( + $fnname, + pub const fn $fnname$(<$(const $generic_arg: $generic_ty),+>)?($($arg: $arg_ty),+) -> Self { + Bits(Uint::$fnname($($arg),+)) + } + ); + )* + }; + ($(const fn $fnname:ident$(<$(const $generic_arg:ident: $generic_ty:ty),+>)?(&self) -> $res_ty:ty;)*) => { + $( + forward_attributes!( + $fnname, + pub const fn $fnname$(<$(const $generic_arg: $generic_ty),+>)?(&self) -> $res_ty { + Uint::$fnname(&self.0) + } + ); + )* + }; +} + +#[allow(clippy::missing_safety_doc, clippy::missing_errors_doc)] +impl Bits { + forward! { + fn reverse_bits(self) -> Self; + } + #[cfg(feature = "alloc")] + forward! { + fn as_le_bytes(&self) -> Cow<'_, [u8]>; + fn to_be_bytes_vec(&self) -> Vec; + } + forward! { + fn to_le_bytes(&self) -> [u8; BYTES]; + fn to_be_bytes(&self) -> [u8; BYTES]; + fn leading_zeros(&self) -> usize; + fn leading_ones(&self) -> usize; + fn trailing_zeros(&self) -> usize; + fn trailing_ones(&self) -> usize; + } + forward! { + unsafe fn as_limbs_mut(&mut self) -> &mut [u64; LIMBS]; + } + forward! { + fn checked_shl(self, rhs: usize) -> Option; + fn checked_shr(self, rhs: usize) -> Option; + } + forward! { + fn overflowing_shl(self, rhs: usize) -> (Self, bool); + fn overflowing_shr(self, rhs: usize) -> (Self, bool); + } + forward! { + fn wrapping_shl(self, rhs: usize) -> Self; + fn wrapping_shr(self, rhs: usize) -> Self; + fn rotate_left(self, rhs: usize) -> Self; + fn rotate_right(self, rhs: usize) -> Self; + } + forward! { + fn try_from_be_slice(bytes: &[u8]) -> Option; + fn try_from_le_slice(bytes: &[u8]) -> Option; + } + forward! { + fn from_str_radix(src: &str, radix: u64) -> Result; + } + forward! { + fn from_be_bytes(bytes: [u8; BYTES]) -> Self; + fn from_le_bytes(bytes: [u8; BYTES]) -> Self; + } + forward! { + const fn from_limbs(limbs: [u64; LIMBS]) -> Self; + } + forward! { + const fn as_limbs(&self) -> &[u64; LIMBS]; + } +} + +impl Index for Bits { + type Output = bool; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + if self.0.bit(index) { + &true + } else { + &false + } + } +} + +impl Not for Bits { + type Output = Self; + + #[inline] + fn not(self) -> Self { + self.0.not().into() + } +} + +impl Not for &Bits { + type Output = Bits; + + #[inline] + fn not(self) -> Bits { + self.0.not().into() + } +} + +macro_rules! impl_bit_op { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { + impl $trait_assign> + for Bits + { + #[inline(always)] + fn $fn_assign(&mut self, rhs: Bits) { + self.0.$fn_assign(&rhs.0); + } + } + impl $trait_assign<&Bits> + for Bits + { + #[inline(always)] + fn $fn_assign(&mut self, rhs: &Bits) { + self.0.$fn_assign(rhs.0); + } + } + impl $trait> + for Bits + { + type Output = Bits; + + #[inline(always)] + fn $fn(mut self, rhs: Bits) -> Self::Output { + self.0.$fn_assign(rhs.0); + self + } + } + impl $trait<&Bits> + for Bits + { + type Output = Bits; + + #[inline(always)] + fn $fn(mut self, rhs: &Bits) -> Self::Output { + self.0.$fn_assign(rhs.0); + self + } + } + impl $trait> + for &Bits + { + type Output = Bits; + + #[inline(always)] + fn $fn(self, mut rhs: Bits) -> Self::Output { + rhs.0.$fn_assign(self.0); + rhs + } + } + impl $trait<&Bits> + for &Bits + { + type Output = Bits; + + #[inline(always)] + fn $fn(self, rhs: &Bits) -> Self::Output { + self.0.clone().$fn(rhs.0).into() + } + } + }; +} + +impl_bit_op!(BitOr, bitor, BitOrAssign, bitor_assign); +impl_bit_op!(BitAnd, bitand, BitAndAssign, bitand_assign); +impl_bit_op!(BitXor, bitxor, BitXorAssign, bitxor_assign); + +macro_rules! impl_shift { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { + impl $trait_assign for Bits { + #[inline(always)] + fn $fn_assign(&mut self, rhs: usize) { + self.0.$fn_assign(rhs); + } + } + + impl $trait_assign<&usize> for Bits { + #[inline(always)] + fn $fn_assign(&mut self, rhs: &usize) { + self.0.$fn_assign(rhs); + } + } + + impl $trait for Bits { + type Output = Self; + + #[inline(always)] + fn $fn(self, rhs: usize) -> Self { + self.0.$fn(rhs).into() + } + } + + impl $trait for &Bits { + type Output = Bits; + + #[inline(always)] + fn $fn(self, rhs: usize) -> Self::Output { + self.0.$fn(rhs).into() + } + } + + impl $trait<&usize> for Bits { + type Output = Self; + + #[inline(always)] + fn $fn(self, rhs: &usize) -> Self { + self.0.$fn(rhs).into() + } + } + + impl $trait<&usize> for &Bits { + type Output = Bits; + + #[inline(always)] + fn $fn(self, rhs: &usize) -> Self::Output { + self.0.$fn(rhs).into() + } + } + }; +} + +impl_shift!(Shl, shl, ShlAssign, shl_assign); +impl_shift!(Shr, shr, ShrAssign, shr_assign); diff --git a/guest-libs/ruint/src/bits.rs b/guest-libs/ruint/src/bits.rs new file mode 100644 index 0000000000..d5de28bf16 --- /dev/null +++ b/guest-libs/ruint/src/bits.rs @@ -0,0 +1,1123 @@ +use core::ops::{ + BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, ShlAssign, Shr, + ShrAssign, +}; + +use crate::Uint; + +impl Uint { + /// Returns whether a specific bit is set. + /// + /// Returns `false` if `index` exceeds the bit width of the number. + #[must_use] + #[inline] + pub const fn bit(&self, index: usize) -> bool { + if index >= BITS { + return false; + } + let (limbs, bits) = (index / 64, index % 64); + self.limbs[limbs] & (1 << bits) != 0 + } + + /// Sets a specific bit to a value. + #[inline] + pub fn set_bit(&mut self, index: usize, value: bool) { + if index >= BITS { + return; + } + let (limbs, bits) = (index / 64, index % 64); + if value { + self.limbs[limbs] |= 1 << bits; + } else { + self.limbs[limbs] &= !(1 << bits); + } + } + + /// Returns a specific byte. The byte at index `0` is the least significant + /// byte (little endian). + /// + /// # Panics + /// + /// Panics if `index` is greater than or equal to the byte width of the + /// number. + /// + /// # Examples + /// + /// ``` + /// # use ruint::uint; + /// let x = uint!(0x1234567890_U64); + /// let bytes = [ + /// x.byte(0), // 0x90 + /// x.byte(1), // 0x78 + /// x.byte(2), // 0x56 + /// x.byte(3), // 0x34 + /// x.byte(4), // 0x12 + /// x.byte(5), // 0x00 + /// x.byte(6), // 0x00 + /// x.byte(7), // 0x00 + /// ]; + /// assert_eq!(bytes, x.to_le_bytes()); + /// ``` + /// + /// Panics if out of range. + /// + /// ```should_panic + /// # use ruint::uint; + /// let x = uint!(0x1234567890_U64); + /// let _ = x.byte(8); + /// ``` + #[inline] + #[must_use] + #[track_caller] + pub const fn byte(&self, index: usize) -> u8 { + #[cfg(target_endian = "little")] + { + self.as_le_slice()[index] + } + + #[cfg(target_endian = "big")] + #[allow(clippy::cast_possible_truncation)] // intentional + { + (self.limbs[index / 8] >> ((index % 8) * 8)) as u8 + } + } + + /// Returns a specific byte, or `None` if `index` is out of range. The byte + /// at index `0` is the least significant byte (little endian). + /// + /// # Examples + /// + /// ``` + /// # use ruint::uint; + /// let x = uint!(0x1234567890_U64); + /// assert_eq!(x.checked_byte(0), Some(0x90)); + /// assert_eq!(x.checked_byte(7), Some(0x00)); + /// // Out of range + /// assert_eq!(x.checked_byte(8), None); + /// ``` + #[inline] + #[must_use] + pub const fn checked_byte(&self, index: usize) -> Option { + if index < Self::BYTES { + Some(self.byte(index)) + } else { + None + } + } + + /// Reverses the order of bits in the integer. The least significant bit + /// becomes the most significant bit, second least-significant bit becomes + /// second most-significant bit, etc. + #[inline] + #[must_use] + pub fn reverse_bits(mut self) -> Self { + self.limbs.reverse(); + for limb in &mut self.limbs { + *limb = limb.reverse_bits(); + } + if BITS % 64 != 0 { + self >>= 64 - BITS % 64; + } + self + } + + /// Inverts all the bits in the integer. + #[inline] + #[must_use] + pub const fn not(mut self) -> Self { + if BITS == 0 { + return Self::ZERO; + } + + let mut i = 0; + while i < LIMBS { + self.limbs[i] = !self.limbs[i]; + i += 1; + } + + self.masked() + } + + /// Returns the number of leading zeros in the binary representation of + /// `self`. + #[inline] + #[must_use] + pub const fn leading_zeros(&self) -> usize { + let mut i = LIMBS; + while i > 0 { + i -= 1; + if self.limbs[i] != 0 { + let n = LIMBS - 1 - i; + let skipped = n * 64; + let fixed = Self::MASK.leading_zeros() as usize; + let top = self.limbs[i].leading_zeros() as usize; + return skipped + top - fixed; + } + } + + BITS + } + + /// Returns the number of leading ones in the binary representation of + /// `self`. + #[inline] + #[must_use] + pub const fn leading_ones(&self) -> usize { + Self::not(*self).leading_zeros() + } + + /// Returns the number of trailing zeros in the binary representation of + /// `self`. + #[inline] + #[must_use] + pub fn trailing_zeros(&self) -> usize { + self.as_limbs() + .iter() + .position(|&limb| limb != 0) + .map_or(BITS, |n| { + n * 64 + self.as_limbs()[n].trailing_zeros() as usize + }) + } + + /// Returns the number of trailing ones in the binary representation of + /// `self`. + #[inline] + #[must_use] + pub fn trailing_ones(&self) -> usize { + self.as_limbs() + .iter() + .position(|&limb| limb != u64::MAX) + .map_or(BITS, |n| { + n * 64 + self.as_limbs()[n].trailing_ones() as usize + }) + } + + /// Returns the number of ones in the binary representation of `self`. + #[inline] + #[must_use] + pub const fn count_ones(&self) -> usize { + let mut total = 0; + + let mut i = 0; + while i < LIMBS { + total += self.limbs[i].count_ones() as usize; + i += 1; + } + + total + } + + /// Returns the number of zeros in the binary representation of `self`. + #[must_use] + #[inline] + pub const fn count_zeros(&self) -> usize { + BITS - self.count_ones() + } + + /// Returns the dynamic length of this number in bits, ignoring leading + /// zeros. + /// + /// For the maximum length of the type, use [`Uint::BITS`](Self::BITS). + #[must_use] + #[inline] + pub const fn bit_len(&self) -> usize { + BITS - self.leading_zeros() + } + + /// Returns the dynamic length of this number in bytes, ignoring leading + /// zeros. + /// + /// For the maximum length of the type, use [`Uint::BYTES`](Self::BYTES). + #[must_use] + #[inline] + pub const fn byte_len(&self) -> usize { + (self.bit_len() + 7) / 8 + } + + /// Returns the most significant 64 bits of the number and the exponent. + /// + /// Given return value $(\mathtt{bits}, \mathtt{exponent})$, the `self` can + /// be approximated as + /// + /// $$ + /// \mathtt{self} ≈ \mathtt{bits} ⋅ 2^\mathtt{exponent} + /// $$ + /// + /// If `self` is $<≥> 2^{63}$, then `exponent` will be zero and `bits` will + /// have leading zeros. + #[inline] + #[must_use] + pub fn most_significant_bits(&self) -> (u64, usize) { + let first_set_limb = self + .as_limbs() + .iter() + .rposition(|&limb| limb != 0) + .unwrap_or(0); + if first_set_limb == 0 { + (self.as_limbs().first().copied().unwrap_or(0), 0) + } else { + let hi = self.as_limbs()[first_set_limb]; + let lo = self.as_limbs()[first_set_limb - 1]; + let leading_zeros = hi.leading_zeros(); + let bits = if leading_zeros > 0 { + (hi << leading_zeros) | (lo >> (64 - leading_zeros)) + } else { + hi + }; + let exponent = first_set_limb * 64 - leading_zeros as usize; + (bits, exponent) + } + } + + /// Checked left shift by `rhs` bits. + /// + /// Returns $\mathtt{self} ⋅ 2^{\mathtt{rhs}}$ or [`None`] if the result + /// would $≥ 2^{\mathtt{BITS}}$. That is, it returns [`None`] if the bits + /// shifted out would be non-zero. + /// + /// Note: This differs from [`u64::checked_shl`] which returns `None` if the + /// shift is larger than BITS (which is IMHO not very useful). + #[inline(always)] + #[must_use] + pub fn checked_shl(self, rhs: usize) -> Option { + match self.overflowing_shl(rhs) { + (value, false) => Some(value), + _ => None, + } + } + + /// Saturating left shift by `rhs` bits. + /// + /// Returns $\mathtt{self} ⋅ 2^{\mathtt{rhs}}$ or [`Uint::MAX`] if the + /// result would $≥ 2^{\mathtt{BITS}}$. That is, it returns + /// [`Uint::MAX`] if the bits shifted out would be non-zero. + #[inline(always)] + #[must_use] + pub fn saturating_shl(self, rhs: usize) -> Self { + match self.overflowing_shl(rhs) { + (value, false) => value, + _ => Self::MAX, + } + } + + /// Left shift by `rhs` bits with overflow detection. + /// + /// Returns $\mod{\mathtt{value} ⋅ 2^{\mathtt{rhs}}}_{2^{\mathtt{BITS}}}$. + /// If the product is $≥ 2^{\mathtt{BITS}}$ it returns `true`. That is, it + /// returns true if the bits shifted out are non-zero. + /// + /// Note: This differs from [`u64::overflowing_shl`] which returns `true` if + /// the shift is larger than `BITS` (which is IMHO not very useful). + #[inline] + #[must_use] + pub fn overflowing_shl(self, rhs: usize) -> (Self, bool) { + let (limbs, bits) = (rhs / 64, rhs % 64); + if limbs >= LIMBS { + return (Self::ZERO, self != Self::ZERO); + } + + let word_bits = 64; + let mut r = Self::ZERO; + let mut carry = 0; + for i in 0..Self::LIMBS - limbs { + let x = self.limbs[i]; + r.limbs[i + limbs] = (x << bits) | carry; + carry = (x >> (word_bits - bits - 1)) >> 1; + } + r.apply_mask(); + (r, carry != 0) + } + + /// Left shift by `rhs` bits. + /// + /// Returns $\mod{\mathtt{value} ⋅ 2^{\mathtt{rhs}}}_{2^{\mathtt{BITS}}}$. + /// + /// Note: This differs from [`u64::wrapping_shl`] which first reduces `rhs` + /// by `BITS` (which is IMHO not very useful). + #[cfg(not(target_os = "zkvm"))] + #[inline(always)] + #[must_use] + pub fn wrapping_shl(self, rhs: usize) -> Self { + self.overflowing_shl(rhs).0 + } + + /// Left shift by `rhs` bits. + /// + /// Returns $\mod{\mathtt{value} ⋅ 2^{\mathtt{rhs}}}_{2^{\mathtt{BITS}}}$. + /// + /// Note: This differs from [`u64::wrapping_shl`] which first reduces `rhs` + /// by `BITS` (which is IMHO not very useful). + #[cfg(target_os = "zkvm")] + #[inline(always)] + #[must_use] + pub fn wrapping_shl(mut self, rhs: usize) -> Self { + if BITS == 256 { + if rhs >= 256 { + return Self::ZERO; + } + use crate::support::zkvm::zkvm_u256_wrapping_shl_impl; + let rhs = rhs as u64; + unsafe { + zkvm_u256_wrapping_shl_impl( + self.limbs.as_mut_ptr() as *mut u8, + self.limbs.as_ptr() as *const u8, + [rhs].as_ptr() as *const u8, + ); + } + self + } else { + self.overflowing_shl(rhs).0 + } + } + + /// Checked right shift by `rhs` bits. + /// + /// $$ + /// \frac{\mathtt{self}}{2^{\mathtt{rhs}}} + /// $$ + /// + /// Returns the above or [`None`] if the division is not exact. This is the + /// same as + /// + /// Note: This differs from [`u64::checked_shr`] which returns `None` if the + /// shift is larger than BITS (which is IMHO not very useful). + #[inline(always)] + #[must_use] + pub fn checked_shr(self, rhs: usize) -> Option { + match self.overflowing_shr(rhs) { + (value, false) => Some(value), + _ => None, + } + } + + /// Right shift by `rhs` bits with underflow detection. + /// + /// $$ + /// \floor{\frac{\mathtt{self}}{2^{\mathtt{rhs}}}} + /// $$ + /// + /// Returns the above and `false` if the division was exact, and `true` if + /// it was rounded down. This is the same as non-zero bits being shifted + /// out. + /// + /// Note: This differs from [`u64::overflowing_shr`] which returns `true` if + /// the shift is larger than `BITS` (which is IMHO not very useful). + #[inline] + #[must_use] + pub fn overflowing_shr(self, rhs: usize) -> (Self, bool) { + let (limbs, bits) = (rhs / 64, rhs % 64); + if limbs >= LIMBS { + return (Self::ZERO, self != Self::ZERO); + } + + let word_bits = 64; + let mut r = Self::ZERO; + let mut carry = 0; + for i in 0..LIMBS - limbs { + let x = self.limbs[LIMBS - 1 - i]; + r.limbs[LIMBS - 1 - i - limbs] = (x >> bits) | carry; + carry = (x << (word_bits - bits - 1)) << 1; + } + (r, carry != 0) + } + + /// Right shift by `rhs` bits. + /// + /// $$ + /// \mathtt{wrapping\\_shr}(\mathtt{self}, \mathtt{rhs}) = + /// \floor{\frac{\mathtt{self}}{2^{\mathtt{rhs}}}} + /// $$ + /// + /// Note: This differs from [`u64::wrapping_shr`] which first reduces `rhs` + /// by `BITS` (which is IMHO not very useful). + #[cfg(not(target_os = "zkvm"))] + #[inline(always)] + #[must_use] + pub fn wrapping_shr(self, rhs: usize) -> Self { + self.overflowing_shr(rhs).0 + } + + /// Right shift by `rhs` bits. + /// + /// $$ + /// \mathtt{wrapping\\_shr}(\mathtt{self}, \mathtt{rhs}) = + /// \floor{\frac{\mathtt{self}}{2^{\mathtt{rhs}}}} + /// $$ + /// + /// Note: This differs from [`u64::wrapping_shr`] which first reduces `rhs` + /// by `BITS` (which is IMHO not very useful). + #[cfg(target_os = "zkvm")] + #[inline(always)] + #[must_use] + pub fn wrapping_shr(mut self, rhs: usize) -> Self { + if BITS == 256 { + if rhs >= 256 { + return Self::ZERO; + } + use crate::support::zkvm::zkvm_u256_wrapping_shr_impl; + let rhs = rhs as u64; + unsafe { + zkvm_u256_wrapping_shr_impl( + self.limbs.as_mut_ptr() as *mut u8, + self.limbs.as_ptr() as *const u8, + [rhs].as_ptr() as *const u8, + ); + } + self + } else { + self.overflowing_shr(rhs).0 + } + } + + /// Arithmetic shift right by `rhs` bits. + #[cfg(not(target_os = "zkvm"))] + #[inline] + #[must_use] + pub fn arithmetic_shr(self, rhs: usize) -> Self { + if BITS == 0 { + return Self::ZERO; + } + let sign = self.bit(BITS - 1); + let mut r = self >> rhs; + if sign { + r |= Self::MAX << BITS.saturating_sub(rhs); + } + r + } + + /// Arithmetic shift right by `rhs` bits. + #[cfg(target_os = "zkvm")] + #[inline] + #[must_use] + pub fn arithmetic_shr(mut self, rhs: usize) -> Self { + if BITS == 256 { + let rhs = if rhs >= 256 { 255 } else { rhs }; + use crate::support::zkvm::zkvm_u256_arithmetic_shr_impl; + let rhs = rhs as u64; + unsafe { + zkvm_u256_arithmetic_shr_impl( + self.limbs.as_mut_ptr() as *mut u8, + self.limbs.as_ptr() as *const u8, + [rhs].as_ptr() as *const u8, + ); + } + self + } else { + if BITS == 0 { + return Self::ZERO; + } + let sign = self.bit(BITS - 1); + let mut r = self >> rhs; + if sign { + r |= Self::MAX << BITS.saturating_sub(rhs); + } + r + } + } + + /// Shifts the bits to the left by a specified amount, `rhs`, wrapping the + /// truncated bits to the end of the resulting integer. + #[inline] + #[must_use] + #[allow(clippy::missing_const_for_fn)] // False positive + pub fn rotate_left(self, rhs: usize) -> Self { + if BITS == 0 { + return Self::ZERO; + } + let rhs = rhs % BITS; + (self << rhs) | (self >> (BITS - rhs)) + } + + /// Shifts the bits to the right by a specified amount, `rhs`, wrapping the + /// truncated bits to the beginning of the resulting integer. + #[inline(always)] + #[must_use] + pub fn rotate_right(self, rhs: usize) -> Self { + if BITS == 0 { + return Self::ZERO; + } + let rhs = rhs % BITS; + self.rotate_left(BITS - rhs) + } +} + +impl Not for Uint { + type Output = Self; + + #[cfg(not(target_os = "zkvm"))] + #[inline] + fn not(self) -> Self::Output { + Self::not(self) + } + + #[cfg(target_os = "zkvm")] + #[inline(always)] + fn not(mut self) -> Self::Output { + use crate::support::zkvm::zkvm_u256_wrapping_sub_impl; + if BITS == 256 { + unsafe { + zkvm_u256_wrapping_sub_impl( + self.limbs.as_mut_ptr() as *mut u8, + Self::MAX.limbs.as_ptr() as *const u8, + self.limbs.as_ptr() as *const u8, + ); + } + self + } else { + if BITS == 0 { + return Self::ZERO; + } + for limb in &mut self.limbs { + *limb = u64::not(*limb); + } + self.limbs[LIMBS - 1] &= Self::MASK; + self + } + } +} + +impl Not for &Uint { + type Output = Uint; + + #[inline] + fn not(self) -> Self::Output { + (*self).not() + } +} + +macro_rules! impl_bit_op { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident, $fn_zkvm_impl:ident) => { + impl $trait_assign> + for Uint + { + #[inline(always)] + fn $fn_assign(&mut self, rhs: Uint) { + self.$fn_assign(&rhs); + } + } + + impl $trait_assign<&Uint> + for Uint + { + #[cfg(not(target_os = "zkvm"))] + #[inline] + fn $fn_assign(&mut self, rhs: &Uint) { + for i in 0..LIMBS { + u64::$fn_assign(&mut self.limbs[i], rhs.limbs[i]); + } + } + + #[cfg(target_os = "zkvm")] + #[inline(always)] + fn $fn_assign(&mut self, rhs: &Uint) { + use crate::support::zkvm::$fn_zkvm_impl; + unsafe { + $fn_zkvm_impl( + self.limbs.as_mut_ptr() as *mut u8, + self.limbs.as_ptr() as *const u8, + rhs.limbs.as_ptr() as *const u8, + ); + } + } + } + + impl $trait> + for Uint + { + type Output = Uint; + + #[inline(always)] + fn $fn(mut self, rhs: Uint) -> Self::Output { + self.$fn_assign(rhs); + self + } + } + + impl $trait<&Uint> + for Uint + { + type Output = Uint; + + #[inline(always)] + fn $fn(mut self, rhs: &Uint) -> Self::Output { + self.$fn_assign(rhs); + self + } + } + + impl $trait> + for &Uint + { + type Output = Uint; + + #[inline(always)] + fn $fn(self, mut rhs: Uint) -> Self::Output { + rhs.$fn_assign(self); + rhs + } + } + + impl $trait<&Uint> + for &Uint + { + type Output = Uint; + + #[inline(always)] + fn $fn(self, rhs: &Uint) -> Self::Output { + self.clone().$fn(rhs) + } + } + }; +} + +impl_bit_op!( + BitOr, + bitor, + BitOrAssign, + bitor_assign, + zkvm_u256_bitor_impl +); +impl_bit_op!( + BitAnd, + bitand, + BitAndAssign, + bitand_assign, + zkvm_u256_bitand_impl +); +impl_bit_op!( + BitXor, + bitxor, + BitXorAssign, + bitxor_assign, + zkvm_u256_bitxor_impl +); + +impl Shl for Uint { + type Output = Self; + + #[inline(always)] + fn shl(self, rhs: Self) -> Self::Output { + // This check shortcuts, and prevents panics on the `[0]` later + if BITS == 0 { + return self; + } + // Rationale: if BITS is larger than 2**64 - 1, it means we're running + // on a 128-bit platform with 2.3 exabytes of memory. In this case, + // the code produces incorrect output. + #[allow(clippy::cast_possible_truncation)] + self.wrapping_shl(rhs.as_limbs()[0] as usize) + } +} + +impl Shl<&Self> for Uint { + type Output = Self; + + #[inline(always)] + fn shl(self, rhs: &Self) -> Self::Output { + self << *rhs + } +} + +impl Shr for Uint { + type Output = Self; + + #[inline(always)] + fn shr(self, rhs: Self) -> Self::Output { + // This check shortcuts, and prevents panics on the `[0]` later + if BITS == 0 { + return self; + } + // Rationale: if BITS is larger than 2**64 - 1, it means we're running + // on a 128-bit platform with 2.3 exabytes of memory. In this case, + // the code produces incorrect output. + #[allow(clippy::cast_possible_truncation)] + self.wrapping_shr(rhs.as_limbs()[0] as usize) + } +} + +impl Shr<&Self> for Uint { + type Output = Self; + + #[inline(always)] + fn shr(self, rhs: &Self) -> Self::Output { + self >> *rhs + } +} + +impl ShlAssign for Uint { + #[inline(always)] + fn shl_assign(&mut self, rhs: Self) { + *self = *self << rhs; + } +} + +impl ShlAssign<&Self> for Uint { + #[inline(always)] + fn shl_assign(&mut self, rhs: &Self) { + *self = *self << rhs; + } +} + +impl ShrAssign for Uint { + #[inline(always)] + fn shr_assign(&mut self, rhs: Self) { + *self = *self >> rhs; + } +} + +impl ShrAssign<&Self> for Uint { + #[inline(always)] + fn shr_assign(&mut self, rhs: &Self) { + *self = *self >> rhs; + } +} + +macro_rules! impl_shift { + (@main $u:ty) => { + impl Shl<$u> for Uint { + type Output = Self; + + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] + fn shl(self, rhs: $u) -> Self::Output { + self.wrapping_shl(rhs as usize) + } + } + + impl Shr<$u> for Uint { + type Output = Self; + + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] + fn shr(self, rhs: $u) -> Self::Output { + self.wrapping_shr(rhs as usize) + } + } + }; + + (@ref $u:ty) => { + impl Shl<&$u> for Uint { + type Output = Self; + + #[inline(always)] + fn shl(self, rhs: &$u) -> Self::Output { + ::shl(self, *rhs) + } + } + + impl Shr<&$u> for Uint { + type Output = Self; + + #[inline(always)] + fn shr(self, rhs: &$u) -> Self::Output { + ::shr(self, *rhs) + } + } + }; + + (@assign $u:ty) => { + impl ShlAssign<$u> for Uint { + #[inline(always)] + fn shl_assign(&mut self, rhs: $u) { + *self = *self << rhs; + } + } + + impl ShrAssign<$u> for Uint { + #[inline(always)] + fn shr_assign(&mut self, rhs: $u) { + *self = *self >> rhs; + } + } + }; + + ($u:ty) => { + impl_shift!(@main $u); + impl_shift!(@ref $u); + impl_shift!(@assign $u); + impl_shift!(@assign &$u); + }; + + ($u:ty, $($tail:ty),*) => { + impl_shift!($u); + impl_shift!($($tail),*); + }; +} + +impl_shift!(usize, u8, u16, u32, isize, i8, i16, i32); + +// Only when losslessy castable to usize. +#[cfg(target_pointer_width = "64")] +impl_shift!(u64, i64); + +#[cfg(test)] +mod tests { + use core::cmp::min; + + use proptest::proptest; + + use super::*; + use crate::{aliases::U128, const_for, nlimbs}; + + #[test] + fn test_leading_zeros() { + assert_eq!(Uint::<0, 0>::ZERO.leading_zeros(), 0); + assert_eq!(Uint::<1, 1>::ZERO.leading_zeros(), 1); + assert_eq!(Uint::<1, 1>::ONE.leading_zeros(), 0); + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint::; + assert_eq!(U::ZERO.leading_zeros(), BITS); + assert_eq!(U::MAX.leading_zeros(), 0); + assert_eq!(U::ONE.leading_zeros(), BITS - 1); + proptest!(|(value: U)| { + let zeros = value.leading_zeros(); + assert!(zeros <= BITS); + assert!(zeros < BITS || value == U::ZERO); + if zeros < BITS { + let (left, overflow) = value.overflowing_shl(zeros); + assert!(!overflow); + assert!(left.leading_zeros() == 0 || value == U::ZERO); + assert!(left.bit(BITS - 1)); + assert_eq!(value >> (BITS - zeros), Uint::ZERO); + } + }); + }); + proptest!(|(value: u128)| { + let uint = U128::from(value); + assert_eq!(uint.leading_zeros(), value.leading_zeros() as usize); + }); + } + + #[test] + fn test_leading_ones() { + assert_eq!(Uint::<0, 0>::ZERO.leading_ones(), 0); + assert_eq!(Uint::<1, 1>::ZERO.leading_ones(), 0); + assert_eq!(Uint::<1, 1>::ONE.leading_ones(), 1); + } + + #[test] + fn test_most_significant_bits() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint::; + proptest!(|(value: u64)| { + let value = if U::LIMBS <= 1 { value & U::MASK } else { value }; + assert_eq!(U::from(value).most_significant_bits(), (value, 0)); + }); + }); + proptest!(|(mut limbs: [u64; 2])| { + if limbs[1] == 0 { + limbs[1] = 1; + } + let (bits, exponent) = U128::from_limbs(limbs).most_significant_bits(); + assert!(bits >= 1_u64 << 63); + assert_eq!(exponent, 64 - limbs[1].leading_zeros() as usize); + }); + } + + #[test] + fn test_checked_shl() { + assert_eq!( + Uint::<65, 2>::from_limbs([0x0010_0000_0000_0000, 0]).checked_shl(1), + Some(Uint::<65, 2>::from_limbs([0x0020_0000_0000_0000, 0])) + ); + assert_eq!( + Uint::<127, 2>::from_limbs([0x0010_0000_0000_0000, 0]).checked_shl(64), + Some(Uint::<127, 2>::from_limbs([0, 0x0010_0000_0000_0000])) + ); + } + + #[test] + #[allow( + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap + )] + fn test_small() { + const_for!(BITS in [1, 2, 8, 16, 32, 63, 64] { + type U = Uint::; + proptest!(|(a: U, b: U)| { + assert_eq!(a | b, U::from_limbs([a.limbs[0] | b.limbs[0]])); + assert_eq!(a & b, U::from_limbs([a.limbs[0] & b.limbs[0]])); + assert_eq!(a ^ b, U::from_limbs([a.limbs[0] ^ b.limbs[0]])); + }); + proptest!(|(a: U, s in 0..BITS)| { + assert_eq!(a << s, U::from_limbs([a.limbs[0] << s & U::MASK])); + assert_eq!(a >> s, U::from_limbs([a.limbs[0] >> s])); + }); + }); + proptest!(|(a: Uint::<32, 1>, s in 0_usize..=34)| { + assert_eq!(a.reverse_bits(), Uint::from((a.limbs[0] as u32).reverse_bits() as u64)); + assert_eq!(a.rotate_left(s), Uint::from((a.limbs[0] as u32).rotate_left(s as u32) as u64)); + assert_eq!(a.rotate_right(s), Uint::from((a.limbs[0] as u32).rotate_right(s as u32) as u64)); + if s < 32 { + let arr_shifted = (((a.limbs[0] as i32) >> s) as u32) as u64; + assert_eq!(a.arithmetic_shr(s), Uint::from_limbs([arr_shifted])); + } + }); + proptest!(|(a: Uint::<64, 1>, s in 0_usize..=66)| { + assert_eq!(a.reverse_bits(), Uint::from(a.limbs[0].reverse_bits())); + assert_eq!(a.rotate_left(s), Uint::from(a.limbs[0].rotate_left(s as u32))); + assert_eq!(a.rotate_right(s), Uint::from(a.limbs[0].rotate_right(s as u32))); + if s < 64 { + let arr_shifted = ((a.limbs[0] as i64) >> s) as u64; + assert_eq!(a.arithmetic_shr(s), Uint::from_limbs([arr_shifted])); + } + }); + } + + #[test] + fn test_shift_reverse() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint::; + proptest!(|(value: U, shift in 0..=BITS + 2)| { + let left = (value << shift).reverse_bits(); + let right = value.reverse_bits() >> shift; + assert_eq!(left, right); + }); + }); + } + + #[test] + fn test_rotate() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint::; + proptest!(|(value: U, shift in 0..=BITS + 2)| { + let rotated = value.rotate_left(shift).rotate_right(shift); + assert_eq!(value, rotated); + }); + }); + } + + #[test] + fn test_arithmetic_shr() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint::; + proptest!(|(value: U, shift in 0..=BITS + 2)| { + let shifted = value.arithmetic_shr(shift); + assert_eq!(shifted.leading_ones(), match value.leading_ones() { + 0 => 0, + n => min(BITS, n + shift) + }); + }); + }); + } + + #[test] + fn test_overflowing_shr() { + // Test: Single limb right shift from 40u64 by 1 bit. + // Expects resulting integer: 20 with no fractional part. + assert_eq!( + Uint::<64, 1>::from_limbs([40u64]).overflowing_shr(1), + (Uint::<64, 1>::from(20), false) + ); + + // Test: Single limb right shift from 41u64 by 1 bit. + // Expects resulting integer: 20 with a detected fractional part. + assert_eq!( + Uint::<64, 1>::from_limbs([41u64]).overflowing_shr(1), + (Uint::<64, 1>::from(20), true) + ); + + // Test: Two limbs right shift from 0x0010_0000_0000_0000 and 0 by 1 bit. + // Expects resulting limbs: [0x0080_0000_0000_000, 0] with no fractional part. + assert_eq!( + Uint::<65, 2>::from_limbs([0x0010_0000_0000_0000, 0]).overflowing_shr(1), + (Uint::<65, 2>::from_limbs([0x0008_0000_0000_0000, 0]), false) + ); + + // Test: Shift beyond single limb capacity with MAX value. + // Expects the highest possible value in 256-bit representation with a detected + // fractional part. + assert_eq!( + Uint::<256, 4>::MAX.overflowing_shr(65), + ( + Uint::<256, 4>::from_str_radix( + "7fffffffffffffffffffffffffffffffffffffffffffffff", + 16 + ) + .unwrap(), + true + ) + ); + // Test: Large 4096-bit integer right shift by 34 bits. + // Expects a specific value with no fractional part. + assert_eq!( + Uint::<4096, 64>::from_str_radix("3ffffffffffffffffffffffffffffc00000000", 16,) + .unwrap() + .overflowing_shr(34), + ( + Uint::<4096, 64>::from_str_radix("fffffffffffffffffffffffffffff", 16).unwrap(), + false + ) + ); + // Test: Extremely large 4096-bit integer right shift by 100 bits. + // Expects a specific value with no fractional part. + assert_eq!( + Uint::<4096, 64>::from_str_radix( + "fffffffffffffffffffffffffffff0000000000000000000000000", + 16, + ) + .unwrap() + .overflowing_shr(100), + ( + Uint::<4096, 64>::from_str_radix("fffffffffffffffffffffffffffff", 16).unwrap(), + false + ) + ); + // Test: Complex 4096-bit integer right shift by 1 bit. + // Expects a specific value with no fractional part. + assert_eq!( + Uint::<4096, 64>::from_str_radix( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0bdbfe", + 16, + ) + .unwrap() + .overflowing_shr(1), + ( + Uint::<4096, 64>::from_str_radix( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff85edff", + 16 + ) + .unwrap(), + false + ) + ); + // Test: Large 4096-bit integer right shift by 1000 bits. + // Expects a specific value with no fractional part. + assert_eq!( + Uint::<4096, 64>::from_str_radix( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + 16, + ) + .unwrap() + .overflowing_shr(1000), + ( + Uint::<4096, 64>::from_str_radix( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + 16 + ) + .unwrap(), + false + ) + ); + // Test: MAX 4096-bit integer right shift by 34 bits. + // Expects a specific value with a detected fractional part. + assert_eq!( + Uint::<4096, 64>::MAX + .overflowing_shr(34), + ( + Uint::<4096, 64>::from_str_radix( + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + 16 + ) + .unwrap(), + true + ) + ); + } +} diff --git a/guest-libs/ruint/src/bytes.rs b/guest-libs/ruint/src/bytes.rs new file mode 100644 index 0000000000..10e4790db6 --- /dev/null +++ b/guest-libs/ruint/src/bytes.rs @@ -0,0 +1,660 @@ +// OPT: Use u64::from_{be/le}_bytes() to work 8 bytes at a time. +// FEATURE: (BLOCKED) Make `const fn`s when `const_for` is stable. + +use crate::Uint; +use core::slice; + +#[cfg(feature = "alloc")] +#[allow(unused_imports)] +use alloc::{borrow::Cow, vec::Vec}; + +// OPT: *_to_smallvec to avoid allocation. +impl Uint { + /// The size of this integer type in bytes. Note that some bits may be + /// forced zero if BITS is not cleanly divisible by eight. + pub const BYTES: usize = (BITS + 7) / 8; + + /// Access the underlying store as a little-endian slice of bytes. + /// + /// Only available on little-endian targets. + /// + /// If `BITS` does not evenly divide 8, it is padded with zero bits in the + /// most significant position. + #[cfg(target_endian = "little")] + #[must_use] + #[inline(always)] + pub const fn as_le_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.limbs.as_ptr().cast(), Self::BYTES) } + } + + /// Access the underlying store as a mutable little-endian slice of bytes. + /// + /// Only available on litte-endian targets. + /// + /// # Safety + /// + /// If `BITS` does not evenly divide 8, it is padded with zero bits in the + /// most significant position. Setting those bits puts the [`Uint`] in an + /// invalid state. + #[cfg(target_endian = "little")] + #[must_use] + #[inline(always)] + pub unsafe fn as_le_slice_mut(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.limbs.as_mut_ptr().cast(), Self::BYTES) } + } + + /// Access the underlying store as a little-endian bytes. + /// + /// Uses an optimized implementation on little-endian targets. + #[cfg(feature = "alloc")] + #[must_use] + #[inline] + #[allow(clippy::missing_const_for_fn)] + pub fn as_le_bytes(&self) -> Cow<'_, [u8]> { + // On little endian platforms this is a no-op. + #[cfg(target_endian = "little")] + return Cow::Borrowed(self.as_le_slice()); + + // In others, reverse each limb and return a copy. + #[cfg(target_endian = "big")] + return Cow::Owned({ + let mut cpy = *self; + for limb in &mut cpy.limbs { + *limb = limb.swap_bytes(); + } + unsafe { slice::from_raw_parts(cpy.limbs.as_ptr().cast(), Self::BYTES).to_vec() } + }); + } + + /// Access the underlying store as a little-endian bytes with trailing zeros + /// removed. + /// + /// Uses an optimized implementation on little-endian targets. + #[cfg(feature = "alloc")] + #[must_use] + #[inline] + pub fn as_le_bytes_trimmed(&self) -> Cow<'_, [u8]> { + match self.as_le_bytes() { + Cow::Borrowed(slice) => Cow::Borrowed(crate::utils::trim_end_slice(slice, &0)), + Cow::Owned(mut vec) => { + crate::utils::trim_end_vec(&mut vec, &0); + Cow::Owned(vec) + } + } + } + + /// Converts the [`Uint`] to a little-endian byte array of size exactly + /// [`Self::BYTES`]. + /// + /// # Panics + /// + /// Panics if the generic parameter `BYTES` is not exactly [`Self::BYTES`]. + /// Ideally this would be a compile time error, but this is blocked by + /// Rust issue [#60551]. + /// + /// [#60551]: https://github.com/rust-lang/rust/issues/60551 + #[inline] + #[must_use] + pub const fn to_le_bytes(&self) -> [u8; BYTES] { + // TODO: Use a `const {}` block for this assertion + assert!(BYTES == Self::BYTES, "BYTES must be equal to Self::BYTES"); + + // Specialized impl + #[cfg(target_endian = "little")] + // SAFETY: BYTES == Self::BYTES == self.as_le_slice().len() + return unsafe { *self.as_le_slice().as_ptr().cast() }; + + // Generic impl + #[cfg(target_endian = "big")] + { + let mut limbs = self.limbs; + let mut i = 0; + while i < LIMBS { + limbs[i] = limbs[i].to_le(); + i += 1; + } + // SAFETY: BYTES <= LIMBS * 8 + unsafe { *limbs.as_ptr().cast() } + } + } + + /// Converts the [`Uint`] to a little-endian byte vector of size exactly + /// [`Self::BYTES`]. + /// + /// This method is useful when [`Self::to_le_bytes`] can not be used because + /// byte size is not known compile time. + #[cfg(feature = "alloc")] + #[must_use] + #[inline] + pub fn to_le_bytes_vec(&self) -> Vec { + self.as_le_bytes().into_owned() + } + + /// Converts the [`Uint`] to a little-endian byte vector with trailing zeros + /// bytes removed. + #[cfg(feature = "alloc")] + #[must_use] + #[inline] + pub fn to_le_bytes_trimmed_vec(&self) -> Vec { + self.as_le_bytes_trimmed().into_owned() + } + + /// Converts the [`Uint`] to a big-endian byte array of size exactly + /// [`Self::BYTES`]. + /// + /// # Panics + /// + /// Panics if the generic parameter `BYTES` is not exactly [`Self::BYTES`]. + /// Ideally this would be a compile time error, but this is blocked by + /// Rust issue [#60551]. + /// + /// [#60551]: https://github.com/rust-lang/rust/issues/60551 + #[must_use] + #[inline] + pub const fn to_be_bytes(&self) -> [u8; BYTES] { + let mut bytes = self.to_le_bytes::(); + + // bytes.reverse() + let len = bytes.len(); + let half_len = len / 2; + let mut i = 0; + while i < half_len { + let tmp = bytes[i]; + bytes[i] = bytes[len - 1 - i]; + bytes[len - 1 - i] = tmp; + i += 1; + } + + bytes + } + + /// Converts the [`Uint`] to a big-endian byte vector of size exactly + /// [`Self::BYTES`]. + /// + /// This method is useful when [`Self::to_be_bytes`] can not be used because + /// byte size is not known compile time. + #[cfg(feature = "alloc")] + #[must_use] + #[inline] + pub fn to_be_bytes_vec(&self) -> Vec { + let mut bytes = self.to_le_bytes_vec(); + bytes.reverse(); + bytes + } + + /// Converts the [`Uint`] to a big-endian byte vector with leading zeros + /// bytes removed. + #[cfg(feature = "alloc")] + #[must_use] + #[inline] + pub fn to_be_bytes_trimmed_vec(&self) -> Vec { + let mut bytes = self.to_le_bytes_trimmed_vec(); + bytes.reverse(); + bytes + } + + /// Converts a big-endian byte array of size exactly + /// [`Self::BYTES`] to [`Uint`]. + /// + /// # Panics + /// + /// Panics if the generic parameter `BYTES` is not exactly [`Self::BYTES`]. + /// Ideally this would be a compile time error, but this is blocked by + /// Rust issue [#60551]. + /// + /// [#60551]: https://github.com/rust-lang/rust/issues/60551 + /// + /// Panics if the value is too large for the bit-size of the Uint. + #[must_use] + #[track_caller] + #[inline] + pub const fn from_be_bytes(bytes: [u8; BYTES]) -> Self { + // TODO: Use a `const {}` block for this assertion + assert!(BYTES == Self::BYTES, "BYTES must be equal to Self::BYTES"); + Self::from_be_slice(&bytes) + } + + /// Creates a new integer from a big endian slice of bytes. + /// + /// The slice is interpreted as a big endian number, and must be at most + /// [`Self::BYTES`] long. + /// + /// # Panics + /// + /// Panics if the value is larger than fits the [`Uint`]. + #[must_use] + #[track_caller] + #[inline] + pub const fn from_be_slice(bytes: &[u8]) -> Self { + match Self::try_from_be_slice(bytes) { + Some(value) => value, + None => panic!("Value too large for Uint"), + } + } + + /// Creates a new integer from a big endian slice of bytes. + /// + /// The slice is interpreted as a big endian number, and must be at most + /// [`Self::BYTES`] long. + /// + /// Returns [`None`] if the value is larger than fits the [`Uint`]. + #[must_use] + #[inline] + pub const fn try_from_be_slice(bytes: &[u8]) -> Option { + if bytes.len() > Self::BYTES { + return None; + } + + if Self::BYTES % 8 == 0 && bytes.len() == Self::BYTES { + // Optimized implementation for full-limb types. + let mut limbs = [0; LIMBS]; + let end = bytes.as_ptr_range().end; + let mut i = 0; + while i < LIMBS { + limbs[i] = u64::from_be_bytes(unsafe { *end.sub((i + 1) * 8).cast() }); + i += 1; + } + return Some(Self::from_limbs(limbs)); + } + + let mut limbs = [0; LIMBS]; + let mut i = 0; + let mut c = bytes.len(); + while i < bytes.len() { + c -= 1; + let (limb, byte) = (i / 8, i % 8); + limbs[limb] += (bytes[c] as u64) << (byte * 8); + i += 1; + } + if Self::LIMBS > 0 && limbs[Self::LIMBS - 1] > Self::MASK { + return None; + } + Some(Self::from_limbs(limbs)) + } + + /// Converts a little-endian byte array of size exactly + /// [`Self::BYTES`] to [`Uint`]. + /// + /// # Panics + /// + /// Panics if the generic parameter `BYTES` is not exactly [`Self::BYTES`]. + /// Ideally this would be a compile time error, but this is blocked by + /// Rust issue [#60551]. + /// + /// [#60551]: https://github.com/rust-lang/rust/issues/60551 + /// + /// Panics if the value is too large for the bit-size of the Uint. + #[must_use] + #[track_caller] + #[inline] + pub const fn from_le_bytes(bytes: [u8; BYTES]) -> Self { + // TODO: Use a `const {}` block for this assertion + assert!(BYTES == Self::BYTES, "BYTES must be equal to Self::BYTES"); + Self::from_le_slice(&bytes) + } + + /// Creates a new integer from a little endian slice of bytes. + /// + /// The slice is interpreted as a little endian number, and must be at most + /// [`Self::BYTES`] long. + /// + /// # Panics + /// + /// Panics if the value is larger than fits the [`Uint`]. + #[must_use] + #[track_caller] + #[inline] + pub const fn from_le_slice(bytes: &[u8]) -> Self { + match Self::try_from_le_slice(bytes) { + Some(value) => value, + None => panic!("Value too large for Uint"), + } + } + + /// Creates a new integer from a little endian slice of bytes. + /// + /// The slice is interpreted as a little endian number, and must be at most + /// [`Self::BYTES`] long. + /// + /// Returns [`None`] if the value is larger than fits the [`Uint`]. + #[must_use] + #[inline] + pub const fn try_from_le_slice(bytes: &[u8]) -> Option { + if bytes.len() > Self::BYTES { + return None; + } + + if Self::BYTES % 8 == 0 && bytes.len() == Self::BYTES { + // Optimized implementation for full-limb types. + let mut limbs = [0; LIMBS]; + let mut i = 0; + while i < LIMBS { + limbs[i] = u64::from_le_bytes(unsafe { *bytes.as_ptr().add(i * 8).cast() }); + i += 1; + } + return Some(Self::from_limbs(limbs)); + } + + let mut limbs = [0; LIMBS]; + let mut i = 0; + while i < bytes.len() { + let (limb, byte) = (i / 8, i % 8); + limbs[limb] += (bytes[i] as u64) << (byte * 8); + i += 1; + } + if Self::LIMBS > 0 && limbs[Self::LIMBS - 1] > Self::MASK { + return None; + } + Some(Self::from_limbs(limbs)) + } + + /// Writes the little-endian representation of the [`Uint`] to the given + /// buffer. The buffer must be large enough to hold [`Self::BYTES`] bytes. + /// + /// # Panics + /// + /// Panics if the buffer is not large enough to hold [`Self::BYTES`] bytes. + /// + /// # Returns + /// + /// The number of bytes written to the buffer (always equal to + /// [`Self::BYTES`], but often useful to make explicit for encoders). + #[inline] + pub fn copy_le_bytes_to(&self, buf: &mut [u8]) -> usize { + // This is debug only. Release panics occur later in copy_from_slice + debug_assert!( + buf.len() >= Self::BYTES, + "Buffer is too small to hold the bytes of the Uint" + ); + + #[cfg(target_endian = "little")] + buf[..Self::BYTES].copy_from_slice(self.as_le_slice()); + + #[cfg(target_endian = "big")] + { + let chunks = buf[..Self::BYTES].chunks_mut(8); + + self.limbs.iter().zip(chunks).for_each(|(&limb, chunk)| { + let le = limb.to_le_bytes(); + chunk.copy_from_slice(&le[..chunk.len()]); + }); + } + + Self::BYTES + } + + /// Writes the little-endian representation of the [`Uint`] to the given + /// buffer. The buffer must be large enough to hold [`Self::BYTES`] bytes. + /// + /// # Returns + /// + /// [`None`], if the buffer is not large enough to hold [`Self::BYTES`] + /// bytes, and does not modify the buffer. + /// + /// [`Some`] with the number of bytes written to the buffer (always + /// equal to [`Self::BYTES`], but often useful to make explicit for + /// encoders). + #[inline] + pub fn checked_copy_le_bytes_to(&self, buf: &mut [u8]) -> Option { + if buf.len() < Self::BYTES { + return None; + } + + Some(self.copy_le_bytes_to(buf)) + } + + /// Writes the big-endian representation of the [`Uint`] to the given + /// buffer. The buffer must be large enough to hold [`Self::BYTES`] bytes. + /// + /// # Panics + /// + /// Panics if the buffer is not large enough to hold [`Self::BYTES`] bytes. + /// + /// # Returns + /// + /// The number of bytes written to the buffer (always equal to + /// [`Self::BYTES`], but often useful to make explicit for encoders). + #[inline] + pub fn copy_be_bytes_to(&self, buf: &mut [u8]) -> usize { + // This is debug only. Release panics occur later in copy_from_slice + debug_assert!( + buf.len() >= Self::BYTES, + "Buffer is too small to hold the bytes of the Uint" + ); + + // start from the end of the slice + let chunks = buf[..Self::BYTES].rchunks_mut(8); + + self.limbs.iter().zip(chunks).for_each(|(&limb, chunk)| { + let be = limb.to_be_bytes(); + let copy_from = 8 - chunk.len(); + chunk.copy_from_slice(&be[copy_from..]); + }); + + Self::BYTES + } + + /// Writes the big-endian representation of the [`Uint`] to the given + /// buffer. The buffer must be large enough to hold [`Self::BYTES`] bytes. + /// + /// # Returns + /// + /// [`None`], if the buffer is not large enough to hold [`Self::BYTES`] + /// bytes, and does not modify the buffer. + /// + /// [`Some`] with the number of bytes written to the buffer (always + /// equal to [`Self::BYTES`], but often useful to make explicit for + /// encoders). + #[inline] + pub fn checked_copy_be_bytes_to(&self, buf: &mut [u8]) -> Option { + if buf.len() < Self::BYTES { + return None; + } + + Some(self.copy_be_bytes_to(buf)) + } +} + +/// Number of bytes required to represent the given number of bits. +/// +/// This needs to be public because it is used in the `Uint` type, +/// specifically in the [`to_be_bytes()`][Uint::to_be_bytes] and related +/// functions. +#[inline] +#[must_use] +pub const fn nbytes(bits: usize) -> usize { + (bits + 7) / 8 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::proptest; + + const N: Uint<128, 2> = + Uint::from_limbs([0x7890_1234_5678_9012_u64, 0x1234_5678_9012_3456_u64]); + const BE: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, + 0x12, + ]; + const LE: [u8; 16] = [ + 0x12, 0x90, 0x78, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, + 0x12, + ]; + + const K: Uint<72, 2> = Uint::from_limbs([0x3456_7890_1234_5678_u64, 0x12_u64]); + const KBE: [u8; 9] = [0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78]; + const KLE: [u8; 9] = [0x78, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12]; + + #[test] + const fn const_from_to_bytes() { + const NL: [u64; 2] = N.limbs; + const KL: [u64; 2] = K.limbs; + assert!(matches!(Uint::<128, 2>::from_be_bytes(BE).limbs, NL)); + assert!(matches!(Uint::<128, 2>::from_le_bytes(LE).limbs, NL)); + assert!(matches!(N.to_be_bytes::<{ BE.len() }>(), BE)); + assert!(matches!(N.to_le_bytes::<{ LE.len() }>(), LE)); + + assert!(matches!(Uint::<72, 2>::from_be_bytes(KBE).limbs, KL)); + assert!(matches!(Uint::<72, 2>::from_le_bytes(KLE).limbs, KL)); + assert!(matches!(K.to_be_bytes::<{ KBE.len() }>(), KBE)); + assert!(matches!(K.to_le_bytes::<{ KLE.len() }>(), KLE)); + + assert!(matches!(Uint::<0, 0>::ZERO.to_be_bytes::<0>(), [])); + assert!(matches!(Uint::<1, 1>::ZERO.to_be_bytes::<1>(), [0])); + assert!(matches!( + Uint::<1, 1>::from_limbs([1]).to_be_bytes::<1>(), + [1] + )); + assert!(matches!( + Uint::<16, 1>::from_limbs([0x1234]).to_be_bytes::<2>(), + [0x12, 0x34] + )); + + assert!(matches!(Uint::<0, 0>::ZERO.to_be_bytes::<0>(), [])); + assert!(matches!(Uint::<0, 0>::ZERO.to_le_bytes::<0>(), [])); + assert!(matches!(Uint::<1, 1>::ZERO.to_be_bytes::<1>(), [0])); + assert!(matches!(Uint::<1, 1>::ZERO.to_le_bytes::<1>(), [0])); + assert!(matches!( + Uint::<1, 1>::from_limbs([1]).to_be_bytes::<1>(), + [1] + )); + assert!(matches!( + Uint::<1, 1>::from_limbs([1]).to_le_bytes::<1>(), + [1] + )); + assert!(matches!( + Uint::<16, 1>::from_limbs([0x1234]).to_be_bytes::<2>(), + [0x12, 0x34] + )); + assert!(matches!( + Uint::<16, 1>::from_limbs([0x1234]).to_le_bytes::<2>(), + [0x34, 0x12] + )); + + assert!(matches!( + Uint::<63, 1>::from_limbs([0x010203]).to_be_bytes::<8>(), + [0, 0, 0, 0, 0, 1, 2, 3] + )); + assert!(matches!( + Uint::<63, 1>::from_limbs([0x010203]).to_le_bytes::<8>(), + [3, 2, 1, 0, 0, 0, 0, 0] + )); + } + + #[test] + fn test_from_bytes() { + assert_eq!(Uint::<0, 0>::from_be_bytes([]), Uint::ZERO); + assert_eq!(Uint::<0, 0>::from_le_bytes([]), Uint::ZERO); + assert_eq!( + Uint::<12, 1>::from_be_bytes([0x01, 0x23]), + Uint::from(0x0123) + ); + assert_eq!( + Uint::<12, 1>::from_le_bytes([0x23, 0x01]), + Uint::from(0x0123) + ); + assert_eq!( + Uint::<16, 1>::from_be_bytes([0x12, 0x34]), + Uint::from(0x1234) + ); + assert_eq!( + Uint::<16, 1>::from_le_bytes([0x34, 0x12]), + Uint::from(0x1234) + ); + + assert_eq!(Uint::from_be_bytes(BE), N); + assert_eq!(Uint::from_le_bytes(LE), N); + assert_eq!(Uint::from_be_bytes(KBE), K); + assert_eq!(Uint::from_le_bytes(KLE), K); + + assert_eq!(Uint::<128, 2>::try_from_be_slice(&BE), Some(N)); + assert_eq!( + Uint::<128, 2>::try_from_be_slice(&[&BE[..], &[0xff][..]].concat()), + None + ); + assert_eq!(Uint::<128, 2>::try_from_le_slice(&LE), Some(N)); + assert_eq!( + Uint::<128, 2>::try_from_le_slice(&[&LE[..], &[0xff]].concat()), + None + ); + assert_eq!(Uint::<72, 2>::try_from_be_slice(&KBE), Some(K)); + assert_eq!( + Uint::<72, 2>::try_from_be_slice(&[&KBE[..], &[0xff][..]].concat()), + None + ); + assert_eq!(Uint::<72, 2>::try_from_le_slice(&KLE), Some(K)); + assert_eq!( + Uint::<72, 2>::try_from_le_slice(&[&KLE[..], &[0xff]].concat()), + None + ); + } + + #[test] + fn test_to_bytes() { + assert_eq!(Uint::<0, 0>::ZERO.to_le_bytes(), [0_u8; 0]); + assert_eq!(Uint::<0, 0>::ZERO.to_be_bytes(), [0_u8; 0]); + assert_eq!(Uint::<12, 1>::from(0x0123_u64).to_le_bytes(), [0x23, 0x01]); + assert_eq!(Uint::<12, 1>::from(0x0123_u64).to_be_bytes(), [0x01, 0x23]); + assert_eq!(Uint::<16, 1>::from(0x1234_u64).to_le_bytes(), [0x34, 0x12]); + assert_eq!(Uint::<16, 1>::from(0x1234_u64).to_be_bytes(), [0x12, 0x34]); + assert_eq!(K.to_be_bytes(), KBE); + assert_eq!(K.to_le_bytes(), KLE); + } + + #[test] + fn test_bytes_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + const BYTES: usize = nbytes(BITS); + proptest!(|(value: Uint)| { + assert_eq!(value, Uint::try_from_le_slice(&value.as_le_bytes()).unwrap()); + assert_eq!(value, Uint::try_from_le_slice(&value.as_le_bytes_trimmed()).unwrap()); + assert_eq!(value, Uint::try_from_be_slice(&value.to_be_bytes_trimmed_vec()).unwrap()); + assert_eq!(value, Uint::try_from_le_slice(&value.to_le_bytes_trimmed_vec()).unwrap()); + assert_eq!(value, Uint::from_be_bytes(value.to_be_bytes::())); + assert_eq!(value, Uint::from_le_bytes(value.to_le_bytes::())); + }); + }); + } + + #[test] + fn copy_to() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + const BYTES: usize = nbytes(BITS); + proptest!(|(value: Uint)|{ + let mut buf = [0; BYTES]; + value.copy_le_bytes_to(&mut buf); + assert_eq!(buf, value.to_le_bytes::()); + assert_eq!(value, Uint::try_from_le_slice(&buf).unwrap()); + + let mut buf = [0; BYTES]; + value.copy_be_bytes_to(&mut buf); + assert_eq!(buf, value.to_be_bytes::()); + assert_eq!(value, Uint::try_from_be_slice(&buf).unwrap()); + }); + }); + } + + #[test] + fn checked_copy_to() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + const BYTES: usize = nbytes(BITS); + proptest!(|(value: Uint)|{ + if BYTES != 0 { + let mut buf = [0; BYTES]; + let too_short = buf.len() - 1; + + assert_eq!(value.checked_copy_le_bytes_to(&mut buf[..too_short]), None); + assert_eq!(buf, [0; BYTES], "buffer was modified"); + + assert_eq!(value.checked_copy_be_bytes_to(&mut buf[..too_short]), None); + assert_eq!(buf, [0; BYTES], "buffer was modified"); + } + }); + }); + } +} diff --git a/guest-libs/ruint/src/cmp.rs b/guest-libs/ruint/src/cmp.rs new file mode 100644 index 0000000000..c1b69403b4 --- /dev/null +++ b/guest-libs/ruint/src/cmp.rs @@ -0,0 +1,58 @@ +use crate::Uint; +use core::cmp::Ordering; + +impl PartialOrd for Uint { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Uint { + #[cfg(not(target_os = "zkvm"))] + #[inline] + fn cmp(&self, rhs: &Self) -> Ordering { + crate::algorithms::cmp(self.as_limbs(), rhs.as_limbs()) + } + + #[cfg(target_os = "zkvm")] + #[inline] + fn cmp(&self, rhs: &Self) -> Ordering { + use crate::support::zkvm::zkvm_u256_cmp_impl; + if BITS == 256 { + return unsafe { + zkvm_u256_cmp_impl( + self.limbs.as_ptr() as *const u8, + rhs.limbs.as_ptr() as *const u8, + ) + }; + } + self.cmp(rhs) + } +} + +impl Uint { + /// Returns true if the value is zero. + #[inline] + #[must_use] + pub fn is_zero(&self) -> bool { + *self == Self::ZERO + } +} + +#[cfg(test)] +mod tests { + use crate::Uint; + + #[test] + fn test_is_zero() { + assert!(Uint::<0, 0>::ZERO.is_zero()); + assert!(Uint::<1, 1>::ZERO.is_zero()); + assert!(Uint::<7, 1>::ZERO.is_zero()); + assert!(Uint::<64, 1>::ZERO.is_zero()); + + assert!(!Uint::<1, 1>::from_limbs([1]).is_zero()); + assert!(!Uint::<7, 1>::from_limbs([1]).is_zero()); + assert!(!Uint::<64, 1>::from_limbs([1]).is_zero()); + } +} diff --git a/guest-libs/ruint/src/const_for.rs b/guest-libs/ruint/src/const_for.rs new file mode 100644 index 0000000000..f15717755f --- /dev/null +++ b/guest-libs/ruint/src/const_for.rs @@ -0,0 +1,68 @@ +/// Compile time for loops with a `const` variable for testing. +/// +/// Repeats a block of code with different values assigned to a constant. +/// +/// ```rust +/// # use ruint::{const_for, nlimbs, Uint}; +/// const_for!(BITS in [0, 10, 100] { +/// const LIMBS: usize = nlimbs(BITS); +/// println!("{:?}", Uint::::MAX); +/// }); +/// ``` +/// +/// is equivalent to +/// +/// ```rust +/// # use ruint::{const_for, Uint}; +/// println!("{:?}", Uint::<0, 0>::MAX); +/// println!("{:?}", Uint::<10, 1>::MAX); +/// println!("{:?}", Uint::<100, 2>::MAX); +/// ``` +/// +/// It comes with two built-in lists: `NON_ZERO` which is equivalent to +/// +/// ```text +/// [1, 2, 63, 64, 65, 127, 128, 129, 256, 384, 512, 4096] +/// ``` +/// +/// and `SIZES` which is the same but also has `0` as a value. +/// +/// In combination with [`proptest!`][proptest::proptest] this allows for +/// testing over a large range of [`Uint`][crate::Uint] types and values: +/// +/// ```rust +/// # use proptest::prelude::*; +/// # use ruint::{const_for, nlimbs, Uint}; +/// const_for!(BITS in SIZES { +/// const LIMBS: usize = nlimbs(BITS); +/// proptest!(|(value: Uint)| { +/// // ... test code +/// }); +/// }); +/// ``` +#[macro_export] +macro_rules! const_for { + ($C:ident in [ $( $n:literal ),* ] $x:block) => { + $({ + const $C: usize = $n; + $x + })* + }; + ($C:ident in SIZES $x:block) => { + $crate::const_for!($C in [0] $x); + $crate::const_for!($C in NON_ZERO $x); + }; + ($C:ident in NON_ZERO $x:block) => { + $crate::const_for!($C in [1, 2, 63, 64, 65, 127, 128, 129, 256, 384, 512, 4096] $x); + }; + ($C:ident in BENCH $x:block) => { + $crate::const_for!($C in [0, 64, 128, 192, 256, 384, 512, 4096] $x); + }; + ($C:ident in $S:ident if ( $c:expr ) $x:block) => { + $crate::const_for!($C in $S { + if $c { + $x + } + }); + }; +} diff --git a/guest-libs/ruint/src/div.rs b/guest-libs/ruint/src/div.rs new file mode 100644 index 0000000000..56e4cee034 --- /dev/null +++ b/guest-libs/ruint/src/div.rs @@ -0,0 +1,142 @@ +use crate::{algorithms, Uint}; +use core::ops::{Div, DivAssign, Rem, RemAssign}; + +impl Uint { + /// Computes `self / rhs`, returning [`None`] if `rhs == 0`. + #[inline] + #[must_use] + #[allow(clippy::missing_const_for_fn)] // False positive + pub fn checked_div(self, rhs: Self) -> Option { + if rhs.is_zero() { + return None; + } + Some(self.div(rhs)) + } + + /// Computes `self % rhs`, returning [`None`] if `rhs == 0`. + #[inline] + #[must_use] + #[allow(clippy::missing_const_for_fn)] // False positive + pub fn checked_rem(self, rhs: Self) -> Option { + if rhs.is_zero() { + return None; + } + Some(self.rem(rhs)) + } + + /// Computes `self / rhs` rounding up. + /// + /// # Panics + /// + /// Panics if `rhs == 0`. + #[inline] + #[must_use] + #[track_caller] + pub fn div_ceil(self, rhs: Self) -> Self { + let (q, r) = self.div_rem(rhs); + if r.is_zero() { + q + } else { + q + Self::ONE + } + } + + /// Computes `self / rhs` and `self % rhs`. + /// + /// # Panics + /// + /// Panics if `rhs == 0`. + #[inline] + #[must_use] + #[track_caller] + pub fn div_rem(mut self, mut rhs: Self) -> (Self, Self) { + algorithms::div(&mut self.limbs, &mut rhs.limbs); + (self, rhs) + } + + /// Computes `self / rhs` rounding down. + /// + /// # Panics + /// + /// Panics if `rhs == 0`. + #[inline] + #[must_use] + #[track_caller] + pub fn wrapping_div(self, rhs: Self) -> Self { + self.div_rem(rhs).0 + } + + /// Computes `self % rhs`. + /// + /// # Panics + /// + /// Panics if `rhs == 0`. + #[inline] + #[must_use] + #[track_caller] + pub fn wrapping_rem(self, rhs: Self) -> Self { + self.div_rem(rhs).1 + } +} + +impl_bin_op!(Div, div, DivAssign, div_assign, wrapping_div); +impl_bin_op!(Rem, rem, RemAssign, rem_assign, wrapping_rem); + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::{prop_assume, proptest}; + + #[test] + fn test_div_ceil() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(n: U, mut d: U)| { + d >>= BITS / 2; // make d small + prop_assume!(d != U::ZERO); + let qf = n / d; + let qc = n.div_ceil(d); + assert!(qf <= qc); + assert!(qf == qc || qf == qc - U::ONE); + if qf == qc { + assert!(n % d == U::ZERO); + } + }); + }); + } + + #[test] + fn test_divrem() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(n: U, mut d: u64)| { + if BITS < 64 { + d &= U::MASK; + } + if d == 0 { + d = 1; + } + let d = U::from(d); + let (q, r) = n.div_rem(d); + assert!(r < d); + assert_eq!(q * d + r, n); + }); + proptest!(|(n: U, mut d: U)| { + d >>= BITS / 2; // make d small + prop_assume!(d != U::ZERO); + let (q, r) = n.div_rem(d); + assert!(r < d); + assert_eq!(q * d + r, n); + }); + proptest!(|(n: U, d: U)| { + prop_assume!(d != U::ZERO); + let (q, r) = n.div_rem(d); + assert!(r < d); + assert_eq!(q * d + r, n); + }); + }); + } +} diff --git a/guest-libs/ruint/src/fmt.rs b/guest-libs/ruint/src/fmt.rs new file mode 100644 index 0000000000..568129fccd --- /dev/null +++ b/guest-libs/ruint/src/fmt.rs @@ -0,0 +1,216 @@ +#![allow(clippy::missing_inline_in_public_items)] // allow format functions +#![cfg(feature = "alloc")] + +use crate::Uint; +use core::{ + fmt::{self, Write}, + mem::MaybeUninit, +}; + +mod base { + pub(super) trait Base { + /// Highest power of the base that fits in a `u64`. + const MAX: u64; + /// Number of characters written using `MAX` as the base in + /// `to_base_be`. + /// + /// This is `MAX.log(base)`. + const WIDTH: usize; + /// The prefix for the base. + const PREFIX: &'static str; + } + + pub(super) struct Binary; + impl Base for Binary { + const MAX: u64 = 1 << 63; + const WIDTH: usize = 63; + const PREFIX: &'static str = "0b"; + } + + pub(super) struct Octal; + impl Base for Octal { + const MAX: u64 = 1 << 63; + const WIDTH: usize = 21; + const PREFIX: &'static str = "0o"; + } + + pub(super) struct Decimal; + impl Base for Decimal { + const MAX: u64 = 10_000_000_000_000_000_000; + const WIDTH: usize = 19; + const PREFIX: &'static str = ""; + } + + pub(super) struct Hexadecimal; + impl Base for Hexadecimal { + const MAX: u64 = 1 << 60; + const WIDTH: usize = 15; + const PREFIX: &'static str = "0x"; + } +} +use base::Base; + +macro_rules! write_digits { + ($self:expr, $f:expr; $base:ty, $base_char:literal) => { + if LIMBS == 0 || $self.is_zero() { + return $f.pad_integral(true, <$base>::PREFIX, "0"); + } + // Use `BITS` for all bases since `generic_const_exprs` is not yet stable. + let mut buffer = DisplayBuffer::::new(); + for (i, spigot) in $self.to_base_be(<$base>::MAX).enumerate() { + write!( + buffer, + concat!("{:0width$", $base_char, "}"), + spigot, + width = if i == 0 { 0 } else { <$base>::WIDTH }, + ) + .unwrap(); + } + return $f.pad_integral(true, <$base>::PREFIX, buffer.as_str()); + }; +} + +impl fmt::Display for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write_digits!(self, f; base::Decimal, ""); + } +} + +impl fmt::Debug for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Binary for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write_digits!(self, f; base::Binary, "b"); + } +} + +impl fmt::Octal for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write_digits!(self, f; base::Octal, "o"); + } +} + +impl fmt::LowerHex for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write_digits!(self, f; base::Hexadecimal, "x"); + } +} + +impl fmt::UpperHex for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write_digits!(self, f; base::Hexadecimal, "X"); + } +} + +struct DisplayBuffer { + buf: [MaybeUninit; SIZE], + len: usize, +} + +impl DisplayBuffer { + #[inline] + const fn new() -> Self { + Self { + buf: unsafe { MaybeUninit::uninit().assume_init() }, + len: 0, + } + } + + #[inline] + fn as_str(&self) -> &str { + // SAFETY: `buf` is only written to by the `fmt::Write::write_str` + // implementation which writes a valid UTF-8 string to `buf` and + // correctly sets `len`. + unsafe { core::str::from_utf8_unchecked(&self.as_bytes_full()[..self.len]) } + } + + #[inline] + const unsafe fn as_bytes_full(&self) -> &[u8] { + unsafe { &*(self.buf.as_slice() as *const [_] as *const [u8]) } + } +} + +impl fmt::Write for DisplayBuffer { + fn write_str(&mut self, s: &str) -> fmt::Result { + if self.len + s.len() > SIZE { + return Err(fmt::Error); + } + unsafe { + let dst = self.buf.as_mut_ptr().add(self.len).cast(); + core::ptr::copy_nonoverlapping(s.as_ptr(), dst, s.len()); + } + self.len += s.len(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::{prop_assert_eq, proptest}; + + #[allow(unused_imports)] + use alloc::string::ToString; + + #[allow(clippy::unreadable_literal)] + const N: Uint<256, 4> = Uint::from_limbs([ + 0xa8ec92344438aaf4_u64, + 0x9819ebdbd1faaab1_u64, + 0x573b1a7064c19c1a_u64, + 0xc85ef7d79691fe79_u64, + ]); + + #[test] + fn test_num() { + assert_eq!( + N.to_string(), + "90630363884335538722706632492458228784305343302099024356772372330524102404852" + ); + assert_eq!( + format!("{N:x}"), + "c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4" + ); + assert_eq!( + format!("{N:b}"), + "1100100001011110111101111101011110010110100100011111111001111001010101110011101100011010011100000110010011000001100111000001101010011000000110011110101111011011110100011111101010101010101100011010100011101100100100100011010001000100001110001010101011110100" + ); + assert_eq!( + format!("{N:o}"), + "14413675753626443771712563543234062301470152300636573364375252543243544443210416125364" + ); + } + + #[test] + fn test_fmt() { + proptest!(|(value: u128)| { + let n: Uint<128, 2> = Uint::from(value); + + prop_assert_eq!(format!("{n:b}"), format!("{value:b}")); + prop_assert_eq!(format!("{n:064b}"), format!("{value:064b}")); + prop_assert_eq!(format!("{n:#b}"), format!("{value:#b}")); + + prop_assert_eq!(format!("{n:o}"), format!("{value:o}")); + prop_assert_eq!(format!("{n:064o}"), format!("{value:064o}")); + prop_assert_eq!(format!("{n:#o}"), format!("{value:#o}")); + + prop_assert_eq!(format!("{n:}"), format!("{value:}")); + prop_assert_eq!(format!("{n:064}"), format!("{value:064}")); + prop_assert_eq!(format!("{n:#}"), format!("{value:#}")); + prop_assert_eq!(format!("{n:?}"), format!("{value:?}")); + prop_assert_eq!(format!("{n:064}"), format!("{value:064?}")); + prop_assert_eq!(format!("{n:#?}"), format!("{value:#?}")); + + prop_assert_eq!(format!("{n:x}"), format!("{value:x}")); + prop_assert_eq!(format!("{n:064x}"), format!("{value:064x}")); + prop_assert_eq!(format!("{n:#x}"), format!("{value:#x}")); + + prop_assert_eq!(format!("{n:X}"), format!("{value:X}")); + prop_assert_eq!(format!("{n:064X}"), format!("{value:064X}")); + prop_assert_eq!(format!("{n:#X}"), format!("{value:#X}")); + }); + } +} diff --git a/guest-libs/ruint/src/from.rs b/guest-libs/ruint/src/from.rs new file mode 100644 index 0000000000..7ca88cef9e --- /dev/null +++ b/guest-libs/ruint/src/from.rs @@ -0,0 +1,768 @@ +// FEATURE: (BLOCKED) It would be nice to impl From<_> as well, but then the +// generic implementation `impl, U> TryFrom for T` conflicts with +// our own implementation. This means we can only implement one. +// In principle this can be worked around by `specialization`, but that +// triggers other compiler issues at the moment. + +// impl From for Uint +// where +// [(); nlimbs(BITS)]:, +// Uint: TryFrom, +// { +// fn from(t: T) -> Self { +// Self::try_from(t).unwrap() +// } +// } +// See + +// FEATURE: (BLOCKED) It would be nice if we could make TryFrom assignment work +// for all Uints. +// impl< +// const BITS_SRC: usize, +// const LIMBS_SRC: usize, +// const BITS_DST: usize, +// const LIMBS_DST: usize, +// > TryFrom> for Uint +// { +// type Error = ToUintError; + +// fn try_from(value: Uint) -> Result { +// } +// } + +use crate::Uint; +use core::{fmt, fmt::Debug}; + +/// Error for [`TryFrom`][TryFrom] for [`Uint`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum ToUintError { + /// Value is too large to fit the Uint. + /// + /// `.0` is `BITS` and `.1` is the wrapped value. + ValueTooLarge(usize, T), + + /// Negative values can not be represented as Uint. + /// + /// `.0` is `BITS` and `.1` is the wrapped value. + ValueNegative(usize, T), + + /// 'Not a number' (NaN) can not be represented as Uint + NotANumber(usize), +} + +#[cfg(feature = "std")] +impl std::error::Error for ToUintError {} + +impl fmt::Display for ToUintError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ValueTooLarge(bits, _) => write!(f, "Value is too large for Uint<{bits}>"), + Self::ValueNegative(bits, _) => { + write!(f, "Negative values cannot be represented as Uint<{bits}>") + } + Self::NotANumber(bits) => { + write!( + f, + "'Not a number' (NaN) cannot be represented as Uint<{bits}>" + ) + } + } + } +} + +/// Error for [`TryFrom`][TryFrom]. +#[allow(clippy::derive_partial_eq_without_eq)] // False positive +#[allow(clippy::module_name_repetitions)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum FromUintError { + /// The Uint value is too large for the target type. + /// + /// `.0` number of `BITS` in the Uint, `.1` is the wrapped value and + /// `.2` is the maximum representable value in the target type. + Overflow(usize, T, T), +} + +#[cfg(feature = "std")] +impl std::error::Error for FromUintError {} + +impl fmt::Display for FromUintError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Overflow(bits, ..) => write!( + f, + "Uint<{bits}> value is too large for {}", + core::any::type_name::() + ), + } + } +} + +/// Error for [`TryFrom`][TryFrom] for [`ark_ff`](https://docs.rs/ark-ff) and others. +#[allow(dead_code)] // This is used by some support features. +#[derive(Debug, Clone, Copy)] +pub enum ToFieldError { + /// Number is equal or larger than the target field modulus. + NotInField, +} + +#[cfg(feature = "std")] +impl std::error::Error for ToFieldError {} + +impl fmt::Display for ToFieldError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NotInField => { + f.write_str("Number is equal or larger than the target field modulus.") + } + } + } +} + +impl Uint { + /// Constructs a new [`Uint`] from a u64. + /// + /// Saturates at the maximum value of the [`Uint`] if the value is too + /// large. + pub(crate) const fn const_from_u64(x: u64) -> Self { + if BITS == 0 || (BITS < 64 && x >= 1 << BITS) { + return Self::MAX; + } + let mut limbs = [0; LIMBS]; + limbs[0] = x; + Self::from_limbs(limbs) + } + + /// Construct a new [`Uint`] from the value. + /// + /// # Panics + /// + /// Panics if the conversion fails, for example if the value is too large + /// for the bit-size of the [`Uint`]. The panic will be attributed to the + /// call site. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(U8::from(142_u16), 142_U8); + /// assert_eq!(U64::from(0x7014b4c2d1f2_U256), 0x7014b4c2d1f2_U64); + /// assert_eq!(U64::from(3.145), 3_U64); + /// # } + /// ``` + #[inline] + #[must_use] + #[track_caller] + pub fn from(value: T) -> Self + where + Self: UintTryFrom, + { + match Self::uint_try_from(value) { + Ok(n) => n, + Err(e) => panic!("Uint conversion error: {e}"), + } + } + + /// Construct a new [`Uint`] from the value saturating the value to the + /// minimum or maximum value of the [`Uint`]. + /// + /// If the value is not a number (like `f64::NAN`), then the result is + /// set zero. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(U8::saturating_from(300_u16), 255_U8); + /// assert_eq!(U8::saturating_from(-10_i16), 0_U8); + /// assert_eq!(U32::saturating_from(0x7014b4c2d1f2_U256), U32::MAX); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn saturating_from(value: T) -> Self + where + Self: UintTryFrom, + { + match Self::uint_try_from(value) { + Ok(n) => n, + Err(ToUintError::ValueTooLarge(..)) => Self::MAX, + Err(ToUintError::ValueNegative(..) | ToUintError::NotANumber(_)) => Self::ZERO, + } + } + + /// Construct a new [`Uint`] from the value saturating the value to the + /// minimum or maximum value of the [`Uint`]. + /// + /// If the value is not a number (like `f64::NAN`), then the result is + /// set zero. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(U8::wrapping_from(300_u16), 44_U8); + /// assert_eq!(U8::wrapping_from(-10_i16), 246_U8); + /// assert_eq!(U32::wrapping_from(0x7014b4c2d1f2_U256), 0xb4c2d1f2_U32); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn wrapping_from(value: T) -> Self + where + Self: UintTryFrom, + { + match Self::uint_try_from(value) { + Ok(n) | Err(ToUintError::ValueTooLarge(_, n) | ToUintError::ValueNegative(_, n)) => n, + Err(ToUintError::NotANumber(_)) => Self::ZERO, + } + } + + /// # Panics + /// + /// Panics if the conversion fails, for example if the value is too large + /// for the bit-size of the target type. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(300_U12.to::(), 300_i16); + /// assert_eq!(300_U12.to::(), 300_U256); + /// # } + /// ``` + #[inline] + #[must_use] + #[track_caller] + pub fn to(&self) -> T + where + Self: UintTryTo, + T: Debug, + { + self.uint_try_to().expect("Uint conversion error") + } + + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(300_U12.wrapping_to::(), 44_i8); + /// assert_eq!(255_U32.wrapping_to::(), -1_i8); + /// assert_eq!(0x1337cafec0d3_U256.wrapping_to::(), 0xcafec0d3_U32); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn wrapping_to(&self) -> T + where + Self: UintTryTo, + { + match self.uint_try_to() { + Ok(n) | Err(FromUintError::Overflow(_, n, _)) => n, + } + } + + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(300_U12.saturating_to::(), 300_i16); + /// assert_eq!(255_U32.saturating_to::(), 127); + /// assert_eq!(0x1337cafec0d3_U256.saturating_to::(), U32::MAX); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn saturating_to(&self) -> T + where + Self: UintTryTo, + { + match self.uint_try_to() { + Ok(n) | Err(FromUintError::Overflow(_, _, n)) => n, + } + } + + /// Construct a new [`Uint`] from a potentially different sized [`Uint`]. + /// + /// # Panics + /// + /// Panics if the value is too large for the target type. + #[inline] + #[doc(hidden)] + #[must_use] + #[track_caller] + #[deprecated(since = "1.4.0", note = "Use `::from()` instead.")] + pub fn from_uint( + value: Uint, + ) -> Self { + Self::from_limbs_slice(value.as_limbs()) + } + + #[inline] + #[doc(hidden)] + #[must_use] + #[deprecated(since = "1.4.0", note = "Use `::checked_from()` instead.")] + pub fn checked_from_uint( + value: Uint, + ) -> Option { + Self::checked_from_limbs_slice(value.as_limbs()) + } +} + +/// ⚠️ Workaround for [Rust issue #50133](https://github.com/rust-lang/rust/issues/50133). +/// Use [`TryFrom`] instead. +/// +/// We cannot implement [`TryFrom`] for [`Uint`] directly, but we can +/// create a new identical trait and implement it there. We can even give this +/// trait a blanket implementation inheriting all [`TryFrom<_>`] +/// implementations. +#[allow(clippy::module_name_repetitions)] +pub trait UintTryFrom: Sized { + #[doc(hidden)] + fn uint_try_from(value: T) -> Result>; +} + +/// Blanket implementation for any type that implements [`TryFrom`]. +impl UintTryFrom for Uint +where + Self: TryFrom>, +{ + #[inline] + fn uint_try_from(value: T) -> Result> { + Self::try_from(value) + } +} + +impl + UintTryFrom> for Uint +{ + #[inline] + fn uint_try_from(value: Uint) -> Result> { + let (n, overflow) = Self::overflowing_from_limbs_slice(value.as_limbs()); + if overflow { + Err(ToUintError::ValueTooLarge(BITS, n)) + } else { + Ok(n) + } + } +} + +/// ⚠️ Workaround for [Rust issue #50133](https://github.com/rust-lang/rust/issues/50133). +/// Use [`TryFrom`] instead. +pub trait UintTryTo: Sized { + #[doc(hidden)] + fn uint_try_to(&self) -> Result>; +} + +impl UintTryTo for Uint +where + T: for<'a> TryFrom<&'a Self, Error = FromUintError>, +{ + #[inline] + fn uint_try_to(&self) -> Result> { + T::try_from(self) + } +} + +impl + UintTryTo> for Uint +{ + #[inline] + fn uint_try_to( + &self, + ) -> Result, FromUintError>> { + let (n, overflow) = Uint::overflowing_from_limbs_slice(self.as_limbs()); + if overflow { + Err(FromUintError::Overflow(BITS_DST, n, Uint::MAX)) + } else { + Ok(n) + } + } +} + +// u64 is a single limb, so this is the base case +impl TryFrom for Uint { + type Error = ToUintError; + + #[inline] + fn try_from(value: u64) -> Result { + if LIMBS <= 1 { + if value > Self::MASK { + // Construct wrapped value + let mut limbs = [0; LIMBS]; + if LIMBS == 1 { + limbs[0] = value & Self::MASK; + } + return Err(ToUintError::ValueTooLarge(BITS, Self::from_limbs(limbs))); + } + if LIMBS == 0 { + return Ok(Self::ZERO); + } + } + let mut limbs = [0; LIMBS]; + limbs[0] = value; + Ok(Self::from_limbs(limbs)) + } +} + +// u128 version is handled specially in because it covers two limbs. +impl TryFrom for Uint { + type Error = ToUintError; + + #[inline] + #[allow(clippy::cast_lossless)] + #[allow(clippy::cast_possible_truncation)] + fn try_from(value: u128) -> Result { + if value <= u64::MAX as u128 { + return Self::try_from(value as u64); + } + if Self::LIMBS < 2 { + return Self::try_from(value as u64) + .and_then(|n| Err(ToUintError::ValueTooLarge(BITS, n))); + } + let mut limbs = [0; LIMBS]; + limbs[0] = value as u64; + limbs[1] = (value >> 64) as u64; + if Self::LIMBS == 2 && limbs[1] > Self::MASK { + limbs[1] %= Self::MASK; + Err(ToUintError::ValueTooLarge(BITS, Self::from_limbs(limbs))) + } else { + Ok(Self::from_limbs(limbs)) + } + } +} + +// Unsigned int version upcast to u64 +macro_rules! impl_from_unsigned_int { + ($uint:ty) => { + impl TryFrom<$uint> for Uint { + type Error = ToUintError; + + #[inline] + fn try_from(value: $uint) -> Result { + Self::try_from(value as u64) + } + } + }; +} + +impl_from_unsigned_int!(bool); +impl_from_unsigned_int!(u8); +impl_from_unsigned_int!(u16); +impl_from_unsigned_int!(u32); +impl_from_unsigned_int!(usize); + +// Signed int version check for positive and delegate to the corresponding +// `uint`. +macro_rules! impl_from_signed_int { + ($int:ty, $uint:ty) => { + impl TryFrom<$int> for Uint { + type Error = ToUintError; + + #[inline] + fn try_from(value: $int) -> Result { + if value.is_negative() { + Err(match Self::try_from(value as $uint) { + Ok(n) | Err(ToUintError::ValueTooLarge(_, n)) => { + ToUintError::ValueNegative(BITS, n) + } + _ => unreachable!(), + }) + } else { + Self::try_from(value as $uint) + } + } + } + }; +} + +impl_from_signed_int!(i8, u8); +impl_from_signed_int!(i16, u16); +impl_from_signed_int!(i32, u32); +impl_from_signed_int!(i64, u64); +impl_from_signed_int!(i128, u128); +impl_from_signed_int!(isize, usize); + +#[cfg(feature = "std")] +impl TryFrom for Uint { + type Error = ToUintError; + + // TODO: Correctly implement wrapping. + #[inline] + fn try_from(value: f64) -> Result { + if value.is_nan() { + return Err(ToUintError::NotANumber(BITS)); + } + if value < 0.0 { + let wrapped = match Self::try_from(value.abs()) { + Ok(n) | Err(ToUintError::ValueTooLarge(_, n)) => n, + _ => Self::ZERO, + } + .wrapping_neg(); + return Err(ToUintError::ValueNegative(BITS, wrapped)); + } + #[allow(clippy::cast_precision_loss)] // BITS is small-ish + let modulus = (Self::BITS as f64).exp2(); + if value >= modulus { + let wrapped = match Self::try_from(value % modulus) { + Ok(n) | Err(ToUintError::ValueTooLarge(_, n)) => n, + _ => Self::ZERO, + }; + return Err(ToUintError::ValueTooLarge(BITS, wrapped)); // Wrapping + } + if value < 0.5 { + return Ok(Self::ZERO); + } + // All non-normal cases should have been handled above + assert!(value.is_normal()); + + // Add offset to round to nearest integer. + let value = value + 0.5; + + // Parse IEEE-754 double + // Sign should be zero, exponent should be >= 0. + let bits = value.to_bits(); + let sign = bits >> 63; + assert!(sign == 0); + let biased_exponent = (bits >> 52) & 0x7ff; + assert!(biased_exponent >= 1023); + let exponent = biased_exponent - 1023; + let fraction = bits & 0x000f_ffff_ffff_ffff; + let mantissa = 0x0010_0000_0000_0000 | fraction; + + // Convert mantissa * 2^(exponent - 52) to Uint + #[allow(clippy::cast_possible_truncation)] // exponent is small-ish + if exponent as usize > Self::BITS + 52 { + // Wrapped value is zero because the value is extended with zero bits. + return Err(ToUintError::ValueTooLarge(BITS, Self::ZERO)); + } + if exponent <= 52 { + // Truncate mantissa + Self::try_from(mantissa >> (52 - exponent)) + } else { + #[allow(clippy::cast_possible_truncation)] // exponent is small-ish + let exponent = exponent as usize - 52; + let n = Self::try_from(mantissa)?; + let (n, overflow) = n.overflowing_shl(exponent); + if overflow { + Err(ToUintError::ValueTooLarge(BITS, n)) + } else { + Ok(n) + } + } + } +} + +#[cfg(feature = "std")] +impl TryFrom for Uint { + type Error = ToUintError; + + #[inline] + fn try_from(value: f32) -> Result { + #[allow(clippy::cast_lossless)] + Self::try_from(value as f64) + } +} + +// Convert Uint to integer types + +// Required because a generic rule violates the orphan rule +macro_rules! to_value_to_ref { + ($t:ty) => { + impl TryFrom> for $t { + type Error = FromUintError; + + #[inline] + fn try_from(value: Uint) -> Result { + Self::try_from(&value) + } + } + }; +} + +to_value_to_ref!(bool); + +impl TryFrom<&Uint> for bool { + type Error = FromUintError; + + #[inline] + fn try_from(value: &Uint) -> Result { + if BITS == 0 { + return Ok(false); + } + if value.bit_len() > 1 { + return Err(Self::Error::Overflow(BITS, value.bit(0), true)); + } + Ok(value.as_limbs()[0] != 0) + } +} + +macro_rules! to_int { + ($($int:ty)*) => {$( + to_value_to_ref!($int); + + impl TryFrom<&Uint> for $int { + type Error = FromUintError; + + #[inline] + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + fn try_from(value: &Uint) -> Result { + const SIGNED: bool = <$int>::MIN != 0; + const CAPACITY: usize = if SIGNED { <$int>::BITS - 1 } else { <$int>::BITS } as usize; + if BITS == 0 { + return Ok(0); + } + if value.bit_len() > CAPACITY { + return Err(Self::Error::Overflow( + BITS, + value.limbs[0] as Self, + Self::MAX, + )); + } + Ok(value.as_limbs()[0] as Self) + } + } + )*}; +} + +to_int!(i8 u8 i16 u16 i32 u32 i64 u64 isize usize); + +to_value_to_ref!(i128); + +impl TryFrom<&Uint> for i128 { + type Error = FromUintError; + + #[inline] + #[allow(clippy::cast_lossless)] // Safe casts + #[allow(clippy::use_self)] // More readable + fn try_from(value: &Uint) -> Result { + if BITS == 0 { + return Ok(0); + } + let mut result = value.limbs[0] as i128; + if BITS <= 64 { + return Ok(result); + } + result |= (value.limbs[1] as i128) << 64; + if value.bit_len() > 127 { + return Err(Self::Error::Overflow(BITS, result, i128::MAX)); + } + Ok(result) + } +} + +to_value_to_ref!(u128); + +impl TryFrom<&Uint> for u128 { + type Error = FromUintError; + + #[inline] + #[allow(clippy::cast_lossless)] // Safe casts + #[allow(clippy::use_self)] // More readable + fn try_from(value: &Uint) -> Result { + if BITS == 0 { + return Ok(0); + } + let mut result = value.limbs[0] as u128; + if BITS <= 64 { + return Ok(result); + } + result |= (value.limbs[1] as u128) << 64; + if value.bit_len() > 128 { + return Err(Self::Error::Overflow(BITS, result, u128::MAX)); + } + Ok(result) + } +} + +// Convert Uint to floating point + +#[cfg(feature = "std")] +impl From> for f32 { + #[inline] + fn from(value: Uint) -> Self { + Self::from(&value) + } +} + +#[cfg(feature = "std")] +impl From<&Uint> for f32 { + /// Approximate single precision float. + /// + /// Returns `f32::INFINITY` if the value is too large to represent. + #[inline] + #[allow(clippy::cast_precision_loss)] // Documented + fn from(value: &Uint) -> Self { + let (bits, exponent) = value.most_significant_bits(); + (bits as Self) * (exponent as Self).exp2() + } +} + +#[cfg(feature = "std")] +impl From> for f64 { + #[inline] + fn from(value: Uint) -> Self { + Self::from(&value) + } +} + +#[cfg(feature = "std")] +impl From<&Uint> for f64 { + /// Approximate double precision float. + /// + /// Returns `f64::INFINITY` if the value is too large to represent. + #[inline] + #[allow(clippy::cast_precision_loss)] // Documented + fn from(value: &Uint) -> Self { + let (bits, exponent) = value.most_significant_bits(); + (bits as Self) * (exponent as Self).exp2() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{const_for, nlimbs}; + + #[test] + fn test_u64() { + assert_eq!(Uint::<0, 0>::try_from(0_u64), Ok(Uint::ZERO)); + assert_eq!( + Uint::<0, 0>::try_from(1_u64), + Err(ToUintError::ValueTooLarge(0, Uint::ZERO)) + ); + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + assert_eq!(Uint::::try_from(0_u64), Ok(Uint::ZERO)); + assert_eq!(Uint::::try_from(1_u64).unwrap().as_limbs()[0], 1); + }); + } + + #[test] + #[cfg(feature = "std")] + fn test_f64() { + assert_eq!(Uint::<0, 0>::try_from(0.0_f64), Ok(Uint::ZERO)); + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + assert_eq!(Uint::::try_from(0.0_f64), Ok(Uint::ZERO)); + assert_eq!(Uint::::try_from(1.0_f64).unwrap().as_limbs()[0], 1); + }); + assert_eq!( + Uint::<7, 1>::try_from(123.499_f64), + Ok(Uint::from_limbs([123])) + ); + assert_eq!( + Uint::<7, 1>::try_from(123.500_f64), + Ok(Uint::from_limbs([124])) + ); + } +} diff --git a/guest-libs/ruint/src/gcd.rs b/guest-libs/ruint/src/gcd.rs new file mode 100644 index 0000000000..1667c85c03 --- /dev/null +++ b/guest-libs/ruint/src/gcd.rs @@ -0,0 +1,96 @@ +use crate::{algorithms, Uint}; + +impl Uint { + /// Compute the greatest common divisor of two [`Uint`]s. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint! { + /// assert_eq!(0_U128.gcd(0_U128), 0_U128); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn gcd(self, other: Self) -> Self { + algorithms::gcd(self, other) + } + + /// Compute the least common multiple of two [`Uint`]s or [`None`] if the + /// result would be too large. + #[inline] + #[must_use] + pub fn lcm(self, other: Self) -> Option { + let other = other.checked_div(self.gcd(other)).unwrap_or_default(); + self.checked_mul(other) + } + + /// ⚠️ Compute the greatest common divisor and the Bézout coefficients. + /// + /// **Warning.** This is API is unstable and may change in a minor release. + /// + /// Returns $(\mathtt{gcd}, \mathtt{x}, \mathtt{y}, \mathtt{sign})$ such + /// that + /// + /// $$ + /// \gcd(\mathtt{self}, \mathtt{other}) = \mathtt{gcd} = \begin{cases} + /// \mathtt{self} · \mathtt{x} - \mathtt{other} . \mathtt{y} & + /// \mathtt{sign} \\\\ \mathtt{other} · \mathtt{y} - \mathtt{self} · + /// \mathtt{x} & ¬\mathtt{sign} \end{cases} + /// $$ + /// + /// Note that the intermediate products may overflow, even though the result + /// after subtraction will fit in the bit size of the [`Uint`]. + #[inline] + #[must_use] + pub fn gcd_extended(self, other: Self) -> (Self, Self, Self, bool) { + algorithms::gcd_extended(self, other) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::{proptest, test_runner::Config}; + + #[test] + #[allow(clippy::absurd_extreme_comparisons)] // Generated code + fn test_gcd_identities() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + let config = Config { cases: 5, ..Default::default() }; + proptest!(config, |(a: U, b: U)| { + let g = a.gcd(b); + assert_eq!(b.gcd(a), g); + if a == U::ZERO && b == U::ZERO { + assert_eq!(g, U::ZERO); + } else { + assert_eq!(a % g, U::ZERO); + assert_eq!(b % g, U::ZERO); + } + + let l = a.lcm(b); + assert_eq!(b.lcm(a), l); + if let Some(l) = l { + if a == U::ZERO || b == U::ZERO { + assert_eq!(l, U::ZERO); + } else { + assert_eq!(l % a, U::ZERO); + assert_eq!(l % b, U::ZERO); + } + } + + let (ge, x, y, sign) = a.gcd_extended(b); + assert_eq!(ge, g); + if sign { + assert_eq!(a * x - b * y, g); + } else { + assert_eq!(b * y - a * x, g); + } + }); + }); + } +} diff --git a/guest-libs/ruint/src/lib.rs b/guest-libs/ruint/src/lib.rs new file mode 100644 index 0000000000..6becaf8a13 --- /dev/null +++ b/guest-libs/ruint/src/lib.rs @@ -0,0 +1,385 @@ +#![doc = include_str!("../README.md")] +#![doc(issue_tracker_base_url = "https://github.com/recmo/uint/issues/")] +#![allow(clippy::all)] // this is a fork, so silence clippy +#![cfg_attr(test, allow(clippy::wildcard_imports, clippy::cognitive_complexity))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(not(feature = "std"), no_std)] +// Unstable features +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(feature = "nightly", feature(core_intrinsics))] +#![cfg_attr(feature = "nightly", allow(internal_features))] +#![cfg_attr( + feature = "generic_const_exprs", + feature(generic_const_exprs), + allow(incomplete_features) +)] + +#[cfg(feature = "alloc")] +#[allow(unused_imports)] +// `unused_imports` triggers on macro_use, which is required by some support +// modules. +#[macro_use] +extern crate alloc; + +#[macro_use] +mod macros; + +mod add; +pub mod algorithms; +pub mod aliases; +mod base_convert; +mod bit_arr; +mod bits; +mod bytes; +mod cmp; +mod const_for; +mod div; +mod fmt; +mod from; +mod gcd; +mod log; +mod modular; +mod mul; +mod pow; +mod root; +mod special; +mod string; +mod utils; + +pub mod support; + +#[doc(inline)] +pub use bit_arr::Bits; +// For documentation purposes we expose the macro directly, otherwise it is +// wrapped in ./macros.rs. +#[cfg(doc)] +#[doc(inline)] +pub use ruint_macro::uint; + +#[doc(inline)] +pub use self::{ + base_convert::BaseConvertError, + bytes::nbytes, + from::{FromUintError, ToFieldError, ToUintError, UintTryFrom, UintTryTo}, + string::ParseError, +}; + +/// Extra features that are nightly only. +#[cfg(feature = "generic_const_exprs")] +pub mod nightly { + /// Alias for `Uint` specified only by bit size. + /// + /// Compared to [`crate::Uint`] it compile-time computes the required number + /// of limbs. Unfortunately this requires the nightly feature + /// `generic_const_exprs`. + /// + /// # References + /// * [Working group](https://rust-lang.github.io/project-const-generics/) const generics + /// working group. + /// * [RFC2000](https://rust-lang.github.io/rfcs/2000-const-generics.html) const generics. + /// * [#60551](https://github.com/rust-lang/rust/issues/60551) associated constants in const + /// generics. + /// * [#76560](https://github.com/rust-lang/rust/issues/76560) tracking issue for + /// `generic_const_exprs`. + /// * [Rust blog](https://blog.rust-lang.org/inside-rust/2021/09/06/Splitting-const-generics.html) + /// 2021-09-06 Splitting const generics. + pub type Uint = crate::Uint; + + /// Alias for `Bits` specified only by bit size. + /// + /// See [`Uint`] for more information. + pub type Bits = crate::Bits; +} + +// FEATURE: (BLOCKED) Many functions could be made `const` if a number of +// features land. This requires +// #![feature(const_mut_refs)] +// #![feature(const_float_classify)] +// #![feature(const_fn_floating_point_arithmetic)] +// #![feature(const_float_bits_conv)] +// and more. + +/// The ring of numbers modulo $2^{\mathtt{BITS}}$. +/// +/// [`Uint`] implements nearly all traits and methods from the `std` unsigned +/// integer types, including most nightly only ones. +/// +/// # Notable differences from `std` uint types. +/// +/// * The operators `+`, `-`, `*`, etc. using wrapping math by default. The std operators panic on +/// overflow in debug, and are undefined in release, see [reference][std-overflow]. +/// * The [`Uint::checked_shl`], [`Uint::overflowing_shl`], etc return overflow when non-zero bits +/// are shifted out. In std they return overflow when the shift amount is greater than the bit +/// size. +/// * Some methods like [`u64::div_euclid`] and [`u64::rem_euclid`] are left out because they are +/// meaningless or redundant for unsigned integers. Std has them for compatibility with their +/// signed integers. +/// * Many functions that are `const` in std are not in [`Uint`]. +/// * [`Uint::to_le_bytes`] and [`Uint::to_be_bytes`] require the output size to be provided as a +/// const-generic argument. They will runtime panic if the provided size is incorrect. +/// * [`Uint::widening_mul`] takes as argument an [`Uint`] of arbitrary size and returns a result +/// that is sized to fit the product without overflow (i.e. the sum of the bit sizes of self and +/// the argument). The std version requires same-sized arguments and returns a pair of lower and +/// higher bits. +/// +/// [std-overflow]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow +#[cfg(not(target_os = "zkvm"))] +#[derive(Clone, Copy, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct Uint { + limbs: [u64; LIMBS], +} + +/// In case of zkvm, we use the native implementations of `Clone` and `Eq` +#[cfg(target_os = "zkvm")] +#[derive(Hash)] +#[repr(transparent)] +pub struct Uint { + limbs: [u64; LIMBS], +} + +impl Uint { + /// The size of this integer type in 64-bit limbs. + pub const LIMBS: usize = { + let limbs = nlimbs(BITS); + assert!( + LIMBS == limbs, + "Can not construct Uint with incorrect LIMBS" + ); + limbs + }; + + /// Bit mask for the last limb. + pub const MASK: u64 = mask(BITS); + + const SHOULD_MASK: bool = BITS > 0 && Self::MASK != u64::MAX; + + /// The size of this integer type in bits. + pub const BITS: usize = BITS; + + /// The value zero. This is the only value that exists in all [`Uint`] + /// types. + pub const ZERO: Self = Self::from_limbs([0; LIMBS]); + + /// The value one. This is useful to have as a constant for use in const fn. + /// + /// Zero if `BITS` is zero. + pub const ONE: Self = Self::const_from_u64(1); + + /// The smallest value that can be represented by this integer type. + /// Synonym for [`Self::ZERO`]. + pub const MIN: Self = Self::ZERO; + + /// The largest value that can be represented by this integer type, + /// $2^{\mathtt{BITS}} − 1$. + pub const MAX: Self = Self::from_limbs_unmasked([u64::MAX; LIMBS]); + + /// View the array of limbs. + #[inline(always)] + #[must_use] + pub const fn as_limbs(&self) -> &[u64; LIMBS] { + &self.limbs + } + + /// Access the array of limbs. + /// + /// # Safety + /// + /// This function is unsafe because it allows setting a bit outside the bit + /// size if the bit-size is not limb-aligned. + #[inline(always)] + #[must_use] + pub unsafe fn as_limbs_mut(&mut self) -> &mut [u64; LIMBS] { + &mut self.limbs + } + + /// Convert to a array of limbs. + /// + /// Limbs are least significant first. + #[inline(always)] + #[must_use] + pub const fn into_limbs(self) -> [u64; LIMBS] { + self.limbs + } + + /// Construct a new integer from little-endian a array of limbs. + /// + /// # Panics + /// + /// Panics it `LIMBS` is not equal to `nlimbs(BITS)`. + /// + /// Panics if the value is too large for the bit-size of the Uint. + #[inline(always)] + #[must_use] + #[track_caller] + pub const fn from_limbs(limbs: [u64; LIMBS]) -> Self { + if Self::SHOULD_MASK { + // FEATURE: (BLOCKED) Add `<{BITS}>` to the type when Display works in const fn. + assert!( + limbs[Self::LIMBS - 1] <= Self::MASK, + "Value too large for this Uint" + ); + } + Self { limbs } + } + + #[inline(always)] + #[must_use] + const fn from_limbs_unmasked(limbs: [u64; LIMBS]) -> Self { + Self { limbs }.masked() + } + + /// Construct a new integer from little-endian a slice of limbs. + /// + /// # Panics + /// + /// Panics if the value is too large for the bit-size of the Uint. + #[inline] + #[must_use] + #[track_caller] + pub fn from_limbs_slice(slice: &[u64]) -> Self { + match Self::overflowing_from_limbs_slice(slice) { + (n, false) => n, + (_, true) => panic!("Value too large for this Uint"), + } + } + + /// Construct a new integer from little-endian a slice of limbs, or `None` + /// if the value is too large for the [`Uint`]. + #[inline] + #[must_use] + pub fn checked_from_limbs_slice(slice: &[u64]) -> Option { + match Self::overflowing_from_limbs_slice(slice) { + (n, false) => Some(n), + (_, true) => None, + } + } + + /// Construct a new [`Uint`] from a little-endian slice of limbs. Returns + /// a potentially truncated value. + #[inline] + #[must_use] + pub fn wrapping_from_limbs_slice(slice: &[u64]) -> Self { + Self::overflowing_from_limbs_slice(slice).0 + } + + /// Construct a new [`Uint`] from a little-endian slice of limbs. Returns + /// a potentially truncated value and a boolean indicating whether the value + /// was truncated. + #[inline] + #[must_use] + pub fn overflowing_from_limbs_slice(slice: &[u64]) -> (Self, bool) { + if slice.len() < LIMBS { + let mut limbs = [0; LIMBS]; + limbs[..slice.len()].copy_from_slice(slice); + (Self::from_limbs(limbs), false) + } else { + let (head, tail) = slice.split_at(LIMBS); + let mut limbs = [0; LIMBS]; + limbs.copy_from_slice(head); + let mut overflow = tail.iter().any(|&limb| limb != 0); + if LIMBS > 0 { + overflow |= limbs[LIMBS - 1] > Self::MASK; + limbs[LIMBS - 1] &= Self::MASK; + } + (Self::from_limbs(limbs), overflow) + } + } + + /// Construct a new [`Uint`] from a little-endian slice of limbs. Returns + /// the maximum value if the value is too large for the [`Uint`]. + #[inline] + #[must_use] + pub fn saturating_from_limbs_slice(slice: &[u64]) -> Self { + match Self::overflowing_from_limbs_slice(slice) { + (n, false) => n, + (_, true) => Self::MAX, + } + } + + #[inline(always)] + fn apply_mask(&mut self) { + if Self::SHOULD_MASK { + self.limbs[LIMBS - 1] &= Self::MASK; + } + } + + #[inline(always)] + const fn masked(mut self) -> Self { + if Self::SHOULD_MASK { + self.limbs[LIMBS - 1] &= Self::MASK; + } + self + } +} + +impl Default for Uint { + #[inline] + fn default() -> Self { + Self::ZERO + } +} + +/// Number of `u64` limbs required to represent the given number of bits. +/// This needs to be public because it is used in the `Uint` type. +#[inline] +#[must_use] +pub const fn nlimbs(bits: usize) -> usize { + (bits + 63) / 64 +} + +/// Mask to apply to the highest limb to get the correct number of bits. +#[inline] +#[must_use] +pub const fn mask(bits: usize) -> u64 { + if bits == 0 { + return 0; + } + let bits = bits % 64; + if bits == 0 { + u64::MAX + } else { + (1 << bits) - 1 + } +} + +// Not public API. +#[doc(hidden)] +pub mod __private { + pub use ruint_macro; +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_mask() { + assert_eq!(mask(0), 0); + assert_eq!(mask(1), 1); + assert_eq!(mask(5), 0x1f); + assert_eq!(mask(63), u64::MAX >> 1); + assert_eq!(mask(64), u64::MAX); + } + + #[test] + fn test_max() { + assert_eq!(Uint::<0, 0>::MAX, Uint::ZERO); + assert_eq!(Uint::<1, 1>::MAX, Uint::from_limbs([1])); + assert_eq!(Uint::<7, 1>::MAX, Uint::from_limbs([127])); + assert_eq!(Uint::<64, 1>::MAX, Uint::from_limbs([u64::MAX])); + assert_eq!( + Uint::<100, 2>::MAX, + Uint::from_limbs([u64::MAX, u64::MAX >> 28]) + ); + } + + #[test] + fn test_constants() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + assert_eq!(Uint::::MIN, Uint::::ZERO); + let _ = Uint::::MAX; + }); + } +} diff --git a/guest-libs/ruint/src/log.rs b/guest-libs/ruint/src/log.rs new file mode 100644 index 0000000000..7399d0d80d --- /dev/null +++ b/guest-libs/ruint/src/log.rs @@ -0,0 +1,220 @@ +#![cfg(feature = "std")] + +use crate::Uint; + +impl Uint { + /// Returns the logarithm of the number, rounded down. + /// + /// Returns None if the base is less than two, or this number is zero. + #[inline] + #[must_use] + pub fn checked_log(self, base: Self) -> Option { + if base < Self::from(2) || self.is_zero() { + return None; + } + Some(self.log(base)) + } + + /// Returns the base 10 logarithm of the number, rounded down. + /// + /// Returns None if the number is zero. + #[inline] + #[must_use] + pub fn checked_log10(self) -> Option { + self.checked_log(Self::from(10)) + } + + /// Returns the base 2 logarithm of the number, rounded down. + /// + /// This is equivalent to the index of the highest set bit. + /// + /// Returns None if the number is zero. + #[inline] + #[must_use] + pub fn checked_log2(self) -> Option { + self.checked_log(Self::from(2)) + } + + /// Returns the logarithm of the number, rounded down. + /// + /// # Panics + /// + /// Panics if the `base` is less than 2 or if the number is zero. + #[inline] + #[must_use] + pub fn log(self, base: Self) -> usize { + assert!(!self.is_zero()); + assert!(base >= Self::from(2)); + if base == Self::from(2) { + return self.bit_len() - 1; + } + if self < base { + return 0; + } + + // Find approximate result + #[allow(clippy::cast_precision_loss)] // Casting base to `f64` is fine. + let result = self.approx_log2() / base.approx_log2(); + // We handled edge cases above, so the result should be normal and fit `Self`. + assert!(result.is_normal()); + let mut result = result.try_into().unwrap(); + + // Adjust result to get the exact value. At most one of these should happen, but + // we loop regardless. + loop { + if let Some(value) = base.checked_pow(result) { + if value > self { + assert!(!result.is_zero()); + result -= Self::ONE; + continue; + } + } else { + // Overflow, so definitely larger than `value` + result -= Self::ONE; + } + break; + } + while let Some(trial) = result.checked_add(Self::ONE) { + if let Some(value) = base.checked_pow(trial) { + if value <= self { + result = trial; + continue; + } + } + break; + } + + // Should always be possible. + result.to() + } + + /// Returns the base 10 logarithm of the number, rounded down. + /// + /// # Panics + /// + /// Panics if the `base` if the number is zero. + #[inline] + #[must_use] + pub fn log10(self) -> usize { + self.log(Self::from(10)) + } + + /// Returns the base 2 logarithm of the number, rounded down. + /// + /// # Panics + /// + /// Panics if the `base` if the number is zero. + #[inline] + #[must_use] + pub fn log2(self) -> usize { + self.log(Self::from(2)) + } + + /// Double precision logarithm. + #[inline] + #[must_use] + pub fn approx_log(self, base: f64) -> f64 { + self.approx_log2() / base.log2() + } + + /// Double precision binary logarithm. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(0_U64.approx_log2(), f64::NEG_INFINITY); + /// assert_eq!(1_U64.approx_log2(), 0.0); + /// assert_eq!(2_U64.approx_log2(), 1.0); + /// assert_eq!(U64::MAX.approx_log2(), 64.0); + /// # } + /// ``` + #[inline] + #[must_use] + #[allow(clippy::cast_precision_loss)] + pub fn approx_log2(self) -> f64 { + // The naive solution would be `f64::from(self).log2()`, but + // `f64::from(self)` quickly overflows (`f64::MAX` is 2^1024). + // So instead we first approximate as `bits * 2^exp` and then + // compute using`log2(bits * 2^exp) = log2(bits) + exp` + let (bits, exp) = self.most_significant_bits(); + // Convert to floats + let bits = bits as f64; + let exp = exp as f64; + bits.log2() + exp + } + + /// Double precision decimal logarithm. + #[inline] + #[must_use] + pub fn approx_log10(self) -> f64 { + self.approx_log2() / core::f64::consts::LOG2_10 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{aliases::U128, const_for, nlimbs}; + use proptest::{prop_assume, proptest}; + + #[test] + fn test_checked_log2() { + assert_eq!(U128::from(0).checked_log2(), None); + assert_eq!(U128::from(1).checked_log2(), Some(0)); + assert_eq!(U128::from(2).checked_log2(), Some(1)); + assert_eq!(U128::from(3).checked_log2(), Some(1)); + assert_eq!(U128::from(127).checked_log2(), Some(6)); + assert_eq!(U128::from(128).checked_log2(), Some(7)); + } + + #[test] + fn test_approx_log2_pow2() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + let log = value.approx_log2(); + let pow = U::approx_pow2(log).unwrap(); + let error = value.abs_diff(pow); + let correct_bits = value.bit_len() - error.bit_len(); + // The maximum precision we could expect here is 53 bits. + // OPT: Find out exactly where the precision is lost and what + // the bounds are. + assert!(correct_bits == value.bit_len() || correct_bits >= 42); + }); + }); + } + + #[test] + fn test_pow_log() { + const_for!(BITS in NON_ZERO if (BITS >= 64) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(b in 2_u64..100, e in 0..BITS)| { + if let Some(value) = U::from(b).checked_pow(U::from(e)) { + assert!(value > U::ZERO); + assert_eq!(value.log(U::from(b)), e); + // assert_eq!(value.log(b + U::ONE), e as u64); + } + }); + }); + } + + #[test] + fn test_log_pow() { + const_for!(BITS in NON_ZERO if (BITS >= 64) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(b in 2_u64..100, n: U)| { + prop_assume!(n > U::ZERO); + let e = n.log(U::from(b)); + assert!(U::from(b).pow(U::from(e)) <= n); + if let Some(value) = U::from(b).checked_pow(U::from(e + 1)) { + assert!(value > n); + } + }); + }); + } +} diff --git a/guest-libs/ruint/src/macros.rs b/guest-libs/ruint/src/macros.rs new file mode 100644 index 0000000000..92898c2f4e --- /dev/null +++ b/guest-libs/ruint/src/macros.rs @@ -0,0 +1,135 @@ +/// Wrapper for [`ruint_macro::uint!`]. See its documentation for details. +#[macro_export] +#[cfg(not(doc))] // Show the actual macro in docs. +#[doc(hidden)] +macro_rules! uint { + ($($t:tt)*) => { + $crate::__private::ruint_macro::uint_with_path! { [$crate] $($t)* } + } +} + +macro_rules! impl_bin_op { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident, $fdel:ident) => { + impl $trait_assign> + for Uint + { + #[inline(always)] + #[track_caller] + fn $fn_assign(&mut self, rhs: Uint) { + *self = self.$fdel(rhs); + } + } + impl $trait_assign<&Uint> + for Uint + { + #[inline(always)] + #[track_caller] + fn $fn_assign(&mut self, rhs: &Uint) { + *self = self.$fdel(*rhs); + } + } + impl $trait> + for Uint + { + type Output = Uint; + + #[inline(always)] + #[track_caller] + fn $fn(self, rhs: Uint) -> Self::Output { + self.$fdel(rhs) + } + } + impl $trait<&Uint> + for Uint + { + type Output = Uint; + + #[inline(always)] + #[track_caller] + fn $fn(self, rhs: &Uint) -> Self::Output { + self.$fdel(*rhs) + } + } + impl $trait> + for &Uint + { + type Output = Uint; + + #[inline(always)] + #[track_caller] + fn $fn(self, rhs: Uint) -> Self::Output { + self.$fdel(rhs) + } + } + impl $trait<&Uint> + for &Uint + { + type Output = Uint; + + #[inline(always)] + #[track_caller] + fn $fn(self, rhs: &Uint) -> Self::Output { + self.$fdel(*rhs) + } + } + }; +} + +#[allow(unused)] +macro_rules! assume { + ($e:expr $(,)?) => { + if !$e { + debug_unreachable!(stringify!($e)); + } + }; + + ($e:expr, $($t:tt)+) => { + if !$e { + debug_unreachable!($($t)+); + } + }; +} + +#[allow(unused)] +macro_rules! debug_unreachable { + ($($t:tt)*) => { + if cfg!(debug_assertions) { + unreachable!($($t)*); + } else { + unsafe { core::hint::unreachable_unchecked() }; + } + }; +} + +#[cfg(test)] +mod tests { + // https://github.com/recmo/uint/issues/359 + ruint_macro::uint_with_path! { + [crate] + const _A: [crate::aliases::U256; 2] = [ + 0x00006f85d6f68a85ec10345351a23a3aaf07f38af8c952a7bceca70bd2af7ad5_U256, + 0x00004b4110c9ae997782e1509b1d0fdb20a7c02bbd8bea7305462b9f8125b1e8_U256, + ]; + } + + crate::uint! { + const _B: [crate::aliases::U256; 2] = [ + 0x00006f85d6f68a85ec10345351a23a3aaf07f38af8c952a7bceca70bd2af7ad5_U256, + 0x00004b4110c9ae997782e1509b1d0fdb20a7c02bbd8bea7305462b9f8125b1e8_U256, + ]; + } + + #[test] + fn test_uint_macro_with_paths() { + extern crate self as aaa; + use crate as ruint; + use crate as __ruint; + let value = crate::aliases::U256::from(0x10); + assert_eq!(value, uint!(0x10U256)); + assert_eq!(value, ruint_macro::uint_with_path!([crate] 0x10U256)); + assert_eq!(value, ruint_macro::uint_with_path!([aaa] 0x10U256)); + assert_eq!(value, ruint_macro::uint_with_path!([aaa] 0x10U256)); + assert_eq!(value, ruint_macro::uint_with_path!([ruint] 0x10U256)); + assert_eq!(value, ruint_macro::uint_with_path!([__ruint] 0x10U256)); + } +} diff --git a/guest-libs/ruint/src/modular.rs b/guest-libs/ruint/src/modular.rs new file mode 100644 index 0000000000..12b2922c36 --- /dev/null +++ b/guest-libs/ruint/src/modular.rs @@ -0,0 +1,334 @@ +use crate::{algorithms, Uint}; + +// FEATURE: sub_mod, neg_mod, inv_mod, div_mod, root_mod +// See +// FEATURE: mul_mod_redc +// and maybe barrett +// See also +// FEATURE: Modular wrapper class, like Wrapping. + +impl Uint { + /// ⚠️ Compute $\mod{\mathtt{self}}_{\mathtt{modulus}}$. + /// + /// **Warning.** This function is not part of the stable API. + /// + /// Returns zero if the modulus is zero. + // FEATURE: Reduce larger bit-sizes to smaller ones. + #[inline] + #[must_use] + pub fn reduce_mod(mut self, modulus: Self) -> Self { + if modulus.is_zero() { + return Self::ZERO; + } + if self >= modulus { + self %= modulus; + } + self + } + + /// Compute $\mod{\mathtt{self} + \mathtt{rhs}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + #[inline] + #[must_use] + pub fn add_mod(self, rhs: Self, modulus: Self) -> Self { + // Reduce inputs + let lhs = self.reduce_mod(modulus); + let rhs = rhs.reduce_mod(modulus); + + // Compute the sum and conditionally subtract modulus once. + let (mut result, overflow) = lhs.overflowing_add(rhs); + if overflow || result >= modulus { + result -= modulus; + } + result + } + + /// Compute $\mod{\mathtt{self} ⋅ \mathtt{rhs}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + /// + /// See [`mul_redc`](Self::mul_redc) for a faster variant at the cost of + /// some pre-computation. + #[inline] + #[must_use] + pub fn mul_mod(self, rhs: Self, mut modulus: Self) -> Self { + if modulus.is_zero() { + return Self::ZERO; + } + + // Allocate at least `nlimbs(2 * BITS)` limbs to store the product. This array + // casting is a workaround for `generic_const_exprs` not being stable. + let mut product = [[0u64; 2]; LIMBS]; + let product_len = crate::nlimbs(2 * BITS); + debug_assert!(2 * LIMBS >= product_len); + // SAFETY: `[[u64; 2]; LIMBS] == [u64; 2 * LIMBS] >= [u64; nlimbs(2 * BITS)]`. + let product = unsafe { + core::slice::from_raw_parts_mut(product.as_mut_ptr().cast::(), product_len) + }; + + // Compute full product. + let overflow = algorithms::addmul(product, self.as_limbs(), rhs.as_limbs()); + debug_assert!(!overflow); + + // Compute modulus using `div_rem`. + // This stores the remainder in the divisor, `modulus`. + algorithms::div(product, &mut modulus.limbs); + + modulus + } + + /// Compute $\mod{\mathtt{self}^{\mathtt{rhs}}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + #[inline] + #[must_use] + pub fn pow_mod(mut self, mut exp: Self, modulus: Self) -> Self { + if BITS == 0 || modulus <= Self::ONE { + return Self::ZERO; + } + + // Exponentiation by squaring + let mut result = Self::ONE; + while exp > Self::ZERO { + // Multiply by base + if exp.limbs[0] & 1 == 1 { + result = result.mul_mod(self, modulus); + } + + // Square base + self = self.mul_mod(self, modulus); + exp >>= 1; + } + result + } + + /// Compute $\mod{\mathtt{self}^{-1}}_{\mathtt{modulus}}$. + /// + /// Returns `None` if the inverse does not exist. + #[inline] + #[must_use] + pub fn inv_mod(self, modulus: Self) -> Option { + algorithms::inv_mod(self, modulus) + } + + /// Montgomery multiplication. + /// + /// Requires `self` and `other` to be less than `modulus`. + /// + /// Computes + /// + /// $$ + /// \mod{\frac{\mathtt{self} ⋅ \mathtt{other}}{ 2^{64 · + /// \mathtt{LIMBS}}}}_{\mathtt{modulus}} $$ + /// + /// This is useful because it can be computed notably faster than + /// [`mul_mod`](Self::mul_mod). Many computations can be done by + /// pre-multiplying values with $R = 2^{64 · \mathtt{LIMBS}}$ + /// and then using [`mul_redc`](Self::mul_redc) instead of + /// [`mul_mod`](Self::mul_mod). + /// + /// For this algorithm to work, it needs an extra parameter `inv` which must + /// be set to + /// + /// $$ + /// \mathtt{inv} = \mod{\frac{-1}{\mathtt{modulus}} }_{2^{64}} + /// $$ + /// + /// The `inv` value only exists for odd values of `modulus`. It can be + /// computed using [`inv_ring`](Self::inv_ring) from `U64`. + /// + /// ``` + /// # use ruint::{uint, Uint, aliases::*}; + /// # uint!{ + /// # let modulus = 21888242871839275222246405745257275088548364400416034343698204186575808495617_U256; + /// let inv = U64::wrapping_from(modulus).inv_ring().unwrap().wrapping_neg().to(); + /// let prod = 5_U256.mul_redc(6_U256, modulus, inv); + /// # assert_eq!(inv.wrapping_mul(modulus.wrapping_to()), u64::MAX); + /// # assert_eq!(inv, 0xc2e1f593efffffff); + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics if `inv` is not correct in debug mode. + #[inline] + #[must_use] + pub fn mul_redc(self, other: Self, modulus: Self, inv: u64) -> Self { + if BITS == 0 { + return Self::ZERO; + } + let result = algorithms::mul_redc(self.limbs, other.limbs, modulus.limbs, inv); + let result = Self::from_limbs(result); + debug_assert!(result < modulus); + result + } + + /// Montgomery squaring. + /// + /// See [Self::mul_redc]. + #[inline] + #[must_use] + pub fn square_redc(self, modulus: Self, inv: u64) -> Self { + if BITS == 0 { + return Self::ZERO; + } + let result = algorithms::square_redc(self.limbs, modulus.limbs, inv); + let result = Self::from_limbs(result); + debug_assert!(result < modulus); + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{aliases::U64, const_for, nlimbs}; + use proptest::{prop_assume, proptest, test_runner::Config}; + + #[test] + fn test_commutative() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U, m: U)| { + assert_eq!(a.mul_mod(b, m), b.mul_mod(a, m)); + }); + }); + } + + #[test] + fn test_associative() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U, c: U, m: U)| { + assert_eq!(a.mul_mod(b.mul_mod(c, m), m), a.mul_mod(b, m).mul_mod(c, m)); + }); + }); + } + + #[test] + fn test_distributive() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U, c: U, m: U)| { + assert_eq!(a.mul_mod(b.add_mod(c, m), m), a.mul_mod(b, m).add_mod(a.mul_mod(c, m), m)); + }); + }); + } + + #[test] + fn test_add_identity() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U, m: U)| { + assert_eq!(value.add_mod(U::from(0), m), value.reduce_mod(m)); + }); + }); + } + + #[test] + fn test_mul_identity() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U, m: U)| { + assert_eq!(value.mul_mod(U::from(0), m), U::ZERO); + assert_eq!(value.mul_mod(U::from(1), m), value.reduce_mod(m)); + }); + }); + } + + #[test] + fn test_pow_identity() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, m: U)| { + assert_eq!(a.pow_mod(U::from(0), m), U::from(1).reduce_mod(m)); + assert_eq!(a.pow_mod(U::from(1), m), a.reduce_mod(m)); + }); + }); + } + + #[test] + fn test_pow_rules() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + + // Too slow. + if LIMBS > 8 { + return; + } + + let config = Config { cases: 5, ..Default::default() }; + proptest!(config, |(a: U, b: U, c: U, m: U)| { + // TODO: a^(b+c) = a^b * a^c. Which requires carmichael fn. + // TODO: (a^b)^c = a^(b * c). Which requires carmichael fn. + assert_eq!(a.mul_mod(b, m).pow_mod(c, m), a.pow_mod(c, m).mul_mod(b.pow_mod(c, m), m)); + }); + }); + } + + #[test] + fn test_inv() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, m: U)| { + if let Some(inv) = a.inv_mod(m) { + assert_eq!(a.mul_mod(inv, m), U::from(1)); + } + }); + }); + } + + #[test] + fn test_mul_redc() { + const_for!(BITS in NON_ZERO if (BITS >= 16) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U, m: U)| { + prop_assume!(m >= U::from(2)); + if let Some(inv) = U64::from(m.as_limbs()[0]).inv_ring() { + let inv = (-inv).as_limbs()[0]; + + let r = U::from(2).pow_mod(U::from(64 * LIMBS), m); + let ar = a.mul_mod(r, m); + let br = b.mul_mod(r, m); + // TODO: Test for larger (>= m) values of a, b. + + let expected = a.mul_mod(b, m).mul_mod(r, m); + + assert_eq!(ar.mul_redc(br, m, inv), expected); + } + }); + }); + } + + #[test] + fn test_square_redc() { + const_for!(BITS in NON_ZERO if (BITS >= 16) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, m: U)| { + prop_assume!(m >= U::from(2)); + if let Some(inv) = U64::from(m.as_limbs()[0]).inv_ring() { + let inv = (-inv).as_limbs()[0]; + + let r = U::from(2).pow_mod(U::from(64 * LIMBS), m); + let ar = a.mul_mod(r, m); + // TODO: Test for larger (>= m) values of a, b. + + let expected = a.mul_mod(a, m).mul_mod(r, m); + + assert_eq!(ar.square_redc(m, inv), expected); + } + }); + }); + } +} diff --git a/guest-libs/ruint/src/mul.rs b/guest-libs/ruint/src/mul.rs new file mode 100644 index 0000000000..ffd85947ca --- /dev/null +++ b/guest-libs/ruint/src/mul.rs @@ -0,0 +1,289 @@ +use crate::{algorithms, nlimbs, Uint}; +use core::{ + iter::Product, + num::Wrapping, + ops::{Mul, MulAssign}, +}; + +impl Uint { + /// Computes `self * rhs`, returning [`None`] if overflow occurred. + #[inline(always)] + #[must_use] + pub fn checked_mul(self, rhs: Self) -> Option { + match self.overflowing_mul(rhs) { + (value, false) => Some(value), + _ => None, + } + } + + /// Calculates the multiplication of self and rhs. + /// + /// Returns a tuple of the multiplication along with a boolean indicating + /// whether an arithmetic overflow would occur. If an overflow would have + /// occurred then the wrapped value is returned. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint}; + /// # uint!{ + /// assert_eq!(1_U1.overflowing_mul(1_U1), (1_U1, false)); + /// assert_eq!( + /// 0x010000000000000000_U65.overflowing_mul(0x010000000000000000_U65), + /// (0x000000000000000000_U65, true) + /// ); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn overflowing_mul(self, rhs: Self) -> (Self, bool) { + let mut result = Self::ZERO; + let mut overflow = algorithms::addmul(&mut result.limbs, self.as_limbs(), rhs.as_limbs()); + if BITS > 0 { + overflow |= result.limbs[LIMBS - 1] > Self::MASK; + result.apply_mask(); + } + (result, overflow) + } + + /// Computes `self * rhs`, saturating at the numeric bounds instead of + /// overflowing. + #[inline(always)] + #[must_use] + pub fn saturating_mul(self, rhs: Self) -> Self { + match self.overflowing_mul(rhs) { + (value, false) => value, + _ => Self::MAX, + } + } + + /// Computes `self * rhs`, wrapping around at the boundary of the type. + #[cfg(not(target_os = "zkvm"))] + #[inline(always)] + #[must_use] + pub fn wrapping_mul(self, rhs: Self) -> Self { + let mut result = Self::ZERO; + algorithms::addmul_n(&mut result.limbs, self.as_limbs(), rhs.as_limbs()); + if BITS > 0 { + result.apply_mask(); + } + result + } + + /// Computes `self * rhs`, wrapping around at the boundary of the type. + #[cfg(target_os = "zkvm")] + #[inline(always)] + #[must_use] + pub fn wrapping_mul(mut self, rhs: Self) -> Self { + use crate::support::zkvm::zkvm_u256_wrapping_mul_impl; + if BITS == 256 { + unsafe { + zkvm_u256_wrapping_mul_impl( + self.limbs.as_mut_ptr() as *mut u8, + self.limbs.as_ptr() as *const u8, + rhs.limbs.as_ptr() as *const u8, + ); + } + return self; + } + self.overflowing_mul(rhs).0 + } + + /// Computes the inverse modulo $2^{\mathtt{BITS}}$ of `self`, returning + /// [`None`] if the inverse does not exist. + #[inline] + #[must_use] + pub fn inv_ring(self) -> Option { + if BITS == 0 || self.limbs[0] & 1 == 0 { + return None; + } + + // Compute inverse of first limb + let mut result = Self::ZERO; + result.limbs[0] = { + const W2: Wrapping = Wrapping(2); + const W3: Wrapping = Wrapping(3); + let n = Wrapping(self.limbs[0]); + let mut inv = (n * W3) ^ W2; // Correct on 4 bits. + inv *= W2 - n * inv; // Correct on 8 bits. + inv *= W2 - n * inv; // Correct on 16 bits. + inv *= W2 - n * inv; // Correct on 32 bits. + inv *= W2 - n * inv; // Correct on 64 bits. + debug_assert_eq!(n.0.wrapping_mul(inv.0), 1); + inv.0 + }; + + // Continue with rest of limbs + let mut correct_limbs = 1; + while correct_limbs < LIMBS { + result *= Self::from(2) - self * result; + correct_limbs *= 2; + } + result.apply_mask(); + + Some(result) + } + + /// Calculates the complete product `self * rhs` without the possibility to + /// overflow. + /// + /// The argument `rhs` can be any size [`Uint`], the result size is the sum + /// of the bit-sizes of `self` and `rhs`. + /// + /// # Panics + /// + /// This function will runtime panic of the const generic arguments are + /// incorrect. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint}; + /// # uint!{ + /// assert_eq!(0_U0.widening_mul(0_U0), 0_U0); + /// assert_eq!(1_U1.widening_mul(1_U1), 1_U2); + /// assert_eq!(3_U2.widening_mul(7_U3), 21_U5); + /// # } + /// ``` + #[inline] + #[must_use] + #[allow(clippy::similar_names)] // Don't confuse `res` and `rhs`. + pub fn widening_mul< + const BITS_RHS: usize, + const LIMBS_RHS: usize, + const BITS_RES: usize, + const LIMBS_RES: usize, + >( + self, + rhs: Uint, + ) -> Uint { + assert_eq!(BITS_RES, BITS + BITS_RHS); + assert_eq!(LIMBS_RES, nlimbs(BITS_RES)); + let mut result = Uint::::ZERO; + algorithms::addmul(&mut result.limbs, self.as_limbs(), rhs.as_limbs()); + if LIMBS_RES > 0 { + debug_assert!(result.limbs[LIMBS_RES - 1] <= Uint::::MASK); + } + + result + } +} + +impl Product for Uint { + #[inline] + fn product(iter: I) -> Self + where + I: Iterator, + { + if BITS == 0 { + return Self::ZERO; + } + iter.fold(Self::ONE, Self::wrapping_mul) + } +} + +impl<'a, const BITS: usize, const LIMBS: usize> Product<&'a Self> for Uint { + #[inline] + fn product(iter: I) -> Self + where + I: Iterator, + { + if BITS == 0 { + return Self::ZERO; + } + iter.copied().fold(Self::ONE, Self::wrapping_mul) + } +} + +impl_bin_op!(Mul, mul, MulAssign, mul_assign, wrapping_mul); + +#[cfg(test)] +mod tests { + use super::*; + use crate::const_for; + use proptest::proptest; + + #[test] + fn test_commutative() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U)| { + assert_eq!(a * b, b * a); + }); + }); + } + + #[test] + fn test_associative() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U, c: U)| { + assert_eq!(a * (b * c), (a * b) * c); + }); + }); + } + + #[test] + fn test_distributive() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U, c: U)| { + assert_eq!(a * (b + c), (a * b) + (a *c)); + }); + }); + } + + #[test] + fn test_identity() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + assert_eq!(value * U::from(0), U::ZERO); + assert_eq!(value * U::from(1), value); + }); + }); + } + + #[test] + fn test_inverse() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(mut a: U)| { + a |= U::from(1); // Make sure a is invertible + assert_eq!(a * a.inv_ring().unwrap(), U::from(1)); + assert_eq!(a.inv_ring().unwrap().inv_ring().unwrap(), a); + }); + }); + } + + #[test] + fn test_widening_mul() { + // Left hand side + const_for!(BITS_LHS in BENCH { + const LIMBS_LHS: usize = nlimbs(BITS_LHS); + type Lhs = Uint; + + // Right hand side + const_for!(BITS_RHS in BENCH { + const LIMBS_RHS: usize = nlimbs(BITS_RHS); + type Rhs = Uint; + + // Result + const BITS_RES: usize = BITS_LHS + BITS_RHS; + const LIMBS_RES: usize = nlimbs(BITS_RES); + type Res = Uint; + + proptest!(|(lhs: Lhs, rhs: Rhs)| { + // Compute the result using the target size + let expected = Res::from(lhs) * Res::from(rhs); + assert_eq!(lhs.widening_mul(rhs), expected); + }); + }); + }); + } +} diff --git a/guest-libs/ruint/src/pow.rs b/guest-libs/ruint/src/pow.rs new file mode 100644 index 0000000000..4f884916a3 --- /dev/null +++ b/guest-libs/ruint/src/pow.rs @@ -0,0 +1,210 @@ +use crate::Uint; + +impl Uint { + /// Raises self to the power of `exp`. + /// + /// Returns None if the result would overflow. + #[inline] + #[must_use] + pub fn checked_pow(self, exp: Self) -> Option { + match self.overflowing_pow(exp) { + (x, false) => Some(x), + (_, true) => None, + } + } + + /// Raises self to the power of `exp` and if the result would overflow. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint}; + /// # uint!{ + /// assert_eq!( + /// 36_U64.overflowing_pow(12_U64), + /// (0x41c21cb8e1000000_U64, false) + /// ); + /// assert_eq!( + /// 36_U64.overflowing_pow(13_U64), + /// (0x3f4c09ffa4000000_U64, true) + /// ); + /// assert_eq!( + /// 36_U68.overflowing_pow(13_U68), + /// (0x093f4c09ffa4000000_U68, false) + /// ); + /// assert_eq!(16_U65.overflowing_pow(32_U65), (0_U65, true)); + /// # } + /// ``` + /// Small cases: + /// ``` + /// # use ruint::{Uint, uint}; + /// # uint!{ + /// assert_eq!(0_U0.overflowing_pow(0_U0), (0_U0, false)); + /// assert_eq!(0_U1.overflowing_pow(0_U1), (1_U1, false)); + /// assert_eq!(0_U1.overflowing_pow(1_U1), (0_U1, false)); + /// assert_eq!(1_U1.overflowing_pow(0_U1), (1_U1, false)); + /// assert_eq!(1_U1.overflowing_pow(1_U1), (1_U1, false)); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn overflowing_pow(mut self, mut exp: Self) -> (Self, bool) { + if BITS == 0 { + return (self, false); + } + + // Exponentiation by squaring + let mut overflow = false; + let mut base_overflow = false; + let mut result = Self::ONE; + while !exp.is_zero() { + // Multiply by base + if exp.bit(0) { + let (r, o) = result.overflowing_mul(self); + result = r; + overflow |= o | base_overflow; + } + + // Square base + let (s, o) = self.overflowing_mul(self); + self = s; + base_overflow |= o; + exp >>= 1; + } + (result, overflow) + } + + /// Raises self to the power of `exp`, wrapping around on overflow. + #[inline] + #[must_use] + pub fn pow(self, exp: Self) -> Self { + self.wrapping_pow(exp) + } + + /// Raises self to the power of `exp`, saturating on overflow. + #[inline] + #[must_use] + pub fn saturating_pow(self, exp: Self) -> Self { + match self.overflowing_pow(exp) { + (x, false) => x, + (_, true) => Self::MAX, + } + } + + /// Raises self to the power of `exp`, wrapping around on overflow. + #[inline] + #[must_use] + pub fn wrapping_pow(mut self, mut exp: Self) -> Self { + if BITS == 0 { + return self; + } + + // Exponentiation by squaring + let mut result = Self::ONE; + while !exp.is_zero() { + // Multiply by base + if exp.bit(0) { + result = result.wrapping_mul(self); + } + + // Square base + self = self.wrapping_mul(self); + exp >>= 1; + } + result + } + + /// Construct from double precision binary logarithm. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(U64::approx_pow2(-2.0), Some(0_U64)); + /// assert_eq!(U64::approx_pow2(-1.0), Some(1_U64)); + /// assert_eq!(U64::approx_pow2(0.0), Some(1_U64)); + /// assert_eq!(U64::approx_pow2(1.0), Some(2_U64)); + /// assert_eq!(U64::approx_pow2(1.6), Some(3_U64)); + /// assert_eq!(U64::approx_pow2(2.0), Some(4_U64)); + /// assert_eq!(U64::approx_pow2(64.0), None); + /// assert_eq!(U64::approx_pow2(10.385), Some(1337_U64)); + /// # } + /// ``` + #[cfg(feature = "std")] + #[must_use] + #[allow(clippy::missing_inline_in_public_items)] + pub fn approx_pow2(exp: f64) -> Option { + const LN2_1P5: f64 = 0.584_962_500_721_156_2_f64; + const EXP2_63: f64 = 9_223_372_036_854_775_808_f64; + + // FEATURE: Round negative to zero. + #[allow(clippy::cast_precision_loss)] // Self::BITS ~< 2^52 and so fits f64. + if exp < LN2_1P5 { + if exp < -1.0 { + return Some(Self::ZERO); + } + return Self::try_from(1).ok(); + } + #[allow(clippy::cast_precision_loss)] + if exp > Self::BITS as f64 { + return None; + } + + // Since exp < BITS, it has an integer and fractional part. + #[allow(clippy::cast_possible_truncation)] // exp <= BITS <= usize::MAX. + #[allow(clippy::cast_sign_loss)] // exp >= 0. + let shift = exp.trunc() as usize; + let fract = exp.fract(); + + // Compute the leading 64 bits + // Since `fract < 1.0` we have `fract.exp2() < 2`, so we can rescale by + // 2^63 and cast to u64. + #[allow(clippy::cast_possible_truncation)] // fract < 1.0 + #[allow(clippy::cast_sign_loss)] // fract >= 0. + let bits = (fract.exp2() * EXP2_63) as u64; + // Note: If `fract` is zero this will result in `u64::MAX`. + + if shift >= 63 { + // OPT: A dedicated function avoiding full-sized shift. + Some(Self::try_from(bits).ok()?.checked_shl(shift - 63)?) + } else { + let shift = 63 - shift; + // Divide `bits` by `2^shift`, rounding to nearest. + let bits = (bits >> shift) + ((bits >> (shift - 1)) & 1); + Self::try_from(bits).ok() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use core::iter::repeat; + use proptest::proptest; + + #[test] + fn test_pow2_shl() { + const_for!(BITS in NON_ZERO if (BITS >= 2) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(e in 0..=BITS+1)| { + assert_eq!(U::from(2).pow(U::from(e)), U::from(1) << e); + }); + }); + } + + #[test] + fn test_pow_product() { + const_for!(BITS in NON_ZERO if (BITS >= 64) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(b in 2_u64..100, e in 0_usize..100)| { + let b = U::from(b); + let prod = repeat(b).take(e).product(); + assert_eq!(b.pow(U::from(e)), prod); + }); + }); + } +} diff --git a/guest-libs/ruint/src/root.rs b/guest-libs/ruint/src/root.rs new file mode 100644 index 0000000000..481640c8b2 --- /dev/null +++ b/guest-libs/ruint/src/root.rs @@ -0,0 +1,139 @@ +#![cfg(feature = "std")] + +use crate::Uint; +use core::cmp::{min, Ordering}; + +impl Uint { + /// Computes the floor of the `degree`-th root of the number. + /// + /// $$ + /// \floor{\sqrt[\mathtt{degree}]{\mathtt{self}}} + /// $$ + /// + /// # Panics + /// + /// Panics if `degree` is zero. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::*}; + /// # uint!{ + /// assert_eq!(0_U64.root(2), 0_U64); + /// assert_eq!(1_U64.root(63), 1_U64); + /// assert_eq!(0x0032da8b0f88575d_U63.root(64), 1_U63); + /// assert_eq!(0x1756800000000000_U63.root(34), 3_U63); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn root(self, degree: usize) -> Self { + assert!(degree > 0, "degree must be greater than zero"); + + // Handle zero case (including BITS == 0). + if self.is_zero() { + return Self::ZERO; + } + + // Handle case where `degree > Self::BITS`. + if degree >= Self::BITS { + return Self::ONE; + } + + // Handle case where `degree == 1`. + if degree == 1 { + return self; + } + + // Create a first guess. + // Root should be less than the value, so approx_pow2 should always succeed. + #[allow(clippy::cast_precision_loss)] // Approximation is good enough. + #[allow(clippy::cast_sign_loss)] // Result should be positive. + let mut result = Self::approx_pow2(self.approx_log2() / degree as f64).unwrap(); + + let deg_m1 = Self::from(degree - 1); + + // Iterate using Newton's method + // See + // See + let mut decreasing = false; + loop { + // OPT: This could benefit from single-limb multiplication + // and division. + // + // OPT: The division can be turned into bit-shifts when the degree is a power of + // two. + let division = result + .checked_pow(deg_m1) + .map_or(Self::ZERO, |power| self / power); + let iter = (division + deg_m1 * result) / Self::from(degree); + match (decreasing, iter.cmp(&result)) { + // Stop when we hit fix point or stop decreasing. + (_, Ordering::Equal) | (true, Ordering::Greater) => break result, + + // When `degree` is high and the initial guess is less than or equal to the + // (small) true result, it takes a long time to converge. Example: + // 0x215f07147d573ef203e1f268ab1516d3f294619db820c5dfd0b334e4d06320b7_U256. + // root(196) takes 5918 iterations to converge from the initial guess of `2`. + // to the final result of `2`. This is because after the first iteration + // it jumps to `1533576856264507`. To fix this we cap the increase at `2x`. + // Once `result` exceeds the true result, it will converge downwards. + (false, Ordering::Greater) => result = min(iter, result.saturating_shl(1)), + + // Converging downwards. + (_, Ordering::Less) => { + decreasing = true; + result = iter; + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::proptest; + + #[test] + #[allow(clippy::absurd_extreme_comparisons)] // From macro. + fn test_root() { + const_for!(BITS in SIZES if (BITS > 3) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U, degree in 1_usize..=5)| { + let root = value.root(degree); + let lower = root.pow(U::from(degree)); + assert!(value >= lower); + let upper = root + .checked_add(U::ONE) + .and_then(|n| n.checked_pow(U::from(degree))); + if let Some(upper) = upper { + assert!(value < upper); + } + }); + }); + } + + #[test] + #[allow(clippy::absurd_extreme_comparisons)] // From macro. + #[allow(clippy::reversed_empty_ranges)] // From macro. + fn test_root_large() { + const_for!(BITS in SIZES if (BITS > 3) { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U, degree in 1_usize..=BITS)| { + let root = value.root(degree); + let lower = root.pow(U::from(degree)); + assert!(value >= lower); + let upper = root + .checked_add(U::ONE) + .and_then(|n| n.checked_pow(U::from(degree))); + if let Some(upper) = upper { + assert!(value < upper); + } + }); + }); + } +} diff --git a/guest-libs/ruint/src/special.rs b/guest-libs/ruint/src/special.rs new file mode 100644 index 0000000000..f70b065b11 --- /dev/null +++ b/guest-libs/ruint/src/special.rs @@ -0,0 +1,122 @@ +use crate::Uint; + +// FEATURE: Special functions +// * Factorial +// * Extended GCD and LCM +// * https://en.wikipedia.org/wiki/Euler%27s_totient_function +// * https://en.wikipedia.org/wiki/Carmichael_function +// * https://en.wikipedia.org/wiki/Jordan%27s_totient_function +// * Feature parity with GMP: +// * https://gmplib.org/manual/Integer-Functions.html#Integer-Functions + +// https://en.wikipedia.org/wiki/Kronecker_symbol +// Subsumes Jacobi and Legendre symbols. + +// Primality testing +// https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test#Testing_against_small_sets_of_bases + +impl Uint { + /// Returns `true` if and only if `self == 2^k` for some `k`. + #[inline] + #[must_use] + pub const fn is_power_of_two(self) -> bool { + self.count_ones() == 1 + } + + /// Returns the smallest power of two greater than or equal to self. + /// + /// # Panics + /// + /// Panics if the value overlfows. + #[inline] + #[must_use] + pub fn next_power_of_two(self) -> Self { + self.checked_next_power_of_two().unwrap() + } + + /// Returns the smallest power of two greater than or equal to `self`. If + /// the next power of two is greater than the type’s maximum value, + /// [`None`] is returned, otherwise the power of two is wrapped in + /// [`Some`]. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::U64}; + /// # uint!{ + /// assert_eq!(0_U64.checked_next_power_of_two(), Some(1_U64)); + /// assert_eq!(1_U64.checked_next_power_of_two(), Some(1_U64)); + /// assert_eq!(2_U64.checked_next_power_of_two(), Some(2_U64)); + /// assert_eq!(3_U64.checked_next_power_of_two(), Some(4_U64)); + /// assert_eq!(U64::MAX.checked_next_power_of_two(), None); + /// # } + /// ``` + #[inline] + #[must_use] + pub fn checked_next_power_of_two(self) -> Option { + if self.is_power_of_two() { + return Some(self); + } + let exp = self.bit_len(); + if exp >= BITS { + return None; + } + Some(Self::ONE << exp) + } +} + +impl Uint { + /// Calculates the smallest value greater than or equal to self that is a + /// multiple of rhs. + /// + /// # Panics + /// + /// This function will panic if `rhs` is 0 or the operation results in + /// overflow. + #[inline] + #[must_use] + pub fn next_multiple_of(self, rhs: Self) -> Self { + self.checked_next_multiple_of(rhs).unwrap(); + todo!() + } + + /// Calculates the smallest value greater than or equal to `self` that is a + /// multiple of `rhs`. Returns [`None`] is `rhs` is zero or the + /// operation would result in overflow. + /// + /// # Examples + /// + /// ``` + /// # use ruint::{Uint, uint, aliases::U64}; + /// # uint!{ + /// assert_eq!(16_U64.checked_next_multiple_of(8_U64), Some(16_U64)); + /// assert_eq!(23_U64.checked_next_multiple_of(8_U64), Some(24_U64)); + /// assert_eq!(1_U64.checked_next_multiple_of(0_U64), None); + /// assert_eq!(U64::MAX.checked_next_multiple_of(2_U64), None); + /// } + /// ``` + /// + /// ``` + /// # use ruint::{Uint, uint}; + /// # uint!{ + /// assert_eq!(0_U0.checked_next_multiple_of(0_U0), None); + /// assert_eq!(0_U1.checked_next_multiple_of(0_U1), None); + /// assert_eq!(0_U1.checked_next_multiple_of(1_U1), Some(0_U1)); + /// assert_eq!(1_U1.checked_next_multiple_of(0_U1), None); + /// assert_eq!(1_U1.checked_next_multiple_of(1_U1), Some(1_U1)); + /// } + /// ``` + #[inline] + #[must_use] + pub fn checked_next_multiple_of(self, rhs: Self) -> Option { + if rhs.is_zero() { + return None; + } + let (q, r) = self.div_rem(rhs); + if r.is_zero() { + return Some(self); + } + let q = q.checked_add(Self::ONE)?; + q.checked_mul(rhs) + } +} diff --git a/guest-libs/ruint/src/string.rs b/guest-libs/ruint/src/string.rs new file mode 100644 index 0000000000..4bc4bcf6aa --- /dev/null +++ b/guest-libs/ruint/src/string.rs @@ -0,0 +1,140 @@ +#![allow(clippy::missing_inline_in_public_items)] // allow format functions + +use crate::{base_convert::BaseConvertError, Uint}; +use core::{fmt, str::FromStr}; + +/// Error for [`from_str_radix`](Uint::from_str_radix). +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ParseError { + /// Invalid digit in string. + InvalidDigit(char), + + /// Invalid radix, up to base 64 is supported. + InvalidRadix(u64), + + /// Error from [`Uint::from_base_be`]. + BaseConvertError(BaseConvertError), +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseError { + #[inline] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::BaseConvertError(e) => Some(e), + _ => None, + } + } +} + +impl From for ParseError { + #[inline] + fn from(value: BaseConvertError) -> Self { + Self::BaseConvertError(value) + } +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::BaseConvertError(e) => e.fmt(f), + Self::InvalidDigit(c) => write!(f, "invalid digit: {c}"), + Self::InvalidRadix(r) => write!(f, "invalid radix {r}, up to 64 is supported"), + } + } +} + +impl Uint { + /// Parse a string into a [`Uint`]. + /// + /// For bases 2 to 36, the case-agnostic alphabet 0—1, a—b is used and `_` + /// are ignored. For bases 37 to 64, the case-sensitive alphabet a—z, A—Z, + /// 0—9, {+-}, {/,_} is used. That is, for base 64 it is compatible with + /// all the common base64 variants. + /// + /// # Errors + /// + /// * [`ParseError::InvalidDigit`] if the string contains a non-digit. + /// * [`ParseError::InvalidRadix`] if the radix is larger than 64. + /// * [`ParseError::BaseConvertError`] if [`Uint::from_base_be`] fails. + // FEATURE: Support proper unicode. Ignore zero-width spaces, joiners, etc. + // Recognize digits from other alphabets. + pub fn from_str_radix(src: &str, radix: u64) -> Result { + if radix > 64 { + return Err(ParseError::InvalidRadix(radix)); + } + let mut err = None; + let digits = src.chars().filter_map(|c| { + if err.is_some() { + return None; + } + let digit = if radix <= 36 { + // Case insensitive 0—9, a—z. + match c { + '0'..='9' => u64::from(c) - u64::from('0'), + 'a'..='z' => u64::from(c) - u64::from('a') + 10, + 'A'..='Z' => u64::from(c) - u64::from('A') + 10, + '_' => return None, // Ignored character. + _ => { + err = Some(ParseError::InvalidDigit(c)); + return None; + } + } + } else { + // The Base-64 alphabets + match c { + 'A'..='Z' => u64::from(c) - u64::from('A'), + 'a'..='f' => u64::from(c) - u64::from('a') + 26, + '0'..='9' => u64::from(c) - u64::from('0') + 52, + '+' | '-' => 62, + '/' | ',' | '_' => 63, + '=' | '\r' | '\n' => return None, // Ignored characters. + _ => { + err = Some(ParseError::InvalidDigit(c)); + return None; + } + } + }; + Some(digit) + }); + let value = Self::from_base_be(radix, digits)?; + err.map_or(Ok(value), Err) + } +} + +impl FromStr for Uint { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + let (src, radix) = if src.is_char_boundary(2) { + let (prefix, rest) = src.split_at(2); + match prefix { + "0x" | "0X" => (rest, 16), + "0o" | "0O" => (rest, 8), + "0b" | "0B" => (rest, 2), + _ => (src, 10), + } + } else { + (src, 10) + }; + Self::from_str_radix(src, radix) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::{prop_assert_eq, proptest}; + + #[test] + fn test_parse() { + proptest!(|(value: u128)| { + type U = Uint<128, 2>; + prop_assert_eq!(U::from_str(&format!("{value:#b}")), Ok(U::from(value))); + prop_assert_eq!(U::from_str(&format!("{value:#o}")), Ok(U::from(value))); + prop_assert_eq!(U::from_str(&format!("{value:}")), Ok(U::from(value))); + prop_assert_eq!(U::from_str(&format!("{value:#x}")), Ok(U::from(value))); + prop_assert_eq!(U::from_str(&format!("{value:#X}")), Ok(U::from(value))); + }); + } +} diff --git a/guest-libs/ruint/src/support/alloy_rlp.rs b/guest-libs/ruint/src/support/alloy_rlp.rs new file mode 100644 index 0000000000..50ebb97662 --- /dev/null +++ b/guest-libs/ruint/src/support/alloy_rlp.rs @@ -0,0 +1,190 @@ +//! Support for the [`alloy-rlp`](https://crates.io/crates/alloy-rlp) crate. + +#![cfg(feature = "alloy-rlp")] +#![cfg_attr(docsrs, doc(cfg(feature = "alloy-rlp")))] + +use crate::Uint; +use alloy_rlp::{ + length_of_length, BufMut, Decodable, Encodable, Error, Header, MaxEncodedLen, + MaxEncodedLenAssoc, EMPTY_STRING_CODE, +}; + +const MAX_BITS: usize = 55 * 8; + +/// Allows a [`Uint`] to be serialized as RLP. +/// +/// See +impl Encodable for Uint { + #[inline] + fn length(&self) -> usize { + let bits = self.bit_len(); + if bits <= 7 { + 1 + } else { + let bytes = (bits + 7) / 8; + bytes + length_of_length(bytes) + } + } + + #[inline] + fn encode(&self, out: &mut dyn BufMut) { + // fast paths, avoiding allocation due to `to_be_bytes_vec` + match LIMBS { + 0 => return out.put_u8(EMPTY_STRING_CODE), + 1 => return self.limbs[0].encode(out), + #[allow(clippy::cast_lossless)] + 2 => return (self.limbs[0] as u128 | ((self.limbs[1] as u128) << 64)).encode(out), + _ => {} + } + + match self.bit_len() { + 0 => out.put_u8(EMPTY_STRING_CODE), + 1..=7 => { + #[allow(clippy::cast_possible_truncation)] // self < 128 + out.put_u8(self.limbs[0] as u8); + } + bits => { + // avoid heap allocation in `to_be_bytes_vec` + // SAFETY: we don't re-use `copy` + #[cfg(target_endian = "little")] + let mut copy = *self; + #[cfg(target_endian = "little")] + let bytes = unsafe { copy.as_le_slice_mut() }; + #[cfg(target_endian = "little")] + bytes.reverse(); + + #[cfg(target_endian = "big")] + let bytes = self.to_be_bytes_vec(); + + let leading_zero_bytes = Self::BYTES - (bits + 7) / 8; + let trimmed = &bytes[leading_zero_bytes..]; + if bits > MAX_BITS { + trimmed.encode(out); + } else { + #[allow(clippy::cast_possible_truncation)] // bytes.len() < 56 < 256 + out.put_u8(EMPTY_STRING_CODE + trimmed.len() as u8); + out.put_slice(trimmed); + } + } + } + } +} + +/// Allows a [`Uint`] to be deserialized from RLP. +/// +/// See +impl Decodable for Uint { + #[inline] + fn decode(buf: &mut &[u8]) -> Result { + let bytes = Header::decode_bytes(buf, false)?; + + // The RLP spec states that deserialized positive integers with leading zeroes + // get treated as invalid. + // + // See: + // https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ + // + // To check this, we only need to check if the first byte is zero to make sure + // there are no leading zeros + if !bytes.is_empty() && bytes[0] == 0 { + return Err(Error::LeadingZero); + } + + Self::try_from_be_slice(bytes).ok_or(Error::Overflow) + } +} + +#[cfg(feature = "generic_const_exprs")] +unsafe impl + MaxEncodedLen<{ Self::BYTES + length_of_length(Self::BYTES) }> for Uint +{ +} + +#[cfg(not(feature = "generic_const_exprs"))] +const _: () = { + crate::const_for!(BITS in [0, 1, 2, 8, 16, 32, 64, 128, 160, 192, 256, 384, 512, 4096] { + const LIMBS: usize = crate::nlimbs(BITS); + const BYTES: usize = Uint::::BYTES; + unsafe impl MaxEncodedLen<{ BYTES + length_of_length(BYTES) }> for Uint {} + }); +}; + +unsafe impl MaxEncodedLenAssoc for Uint { + const LEN: usize = Self::BYTES + length_of_length(Self::BYTES); +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + aliases::{U0, U256}, + const_for, nlimbs, + }; + use hex_literal::hex; + use proptest::proptest; + + fn encode(value: T) -> Vec { + let mut buf = vec![]; + value.encode(&mut buf); + buf + } + + #[test] + fn test_rlp() { + // See + assert_eq!(encode(U0::from(0))[..], hex!("80")); + assert_eq!(encode(U256::from(0))[..], hex!("80")); + assert_eq!(encode(U256::from(15))[..], hex!("0f")); + assert_eq!(encode(U256::from(1024))[..], hex!("820400")); + assert_eq!(encode(U256::from(0x1234_5678))[..], hex!("8412345678")); + } + + #[test] + fn test_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = encode(value); + + #[cfg(feature = "rlp")] + { + use rlp::Encodable as _; + let serialized_rlp = value.rlp_bytes(); + assert_eq!(serialized, serialized_rlp.freeze()[..]); + } + + assert_eq!(serialized.len(), value.length()); + let mut reader = &serialized[..]; + let deserialized = Uint::decode(&mut reader).unwrap(); + assert_eq!(reader.len(), 0); + assert_eq!(value, deserialized); + }); + }); + } + + #[test] + fn test_invalid_uints() { + // these are non-canonical because they have leading zeros + assert_eq!( + U256::decode(&mut &hex!("820000")[..]), + Err(Error::LeadingZero) + ); + // 00 is not a valid uint + // See https://github.com/ethereum/go-ethereum/blob/cd2953567268777507b1ec29269315324fb5aa9c/rlp/decode_test.go#L118 + assert_eq!(U256::decode(&mut &hex!("00")[..]), Err(Error::LeadingZero)); + // these are non-canonical because they can fit in a single byte, i.e. + // 0x7f, 0x33 + assert_eq!( + U256::decode(&mut &hex!("8100")[..]), + Err(Error::NonCanonicalSingleByte) + ); + assert_eq!( + U256::decode(&mut &hex!("817f")[..]), + Err(Error::NonCanonicalSingleByte) + ); + assert_eq!( + U256::decode(&mut &hex!("8133")[..]), + Err(Error::NonCanonicalSingleByte) + ); + } +} diff --git a/guest-libs/ruint/src/support/arbitrary.rs b/guest-libs/ruint/src/support/arbitrary.rs new file mode 100644 index 0000000000..f2e27c7d6b --- /dev/null +++ b/guest-libs/ruint/src/support/arbitrary.rs @@ -0,0 +1,52 @@ +//! Support for the [`arbitrary`](https://crates.io/crates/arbitrary) crate. + +#![cfg(feature = "arbitrary")] +#![cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))] + +use crate::Uint; +use arbitrary::{Arbitrary, Result, Unstructured}; + +// TODO: Instead of uniform random sampling, we should use a distribution that +// exercises different scales more. Something like sum(±2ⁱ for random i). The +// reduction step can then remove terms or make them smaller. + +// TODO: We should use `rand` in tests, not `arbitrary`. + +impl<'a, const BITS: usize, const LIMBS: usize> Arbitrary<'a> for Uint { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let mut limbs = [0; LIMBS]; + if let Some((last, rest)) = limbs.split_last_mut() { + for limb in rest { + *limb = u64::arbitrary(u)?; + } + *last = u.int_in_range(0..=Self::MASK)?; + } + Ok(Self::from_limbs(limbs)) + } + + fn size_hint(_depth: usize) -> (usize, Option) { + let bytes = (BITS + 7) / 8; + (bytes, Some(bytes)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use core::iter::repeat; + + #[allow(unused_imports)] + use alloc::vec::Vec; + + #[test] + fn test_arbitrary() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + let (num_bytes, _) = Uint::::size_hint(0); + let bytes = repeat(0x55u8).take(num_bytes).collect::>(); + let mut u = arbitrary::Unstructured::new(&bytes); + Uint::::arbitrary(&mut u).unwrap(); + }); + } +} diff --git a/guest-libs/ruint/src/support/ark_ff.rs b/guest-libs/ruint/src/support/ark_ff.rs new file mode 100644 index 0000000000..5713d38924 --- /dev/null +++ b/guest-libs/ruint/src/support/ark_ff.rs @@ -0,0 +1,149 @@ +//! Support for the [`ark-ff`](https://crates.io/crates/ark-ff) crate 0.3 +//! version. +#![cfg(feature = "ark-ff")] +#![cfg_attr(docsrs, doc(cfg(feature = "ark-ff")))] + +use crate::{ToFieldError, Uint}; +use ark_ff_03::{ + biginteger::{ + BigInteger128, BigInteger256, BigInteger320, BigInteger384, BigInteger448, BigInteger64, + BigInteger768, BigInteger832, + }, + fields::models::{ + Fp256, Fp256Parameters, Fp320, Fp320Parameters, Fp384, Fp384Parameters, Fp448, + Fp448Parameters, Fp64, Fp64Parameters, Fp768, Fp768Parameters, Fp832, Fp832Parameters, + }, + PrimeField, +}; + +// FEATURE: Implement the `BigInteger` trait. + +macro_rules! impl_from_ark { + ($ark:ty, $bits:expr, $limbs:expr) => { + impl From<$ark> for Uint<$bits, $limbs> { + fn from(value: $ark) -> Self { + Self::from_limbs(value.0) + } + } + + impl From<&$ark> for Uint<$bits, $limbs> { + fn from(value: &$ark) -> Self { + Self::from_limbs(value.0) + } + } + + impl From> for $ark { + fn from(value: Uint<$bits, $limbs>) -> Self { + Self(value.into_limbs()) + } + } + + impl From<&Uint<$bits, $limbs>> for $ark { + fn from(value: &Uint<$bits, $limbs>) -> Self { + Self(value.into_limbs()) + } + } + }; +} + +impl_from_ark!(BigInteger64, 64, 1); +impl_from_ark!(BigInteger128, 128, 2); +impl_from_ark!(BigInteger256, 256, 4); +impl_from_ark!(BigInteger320, 320, 5); +impl_from_ark!(BigInteger384, 384, 6); +impl_from_ark!(BigInteger448, 448, 7); +impl_from_ark!(BigInteger768, 768, 12); +impl_from_ark!(BigInteger832, 832, 13); + +macro_rules! impl_from_ark_field { + ($field:ident, $params:ident, $bits:expr, $limbs:expr) => { + impl From<$field

> for Uint<$bits, $limbs> { + fn from(value: $field

) -> Self { + value.into_repr().into() + } + } + + impl From<&$field

> for Uint<$bits, $limbs> { + fn from(value: &$field

) -> Self { + value.into_repr().into() + } + } + + impl TryFrom> for $field

{ + type Error = ToFieldError; + + fn try_from(value: Uint<$bits, $limbs>) -> Result { + Self::from_repr(value.into()).ok_or(ToFieldError::NotInField) + } + } + + impl TryFrom<&Uint<$bits, $limbs>> for $field

{ + type Error = ToFieldError; + + fn try_from(value: &Uint<$bits, $limbs>) -> Result { + Self::from_repr(value.into()).ok_or(ToFieldError::NotInField) + } + } + }; +} + +impl_from_ark_field!(Fp64, Fp64Parameters, 64, 1); +impl_from_ark_field!(Fp256, Fp256Parameters, 256, 4); +impl_from_ark_field!(Fp320, Fp320Parameters, 320, 5); +impl_from_ark_field!(Fp384, Fp384Parameters, 384, 6); +impl_from_ark_field!(Fp448, Fp448Parameters, 448, 7); +impl_from_ark_field!(Fp768, Fp768Parameters, 768, 12); +impl_from_ark_field!(Fp832, Fp832Parameters, 832, 13); + +#[cfg(test)] +mod tests { + use super::*; + use crate::aliases::U256; + use ark_bn254_03::{Fq, FqParameters, Fr, FrParameters}; + use ark_ff_03::FpParameters; + use proptest::proptest; + + macro_rules! test_roundtrip { + ($ark:ty, $bits:expr, $limbs:expr) => { + proptest!(|(value: Uint<$bits, $limbs>)| { + let ark: $ark = value.into(); + let back: Uint<$bits, $limbs> = ark.into(); + assert_eq!(back, value); + }); + } + } + + #[test] + fn test_roundtrip() { + test_roundtrip!(BigInteger64, 64, 1); + test_roundtrip!(BigInteger128, 128, 2); + test_roundtrip!(BigInteger256, 256, 4); + test_roundtrip!(BigInteger320, 320, 5); + test_roundtrip!(BigInteger384, 384, 6); + test_roundtrip!(BigInteger448, 448, 7); + test_roundtrip!(BigInteger768, 768, 12); + test_roundtrip!(BigInteger832, 832, 13); + } + + #[test] + fn test_fq_roundtrip() { + let modulus: U256 = FqParameters::MODULUS.into(); + proptest!(|(value: U256)| { + let value: U256 = value % modulus; + let f: Fq = value.try_into().unwrap(); + let back: U256 = f.into(); + assert_eq!(back, value); + }); + } + + #[test] + fn test_fr_roundtrip() { + let modulus: U256 = FrParameters::MODULUS.into(); + proptest!(|(value: U256)| { + let value: U256 = value % modulus; + let f: Fr = value.try_into().unwrap(); + let back: U256 = f.into(); + assert_eq!(back, value); + }); + } +} diff --git a/guest-libs/ruint/src/support/ark_ff_04.rs b/guest-libs/ruint/src/support/ark_ff_04.rs new file mode 100644 index 0000000000..95ab41339b --- /dev/null +++ b/guest-libs/ruint/src/support/ark_ff_04.rs @@ -0,0 +1,130 @@ +//! Support for the [`ark-ff`](https://crates.io/crates/ark-ff) crate. +#![cfg(feature = "ark-ff-04")] +#![cfg_attr(docsrs, doc(cfg(feature = "ark-ff-04")))] + +use crate::{ToFieldError, Uint}; +use ark_ff_04::{ + biginteger::BigInt, + fields::models::{Fp, FpConfig}, + PrimeField, +}; + +// FEATURE: Implement the `BigInteger` trait. + +// BigInt + +impl From> for Uint { + fn from(value: BigInt) -> Self { + Self::from_limbs(value.0) + } +} + +impl From<&BigInt> for Uint { + fn from(value: &BigInt) -> Self { + Self::from_limbs(value.0) + } +} + +impl From> for BigInt { + fn from(value: Uint) -> Self { + Self::new(value.into_limbs()) + } +} + +impl From<&Uint> for BigInt { + fn from(value: &Uint) -> Self { + Self::new(value.into_limbs()) + } +} + +// Fp + +impl, const BITS: usize, const LIMBS: usize> From> + for Uint +{ + fn from(value: Fp) -> Self { + value.into_bigint().into() + } +} + +impl, const BITS: usize, const LIMBS: usize> From<&Fp> + for Uint +{ + fn from(value: &Fp) -> Self { + value.into_bigint().into() + } +} + +impl, const BITS: usize, const LIMBS: usize> TryFrom> + for Fp +{ + type Error = ToFieldError; + + fn try_from(value: Uint) -> Result { + Self::from_bigint(value.into()).ok_or(ToFieldError::NotInField) + } +} + +impl, const BITS: usize, const LIMBS: usize> TryFrom<&Uint> + for Fp +{ + type Error = ToFieldError; + + fn try_from(value: &Uint) -> Result { + Self::from_bigint(value.into()).ok_or(ToFieldError::NotInField) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::aliases::U256; + use ark_bn254_04::{Fq, FqConfig, Fr, FrConfig}; + use ark_ff_04::MontConfig; + use proptest::proptest; + + macro_rules! test_roundtrip { + ($ark:ty, $bits:expr, $limbs:expr) => { + proptest!(|(value: Uint<$bits, $limbs>)| { + let ark: $ark = value.into(); + let back: Uint<$bits, $limbs> = ark.into(); + assert_eq!(back, value); + }); + } + } + + #[test] + fn test_roundtrip() { + use ark_ff_04::*; + test_roundtrip!(BigInteger64, 64, 1); + test_roundtrip!(BigInteger128, 128, 2); + test_roundtrip!(BigInteger256, 256, 4); + test_roundtrip!(BigInteger320, 320, 5); + test_roundtrip!(BigInteger384, 384, 6); + test_roundtrip!(BigInteger448, 448, 7); + test_roundtrip!(BigInteger768, 768, 12); + test_roundtrip!(BigInteger832, 832, 13); + } + + #[test] + fn test_fq_roundtrip() { + let modulus: U256 = FqConfig::MODULUS.into(); + proptest!(|(value: U256)| { + let value: U256 = value % modulus; + let f: Fq = value.try_into().unwrap(); + let back: U256 = f.into(); + assert_eq!(back, value); + }); + } + + #[test] + fn test_fr_roundtrip() { + let modulus: U256 = FrConfig::MODULUS.into(); + proptest!(|(value: U256)| { + let value: U256 = value % modulus; + let f: Fr = value.try_into().unwrap(); + let back: U256 = f.into(); + assert_eq!(back, value); + }); + } +} diff --git a/guest-libs/ruint/src/support/bn_rs.rs b/guest-libs/ruint/src/support/bn_rs.rs new file mode 100644 index 0000000000..e55543cd00 --- /dev/null +++ b/guest-libs/ruint/src/support/bn_rs.rs @@ -0,0 +1,155 @@ +//! Support for the [`bn-rs`](https://crates.io/crates/bn-rs) crate. + +#![cfg(feature = "bn-rs")] +#![cfg_attr(docsrs, doc(cfg(feature = "bn-rs")))] + +use crate::{from::ToUintError, BaseConvertError, Bits, ParseError, Uint}; +use bn_rs::{BigNumber, BN}; + +impl TryFrom<&BN> for Uint { + type Error = ToUintError; + + // FIXME: Return wrapped values. + fn try_from(value: &BN) -> Result { + if value.negative() == 1 { + return Err(ToUintError::ValueNegative(BITS, Self::ZERO)); + } + if value.byte_length() as usize > Self::BYTES { + return Err(ToUintError::ValueTooLarge(BITS, Self::ZERO)); + } + // Binding for `toArray` + // `a.toArray(endian, length)` - convert to byte array, and optionally zero pad + // to length, throwing if already exceeding. + value.to_array("le".into(), 0).map_or_else( + |_| Err(ToUintError::NotANumber(BITS)), + |bytes| { + Self::try_from_le_slice(&bytes).ok_or(ToUintError::ValueTooLarge(BITS, Self::ZERO)) + }, + ) + } +} + +impl TryFrom for Uint { + type Error = ToUintError; + + fn try_from(value: BN) -> Result { + Self::try_from(&value) + } +} + +impl From<&Uint> for BN { + fn from(value: &Uint) -> Self { + Self::new_from_array(&value.as_le_bytes(), 256) + } +} + +impl From> for BN { + fn from(value: Uint) -> Self { + (&value).into() + } +} + +impl TryFrom<&BigNumber> for Uint { + type Error = ToUintError; + + // FIXME: Return wrapped values. + fn try_from(value: &BigNumber) -> Result { + let hex = value.hex(); + Self::from_str_radix(&hex, 16).map_err(|e| match e { + ParseError::BaseConvertError(BaseConvertError::Overflow) => { + ToUintError::ValueTooLarge(BITS, Self::ZERO) + } + _ => ToUintError::NotANumber(BITS), + }) + } +} + +impl TryFrom for Uint { + type Error = ToUintError; + + fn try_from(value: BigNumber) -> Result { + Self::try_from(&value) + } +} + +impl From<&Uint> for BigNumber { + fn from(value: &Uint) -> Self { + Self::new(format!("{value:#x}")) + } +} + +impl From> for BigNumber { + fn from(value: Uint) -> Self { + (&value).into() + } +} + +macro_rules! impl_bits { + ($($ty:ty)*) => { + $( + impl TryFrom<$ty> for Bits { + type Error = as TryFrom<$ty>>::Error; + + fn try_from(value: $ty) -> Result { + Uint::try_from(value).map(Self::from) + } + } + + impl<'a, const BITS: usize, const LIMBS: usize> TryFrom<&'a $ty> for Bits { + type Error = as TryFrom<&'a $ty>>::Error; + + fn try_from(value: &'a $ty) -> Result { + Uint::try_from(value).map(Self::from) + } + } + + impl From> for $ty { + fn from(value: Bits) -> Self { + Self::from(value.into_inner()) + } + } + + impl From<&Bits> for $ty { + fn from(value: &Bits) -> Self { + Self::from(value.as_uint()) + } + } + )* + } +} + +impl_bits!(BN BigNumber); + +#[cfg(test)] +#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] // Tests require wasm +mod test { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::proptest; + + #[test] + fn test_bn_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + let obj: BN = value.into(); + let native = obj.try_into().unwrap(); + assert_eq!(value, native); + }); + }); + } + + #[test] + fn test_bignumber_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + let obj: BigNumber = value.into(); + let native = obj.try_into().unwrap(); + assert_eq!(value, native); + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/borsh.rs b/guest-libs/ruint/src/support/borsh.rs new file mode 100644 index 0000000000..00dec73656 --- /dev/null +++ b/guest-libs/ruint/src/support/borsh.rs @@ -0,0 +1,165 @@ +//! Support for the [`borsh`](https://crates.io/crates/borsh) crate. + +#![cfg(feature = "borsh")] +#![cfg_attr(docsrs, doc(cfg(feature = "borsh")))] + +use crate::{Bits, Uint}; +use borsh::{io, BorshDeserialize, BorshSerialize}; + +impl BorshDeserialize for Uint { + #[inline] + fn deserialize_reader(reader: &mut R) -> io::Result { + // This is a bit of an end-run around missing `generic_const_exprs` + // We cannot declare a `[u8; Self::BYTES]` or `[u8; LIMBS * 8]`, + // so we declare a `[u8; LIMBS]` and use unsafe to write to it. + + // TODO: Replace the unsafety with `generic_const_exprs` when + // available + let mut limbs = [0u64; LIMBS]; + + // SAFETY: `limbs` is known to have identical memory layout and + // alignment to `[u8; LIMBS * 8]`, which is guaranteed to safely + // contain [u8; Self::BYTES]`, as `LIMBS * 8 >= Self::BYTES`. + // Reference: + // https://doc.rust-lang.org/reference/type-layout.html#array-layout + let target = unsafe { + core::slice::from_raw_parts_mut(limbs.as_mut_ptr().cast::(), Self::BYTES) + }; + reader.read_exact(target)?; + + // Using `Self::from_limbs(limbs)` would be incorrect here, as the + // inner u64s are encoded in LE, and the platform may be BE. + Self::try_from_le_slice(target).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + "value is too large for the type", + ) + }) + } +} + +impl BorshSerialize for Uint { + #[inline] + fn serialize(&self, writer: &mut W) -> io::Result<()> { + #[cfg(target_endian = "little")] + return writer.write_all(self.as_le_slice()); + + // TODO: Replace the unsafety with `generic_const_exprs` when + // available + #[cfg(target_endian = "big")] + { + let mut limbs = [0u64; LIMBS]; + // SAFETY: `limbs` is known to have identical memory layout and + // alignment to `[u8; LIMBS * 8]`, which is guaranteed to safely + // contain [u8; Self::BYTES]`, as `LIMBS * 8 >= Self::BYTES`. + // Reference: + // https://doc.rust-lang.org/reference/type-layout.html#array-layout + let mut buf = unsafe { + core::slice::from_raw_parts_mut(limbs.as_mut_ptr().cast::(), Self::BYTES) + }; + self.copy_le_bytes_to(&mut buf); + writer.write_all(&buf) + } + } +} + +impl BorshDeserialize for Bits { + #[inline] + fn deserialize_reader(reader: &mut R) -> io::Result { + Uint::::deserialize_reader(reader).map(Into::into) + } +} + +impl BorshSerialize for Bits { + #[inline] + fn serialize(&self, writer: &mut W) -> io::Result<()> { + self.as_uint().serialize(writer) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[derive(Debug, BorshDeserialize, BorshSerialize, PartialEq, Eq)] + struct Something { + is_it: bool, + value: Uint<256, 4>, + } + + #[test] + fn test_uint() { + let something = Something { + is_it: true, + value: Uint::<256, 4>::from_limbs([1, 2, 3, 4]), + }; + let mut buf = [0; 33]; + + something.serialize(&mut buf.as_mut_slice()).unwrap(); + assert_eq!(buf, [ + 1, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0 + ]); + assert_eq!(&something.value.to_le_bytes::<32>(), &buf[1..]); + assert_eq!(Something::try_from_slice(&buf[..]).unwrap(), something); + } + + #[derive(Debug, BorshDeserialize, BorshSerialize, PartialEq, Eq)] + struct AnotherThing { + is_it: bool, + value: Bits<256, 4>, + } + + #[test] + fn test_bits() { + let another_thing = AnotherThing { + is_it: true, + value: Bits::<256, 4>::from_limbs([1, 2, 3, 4]), + }; + let mut buf = [0; 33]; + + another_thing.serialize(&mut buf.as_mut_slice()).unwrap(); + + assert_eq!(buf, [ + 1, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0 + ]); + assert_eq!(&another_thing.value.to_le_bytes::<32>(), &buf[1..]); + assert_eq!( + AnotherThing::try_from_slice(&buf[..]).unwrap(), + another_thing + ); + } + + #[test] + fn deser_invalid_value() { + let buf = [0xff; 4]; + let mut reader = &mut &buf[..]; + + let result = Uint::<31, 1>::deserialize_reader(&mut reader); + let err = result.unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::InvalidData); + assert_eq!(err.to_string(), "value is too large for the type"); + } + + #[derive(Debug, BorshDeserialize, BorshSerialize, PartialEq, Eq)] + struct AThirdThing { + value: Uint<64, 1>, + bool_value: bool, + } + + #[test] + fn roundtrip_trailing_zeroes() { + let instance = AThirdThing { + value: Uint::<64, 1>::from_limbs([1]), + bool_value: true, + }; + + let mut buf = [0u8; 9]; + + instance.serialize(&mut buf.as_mut_slice()).unwrap(); + assert_eq!(buf, [1, 0, 0, 0, 0, 0, 0, 0, 1]); + assert_eq!(&instance.value.to_le_bytes::<8>(), &buf[..8]); + assert_eq!(AThirdThing::try_from_slice(&buf[..]).unwrap(), instance); + } +} diff --git a/guest-libs/ruint/src/support/bytemuck.rs b/guest-libs/ruint/src/support/bytemuck.rs new file mode 100644 index 0000000000..cfdb67f941 --- /dev/null +++ b/guest-libs/ruint/src/support/bytemuck.rs @@ -0,0 +1,79 @@ +//! Support for the [`bytemuck`](https://crates.io/crates/bytemuck) crate. +#![cfg(feature = "bytemuck")] +#![cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))] + +use crate::Uint; +use bytemuck::{Pod, Zeroable}; + +// Implement Zeroable for all `Uint` types. +unsafe impl Zeroable for Uint<{ BITS }, { LIMBS }> {} + +// Implement the `Pod` trait for `Uint` types with a size that is a multiple of +// 64, up to 1024. Note that implementors must have a size that is divisible by +// 64, and using `Uint` sizes not divisible by 64 would violate Pod's +// guarantees potentially leading to undefined behavior. +macro_rules! impl_pod { + ($(($bits:expr, $limbs:expr)),+ $(,)?) => { + $( + unsafe impl Pod for Uint<{$bits}, $limbs> {} + )+ + }; +} + +impl_pod! { + (64, 1), + (128, 2), + (192, 3), + (256, 4), + (320, 5), + (384, 6), + (448, 7), + (512, 8), + (576, 9), + (640, 10), + (704, 11), + (768, 12), + (832, 13), + (896, 14), + (960, 15), + (1024, 16), +} + +#[cfg(test)] +mod tests { + use bytemuck::{Pod, Zeroable}; + use ruint::Uint; + + #[test] + fn test_uint_pod() { + test_pod::<64, 1>(); + test_pod::<128, 2>(); + test_pod::<192, 3>(); + test_pod::<256, 4>(); + test_pod::<320, 5>(); + test_pod::<384, 6>(); + test_pod::<448, 7>(); + test_pod::<512, 8>(); + test_pod::<576, 9>(); + test_pod::<640, 10>(); + test_pod::<704, 11>(); + test_pod::<768, 12>(); + test_pod::<832, 13>(); + test_pod::<896, 14>(); + test_pod::<960, 15>(); + test_pod::<1024, 16>(); + } + + fn test_pod() + where + Uint<{ BITS }, { LIMBS }>: Zeroable + Pod + Eq + Default, + { + let val = Uint::<{ BITS }, { LIMBS }>::default(); + let bytes = bytemuck::bytes_of(&val); + + assert_eq!(bytes.len(), core::mem::size_of::>()); + + let zeroed_val: Uint<{ BITS }, { LIMBS }> = Zeroable::zeroed(); + assert_eq!(zeroed_val, Uint::<{ BITS }, { LIMBS }>::default()); + } +} diff --git a/guest-libs/ruint/src/support/der.rs b/guest-libs/ruint/src/support/der.rs new file mode 100644 index 0000000000..89234785f6 --- /dev/null +++ b/guest-libs/ruint/src/support/der.rs @@ -0,0 +1,269 @@ +//! Support for the [`der`](https://crates.io/crates/der) crate. +#![cfg(feature = "der")] +#![cfg_attr(docsrs, doc(cfg(feature = "der")))] + +use crate::Uint; +use alloc::boxed::Box; +use core::cmp::Ordering; +use der::{ + asn1::{Any, AnyRef, Int, IntRef, Uint as DerUint, UintRef}, + DecodeValue, EncodeValue, Error, FixedTag, Header, Length, Reader, Result, Tag, ValueOrd, + Writer, +}; + +impl ValueOrd for Uint { + fn value_cmp(&self, other: &Self) -> Result { + // DER encoding corresponds to integer comparison. + Ok(self.cmp(other)) + } +} + +impl FixedTag for Uint { + const TAG: Tag = Tag::Integer; +} + +impl EncodeValue for Uint { + fn value_len(&self) -> Result { + (1 + self.bit_len() / 8).try_into() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { + // Write bytes in big-endian order without leading zeros. + let bytes = self.to_be_bytes_trimmed_vec(); + // Add leading `0x00` byte if the first byte has the highest bit set. + // or if the sequence is empty. + if bytes.first().copied().unwrap_or(0x80) >= 0x80 { + writer.write_byte(0x00)?; + } + writer.write(&bytes) + } +} + +impl<'a, const BITS: usize, const LIMBS: usize> DecodeValue<'a> for Uint { + fn decode_value>(reader: &mut R, header: Header) -> Result { + if header.length > Length::try_from(Self::BYTES + 1)? { + return Err(Self::TAG.non_canonical_error()); + } + from_der_slice(reader.read_vec(header.length)?.as_slice()) + } +} + +impl TryFrom> for Uint { + type Error = Error; + + fn try_from(any: AnyRef<'_>) -> Result { + any.decode_as() + } +} + +impl TryFrom> for Uint { + type Error = Error; + + fn try_from(int: IntRef<'_>) -> Result { + from_der_slice(int.as_bytes()) + } +} + +impl TryFrom> for Uint { + type Error = Error; + + fn try_from(uint: UintRef<'_>) -> Result { + from_der_uint_slice(uint.as_bytes()) + } +} + +impl TryFrom<&Any> for Uint { + type Error = Error; + + fn try_from(any: &Any) -> Result { + any.decode_as() + } +} + +impl TryFrom<&Int> for Uint { + type Error = Error; + + fn try_from(int: &Int) -> Result { + from_der_slice(int.as_bytes()) + } +} + +impl TryFrom<&DerUint> for Uint { + type Error = Error; + + fn try_from(uint: &DerUint) -> Result { + from_der_uint_slice(uint.as_bytes()) + } +} + +// `Any::new()` only returns error when length > u32:MAX, which is out of scope +// for Uint. +#[allow(clippy::fallible_impl_from)] +impl From<&Uint> for Any { + fn from(uint: &Uint) -> Self { + if uint.is_zero() { + Self::new(Tag::Integer, Box::new([0_u8]) as Box<[u8]>).unwrap() + } else { + let mut bytes = uint.to_be_bytes_trimmed_vec(); + if bytes[0] >= 0x80 { + bytes.insert(0, 0); + } + Self::new(Tag::Integer, bytes).unwrap() + } + } +} + +// `Int::new()` only returns error when length > u32:MAX, which is out of scope +// for Uint. +#[allow(clippy::fallible_impl_from)] +impl From<&Uint> for Int { + fn from(uint: &Uint) -> Self { + if uint.is_zero() { + Self::new(&[0]).unwrap() + } else { + let mut bytes = uint.to_be_bytes_trimmed_vec(); + if bytes[0] >= 0x80 { + bytes.insert(0, 0); + } + Self::new(&bytes).unwrap() + } + } +} + +// `DerUint::new()` only returns error when length > u32:MAX, which is out of +// scope for Uint. +#[allow(clippy::fallible_impl_from)] +impl From<&Uint> for DerUint { + fn from(uint: &Uint) -> Self { + if uint.is_zero() { + Self::new(&[0]).unwrap() + } else { + // Panics: + // The only error is if the length is more than can be represented in u32. + // This is well outside of the inteded usecase for this library. + Self::new(&uint.to_be_bytes_trimmed_vec()).unwrap() + } + } +} + +macro_rules! forward_ref { + ($ty:ty) => { + impl TryFrom<$ty> for Uint { + type Error = Error; + + fn try_from(obj: $ty) -> Result { + Self::try_from(&obj) + } + } + + impl From> for $ty { + fn from(uint: Uint) -> Self { + <$ty>::from(&uint) + } + } + }; +} + +forward_ref!(Any); +forward_ref!(Int); +forward_ref!(DerUint); + +fn from_der_slice( + bytes: &[u8], +) -> Result> { + // Handle sign bits and zero-prefix. + let bytes = match bytes { + [] => Err(Tag::Integer.length_error()), + [0, byte, ..] if *byte < 0x80 => Err(Tag::Integer.non_canonical_error()), + [0, rest @ ..] => Ok(rest), + [byte, ..] if *byte >= 0x80 => Err(Tag::Integer.value_error()), + bytes => Ok(bytes), + }?; + Uint::try_from_be_slice(bytes).ok_or_else(|| Tag::Integer.non_canonical_error()) +} + +fn from_der_uint_slice( + bytes: &[u8], +) -> Result> { + // UintRef and Uint have the leading 0x00 removed. + match bytes { + [] => Err(Tag::Integer.length_error()), + [0] => Ok(Uint::ZERO), + [0, ..] => Err(Tag::Integer.non_canonical_error()), + bytes => Uint::try_from_be_slice(bytes).ok_or_else(|| Tag::Integer.non_canonical_error()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use der::{Decode, Encode}; + use proptest::proptest; + + #[test] + fn test_der_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = value.to_der().unwrap(); + let deserialized = Uint::from_der(&serialized).unwrap(); + assert_eq!(value, deserialized); + }); + }); + } + + #[test] + fn test_u128_equiv() { + proptest!(|(value: u128)| { + let uint = Uint::<128, 2>::from(value); + let serialized1 = value.to_der().unwrap(); + let serialized2 = uint.to_der().unwrap(); + assert_eq!(serialized1, serialized2); + }); + } + + macro_rules! test_roundtrip { + ($name:ident, $ty:ty) => { + #[test] + fn $name() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = value.to_der().unwrap(); + let der = <$ty>::from_der(&serialized).unwrap(); + let deserialized = der.try_into().unwrap(); + assert_eq!(value, deserialized); + }); + }); + } + } + } + + test_roundtrip!(test_der_anyref_roundtrip, AnyRef); + test_roundtrip!(test_der_intref_roundtrip, IntRef); + test_roundtrip!(test_der_uintref_roundtrip, UintRef); + test_roundtrip!(test_der_any_roundtrip, Any); + test_roundtrip!(test_der_int_roundtrip, Int); + test_roundtrip!(test_der_uint_roundtrip, DerUint); + + macro_rules! test_into { + ($name:ident, $ty:ty) => { + #[test] + fn $name() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let obj: $ty = value.into(); + let result: Uint = obj.try_into().unwrap(); + assert_eq!(result, value); + }); + }); + } + } + } + + test_into!(test_into_any, Any); + test_into!(test_into_int, Int); + test_into!(test_into_uint, DerUint); +} diff --git a/guest-libs/ruint/src/support/diesel.rs b/guest-libs/ruint/src/support/diesel.rs new file mode 100644 index 0000000000..802b9f6e7a --- /dev/null +++ b/guest-libs/ruint/src/support/diesel.rs @@ -0,0 +1,120 @@ +//! Support for the [`diesel`](https://crates.io/crates/diesel) crate. +//! +//! Currently only encodes to/from a big-endian byte array. + +#![cfg(feature = "diesel")] +#![cfg_attr(docsrs, doc(cfg(feature = "diesel")))] + +use diesel::{ + backend::Backend, + deserialize::{FromSql, Result as DeserResult}, + expression::AsExpression, + internal::derives::as_expression::Bound, + query_builder::bind_collector::RawBytesBindCollector, + serialize::{IsNull, Output, Result as SerResult, ToSql}, + sql_types::{Binary, Nullable, SingleValue}, + Queryable, +}; +use std::io::Write; +use thiserror::Error; + +use crate::Uint; + +#[derive(Error, Debug)] +pub enum DecodeError { + #[error("Value too large for target type")] + Overflow, +} + +impl ToSql for Uint +where + for<'c> Db: Backend = RawBytesBindCollector>, +{ + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Db>) -> SerResult { + out.write_all(&self.to_be_bytes_vec())?; + Ok(IsNull::No) + } +} + +impl FromSql for Uint +where + *const [u8]: FromSql, +{ + fn from_sql(bytes: Db::RawValue<'_>) -> DeserResult { + let bytes: *const [u8] = FromSql::::from_sql(bytes)?; + let bytes: &[u8] = unsafe { &*bytes }; + Self::try_from_be_slice(bytes).ok_or_else(|| DecodeError::Overflow.into()) + } +} + +// NB: the following code is expanded derive macros. They were produced by +// expanding the the following code: +// ``` +// #[derive(diesel::AsExpression, diesel::FromSqlRow)] +// #[diesel(sql_type = diesel::sql_types::Binary)] +// pub struct Uint { .. } +// ``` + +impl AsExpression for &Uint { + type Expression = Bound; + fn as_expression(self) -> Self::Expression { + Bound::new(self) + } +} + +impl AsExpression> for &Uint { + type Expression = Bound, Self>; + fn as_expression(self) -> Self::Expression { + Bound::new(self) + } +} + +impl AsExpression for &&Uint { + type Expression = Bound; + fn as_expression(self) -> Self::Expression { + Bound::new(self) + } +} + +impl AsExpression> for &&Uint { + type Expression = Bound, Self>; + fn as_expression(self) -> Self::Expression { + Bound::new(self) + } +} + +impl ToSql, Db> for Uint +where + Db: Backend, + Self: ToSql, +{ + fn to_sql<'a>(&'a self, out: &mut Output<'a, '_, Db>) -> SerResult { + ToSql::::to_sql(self, out) + } +} + +impl AsExpression for Uint { + type Expression = Bound; + fn as_expression(self) -> Self::Expression { + Bound::new(self) + } +} + +impl AsExpression> for Uint { + type Expression = Bound, Self>; + fn as_expression(self) -> Self::Expression { + Bound::new(self) + } +} + +impl Queryable for Uint +where + Db: Backend, + St: SingleValue, + Self: FromSql, +{ + type Row = Self; + fn build(row: Self::Row) -> DeserResult { + Ok(row) + } +} diff --git a/guest-libs/ruint/src/support/fastrlp_03.rs b/guest-libs/ruint/src/support/fastrlp_03.rs new file mode 100644 index 0000000000..4026e9ef3d --- /dev/null +++ b/guest-libs/ruint/src/support/fastrlp_03.rs @@ -0,0 +1,171 @@ +//! Support for the [`fastrlp`](https://crates.io/crates/fastrlp) crate. + +#![cfg(feature = "fastrlp")] +#![cfg_attr(docsrs, doc(cfg(feature = "fastrlp")))] + +use crate::Uint; +use fastrlp_03::{ + length_of_length, BufMut, Decodable, DecodeError, Encodable, Header, MaxEncodedLen, + MaxEncodedLenAssoc, EMPTY_STRING_CODE, +}; + +const MAX_BITS: usize = 55 * 8; + +/// Allows a [`Uint`] to be serialized as RLP. +/// +/// See +impl Encodable for Uint { + #[inline] + fn length(&self) -> usize { + let bits = self.bit_len(); + if bits <= 7 { + 1 + } else { + let bytes = (bits + 7) / 8; + bytes + length_of_length(bytes) + } + } + + #[inline] + fn encode(&self, out: &mut dyn BufMut) { + // fast paths, avoiding allocation due to `to_be_bytes_vec` + match LIMBS { + 0 => return out.put_u8(EMPTY_STRING_CODE), + 1 => return self.limbs[0].encode(out), + #[allow(clippy::cast_lossless)] + 2 => return (self.limbs[0] as u128 | ((self.limbs[1] as u128) << 64)).encode(out), + _ => {} + } + + match self.bit_len() { + 0 => out.put_u8(EMPTY_STRING_CODE), + 1..=7 => { + #[allow(clippy::cast_possible_truncation)] // self < 128 + out.put_u8(self.limbs[0] as u8); + } + bits => { + // avoid heap allocation in `to_be_bytes_vec` + // SAFETY: we don't re-use `copy` + #[cfg(target_endian = "little")] + let mut copy = *self; + #[cfg(target_endian = "little")] + let bytes = unsafe { copy.as_le_slice_mut() }; + #[cfg(target_endian = "little")] + bytes.reverse(); + + #[cfg(target_endian = "big")] + let bytes = self.to_be_bytes_vec(); + + let leading_zero_bytes = Self::BYTES - (bits + 7) / 8; + let trimmed = &bytes[leading_zero_bytes..]; + if bits > MAX_BITS { + trimmed.encode(out); + } else { + #[allow(clippy::cast_possible_truncation)] // bytes.len() < 56 < 256 + out.put_u8(EMPTY_STRING_CODE + trimmed.len() as u8); + out.put_slice(trimmed); + } + } + } + } +} + +/// Allows a [`Uint`] to be deserialized from RLP. +/// +/// See +impl Decodable for Uint { + #[inline] + fn decode(buf: &mut &[u8]) -> Result { + // let bytes = Header::decode_bytes(buf, false)?; + let header = Header::decode(buf)?; + if header.list { + return Err(DecodeError::UnexpectedList); + } + + let bytes = &buf[..header.payload_length]; + *buf = &buf[header.payload_length..]; + + // The RLP spec states that deserialized positive integers with leading zeroes + // get treated as invalid. + // + // See: + // https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ + // + // To check this, we only need to check if the first byte is zero to make sure + // there are no leading zeros + if !bytes.is_empty() && bytes[0] == 0 { + return Err(DecodeError::LeadingZero); + } + + Self::try_from_be_slice(bytes).ok_or(DecodeError::Overflow) + } +} + +#[cfg(feature = "generic_const_exprs")] +unsafe impl + MaxEncodedLen<{ Self::BYTES + length_of_length(Self::BYTES) }> for Uint +{ +} + +#[cfg(not(feature = "generic_const_exprs"))] +const _: () = { + crate::const_for!(BITS in [0, 1, 2, 8, 16, 32, 64, 128, 160, 192, 256, 384, 512, 4096] { + const LIMBS: usize = crate::nlimbs(BITS); + const BYTES: usize = Uint::::BYTES; + unsafe impl MaxEncodedLen<{ BYTES + length_of_length(BYTES) }> for Uint {} + }); +}; + +unsafe impl MaxEncodedLenAssoc for Uint { + const LEN: usize = Self::BYTES + length_of_length(Self::BYTES); +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + aliases::{U0, U256}, + const_for, nlimbs, + }; + use hex_literal::hex; + use proptest::proptest; + + fn encode(value: T) -> Vec { + let mut buf = vec![]; + value.encode(&mut buf); + buf + } + + #[test] + fn test_rlp() { + // See + assert_eq!(encode(U0::from(0))[..], hex!("80")); + assert_eq!(encode(U256::from(0))[..], hex!("80")); + assert_eq!(encode(U256::from(15))[..], hex!("0f")); + assert_eq!(encode(U256::from(1024))[..], hex!("820400")); + assert_eq!(encode(U256::from(0x1234_5678))[..], hex!("8412345678")); + } + + #[test] + fn test_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = encode(value); + + #[cfg(feature = "rlp")] + { + use rlp::Encodable as _; + let serialized_rlp = value.rlp_bytes(); + assert_eq!(serialized, serialized_rlp.freeze()[..]); + } + + assert_eq!(serialized.len(), value.length()); + let mut reader = &serialized[..]; + let deserialized = Uint::decode(&mut reader).unwrap(); + assert_eq!(reader.len(), 0); + assert_eq!(value, deserialized); + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/fastrlp_04.rs b/guest-libs/ruint/src/support/fastrlp_04.rs new file mode 100644 index 0000000000..264bb133e9 --- /dev/null +++ b/guest-libs/ruint/src/support/fastrlp_04.rs @@ -0,0 +1,171 @@ +//! Support for the [`fastrlp`](https://crates.io/crates/fastrlp) crate. + +#![cfg(feature = "fastrlp-04")] +#![cfg_attr(docsrs, doc(cfg(feature = "fastrlp-04")))] + +use crate::Uint; +use fastrlp_04::{ + length_of_length, BufMut, Decodable, DecodeError, Encodable, Header, MaxEncodedLen, + MaxEncodedLenAssoc, EMPTY_STRING_CODE, +}; + +const MAX_BITS: usize = 55 * 8; + +/// Allows a [`Uint`] to be serialized as RLP. +/// +/// See +impl Encodable for Uint { + #[inline] + fn length(&self) -> usize { + let bits = self.bit_len(); + if bits <= 7 { + 1 + } else { + let bytes = (bits + 7) / 8; + bytes + length_of_length(bytes) + } + } + + #[inline] + fn encode(&self, out: &mut dyn BufMut) { + // fast paths, avoiding allocation due to `to_be_bytes_vec` + match LIMBS { + 0 => return out.put_u8(EMPTY_STRING_CODE), + 1 => return self.limbs[0].encode(out), + #[allow(clippy::cast_lossless)] + 2 => return (self.limbs[0] as u128 | ((self.limbs[1] as u128) << 64)).encode(out), + _ => {} + } + + match self.bit_len() { + 0 => out.put_u8(EMPTY_STRING_CODE), + 1..=7 => { + #[allow(clippy::cast_possible_truncation)] // self < 128 + out.put_u8(self.limbs[0] as u8); + } + bits => { + // avoid heap allocation in `to_be_bytes_vec` + // SAFETY: we don't re-use `copy` + #[cfg(target_endian = "little")] + let mut copy = *self; + #[cfg(target_endian = "little")] + let bytes = unsafe { copy.as_le_slice_mut() }; + #[cfg(target_endian = "little")] + bytes.reverse(); + + #[cfg(target_endian = "big")] + let bytes = self.to_be_bytes_vec(); + + let leading_zero_bytes = Self::BYTES - (bits + 7) / 8; + let trimmed = &bytes[leading_zero_bytes..]; + if bits > MAX_BITS { + trimmed.encode(out); + } else { + #[allow(clippy::cast_possible_truncation)] // bytes.len() < 56 < 256 + out.put_u8(EMPTY_STRING_CODE + trimmed.len() as u8); + out.put_slice(trimmed); + } + } + } + } +} + +/// Allows a [`Uint`] to be deserialized from RLP. +/// +/// See +impl Decodable for Uint { + #[inline] + fn decode(buf: &mut &[u8]) -> Result { + // let bytes = Header::decode_bytes(buf, false)?; + let header = Header::decode(buf)?; + if header.list { + return Err(DecodeError::UnexpectedList); + } + + let bytes = &buf[..header.payload_length]; + *buf = &buf[header.payload_length..]; + + // The RLP spec states that deserialized positive integers with leading zeroes + // get treated as invalid. + // + // See: + // https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ + // + // To check this, we only need to check if the first byte is zero to make sure + // there are no leading zeros + if !bytes.is_empty() && bytes[0] == 0 { + return Err(DecodeError::LeadingZero); + } + + Self::try_from_be_slice(bytes).ok_or(DecodeError::Overflow) + } +} + +#[cfg(feature = "generic_const_exprs")] +unsafe impl + MaxEncodedLen<{ Self::BYTES + length_of_length(Self::BYTES) }> for Uint +{ +} + +#[cfg(not(feature = "generic_const_exprs"))] +const _: () = { + crate::const_for!(BITS in [0, 1, 2, 8, 16, 32, 64, 128, 160, 192, 256, 384, 512, 4096] { + const LIMBS: usize = crate::nlimbs(BITS); + const BYTES: usize = Uint::::BYTES; + unsafe impl MaxEncodedLen<{ BYTES + length_of_length(BYTES) }> for Uint {} + }); +}; + +unsafe impl MaxEncodedLenAssoc for Uint { + const LEN: usize = Self::BYTES + length_of_length(Self::BYTES); +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + aliases::{U0, U256}, + const_for, nlimbs, + }; + use hex_literal::hex; + use proptest::proptest; + + fn encode(value: T) -> Vec { + let mut buf = vec![]; + value.encode(&mut buf); + buf + } + + #[test] + fn test_rlp() { + // See + assert_eq!(encode(U0::from(0))[..], hex!("80")); + assert_eq!(encode(U256::from(0))[..], hex!("80")); + assert_eq!(encode(U256::from(15))[..], hex!("0f")); + assert_eq!(encode(U256::from(1024))[..], hex!("820400")); + assert_eq!(encode(U256::from(0x1234_5678))[..], hex!("8412345678")); + } + + #[test] + fn test_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = encode(value); + + #[cfg(feature = "rlp")] + { + use rlp::Encodable as _; + let serialized_rlp = value.rlp_bytes(); + assert_eq!(serialized, serialized_rlp.freeze()[..]); + } + + assert_eq!(serialized.len(), value.length()); + let mut reader = &serialized[..]; + let deserialized = Uint::decode(&mut reader).unwrap(); + assert_eq!(reader.len(), 0); + assert_eq!(value, deserialized); + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/mod.rs b/guest-libs/ruint/src/support/mod.rs new file mode 100644 index 0000000000..09a6413084 --- /dev/null +++ b/guest-libs/ruint/src/support/mod.rs @@ -0,0 +1,51 @@ +//! Support for external crates. + +#![allow(missing_docs, clippy::missing_inline_in_public_items)] + +mod alloy_rlp; +mod arbitrary; +mod ark_ff; +mod ark_ff_04; +mod bn_rs; +mod borsh; +mod bytemuck; +mod der; +pub mod diesel; +mod fastrlp_03; +mod fastrlp_04; +mod num_bigint; +mod num_integer; +mod num_traits; +pub mod postgres; +mod primitive_types; +mod proptest; +mod pyo3; +mod quickcheck; +mod rand; +mod rand_09; +mod rlp; +pub mod scale; +mod serde; +pub mod sqlx; +pub mod ssz; +mod subtle; +mod valuable; +mod zeroize; +#[cfg(target_os = "zkvm")] +pub mod zkvm; + +// FEATURE: Support for many more traits and crates. +// * https://crates.io/crates/der +// * https://crates.io/crates/bitvec + +// * open-fastrlp + +// Big int types: +// * https://crates.io/crates/crypto-bigint +// * https://crates.io/crates/rug +// * https://crates.io/crates/bigdecimal +// * https://crates.io/crates/rust_decimal + +// * wasm-bindgen `JsValue` bigint: https://docs.rs/wasm-bindgen/latest/wasm_bindgen/struct.JsValue.html#method.bigint_from_str +// or from_f64. +// * Neon `JsBigInt` once it lands: https://github.com/neon-bindings/neon/pull/861 diff --git a/guest-libs/ruint/src/support/num_bigint.rs b/guest-libs/ruint/src/support/num_bigint.rs new file mode 100644 index 0000000000..035aabb736 --- /dev/null +++ b/guest-libs/ruint/src/support/num_bigint.rs @@ -0,0 +1,112 @@ +//! Support for the [`num-bigint`](https://crates.io/crates/num-bigint) crate. + +#![cfg(feature = "num-bigint")] +#![cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))] + +use crate::{from::ToUintError, Uint}; +use num_bigint::{BigInt, BigUint, Sign}; + +impl TryFrom for Uint { + type Error = ToUintError; + + #[allow(clippy::only_used_in_recursion)] // False positive + fn try_from(value: BigUint) -> Result { + Self::try_from(&value) + } +} + +impl TryFrom<&BigUint> for Uint { + type Error = ToUintError; + + fn try_from(value: &BigUint) -> Result { + let (n, overflow) = Self::overflowing_from_limbs_slice(value.to_u64_digits().as_slice()); + if overflow { + Err(ToUintError::ValueTooLarge(BITS, n)) + } else { + Ok(n) + } + } +} + +impl From> for BigUint { + fn from(value: Uint) -> Self { + Self::from(&value) + } +} + +impl From<&Uint> for BigUint { + fn from(value: &Uint) -> Self { + Self::from_bytes_le(&value.as_le_bytes()) + } +} + +impl TryFrom for Uint { + type Error = ToUintError; + + fn try_from(value: BigInt) -> Result { + Self::try_from(&value) + } +} + +impl TryFrom<&BigInt> for Uint { + type Error = ToUintError; + + fn try_from(value: &BigInt) -> Result { + let (sign, digits) = value.to_u64_digits(); + let (n, overflow) = Self::overflowing_from_limbs_slice(digits.as_slice()); + if sign == Sign::Minus { + Err(ToUintError::ValueNegative(BITS, n)) + } else if overflow { + Err(ToUintError::ValueTooLarge(BITS, n)) + } else { + Ok(n) + } + } +} + +impl From> for BigInt { + fn from(value: Uint) -> Self { + Self::from(&value) + } +} + +impl From<&Uint> for BigInt { + fn from(value: &Uint) -> Self { + Self::from_bytes_le(Sign::Plus, &value.as_le_bytes()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::proptest; + + #[test] + #[allow(clippy::unreadable_literal)] + fn test_roundtrip_biguint() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + let big: BigUint = value.into(); + let back: U = big.try_into().unwrap(); + assert_eq!(back, value); + }); + }); + } + + #[test] + #[allow(clippy::unreadable_literal)] + fn test_roundtrip_bigint() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + let big: BigInt = value.into(); + let back: U = big.try_into().unwrap(); + assert_eq!(back, value); + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/num_integer.rs b/guest-libs/ruint/src/support/num_integer.rs new file mode 100644 index 0000000000..2a74b5027a --- /dev/null +++ b/guest-libs/ruint/src/support/num_integer.rs @@ -0,0 +1,99 @@ +//! Support for the [`num-integer`](https://crates.io/crates/num-integer) crate. + +#![cfg(feature = "num-integer")] + +use crate::Uint; +use num_integer::{ExtendedGcd, Integer}; + +impl Integer for Uint { + #[inline] + #[track_caller] + fn div_floor(&self, other: &Self) -> Self { + Self::wrapping_div(*self, *other) + } + + #[inline] + #[track_caller] + fn mod_floor(&self, other: &Self) -> Self { + Self::wrapping_rem(*self, *other) + } + + #[inline] + fn gcd(&self, other: &Self) -> Self { + ::gcd(*self, *other) + } + + #[inline] + #[track_caller] + fn lcm(&self, other: &Self) -> Self { + ::lcm(*self, *other).unwrap() + } + + #[inline] + fn is_multiple_of(&self, other: &Self) -> bool { + if other.is_zero() { + return self.is_zero(); + } + *self % *other == Self::ZERO + } + + #[inline] + fn is_even(&self) -> bool { + !self.bit(0) + } + + #[inline] + fn is_odd(&self) -> bool { + self.bit(0) + } + + #[inline] + #[track_caller] + fn div_rem(&self, other: &Self) -> (Self, Self) { + ::div_rem(*self, *other) + } + + #[inline] + #[track_caller] + fn div_ceil(&self, other: &Self) -> Self { + ::div_ceil(*self, *other) + } + + #[inline] + #[track_caller] + fn div_mod_floor(&self, other: &Self) -> (Self, Self) { + // Same as `div_rem` for unsigned integers. + ::div_rem(*self, *other) + } + + #[inline] + fn extended_gcd(&self, other: &Self) -> ExtendedGcd { + let (gcd, x, y, _sign) = ::gcd_extended(*self, *other); + ExtendedGcd { gcd, x, y } + } + + #[inline] + fn dec(&mut self) { + *self -= Self::ONE; + } + + #[inline] + fn inc(&mut self) { + *self += Self::ONE; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_even() { + let mut a = Uint::<64, 1>::from(0u32); + for _ in 0..10 { + a.inc(); + assert_eq!(a.is_even(), a.to::() % 2 == 0); + assert_eq!(a.is_odd(), a.to::() % 2 != 0); + } + } +} diff --git a/guest-libs/ruint/src/support/num_traits.rs b/guest-libs/ruint/src/support/num_traits.rs new file mode 100644 index 0000000000..0ff582b6d0 --- /dev/null +++ b/guest-libs/ruint/src/support/num_traits.rs @@ -0,0 +1,535 @@ +//! Support for the [`num-traits`](https://crates.io/crates/num-traits) crate. +#![cfg(feature = "num-traits")] +#![cfg_attr(docsrs, doc(cfg(feature = "num-traits")))] +// This is a particularly big risk with these traits. Make sure +// to call functions on the `Uint::` type. +#![deny(unconditional_recursion)] +use crate::Uint; +use core::ops::{Shl, Shr}; +use num_traits::{ + bounds::Bounded, + cast::{FromPrimitive, ToPrimitive}, + identities::{One, Zero}, + int::PrimInt, + ops::{ + bytes::{FromBytes, ToBytes}, + checked::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, + CheckedSub, + }, + overflowing::{OverflowingAdd, OverflowingMul, OverflowingSub}, + saturating::{Saturating, SaturatingAdd, SaturatingMul, SaturatingSub}, + wrapping::{WrappingAdd, WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub}, + }, + pow::Pow, + sign::Unsigned, + CheckedEuclid, Euclid, Inv, MulAdd, MulAddAssign, Num, NumCast, +}; + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +// TODO: AsPrimitive + +// Note. We can not implement `NumBytes` as it requires T to be `AsMut<[u8]>`. +// This is not safe for `Uint` when `BITS % 8 != 0`. + +impl Zero for Uint { + #[inline(always)] + fn zero() -> Self { + Self::ZERO + } + + #[inline(always)] + fn is_zero(&self) -> bool { + self == &Self::ZERO + } +} + +impl One for Uint { + #[inline(always)] + fn one() -> Self { + Self::ONE + } +} + +impl Bounded for Uint { + #[inline(always)] + fn min_value() -> Self { + Self::ZERO + } + + #[inline(always)] + fn max_value() -> Self { + Self::MAX + } +} + +impl FromBytes for Uint { + type Bytes = [u8]; + + #[inline(always)] + fn from_le_bytes(bytes: &[u8]) -> Self { + Self::try_from_le_slice(bytes).unwrap() + } + + #[inline(always)] + fn from_be_bytes(bytes: &[u8]) -> Self { + Self::try_from_be_slice(bytes).unwrap() + } +} + +impl ToBytes for Uint { + type Bytes = Vec; + + #[inline(always)] + fn to_le_bytes(&self) -> Self::Bytes { + self.to_le_bytes_vec() + } + + #[inline(always)] + fn to_be_bytes(&self) -> Self::Bytes { + self.to_be_bytes_vec() + } +} + +impl CheckedAdd for Uint { + #[inline(always)] + fn checked_add(&self, other: &Self) -> Option { + ::checked_add(*self, *other) + } +} + +impl CheckedDiv for Uint { + #[inline(always)] + fn checked_div(&self, other: &Self) -> Option { + ::checked_div(*self, *other) + } +} + +impl CheckedMul for Uint { + #[inline(always)] + fn checked_mul(&self, other: &Self) -> Option { + ::checked_mul(*self, *other) + } +} + +impl CheckedNeg for Uint { + #[inline(always)] + fn checked_neg(&self) -> Option { + ::checked_neg(*self) + } +} + +impl CheckedRem for Uint { + #[inline(always)] + fn checked_rem(&self, other: &Self) -> Option { + ::checked_rem(*self, *other) + } +} + +impl CheckedShl for Uint { + #[inline(always)] + fn checked_shl(&self, other: u32) -> Option { + ::checked_shl(*self, other as usize) + } +} + +impl CheckedShr for Uint { + #[inline(always)] + fn checked_shr(&self, other: u32) -> Option { + ::checked_shr(*self, other as usize) + } +} + +impl CheckedSub for Uint { + #[inline(always)] + fn checked_sub(&self, other: &Self) -> Option { + ::checked_sub(*self, *other) + } +} + +impl CheckedEuclid for Uint { + #[inline(always)] + fn checked_div_euclid(&self, v: &Self) -> Option { + ::checked_div(*self, *v) + } + + #[inline(always)] + fn checked_rem_euclid(&self, v: &Self) -> Option { + ::checked_rem(*self, *v) + } +} + +impl Euclid for Uint { + #[inline(always)] + fn div_euclid(&self, v: &Self) -> Self { + ::wrapping_div(*self, *v) + } + + #[inline(always)] + fn rem_euclid(&self, v: &Self) -> Self { + ::wrapping_rem(*self, *v) + } +} + +impl Inv for Uint { + type Output = Option; + + #[inline(always)] + fn inv(self) -> Self::Output { + ::inv_ring(self) + } +} + +impl MulAdd for Uint { + type Output = Self; + + #[inline(always)] + fn mul_add(self, a: Self, b: Self) -> Self::Output { + // OPT: Expose actual merged mul_add algo. + (self * a) + b + } +} + +impl MulAddAssign for Uint { + #[inline(always)] + fn mul_add_assign(&mut self, a: Self, b: Self) { + *self *= a; + *self += b; + } +} + +impl Saturating for Uint { + #[inline(always)] + fn saturating_add(self, v: Self) -> Self { + ::saturating_add(self, v) + } + + #[inline(always)] + fn saturating_sub(self, v: Self) -> Self { + ::saturating_sub(self, v) + } +} + +macro_rules! binary_op { + ($($trait:ident $fn:ident)*) => {$( + impl $trait for Uint { + #[inline(always)] + fn $fn(&self, v: &Self) -> Self { + ::$fn(*self, *v) + } + } + )*}; +} + +binary_op! { + SaturatingAdd saturating_add + SaturatingSub saturating_sub + SaturatingMul saturating_mul + WrappingAdd wrapping_add + WrappingSub wrapping_sub + WrappingMul wrapping_mul +} + +impl WrappingNeg for Uint { + #[inline(always)] + fn wrapping_neg(&self) -> Self { + ::wrapping_neg(*self) + } +} + +impl WrappingShl for Uint { + #[inline(always)] + fn wrapping_shl(&self, rhs: u32) -> Self { + ::wrapping_shl(*self, rhs as usize) + } +} + +impl WrappingShr for Uint { + #[inline(always)] + fn wrapping_shr(&self, rhs: u32) -> Self { + ::wrapping_shr(*self, rhs as usize) + } +} + +impl OverflowingAdd for Uint { + #[inline(always)] + fn overflowing_add(&self, v: &Self) -> (Self, bool) { + ::overflowing_add(*self, *v) + } +} + +impl OverflowingSub for Uint { + #[inline(always)] + fn overflowing_sub(&self, v: &Self) -> (Self, bool) { + ::overflowing_sub(*self, *v) + } +} + +impl OverflowingMul for Uint { + #[inline(always)] + fn overflowing_mul(&self, v: &Self) -> (Self, bool) { + ::overflowing_mul(*self, *v) + } +} + +impl Num for Uint { + type FromStrRadixErr = crate::ParseError; + + #[inline(always)] + fn from_str_radix(str: &str, radix: u32) -> Result { + #[allow(clippy::cast_lossless)] + ::from_str_radix(str, radix as u64) + } +} + +impl Pow for Uint { + type Output = Self; + + #[inline(always)] + fn pow(self, rhs: Self) -> Self::Output { + ::pow(self, rhs) + } +} + +impl Unsigned for Uint {} + +impl ToPrimitive for Uint { + #[inline(always)] + fn to_i64(&self) -> Option { + self.try_into().ok() + } + + #[inline(always)] + fn to_u64(&self) -> Option { + self.try_into().ok() + } + + #[inline(always)] + fn to_i128(&self) -> Option { + self.try_into().ok() + } + + #[inline(always)] + fn to_u128(&self) -> Option { + self.try_into().ok() + } +} + +impl FromPrimitive for Uint { + #[inline(always)] + fn from_i64(n: i64) -> Option { + Self::try_from(n).ok() + } + + #[inline(always)] + fn from_u64(n: u64) -> Option { + Self::try_from(n).ok() + } + + #[inline(always)] + fn from_i128(n: i128) -> Option { + Self::try_from(n).ok() + } + + #[inline(always)] + fn from_u128(n: u128) -> Option { + Self::try_from(n).ok() + } +} + +impl NumCast for Uint { + #[inline(always)] + fn from(n: T) -> Option { + ::try_from(n.to_u128()?).ok() + } +} + +impl PrimInt for Uint { + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] // Requires BITS > 2^32 + fn count_ones(self) -> u32 { + ::count_ones(&self) as u32 + } + + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] // Requires BITS > 2^32 + fn count_zeros(self) -> u32 { + ::count_zeros(&self) as u32 + } + + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] // Requires BITS > 2^32 + fn leading_zeros(self) -> u32 { + ::leading_zeros(&self) as u32 + } + + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] // Requires BITS > 2^32 + fn leading_ones(self) -> u32 { + ::leading_ones(&self) as u32 + } + + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] // Requires BITS > 2^32 + fn trailing_zeros(self) -> u32 { + ::trailing_zeros(&self) as u32 + } + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] // Requires BITS > 2^32 + fn trailing_ones(self) -> u32 { + ::trailing_ones(&self) as u32 + } + + #[inline(always)] + fn rotate_left(self, n: u32) -> Self { + ::rotate_left(self, n as usize) + } + + #[inline(always)] + fn rotate_right(self, n: u32) -> Self { + ::rotate_right(self, n as usize) + } + + #[inline(always)] + fn signed_shl(self, n: u32) -> Self { + ::shl(self, n as usize) + } + + #[inline(always)] + fn signed_shr(self, n: u32) -> Self { + ::arithmetic_shr(self, n as usize) + } + + #[inline(always)] + fn unsigned_shl(self, n: u32) -> Self { + ::shl(self, n as usize) + } + + #[inline(always)] + fn unsigned_shr(self, n: u32) -> Self { + ::shr(self, n as usize) + } + + /// Note: This is not well-defined when `BITS % 8 != 0`. + fn swap_bytes(self) -> Self { + let mut bytes = self.to_be_bytes_vec(); + bytes.reverse(); + Self::try_from_be_slice(&bytes).unwrap() + } + + #[inline(always)] + fn reverse_bits(self) -> Self { + ::reverse_bits(self) + } + + #[inline(always)] + fn from_be(x: Self) -> Self { + if cfg!(target_endian = "big") { + x + } else { + x.swap_bytes() + } + } + + #[inline(always)] + fn from_le(x: Self) -> Self { + if cfg!(target_endian = "little") { + x + } else { + x.swap_bytes() + } + } + + #[inline(always)] + fn to_be(self) -> Self { + if cfg!(target_endian = "big") { + self + } else { + self.swap_bytes() + } + } + + #[inline(always)] + fn to_le(self) -> Self { + if cfg!(target_endian = "little") { + self + } else { + self.swap_bytes() + } + } + + #[inline(always)] + fn pow(self, exp: u32) -> Self { + self.pow(Self::from(exp)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::aliases::{U256, U64}; + use num_traits::bounds::{LowerBounded, UpperBounded}; + + macro_rules! assert_impl{ + ($type:ident, $($trait:tt),*) => { + $({ + fn assert_impl() {} + assert_impl::<$type>(); + })* + } + } + + #[test] + fn test_assert_impl() { + // All applicable traits from num-traits (except AsPrimitive). + assert_impl!(U256, Bounded, LowerBounded, UpperBounded); + assert_impl!(U256, FromPrimitive, NumCast, ToPrimitive); + assert_impl!(U256, One, Zero); + assert_impl!(U256, PrimInt); + assert_impl!(U256, FromBytes, ToBytes); + assert_impl!( + U256, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedSub, + CheckedShl, CheckedShr, CheckedSub + ); + assert_impl!(U256, CheckedEuclid, Euclid); + assert_impl!(U256, Inv); + assert_impl!(U256, MulAdd, MulAddAssign); + assert_impl!(U256, OverflowingAdd, OverflowingMul, OverflowingSub); + assert_impl!( + U256, + Saturating, + SaturatingAdd, + SaturatingMul, + SaturatingSub + ); + assert_impl!( + U256, + WrappingAdd, + WrappingMul, + WrappingNeg, + WrappingShl, + WrappingShr, + WrappingSub + ); + assert_impl!(U256, (Pow)); + assert_impl!(U256, Unsigned); + } + + #[test] + fn test_signed_shl() { + // Example from num-traits docs. + let n = U64::from(0x0123456789abcdefu64); + let m = U64::from(0x3456789abcdef000u64); + assert_eq!(n.signed_shl(12), m); + } + + #[test] + fn test_signed_shr() { + // Example from num-traits docs. + let n = U64::from(0xfedcba9876543210u64); + let m = U64::from(0xffffedcba9876543u64); + assert_eq!(n.signed_shr(12), m); + } +} diff --git a/guest-libs/ruint/src/support/postgres.rs b/guest-libs/ruint/src/support/postgres.rs new file mode 100644 index 0000000000..ffd08bea73 --- /dev/null +++ b/guest-libs/ruint/src/support/postgres.rs @@ -0,0 +1,546 @@ +//! Support for the [`postgres`](https://crates.io/crates/postgres) crate. + +#![cfg(feature = "postgres")] +#![cfg_attr(docsrs, doc(cfg(feature = "postgres")))] + +use crate::{ + utils::{rem_up, trim_end_vec}, + Uint, +}; +use bytes::{BufMut, BytesMut}; +use core::{ + error::Error, + iter, + str::{from_utf8, FromStr}, +}; +use postgres_types::{to_sql_checked, FromSql, IsNull, ToSql, Type, WrongType}; +use thiserror::Error; + +type BoxedError = Box; + +#[derive(Clone, PartialEq, Eq, Debug, Error)] +pub enum ToSqlError { + #[error("Uint<{0}> value too large to fit target type {1}")] + Overflow(usize, Type), +} + +/// Convert to Postgres types. +/// +/// Compatible [Postgres data types][dt] are: +/// +/// * `BOOL`, `SMALLINT`, `INTEGER`, `BIGINT` which are 1, 16, 32 and 64 bit +/// signed integers respectively. +/// * `OID` which is a 32 bit unsigned integer. +/// * `FLOAT`, `DOUBLE PRECISION` which are 32 and 64 bit floating point. +/// * `DECIMAL` and `NUMERIC`, which are variable length. +/// * `MONEY` which is a 64 bit integer with two decimals. +/// * `BYTEA`, `BIT`, `VARBIT` interpreted as a big-endian binary number. +/// * `CHAR`, `VARCHAR`, `TEXT` as `0x`-prefixed big-endian hex strings. +/// * `JSON`, `JSONB` as a hex string compatible with the Serde serialization. +/// +/// Note: [`Uint`]s are never null, use [`Option`] instead. +/// +/// # Errors +/// +/// Returns an error when trying to convert to a value that is too small to fit +/// the number. Note that this depends on the value, not the type, so a +/// [`Uint<256>`] can be stored in a `SMALLINT` column, as long as the values +/// are less than $2^{16}$. +/// +/// # Implementation details +/// +/// The Postgres binary formats are used in the wire-protocol and the +/// the `COPY BINARY` command, but they have very little documentation. You are +/// pointed to the source code, for example this is the implementation of the +/// the `NUMERIC` type serializer: [`numeric.c`][numeric]. +/// +/// [dt]:https://www.postgresql.org/docs/9.5/datatype.html +/// [numeric]: https://github.com/postgres/postgres/blob/05a5a1775c89f6beb326725282e7eea1373cbec8/src/backend/utils/adt/numeric.c#L1082 +impl ToSql for Uint { + fn accepts(ty: &Type) -> bool { + matches!(*ty, |Type::BOOL| Type::CHAR + | Type::INT2 + | Type::INT4 + | Type::INT8 + | Type::OID + | Type::FLOAT4 + | Type::FLOAT8 + | Type::MONEY + | Type::NUMERIC + | Type::BYTEA + | Type::TEXT + | Type::VARCHAR + | Type::JSON + | Type::JSONB + | Type::BIT + | Type::VARBIT) + } + + // See + fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result { + match *ty { + // Big-endian simple types + // Note `BufMut::put_*` methods write big-endian by default. + Type::BOOL => out.put_u8(u8::from(bool::try_from(*self)?)), + Type::INT2 => out.put_i16(self.try_into()?), + Type::INT4 => out.put_i32(self.try_into()?), + Type::OID => out.put_u32(self.try_into()?), + Type::INT8 => out.put_i64(self.try_into()?), + Type::FLOAT4 => out.put_f32(self.into()), + Type::FLOAT8 => out.put_f64(self.into()), + Type::MONEY => { + // Like i64, but with two decimals. + out.put_i64( + i64::try_from(self)? + .checked_mul(100) + .ok_or_else(|| ToSqlError::Overflow(BITS, ty.clone()))?, + ); + } + + // Binary strings + Type::BYTEA => out.put_slice(&self.to_be_bytes_vec()), + Type::BIT | Type::VARBIT => { + // Bit in little-endian so the the first bit is the least significant. + // Length must be at least one bit. + if BITS == 0 { + if *ty == Type::BIT { + // `bit(0)` is not a valid type, but varbit can be empty. + return Err(Box::new(WrongType::new::(ty.clone()))); + } + out.put_i32(0); + } else { + // Bits are output in big-endian order, but padded at the + // least significant end. + let padding = 8 - rem_up(BITS, 8); + out.put_i32(Self::BITS.try_into()?); + let bytes = self.as_le_bytes(); + let mut bytes = bytes.iter().rev(); + let mut shifted = bytes.next().unwrap() << padding; + for byte in bytes { + shifted |= if padding > 0 { + byte >> (8 - padding) + } else { + 0 + }; + out.put_u8(shifted); + shifted = byte << padding; + } + out.put_u8(shifted); + } + } + + // Hex strings + Type::CHAR | Type::TEXT | Type::VARCHAR => { + out.put_slice(format!("{self:#x}").as_bytes()); + } + Type::JSON | Type::JSONB => { + if *ty == Type::JSONB { + // Version 1 of JSONB is just plain text JSON. + out.put_u8(1); + } + out.put_slice(format!("\"{self:#x}\"").as_bytes()); + } + + // Binary coded decimal types + // See + Type::NUMERIC => { + // Everything is done in big-endian base 1000 digits. + const BASE: u64 = 10000; + let mut digits: Vec<_> = self.to_base_be(BASE).collect(); + let exponent = digits.len().saturating_sub(1).try_into()?; + + // Trailing zeros are removed. + trim_end_vec(&mut digits, &0); + + out.put_i16(digits.len().try_into()?); // Number of digits. + out.put_i16(exponent); // Exponent of first digit. + out.put_i16(0); // sign: 0x0000 = positive, 0x4000 = negative. + out.put_i16(0); // dscale: Number of digits to the right of the decimal point. + for digit in digits { + debug_assert!(digit < BASE); + #[allow(clippy::cast_possible_truncation)] // 10000 < i16::MAX + out.put_i16(digit as i16); + } + } + + // Unsupported types + _ => { + return Err(Box::new(WrongType::new::(ty.clone()))); + } + } + Ok(IsNull::No) + } + + to_sql_checked!(); +} + +#[derive(Clone, PartialEq, Eq, Debug, Error)] +pub enum FromSqlError { + #[error("The value is too large for the Uint type")] + Overflow, + + #[error("Unexpected data for type {0}")] + ParseError(Type), +} + +/// Convert from Postgres types. +/// +/// See [`ToSql`][Self::to_sql] for details. +impl<'a, const BITS: usize, const LIMBS: usize> FromSql<'a> for Uint { + fn accepts(ty: &Type) -> bool { + ::accepts(ty) + } + + fn from_sql(ty: &Type, raw: &'a [u8]) -> Result> { + Ok(match *ty { + Type::BOOL => match raw { + [0] => Self::ZERO, + [1] => Self::try_from(1)?, + _ => return Err(Box::new(FromSqlError::ParseError(ty.clone()))), + }, + Type::INT2 => i16::from_be_bytes(raw.try_into()?).try_into()?, + Type::INT4 => i32::from_be_bytes(raw.try_into()?).try_into()?, + Type::OID => u32::from_be_bytes(raw.try_into()?).try_into()?, + Type::INT8 => i64::from_be_bytes(raw.try_into()?).try_into()?, + Type::FLOAT4 => f32::from_be_bytes(raw.try_into()?).try_into()?, + Type::FLOAT8 => f64::from_be_bytes(raw.try_into()?).try_into()?, + Type::MONEY => (i64::from_be_bytes(raw.try_into()?) / 100).try_into()?, + + // Binary strings + Type::BYTEA => Self::try_from_be_slice(raw).ok_or(FromSqlError::Overflow)?, + Type::BIT | Type::VARBIT => { + // Parse header + if raw.len() < 4 { + return Err(Box::new(FromSqlError::ParseError(ty.clone()))); + } + let len: usize = i32::from_be_bytes(raw[..4].try_into()?).try_into()?; + let raw = &raw[4..]; + + // Shift padding to the other end + let padding = 8 - rem_up(len, 8); + let mut raw = raw.to_owned(); + if padding > 0 { + for i in (1..raw.len()).rev() { + raw[i] = (raw[i] >> padding) | (raw[i - 1] << (8 - padding)); + } + raw[0] >>= padding; + } + // Construct from bits + Self::try_from_be_slice(&raw).ok_or(FromSqlError::Overflow)? + } + + // Hex strings + Type::CHAR | Type::TEXT | Type::VARCHAR => Self::from_str(from_utf8(raw)?)?, + + // Hex strings + Type::JSON | Type::JSONB => { + let raw = if *ty == Type::JSONB { + if raw[0] == 1 { + &raw[1..] + } else { + // Unsupported version + return Err(Box::new(FromSqlError::ParseError(ty.clone()))); + } + } else { + raw + }; + let str = from_utf8(raw)?; + let str = if str.starts_with('"') && str.ends_with('"') { + // Stringified number + &str[1..str.len() - 1] + } else { + str + }; + Self::from_str(str)? + } + + // Numeric types + Type::NUMERIC => { + // Parse header + if raw.len() < 8 { + return Err(Box::new(FromSqlError::ParseError(ty.clone()))); + } + let digits = i16::from_be_bytes(raw[0..2].try_into()?); + let exponent = i16::from_be_bytes(raw[2..4].try_into()?); + let sign = i16::from_be_bytes(raw[4..6].try_into()?); + let dscale = i16::from_be_bytes(raw[6..8].try_into()?); + let raw = &raw[8..]; + #[allow(clippy::cast_sign_loss)] // Signs are checked + if digits < 0 + || exponent < 0 + || sign != 0x0000 + || dscale != 0 + || digits > exponent + 1 + || raw.len() != digits as usize * 2 + { + return Err(Box::new(FromSqlError::ParseError(ty.clone()))); + } + let mut error = false; + let iter = raw.chunks_exact(2).filter_map(|raw| { + if error { + return None; + } + let digit = i16::from_be_bytes(raw.try_into().unwrap()); + if !(0..10000).contains(&digit) { + error = true; + return None; + } + #[allow(clippy::cast_sign_loss)] // Signs are checked + Some(digit as u64) + }); + #[allow(clippy::cast_sign_loss)] + // Expression can not be negative due to checks above + let iter = iter.chain(iter::repeat(0).take((exponent + 1 - digits) as usize)); + + let value = Self::from_base_be(10000, iter)?; + if error { + return Err(Box::new(FromSqlError::ParseError(ty.clone()))); + } + value + } + + // Unsupported types + _ => return Err(Box::new(WrongType::new::(ty.clone()))), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nbytes, nlimbs}; + use approx::assert_ulps_eq; + use hex_literal::hex; + use postgres::{Client, NoTls}; + use proptest::{proptest, test_runner::Config as ProptestConfig}; + use std::{io::Read, sync::Mutex}; + + #[test] + fn test_basic() { + #[allow(clippy::unreadable_literal)] + const N: Uint<256, 4> = Uint::from_limbs([ + 0xa8ec92344438aaf4_u64, + 0x9819ebdbd1faaab1_u64, + 0x573b1a7064c19c1a_u64, + 0xc85ef7d79691fe79_u64, + ]); + #[allow(clippy::needless_pass_by_value)] + fn bytes(ty: Type) -> Vec { + let mut out = BytesMut::new(); + N.to_sql(&ty, &mut out).unwrap(); + out.to_vec() + } + assert_eq!(bytes(Type::FLOAT4), hex!("7f800000")); // +inf + assert_eq!(bytes(Type::FLOAT8), hex!("4fe90bdefaf2d240")); + assert_eq!(bytes(Type::NUMERIC), hex!("0014001300000000000902760e3620f115a21c3b029709bc11e60b3e10d10d6900d123400def1c45091a147900f012f4")); + assert_eq!( + bytes(Type::BYTEA), + hex!("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4") + ); + assert_eq!( + bytes(Type::BIT), + hex!("00000100c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4") + ); + assert_eq!( + bytes(Type::VARBIT), + hex!("00000100c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4") + ); + assert_eq!(bytes(Type::CHAR), hex!("307863383565663764373936393166653739353733623161373036346331396331613938313965626462643166616161623161386563393233343434333861616634")); + assert_eq!(bytes(Type::TEXT), hex!("307863383565663764373936393166653739353733623161373036346331396331613938313965626462643166616161623161386563393233343434333861616634")); + assert_eq!(bytes(Type::VARCHAR), hex!("307863383565663764373936393166653739353733623161373036346331396331613938313965626462643166616161623161386563393233343434333861616634")); + } + + #[test] + fn test_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + let mut serialized = BytesMut::new(); + + if f32::from(value).is_finite() { + serialized.clear(); + if value.to_sql(&Type::FLOAT4, &mut serialized).is_ok() { + // println!("testing {:?} {}", value, Type::FLOAT4); + let deserialized = U::from_sql(&Type::FLOAT4, &serialized).unwrap(); + assert_ulps_eq!(f32::from(value), f32::from(deserialized), max_ulps = 4); + } + } + if f64::from(value).is_finite() { + serialized.clear(); + if value.to_sql(&Type::FLOAT8, &mut serialized).is_ok() { + // println!("testing {:?} {}", value, Type::FLOAT8); + let deserialized = U::from_sql(&Type::FLOAT8, &serialized).unwrap(); + assert_ulps_eq!(f64::from(value), f64::from(deserialized), max_ulps = 4); + } + } + for ty in &[/*Type::BOOL, Type::INT2, Type::INT4, Type::INT8, Type::OID, Type::MONEY, Type::BYTEA, Type::CHAR, Type::TEXT, Type::VARCHAR, Type::JSON, Type::JSONB, Type::NUMERIC,*/ Type::BIT, Type::VARBIT] { + serialized.clear(); + if value.to_sql(ty, &mut serialized).is_ok() { + // println!("testing {:?} {}", value, ty); + let deserialized = U::from_sql(ty, &serialized).unwrap(); + assert_eq!(deserialized, value); + } + } + }); + }); + } + + // Query the binary encoding of an SQL expression + fn get_binary(client: &mut Client, expr: &str) -> Vec { + let query = format!("COPY (SELECT {expr}) TO STDOUT WITH BINARY;"); + + // See + let mut reader = client.copy_out(&query).unwrap(); + let mut buf = Vec::new(); + reader.read_to_end(&mut buf).unwrap(); + + // Parse header + let buf = { + const HEADER: &[u8] = b"PGCOPY\n\xff\r\n\0"; + assert_eq!(&buf[..11], HEADER); + &buf[11 + 4..] + }; + + // Skip extension headers (must be zero length) + assert_eq!(&buf[..4], &0_u32.to_be_bytes()); + let buf = &buf[4..]; + + // Tuple field count must be one + assert_eq!(&buf[..2], &1_i16.to_be_bytes()); + let buf = &buf[2..]; + + // Field length + let len = u32::from_be_bytes(buf[..4].try_into().unwrap()) as usize; + let buf = &buf[4..]; + + // Field data + let data = &buf[..len]; + let buf = &buf[len..]; + + // Trailer must be -1_i16 + assert_eq!(&buf, &(-1_i16).to_be_bytes()); + + data.to_owned() + } + + fn test_to( + client: &Mutex, + value: Uint, + ty: &Type, + ) { + println!("testing {value:?} {ty}"); + + // Encode value locally + let mut serialized = BytesMut::new(); + let result = value.to_sql(ty, &mut serialized); + if result.is_err() { + // Skip values that are out of range for the type + return; + } + // Skip floating point infinities + if ty == &Type::FLOAT4 && f32::from(value).is_infinite() { + return; + } + if ty == &Type::FLOAT8 && f64::from(value).is_infinite() { + return; + } + + // Fetch ground truth value from Postgres + let expr = match *ty { + Type::BIT => format!( + "B'{value:b}'::bit({bits})", + value = value, + bits = if BITS == 0 { 1 } else { BITS }, + ), + Type::VARBIT => format!("B'{value:b}'::varbit"), + Type::BYTEA => format!("'\\x{value:x}'::bytea"), + Type::CHAR => format!("'{value:#x}'::char({})", 2 + 2 * nbytes(BITS)), + Type::TEXT | Type::VARCHAR => format!("'{value:#x}'::{}", ty.name()), + Type::JSON | Type::JSONB => format!("'\"{value:#x}\"'::{}", ty.name()), + _ => format!("{value}::{}", ty.name()), + }; + let ground_truth = { + let mut client = client.lock().unwrap(); + get_binary(&mut client, &expr) + }; + + // Compare with ground truth, for float we allow tiny rounding error + if ty == &Type::FLOAT4 { + let serialized = f32::from_be_bytes(serialized.as_ref().try_into().unwrap()); + let ground_truth = f32::from_be_bytes(ground_truth.try_into().unwrap()); + assert_ulps_eq!(serialized, ground_truth, max_ulps = 4); + } else if ty == &Type::FLOAT8 { + let serialized = f64::from_be_bytes(serialized.as_ref().try_into().unwrap()); + let ground_truth = f64::from_be_bytes(ground_truth.try_into().unwrap()); + assert_ulps_eq!(serialized, ground_truth, max_ulps = 4); + } else { + // Check that the value is exactly the same as the ground truth + assert_eq!(serialized, ground_truth); + } + } + + // This test requires a live postgresql server. + // To start a server, run: + // + // docker run -it --rm -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres + // + // Then run the test using: + // + // PROPTEST_CASES=1000 cargo test --all-features -- --include-ignored + // --nocapture postgres + // + #[test] + #[ignore] + fn test_postgres() { + // docker run -it --rm -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres + let client = Client::connect("postgresql://postgres:postgres@localhost", NoTls).unwrap(); + let client = Mutex::new(client); + + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + + // By default generates 256 random values per bit size. Configurable + // with the `PROPTEST_CASES` env variable. + let mut config = ProptestConfig::default(); + // No point in running many values for small sizes + if BITS < 4 { config.cases = 16; } + + proptest!(config, |(value: Uint)| { + + // Test based on which types value will fit + let bits = value.bit_len(); + if bits <= 1 { + test_to(&client, value, &Type::BOOL); + } + if bits <= 15 { + test_to(&client, value, &Type::INT2); + } + if bits <= 31 { + test_to(&client, value, &Type::INT4); + } + if bits <= 32 { + test_to(&client, value, &Type::OID); + } + if bits <= 50 { + test_to(&client, value, &Type::MONEY); + } + if bits <= 63 { + test_to(&client, value, &Type::INT8); + } + + // Floating points always work, except when the exponent + // overflows. We map that to +∞, mut SQL rejects it. This + // is handled in the `test_to` function. + test_to(&client, value, &Type::FLOAT4); + test_to(&client, value, &Type::FLOAT8); + + // Types that work for any size + for ty in &[Type::NUMERIC, Type::BIT, Type::VARBIT, Type::BYTEA, Type::CHAR, Type::TEXT, Type::VARCHAR, Type::JSON, Type::JSONB] { + test_to(&client, value, ty); + } + + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/primitive_types.rs b/guest-libs/ruint/src/support/primitive_types.rs new file mode 100644 index 0000000000..f4d20fe3a9 --- /dev/null +++ b/guest-libs/ruint/src/support/primitive_types.rs @@ -0,0 +1,81 @@ +//! Support for the [`primitive-types`](https://crates.io/crates/primitive-types) crate. + +#![cfg(feature = "primitive-types")] +#![cfg_attr(docsrs, doc(cfg(feature = "primitive-types")))] + +use crate::aliases as ours; +use primitive_types::{H128, H160, H256, H512, U128, U256, U512}; + +macro_rules! impl_uint_froms { + ($ours:ty, $theirs:ident) => { + impl From<$theirs> for $ours { + #[inline(always)] + fn from(value: $theirs) -> Self { + Self::from_limbs(value.0) + } + } + + impl From<$ours> for $theirs { + #[inline(always)] + fn from(value: $ours) -> Self { + $theirs(value.into_limbs()) + } + } + }; +} + +impl_uint_froms!(ours::U128, U128); +impl_uint_froms!(ours::U256, U256); +impl_uint_froms!(ours::U512, U512); + +/// Hash types (H128, H160, H256, H512) in `primitive-types` are stored as +/// big-endian order bytes. +macro_rules! impl_bits_froms { + ($ours:ty, $theirs:ident) => { + impl From<$theirs> for $ours { + fn from(value: $theirs) -> Self { + Self::from_be_bytes(value.0) + } + } + + impl From<$ours> for $theirs { + fn from(value: $ours) -> Self { + Self::from(value.to_be_bytes()) + } + } + }; +} + +impl_bits_froms!(ours::B128, H128); +impl_bits_froms!(ours::B160, H160); +impl_bits_froms!(ours::B256, H256); +impl_bits_froms!(ours::B512, H512); + +#[cfg(test)] +mod tests { + use super::*; + use proptest::{arbitrary::Arbitrary, proptest}; + + fn test_roundtrip() + where + Ours: Clone + PartialEq + Arbitrary + From, + Theirs: From, + { + proptest!(|(value: Ours)| { + let theirs: Theirs = value.clone().into(); + let ours: Ours = theirs.into(); + assert_eq!(ours, value); + }); + } + + #[test] + fn test_roundtrips() { + test_roundtrip::(); + test_roundtrip::(); + test_roundtrip::(); + test_roundtrip::(); + test_roundtrip::(); + test_roundtrip::(); + test_roundtrip::(); + } +} diff --git a/guest-libs/ruint/src/support/proptest.rs b/guest-libs/ruint/src/support/proptest.rs new file mode 100644 index 0000000000..cff9d5ea6f --- /dev/null +++ b/guest-libs/ruint/src/support/proptest.rs @@ -0,0 +1,49 @@ +//! Support for the [`proptest`](https://crates.io/crates/proptest) crate. + +#![cfg(feature = "proptest")] +#![cfg_attr(docsrs, doc(cfg(feature = "proptest")))] + +use crate::{Bits, Uint}; +use proptest::{arbitrary::Mapped, prelude::*}; + +impl Arbitrary for Uint { + // FEATURE: Would be nice to have a value range as parameter + // and/or a choice between uniform and 'exponential' distribution. + type Parameters = (); + type Strategy = Mapped<[u64; LIMBS], Self>; + + #[inline] + fn arbitrary() -> Self::Strategy { + Self::arbitrary_with(()) + } + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::<[u64; LIMBS]>().prop_map(Self::from_limbs_unmasked) + } +} + +impl Arbitrary for Bits { + type Parameters = as Arbitrary>::Parameters; + type Strategy = Mapped, Self>; + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + Uint::::arbitrary_with(args).prop_map(Self::from) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::proptest; + + #[test] + fn test_arbitrary() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(n: Uint::)| { + let _ = n; + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/pyo3.rs b/guest-libs/ruint/src/support/pyo3.rs new file mode 100644 index 0000000000..d12fab355f --- /dev/null +++ b/guest-libs/ruint/src/support/pyo3.rs @@ -0,0 +1,163 @@ +//! Support for the [`pyo3`](https://crates.io/crates/pyo3) crate. +//! +//! Conversion is to/from Python native integers. Beware that Python native +//! integers are unbounded and not a ring modulo a power of two like [`Uint`]. +//! +//! This uses the not-so-public `_PyLong_FromByteArray`, `_PyLong_AsByteArray` +//! ABI, which according to this [Stackoverflow answer][so] is the accepted way +//! to do efficient bigint conversions. It is supported by [CPython][cpython] +//! and [PyPy][pypy]. +//! +//! [so]: https://stackoverflow.com/a/18326068 +//! [cpython]: https://github.com/python/cpython/blob/e8165d47b852e933c176209ddc0b5836a9b0d5f4/Include/cpython/longobject.h#L47 +//! [pypy]: https://foss.heptapod.net/pypy/pypy/-/blob/branch/default/pypy/module/cpyext/longobject.py#L238 +//! +//! The implementation uses Pyo3 builtin `u64` conversion when $\mathtt{BITS} ≤ +//! 64$ and otherwise uses similar conversion to Pyo3 builtin `num-bigint` +//! support. See Pyo3's [`num.rs`][num] and [`num_bigint.rs`][bigint] for +//! reference. +//! +//! [num]: https://github.com/PyO3/pyo3/blob/caaf7bbda74f873297d277733c157338f5492580/src/types/num.rs#L81 +//! [bigint]: https://github.com/PyO3/pyo3/blob/4a68273b173ef86dac059106cc0b5b3c2c9830e2/src/conversions/num_bigint.rs#L80 + +#![cfg(feature = "pyo3")] +#![cfg_attr(docsrs, doc(cfg(feature = "pyo3")))] + +use crate::Uint; +use core::ffi::c_uchar; +use pyo3::{ + exceptions::PyOverflowError, ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyErr, PyObject, + PyResult, Python, ToPyObject, +}; + +impl ToPyObject for Uint { + fn to_object(&self, py: Python<'_>) -> PyObject { + // Fast path for small ints + if BITS <= 64 { + let value = self.as_limbs().first().copied().unwrap_or(0); + return unsafe { + let obj = ffi::PyLong_FromUnsignedLongLong(value); + assert!(!obj.is_null(), "Out of memory"); + PyObject::from_owned_ptr(py, obj) + }; + } + + // Convert using little endian bytes (trivial on LE archs) + // and `_PyLong_FromByteArray`. + let bytes = self.as_le_bytes(); + unsafe { + let obj = + ffi::_PyLong_FromByteArray(bytes.as_ptr().cast::(), bytes.len(), 1, 0); + PyObject::from_owned_ptr(py, obj) + } + } +} + +impl IntoPy for Uint { + fn into_py(self, py: Python<'_>) -> PyObject { + self.to_object(py) + } +} + +impl<'source, const BITS: usize, const LIMBS: usize> FromPyObject<'source> for Uint { + fn extract(ob: &'source PyAny) -> PyResult { + let mut result = Self::ZERO; + + // On little endian let Python write directly to the uint. + #[cfg(target_endian = "little")] + let py_result = unsafe { + let raw = result.as_le_slice_mut(); + ffi::_PyLong_AsByteArray( + ob.as_ptr().cast::(), + raw.as_mut_ptr(), + raw.len(), + 1, + 0, + ) + }; + + // On big endian we use an intermediate + #[cfg(not(target_endian = "little"))] + let py_result = { + let mut raw = vec![0_u8; Self::LIMBS * 8]; + let py_result = unsafe { + ffi::_PyLong_AsByteArray( + ob.as_ptr().cast::(), + raw.as_mut_ptr(), + raw.len(), + 1, + 0, + ) + }; + result = Self::try_from_le_slice(raw.as_slice()).ok_or_else(|| { + PyOverflowError::new_err(format!("Number to large to fit Uint<{}>", Self::BITS)) + })?; + py_result + }; + + // Handle error from `_PyLong_AsByteArray`. + if py_result != 0 { + // A TypeError is set if the value is negative and an Overflow error if the + // value does not fit `raw.len()` bytes. + return Err(PyErr::fetch(ob.py())); + } + + // Check mask since we wrote raw. + #[cfg(target_endian = "little")] + if let Some(last) = result.as_limbs().last() { + if *last > Self::MASK { + return Err(PyOverflowError::new_err(format!( + "Number to large to fit Uint<{}>", + Self::BITS + ))); + } + } + + Ok(result) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + aliases::{U0, U256, U512, U64, U8}, + const_for, nlimbs, + }; + use proptest::proptest; + + #[test] + fn test_roundtrip() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(value: U)| { + let obj = value.into_py(py); + let native = obj.extract::(py).unwrap(); + assert_eq!(value, native); + }); + }); + }); + } + + #[test] + fn test_errors() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let obj = (-1_i64).to_object(py); + assert!(obj.extract::(py).is_err()); + assert!(obj.extract::(py).is_err()); + + let obj = (1000_i64).to_object(py); + assert!(obj.extract::(py).is_err()); + assert!(obj.extract::(py).is_err()); + + let obj = U512::MAX.to_object(py); + assert!(obj.extract::(py).is_err()); + assert!(obj.extract::(py).is_err()); + assert!(obj.extract::(py).is_err()); + }); + } +} diff --git a/guest-libs/ruint/src/support/quickcheck.rs b/guest-libs/ruint/src/support/quickcheck.rs new file mode 100644 index 0000000000..0436902368 --- /dev/null +++ b/guest-libs/ruint/src/support/quickcheck.rs @@ -0,0 +1,39 @@ +//! Support for the [`quickcheck`](https://crates.io/crates/quickcheck) crate. + +#![cfg(feature = "quickcheck")] +#![cfg_attr(docsrs, doc(cfg(feature = "quickcheck")))] + +use crate::Uint; +use quickcheck::{Arbitrary, Gen}; + +impl Arbitrary for Uint { + fn arbitrary(g: &mut Gen) -> Self { + let mut limbs = [0; LIMBS]; + if let Some((last, rest)) = limbs.split_last_mut() { + for limb in rest { + *limb = u64::arbitrary(g); + } + *last = u64::arbitrary(g) & Self::MASK; + } + Self::from_limbs(limbs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use quickcheck::quickcheck; + + fn test_quickcheck_inner(_n: Uint) -> bool { + true + } + + #[test] + fn test_quickcheck() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + quickcheck(test_quickcheck_inner:: as fn(Uint) -> bool); + }); + } +} diff --git a/guest-libs/ruint/src/support/rand.rs b/guest-libs/ruint/src/support/rand.rs new file mode 100644 index 0000000000..76d1f80005 --- /dev/null +++ b/guest-libs/ruint/src/support/rand.rs @@ -0,0 +1,94 @@ +//! Support for the [`rand`](https://crates.io/crates/rand) crate. + +#![cfg(feature = "rand")] +#![cfg_attr(docsrs, doc(cfg(feature = "rand")))] + +// FEATURE: Implement the Uniform distribution. + +use rand_08 as rand; + +use crate::Uint; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; + +impl Distribution> for Standard { + #[inline] + fn sample(&self, rng: &mut R) -> Uint { + >::random_with_impl(rng) + } +} + +#[cfg(not(feature = "rand-09"))] +impl Uint { + /// Creates a new [`Uint`] with the default cryptographic random number + /// generator. + /// + /// This is currently [`rand::thread_rng`]. + #[inline] + #[must_use] + #[cfg(feature = "std")] + pub fn random() -> Self { + let mut uint = Self::ZERO; + uint.randomize(); + uint + } + + /// Creates a new [`Uint`] with the given random number generator. + #[inline] + #[doc(alias = "random_using")] + #[must_use] + pub fn random_with(rng: &mut R) -> Self { + Self::random_with_impl(rng) + } + + /// Fills this [`Uint`] with the default cryptographic random number + /// generator. + /// + /// See [`random`](Self::random) for more details. + #[inline] + #[cfg(feature = "std")] + pub fn randomize(&mut self) { + self.randomize_with(&mut rand::thread_rng()); + } + + /// Fills this [`Uint`] with the given random number generator. + #[inline] + #[doc(alias = "randomize_using")] + pub fn randomize_with(&mut self, rng: &mut R) { + self.randomize_with_impl(rng); + } +} + +impl Uint { + #[inline] + fn random_with_impl(rng: &mut R) -> Self { + let mut uint = Self::ZERO; + uint.randomize_with_impl(rng); + uint + } + + #[inline] + fn randomize_with_impl(&mut self, rng: &mut R) { + rng.fill(&mut self.limbs[..]); + self.apply_mask(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + + #[test] + fn test_rand() { + let mut rng = rand::thread_rng(); + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + for _ in 0..1000 { + let _: Uint = rng.gen(); + } + }); + } +} diff --git a/guest-libs/ruint/src/support/rand_09.rs b/guest-libs/ruint/src/support/rand_09.rs new file mode 100644 index 0000000000..c06a7b8d3c --- /dev/null +++ b/guest-libs/ruint/src/support/rand_09.rs @@ -0,0 +1,81 @@ +//! Support for the [`rand`](https://crates.io/crates/rand) crate. + +#![cfg(feature = "rand-09")] +#![cfg_attr(docsrs, doc(cfg(feature = "rand-09")))] + +// FEATURE: Implement the Uniform distribution. + +use rand_09 as rand; + +use crate::Uint; +use rand::{ + distr::{Distribution, StandardUniform}, + Rng, +}; + +impl Distribution> for StandardUniform { + #[inline] + fn sample(&self, rng: &mut R) -> Uint { + >::random_with(rng) + } +} + +impl Uint { + /// Creates a new [`Uint`] with the default cryptographic random number + /// generator. + /// + /// This is currently [`rand::rng()`]. + #[inline] + #[must_use] + #[cfg(feature = "std")] + pub fn random() -> Self { + let mut uint = Self::ZERO; + uint.randomize(); + uint + } + + /// Creates a new [`Uint`] with the given random number generator. + #[inline] + #[doc(alias = "random_using")] + #[must_use] + pub fn random_with(rng: &mut R) -> Self { + let mut uint = Self::ZERO; + uint.randomize_with(rng); + uint + } + + /// Fills this [`Uint`] with the default cryptographic random number + /// generator. + /// + /// See [`random`](Self::random) for more details. + #[inline] + #[cfg(feature = "std")] + pub fn randomize(&mut self) { + self.randomize_with(&mut rand::rng()); + } + + /// Fills this [`Uint`] with the given random number generator. + #[inline] + #[doc(alias = "randomize_using")] + pub fn randomize_with(&mut self, rng: &mut R) { + rng.fill(&mut self.limbs[..]); + self.apply_mask(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + + #[test] + fn test_rand() { + let mut rng = rand::rng(); + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + for _ in 0..1000 { + let _: Uint = rng.random(); + } + }); + } +} diff --git a/guest-libs/ruint/src/support/rlp.rs b/guest-libs/ruint/src/support/rlp.rs new file mode 100644 index 0000000000..4f7f11c410 --- /dev/null +++ b/guest-libs/ruint/src/support/rlp.rs @@ -0,0 +1,119 @@ +//! Support for the [`rlp`](https://crates.io/crates/rlp) crate. + +#![cfg(feature = "rlp")] +#![cfg_attr(docsrs, doc(cfg(feature = "rlp")))] + +use crate::{Bits, Uint}; +use core::cmp::Ordering; +use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; + +/// Allows a [`Uint`] to be serialized as RLP. +/// +/// See +impl Encodable for Uint { + fn rlp_append(&self, s: &mut RlpStream) { + let bytes = self.to_be_bytes_vec(); + // Strip most-significant zeros. + let bytes = trim_leading_zeros(&bytes); + bytes.rlp_append(s); + } +} + +/// Allows a [`Uint`] to be deserialized from RLP. +/// +/// See +impl Decodable for Uint { + fn decode(s: &Rlp) -> Result { + Self::try_from_be_slice(s.data()?).ok_or(DecoderError::Custom( + "RLP integer value too large for Uint.", + )) + } +} + +/// Allows a [`Bits`] to be serialized as RLP. +/// +/// See +impl Encodable for Bits { + #[allow(clippy::collection_is_never_read)] // have to use vec + fn rlp_append(&self, s: &mut RlpStream) { + #[allow(clippy::collection_is_never_read)] + let bytes = self.to_be_bytes_vec(); + bytes.rlp_append(s); + } +} + +/// Allows a [`Bits`] to be deserialized from RLP. +/// +/// See +impl Decodable for Bits { + fn decode(s: &Rlp) -> Result { + s.decoder() + .decode_value(|bytes| match bytes.len().cmp(&Self::BYTES) { + Ordering::Less => Err(DecoderError::RlpIsTooShort), + Ordering::Greater => Err(DecoderError::RlpIsTooBig), + Ordering::Equal => Self::try_from_be_slice(bytes).ok_or(DecoderError::RlpIsTooBig), + }) + } +} + +fn trim_leading_zeros(bytes: &[u8]) -> &[u8] { + let zeros = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len()); + &bytes[zeros..] +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + aliases::{B160, U0, U256}, + const_for, nlimbs, + }; + use hex_literal::hex; + use proptest::proptest; + + #[test] + fn test_uint_rlp() { + // See + assert_eq!(U0::from(0).rlp_bytes()[..], hex!("80")); + assert_eq!(U256::from(0).rlp_bytes()[..], hex!("80")); + assert_eq!(U256::from(15).rlp_bytes()[..], hex!("0f")); + assert_eq!(U256::from(1024).rlp_bytes()[..], hex!("820400")); + assert_eq!(U256::from(0x1234_5678).rlp_bytes()[..], hex!("8412345678")); + } + + #[test] + fn test_uint_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = value.rlp_bytes(); + let deserialized = Uint::decode(&Rlp::new(&serialized)).unwrap(); + assert_eq!(value, deserialized); + }); + }); + } + + #[test] + fn test_bits_rlp() { + // See + assert_eq!( + "0xef2d6d194084c2de36e0dabfce45d046b37d1106" + .parse::() + .unwrap() + .rlp_bytes()[..], + hex!("94ef2d6d194084c2de36e0dabfce45d046b37d1106") + ); + } + + #[test] + fn test_bits_roundtrip() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Bits)| { + let serialized = value.rlp_bytes(); + let deserialized = Bits::decode(&Rlp::new(&serialized)).unwrap(); + assert_eq!(value, deserialized); + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/scale.rs b/guest-libs/ruint/src/support/scale.rs new file mode 100644 index 0000000000..243dddb05b --- /dev/null +++ b/guest-libs/ruint/src/support/scale.rs @@ -0,0 +1,333 @@ +//! Support for the [`parity-scale-codec`](https://crates.io/crates/parity-scale-codec) crate. + +#![cfg(feature = "parity-scale-codec")] +#![cfg_attr(docsrs, doc(cfg(feature = "parity-scale-codec")))] + +use crate::Uint; +use parity_scale_codec::{ + Compact, CompactAs, Decode, Encode, EncodeAsRef, EncodeLike, Error, HasCompact, Input, + MaxEncodedLen, Output, +}; + +#[allow(unused_imports)] +use alloc::vec::Vec; + +// Compact encoding is supported only for 0-(2**536-1) values: +// https://docs.substrate.io/reference/scale-codec/#fn-1 +const COMPACT_BITS_LIMIT: usize = 536; + +impl Encode for Uint { + /// u32 prefix for compact encoding + bytes needed for LE bytes + /// representation + fn size_hint(&self) -> usize { + core::mem::size_of::() + Self::BYTES + } + + fn using_encoded R>(&self, f: F) -> R { + self.as_le_bytes().using_encoded(f) + } +} + +impl MaxEncodedLen for Uint { + fn max_encoded_len() -> usize { + core::mem::size_of::() + } +} + +impl Decode for Uint { + fn decode(input: &mut I) -> Result { + Decode::decode(input).and_then(|b: Vec<_>| { + Self::try_from_le_slice(&b) + .ok_or_else(|| Error::from("value is larger than fits the Uint")) + }) + } +} + +// TODO: Use nightly generic const expressions to validate that BITS parameter +// is less than 536 +pub struct CompactUint(pub Uint); + +impl From> for CompactUint { + fn from(v: Uint) -> Self { + Self(v) + } +} + +impl From> for Uint { + fn from(v: CompactUint) -> Self { + v.0 + } +} + +impl From> for CompactUint { + fn from(v: Compact) -> Self { + v.0 + } +} + +impl CompactAs for CompactUint { + type As = Uint; + + fn encode_as(&self) -> &Self::As { + &self.0 + } + + fn decode_from(v: Self::As) -> Result { + Ok(Self(v)) + } +} + +impl HasCompact for Uint { + type Type = CompactUint; +} + +pub struct CompactRefUint<'a, const BITS: usize, const LIMBS: usize>(pub &'a Uint); + +impl<'a, const BITS: usize, const LIMBS: usize> From<&'a Uint> + for CompactRefUint<'a, BITS, LIMBS> +{ + fn from(v: &'a Uint) -> Self { + Self(v) + } +} + +impl<'a, const BITS: usize, const LIMBS: usize> EncodeAsRef<'a, Uint> + for CompactUint +{ + type RefType = CompactRefUint<'a, BITS, LIMBS>; +} + +impl EncodeLike for CompactRefUint<'_, BITS, LIMBS> {} + +/// Compact/general integers are encoded with the two least significant bits +/// denoting the mode: +/// * `0b00`: single-byte mode: upper six bits are the LE encoding of the value. +/// Valid only for values of `0-63`. +/// * `0b01`: two-byte mode: upper six bits and the following byte is the LE +/// encoding of the value. Valid only for values of `64-(2\*\*14-1)`. +/// * `0b10`: four-byte mode: upper six bits and the following three bytes are +/// the LE encoding of the value. Valid only for values of +/// `(2\*\*14)-(2\*\*30-1)`. +/// * `0b11`: Big-integer mode: The upper six bits are the number of bytes +/// following, plus four. The value is contained, LE encoded, in the bytes +/// following. The final (most significant) byte must be non-zero. Valid only +/// for values of `(2\*\*30)-(2\*\*536-1)`. +impl Encode for CompactRefUint<'_, BITS, LIMBS> { + fn size_hint(&self) -> usize { + match self.0.bit_len() { + 0..=6 => 1, + 7..=14 => 2, + 15..=30 => 4, + _ => (32 - self.0.leading_zeros() / 8) + 1, + } + } + + fn encode_to(&self, dest: &mut T) { + assert_compact_supported::(); + + match self.0.bit_len() { + // 0..=0b0011_1111 + 0..=6 => dest.push_byte((self.0.to::()) << 2), + // 0..=0b0011_1111_1111_1111 + 7..=14 => ((self.0.to::() << 2) | 0b01).encode_to(dest), + // 0..=0b0011_1111_1111_1111_1111_1111_1111_1111 + 15..=30 => ((self.0.to::() << 2) | 0b10).encode_to(dest), + _ => { + let bytes_needed = self.0.byte_len(); + assert!( + bytes_needed >= 4, + "Previous match arm matches anything less than 2^30; qed" + ); + #[allow(clippy::cast_possible_truncation)] // bytes_needed < + dest.push_byte(0b11 + ((bytes_needed - 4) << 2) as u8); + dest.write(&self.0.as_le_bytes_trimmed()); + } + } + } +} + +/// Prefix another input with a byte. +struct PrefixInput<'a, T> { + prefix: Option, + input: &'a mut T, +} + +impl<'a, T: 'a + Input> Input for PrefixInput<'a, T> { + fn remaining_len(&mut self) -> Result, Error> { + let len = if let Some(len) = self.input.remaining_len()? { + Some(len.saturating_add(self.prefix.iter().count())) + } else { + None + }; + Ok(len) + } + + fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + match self.prefix.take() { + Some(v) if !buffer.is_empty() => { + buffer[0] = v; + self.input.read(&mut buffer[1..]) + } + _ => self.input.read(buffer), + } + } +} + +const OUT_OF_RANGE: &str = "out of range Uint decoding"; + +impl Decode for CompactUint { + fn decode(input: &mut I) -> Result { + assert_compact_supported::(); + + let prefix = input.read_byte()?; + Ok(Self(match prefix % 4 { + 0 => { + Uint::::try_from(prefix >> 2).map_err(|_| Error::from(OUT_OF_RANGE))? + } // right shift to remove mode bits + 1 => { + let x = u16::decode(&mut PrefixInput { + prefix: Some(prefix), + input, + })? >> 2; // right shift to remove mode bits + if (0b0011_1111..=0b0011_1111_1111_1111).contains(&x) { + x.try_into().map_err(|_| Error::from(OUT_OF_RANGE))? + } else { + return Err(Error::from(OUT_OF_RANGE)); + } + } + 2 => { + let x = u32::decode(&mut PrefixInput { + prefix: Some(prefix), + input, + })? >> 2; // right shift to remove mode bits + if (0b0011_1111_1111_1111..=u32::MAX >> 2).contains(&x) { + x.try_into().map_err(|_| Error::from(OUT_OF_RANGE))? + } else { + return Err(OUT_OF_RANGE.into()); + } + } + _ => match (prefix >> 2) + 4 { + 4 => { + let x = u32::decode(input)?; + if x > u32::MAX >> 2 { + x.try_into().map_err(|_| Error::from(OUT_OF_RANGE))? + } else { + return Err(OUT_OF_RANGE.into()); + } + } + 8 => { + let x = u64::decode(input)?; + if x > u64::MAX >> 8 { + x.try_into().map_err(|_| Error::from(OUT_OF_RANGE))? + } else { + return Err(OUT_OF_RANGE.into()); + } + } + 16 => { + let x = u128::decode(input)?; + if x > u128::MAX >> 8 { + x.try_into().map_err(|_| Error::from(OUT_OF_RANGE))? + } else { + return Err(OUT_OF_RANGE.into()); + } + } + bytes => { + let le_byte_slice = (0..bytes) + .map(|_| input.read_byte()) + .collect::, _>>()?; + let x = Uint::::try_from_le_slice(&le_byte_slice) + .ok_or_else(|| Error::from("value is larger than fits the Uint"))?; + let bits = bytes as usize * 8; + let limbs = (bits + 64 - 1) / 64; + + let mut new_limbs = vec![u64::MAX; limbs]; + if bits > 0 { + new_limbs[limbs - 1] &= if bits % 64 == 0 { + u64::MAX + } else { + (1 << (bits % 64)) - 1 + } + } + if Uint::::from(x) + > Uint::from_limbs_slice(&new_limbs) >> ((68 - bytes as usize + 1) * 8) + { + x + } else { + return Err(OUT_OF_RANGE.into()); + } + } + }, + })) + } +} + +fn assert_compact_supported() { + assert!( + BITS < COMPACT_BITS_LIMIT, + "compact encoding is supported only for 0-(2**536-1) values" + ); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{aliases::U256, const_for, nlimbs, Uint}; + use proptest::proptest; + + #[test] + fn test_scale() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = Encode::encode(&value); + let deserialized = as Decode>::decode(&mut serialized.as_slice()).unwrap(); + assert_eq!(value, deserialized); + }); + }); + } + + #[test] + fn test_scale_compact() { + const_for!(BITS in [1, 2, 3, 7, 8, 9, 15, 16, 17, 29, 30, 31, 32, 33, 63, 64, 65, 127, 128, 129, 256, 384, 512, 535] { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + // value.serialize_compact().deserialize_compact() == value + let serialized_compact = CompactRefUint(&value).encode(); + let deserialized_compact = CompactUint::decode(&mut serialized_compact.as_slice()).unwrap(); + assert_eq!(value, deserialized_compact.0); + + // Only for 0-(2**128-1) values. + // Check that our compact implementation is the same as parity-scale-codec's. + // value.serialize_compact_parity() == value.serialize_compact() + let value_u128: Result = value.try_into(); + if let Ok(value_u128) = value_u128 { + #[allow(clippy::cast_possible_truncation)] // value < 2**BITS + match BITS { + 0..=8 => assert_eq!(serialized_compact, Compact(value_u128 as u8).encode()), + 9..=16 => assert_eq!(serialized_compact, Compact(value_u128 as u16).encode()), + 17..=32 => assert_eq!(serialized_compact, Compact(value_u128 as u32).encode()), + 33..=64 => assert_eq!(serialized_compact, Compact(value_u128 as u64).encode()), + 65..=128 => assert_eq!(serialized_compact, Compact(value_u128).encode()), + _ => {} + } + } + }); + }); + } + + #[test] + fn test_scale_compact_derive() { + #[allow(clippy::semicolon_if_nothing_returned)] // False positive from macro expansion + #[derive(Debug, PartialEq, Encode, Decode)] + struct Data { + #[codec(compact)] + value: U256, + } + + let data = Data { value: U256::MAX }; + let serialized = data.encode(); + let deserialized = Data::decode(&mut serialized.as_slice()).unwrap(); + + assert_eq!(data, deserialized); + } +} diff --git a/guest-libs/ruint/src/support/serde.rs b/guest-libs/ruint/src/support/serde.rs new file mode 100644 index 0000000000..de5627b28b --- /dev/null +++ b/guest-libs/ruint/src/support/serde.rs @@ -0,0 +1,249 @@ +//! Support for the [`serde`](https://crates.io/crates/serde) crate. + +#![cfg(feature = "serde")] +#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] + +use crate::{nbytes, Bits, Uint}; +use core::{ + fmt::{Formatter, Result as FmtResult, Write}, + str, +}; +use serde::{ + de::{Error, Unexpected, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +#[allow(unused_imports)] +use alloc::string::String; + +/// Canonical serialization for all human-readable instances of `Uint<0, 0>`, +/// and minimal human-readable `Uint::ZERO` for any bit size. +const ZERO_STR: &str = "0x0"; + +impl Uint { + fn serialize_human_full(&self, s: S) -> Result { + if BITS == 0 { + return s.serialize_str(ZERO_STR); + } + + let mut result = String::with_capacity(2 + nbytes(BITS) * 2); + result.push_str("0x"); + + self.as_le_bytes() + .iter() + .rev() + .try_for_each(|byte| write!(result, "{byte:02x}")) + .unwrap(); + + s.serialize_str(&result) + } + + fn serialize_human_minimal(&self, s: S) -> Result { + if self.is_zero() { + return s.serialize_str(ZERO_STR); + } + + s.serialize_str(&format!("{self:#x}")) + } + + fn serialize_binary(&self, s: S) -> Result { + s.serialize_bytes(&self.to_be_bytes_vec()) + } +} + +/// Serialize a [`Uint`] value. +/// +/// For human readable formats a `0x` prefixed lower case hex string is used. +/// For binary formats a byte array is used. Leading zeros are included. +impl Serialize for Uint { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + self.serialize_human_minimal(serializer) + } else { + self.serialize_binary(serializer) + } + } +} + +/// Deserialize human readable hex strings or byte arrays into [`Uint`]. +/// +/// Hex strings can be upper/lower/mixed case, have an optional `0x` prefix, and +/// can be any length. They are interpreted big-endian. +impl<'de, const BITS: usize, const LIMBS: usize> Deserialize<'de> for Uint { + fn deserialize>(deserializer: D) -> Result { + if deserializer.is_human_readable() { + deserializer.deserialize_any(HrVisitor) + } else { + deserializer.deserialize_bytes(ByteVisitor) + } + } +} + +impl Serialize for Bits { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + self.as_uint().serialize_human_full(serializer) + } else { + self.as_uint().serialize_binary(serializer) + } + } +} + +impl<'de, const BITS: usize, const LIMBS: usize> Deserialize<'de> for Bits { + fn deserialize>(deserializer: D) -> Result { + Uint::deserialize(deserializer).map(Self::from) + } +} + +/// Serde Visitor for human readable formats. +/// +/// Accepts either a primitive number, a decimal or a hexadecimal string. +struct HrVisitor; + +impl Visitor<'_> for HrVisitor { + type Value = Uint; + + fn expecting(&self, formatter: &mut Formatter) -> FmtResult { + write!(formatter, "a {} byte hex string", nbytes(BITS)) + } + + fn visit_u64(self, v: u64) -> Result { + Uint::try_from(v).map_err(|_| Error::invalid_value(Unexpected::Unsigned(v), &self)) + } + + fn visit_u128(self, v: u128) -> Result { + // `Unexpected::Unsigned` cannot contain a `u128` + Uint::try_from(v).map_err(Error::custom) + } + + fn visit_str(self, value: &str) -> Result { + // Shortcut for common case + if value == ZERO_STR { + return Ok(Uint::::ZERO); + } + // `ZERO_STR` is the only valid serialization of `Uint<0, 0>`, so if we + // have not shortcut, we are in an error case + if BITS == 0 { + return Err(Error::invalid_value(Unexpected::Str(value), &self)); + } + + value + .parse() + .map_err(|_| Error::invalid_value(Unexpected::Str(value), &self)) + } +} + +/// Serde Visitor for non-human readable formats +struct ByteVisitor; + +impl Visitor<'_> for ByteVisitor { + type Value = Uint; + + fn expecting(&self, formatter: &mut Formatter) -> FmtResult { + write!(formatter, "{BITS} bits of binary data in big endian order") + } + + fn visit_bytes(self, value: &[u8]) -> Result { + if value.len() != nbytes(BITS) { + return Err(Error::invalid_length(value.len(), &self)); + } + Uint::try_from_be_slice(value).ok_or_else(|| { + Error::invalid_value( + Unexpected::Other(&format!("too large for Uint<{BITS}>")), + &self, + ) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::proptest; + + #[allow(unused_imports)] + use alloc::vec::Vec; + + #[test] + fn test_serde_human_readable() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = serde_json::to_string(&value).unwrap(); + let deserialized = serde_json::from_str(&serialized).unwrap(); + assert_eq!(value, deserialized); + }); + proptest!(|(value: Bits)| { + let serialized = serde_json::to_string(&value).unwrap(); + let deserialized = serde_json::from_str(&serialized).unwrap(); + assert_eq!(value, deserialized); + }); + }); + } + + #[test] + fn test_human_readable_de() { + let jason = r#"[ + 1, + "0x1", + "0o1", + "0b1" + ]"#; + let numbers: Vec> = serde_json::from_str(jason).unwrap(); + uint! { + assert_eq!(numbers, vec![1_U1, 1_U1, 1_U1, 1_U1]); + } + + let jason = r#"[ + "", + "0x", + "0o", + "0b" + ]"#; + let numbers: Vec> = serde_json::from_str(jason).unwrap(); + uint! { + assert_eq!(numbers, vec![0_U1, 0_U1, 0_U1, 0_U1]); + } + } + + #[test] + fn test_serde_machine_readable() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let serialized = bincode::serialize(&value).unwrap(); + let deserialized = bincode::deserialize(&serialized[..]).unwrap(); + assert_eq!(value, deserialized); + }); + proptest!(|(value: Bits)| { + let serialized = bincode::serialize(&value).unwrap(); + let deserialized = bincode::deserialize(&serialized[..]).unwrap(); + assert_eq!(value, deserialized); + }); + }); + } + + #[test] + fn test_serde_invalid_size_error() { + // Test that if we add a character to a value that is already the max length for + // the given number of bits, we get an error. + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + let value = Uint::::MAX; + let mut serialized = serde_json::to_string(&value).unwrap(); + + // ensure format of serialized value is correct ("0x...") + assert_eq!(&serialized[..3], "\"0x"); + // last character should be a quote + assert_eq!(&serialized[serialized.len() - 1..], "\""); + + // strip the last character, add a zero, and finish with a quote + serialized.pop(); + serialized.push('0'); + serialized.push('"'); + let deserialized = serde_json::from_str::>(&serialized); + assert!(deserialized.is_err(), "{BITS} {serialized}"); + }); + } +} diff --git a/guest-libs/ruint/src/support/sqlx.rs b/guest-libs/ruint/src/support/sqlx.rs new file mode 100644 index 0000000000..de2f029528 --- /dev/null +++ b/guest-libs/ruint/src/support/sqlx.rs @@ -0,0 +1,58 @@ +//! Support for the [`sqlx`](https://crates.io/crates/sqlx) crate. +//! +//! Currently only encodes to/from a big-endian byte array. + +#![cfg(feature = "sqlx")] +#![cfg_attr(docsrs, doc(cfg(feature = "sqlx")))] + +use sqlx_core::{ + database::Database, + decode::Decode, + encode::{Encode, IsNull}, + error::BoxDynError, + types::Type, +}; +use thiserror::Error; + +use crate::Uint; + +#[derive(Error, Debug)] +pub enum DecodeError { + #[error("Value too large for target type")] + Overflow, +} + +impl Type for Uint +where + Vec: Type, +{ + fn type_info() -> DB::TypeInfo { + as Type>::type_info() + } + + fn compatible(ty: &DB::TypeInfo) -> bool { + as Type>::compatible(ty) + } +} + +impl<'a, const BITS: usize, const LIMBS: usize, DB: Database> Encode<'a, DB> for Uint +where + Vec: Encode<'a, DB>, +{ + fn encode_by_ref( + &self, + buf: &mut ::ArgumentBuffer<'a>, + ) -> Result { + self.to_be_bytes_vec().encode_by_ref(buf) + } +} + +impl<'a, const BITS: usize, const LIMBS: usize, DB: Database> Decode<'a, DB> for Uint +where + Vec: Decode<'a, DB>, +{ + fn decode(value: ::ValueRef<'a>) -> Result { + let bytes = Vec::::decode(value)?; + Self::try_from_be_slice(bytes.as_slice()).ok_or_else(|| DecodeError::Overflow.into()) + } +} diff --git a/guest-libs/ruint/src/support/ssz.rs b/guest-libs/ruint/src/support/ssz.rs new file mode 100644 index 0000000000..c1209bb1ba --- /dev/null +++ b/guest-libs/ruint/src/support/ssz.rs @@ -0,0 +1,79 @@ +#![cfg(feature = "ssz")] +#![cfg_attr(docsrs, doc(cfg(feature = "ssz")))] +use ssz::{Decode, DecodeError, Encode}; + +use crate::{nbytes, Uint}; + +impl Encode for Uint { + fn is_ssz_fixed_len() -> bool { + true + } + + fn ssz_fixed_len() -> usize { + nbytes(BITS) + } + + fn ssz_bytes_len(&self) -> usize { + nbytes(BITS) + } + + fn ssz_append(&self, buf: &mut Vec) { + buf.extend_from_slice(&self.as_le_bytes()); + } +} + +impl Decode for Uint { + fn is_ssz_fixed_len() -> bool { + true + } + + fn ssz_fixed_len() -> usize { + nbytes(BITS) + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + if bytes.len() > nbytes(BITS) { + return Err(DecodeError::InvalidByteLength { + len: bytes.len(), + expected: nbytes(BITS), + }); + } + Ok(Self::from_le_slice(bytes)) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + use ruint::{const_for, nlimbs, Uint}; + use ssz::DecodeError; + + #[test] + fn test_ssz_human_readable() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let expected = value; + let encoded = ssz::Encode::as_ssz_bytes(&expected); + let actual = ssz::Decode::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(expected, actual, "Failed for value: {value:?}" ); + }); + + }); + } + + #[test] + fn test_ssz_decode_error_length() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(value: Uint)| { + let encoded = ssz::Encode::as_ssz_bytes(&value); + let mut oversized = encoded; + oversized.push(0); + + let result = as ssz::Decode>::from_ssz_bytes(&oversized); + assert!(matches!(result, Err(DecodeError::InvalidByteLength { len:_, expected:_ }))); + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/subtle.rs b/guest-libs/ruint/src/support/subtle.rs new file mode 100644 index 0000000000..f3a66c2b1b --- /dev/null +++ b/guest-libs/ruint/src/support/subtle.rs @@ -0,0 +1,172 @@ +//! Support for the [`subtle`](https://crates.io/crates/subtle) crate. + +#![cfg(feature = "subtle")] +#![cfg_attr(docsrs, doc(cfg(feature = "subtle")))] + +use crate::Uint; +use subtle::{ + Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess, +}; + +impl Uint { + /// Returns a [`Choice`] if the bit at index is set. + /// + /// Constant time version of [`Uint::bit`] + /// + /// # Panics + /// + /// Panics if `index >= Self::BITS`. + #[must_use] + pub fn bit_ct(&self, index: usize) -> Choice { + assert!(index < BITS); + let (limbs, bits) = (index / 64, index % 64); + (self.limbs[limbs] & (1 << bits)).ct_eq(&(1 << bits)) + } +} + +impl ConditionallySelectable for Uint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + let mut limbs = [0_u64; LIMBS]; + for (limb, (a, b)) in limbs + .iter_mut() + .zip(a.as_limbs().iter().zip(b.as_limbs().iter())) + { + *limb = u64::conditional_select(a, b, choice); + } + Self::from_limbs(limbs) + } +} + +impl ConstantTimeEq for Uint { + #[inline] + fn ct_eq(&self, rhs: &Self) -> Choice { + // Leverage ConstantTimeEq for &[u64] + self.as_limbs().ct_eq(rhs.as_limbs()) + } +} + +impl ConstantTimeGreater for Uint { + fn ct_gt(&self, rhs: &Self) -> Choice { + let mut equal = Choice::from(1); // True + let mut greater = Choice::from(0); // False + + // Iterate limbs in big-endian order. + for (l, r) in self + .as_limbs() + .iter() + .rev() + .zip(rhs.as_limbs().iter().rev()) + { + greater |= equal & l.ct_gt(r); + equal &= l.ct_eq(r); + } + greater + } +} + +impl ConstantTimeLess for Uint { + fn ct_lt(&self, rhs: &Self) -> Choice { + let mut equal = Choice::from(1); // True + let mut less = Choice::from(0); // False + + // Iterate limbs in big-endian order. + for (l, r) in self + .as_limbs() + .iter() + .rev() + .zip(rhs.as_limbs().iter().rev()) + { + less |= equal & l.ct_lt(r); + equal &= l.ct_eq(r); + } + less + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{const_for, nlimbs}; + use proptest::proptest; + use subtle::ConditionallyNegatable; + + #[test] + fn test_bit() { + const_for!(BITS in NON_ZERO { + const LIMBS: usize = nlimbs(BITS); + proptest!(|(n: Uint, i in 0..BITS)| { + let r = n.bit_ct(i); + let e = n.bit(i); + assert_eq!(bool::from(r), e); + }); + }); + } + + #[test] + fn test_select() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U, c: bool)| { + let choice = Choice::from(c as u8); + let r = U::conditional_select(&a, &b, choice); + let e = if c { b } else { a }; + assert_eq!(r, e); + }); + }); + } + + #[test] + fn test_negate() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, c: bool)| { + let choice = Choice::from(c as u8); + let mut r = a; + r.conditional_negate(choice); + let e = if c { -a } else { a }; + assert_eq!(r, e); + }); + }); + } + + #[test] + fn test_eq() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U)| { + let r = a.ct_eq(&b); + let e = a == b; + assert_eq!(bool::from(r), e); + }); + }); + } + + #[test] + fn test_lt() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U)| { + let r = a.ct_lt(&b); + let e = a < b; + assert_eq!(bool::from(r), e); + }); + }); + } + + #[test] + fn test_gt() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + proptest!(|(a: U, b: U)| { + let r = a.ct_gt(&b); + let e = a > b; + assert_eq!(bool::from(r), e); + }); + }); + } +} diff --git a/guest-libs/ruint/src/support/valuable.rs b/guest-libs/ruint/src/support/valuable.rs new file mode 100644 index 0000000000..db5520a254 --- /dev/null +++ b/guest-libs/ruint/src/support/valuable.rs @@ -0,0 +1,25 @@ +//! Support for the [`valuable`](https://crates.io/crates/valuable) crate. + +#![cfg(feature = "valuable")] +#![cfg_attr(docsrs, doc(cfg(feature = "valuable")))] + +use crate::Uint; +use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit}; + +const FIELDS: &[NamedField<'static>] = &[NamedField::new("limbs")]; + +impl Structable for Uint { + fn definition(&self) -> StructDef<'_> { + StructDef::new_static("Uint", Fields::Named(FIELDS)) + } +} + +impl Valuable for Uint { + fn as_value(&self) -> Value<'_> { + Value::Structable(self) + } + + fn visit(&self, visitor: &mut dyn Visit) { + visitor.visit_named_fields(&NamedValues::new(FIELDS, &[self.limbs.as_value()])); + } +} diff --git a/guest-libs/ruint/src/support/zeroize.rs b/guest-libs/ruint/src/support/zeroize.rs new file mode 100644 index 0000000000..8d065217f7 --- /dev/null +++ b/guest-libs/ruint/src/support/zeroize.rs @@ -0,0 +1,24 @@ +//! Support for the [`zeroize`](https://crates.io/crates/zeroize) crate. +//! +//! Currently only encodes to/from a big-endian byte array. + +#![cfg(feature = "zeroize")] +#![cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] + +use crate::{Bits, Uint}; +use zeroize::Zeroize; + +impl Zeroize for Uint { + fn zeroize(&mut self) { + unsafe { + // SAFETY: Setting limbs to zero always safe. + self.as_limbs_mut().zeroize(); + } + } +} + +impl Zeroize for Bits { + fn zeroize(&mut self) { + self.as_uint_mut().zeroize(); + } +} diff --git a/guest-libs/ruint/src/support/zkvm.rs b/guest-libs/ruint/src/support/zkvm.rs new file mode 100644 index 0000000000..aa14a25bd3 --- /dev/null +++ b/guest-libs/ruint/src/support/zkvm.rs @@ -0,0 +1,78 @@ +/// This file allows users to define more efficient native implementations for +/// the zkvm target which can be used to speed up the operations on [Uint]'s. +/// +/// The functions defined here are not meant to be used by the user, but rather +/// to be used by the library to define more efficient native implementations +/// for the zkvm target. +/// +/// Currently these functions are specified to support only 256 bit [Uint]'s and +/// take pointers to their limbs as arguments. Providing other sizes +/// will result in an undefined behavior. +use core::{cmp::Ordering, mem::MaybeUninit}; + +#[allow(unused_imports)] +use openvm_bigint_guest::externs; + +use crate::Uint; // necessary for linking + +extern "C" { + /// Add two 256-bit numbers and store in `result`. + pub fn zkvm_u256_wrapping_add_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Subtract two 256-bit numbers and store in `result`. + pub fn zkvm_u256_wrapping_sub_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Multiply two 256-bit numbers and store in `result`. + pub fn zkvm_u256_wrapping_mul_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Bitwise XOR two 256-bit numbers and store in `result`. + pub fn zkvm_u256_bitxor_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Bitwise AND two 256-bit numbers and store in `result`. + pub fn zkvm_u256_bitand_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Bitwise OR two 256-bit numbers and store in `result`. + pub fn zkvm_u256_bitor_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Shift left two 256-bit numbers and store in `result`. + pub fn zkvm_u256_wrapping_shl_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Shift right two 256-bit numbers and store in `result`. + pub fn zkvm_u256_wrapping_shr_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Arithmetic shift right two 256-bit numbers and store in `result`. + pub fn zkvm_u256_arithmetic_shr_impl(result: *mut u8, a: *const u8, b: *const u8); + /// Check if two 256-bit numbers are equal. + pub fn zkvm_u256_eq_impl(a: *const u8, b: *const u8) -> bool; + /// Compare two 256-bit numbers. + pub fn zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Ordering; + /// Clone a 256-bit number into `result`. + pub fn zkvm_u256_clone_impl(result: *mut u8, a: *const u8); +} + +impl Copy for Uint {} + +impl Clone for Uint { + fn clone(&self) -> Self { + if BITS == 256 { + let mut uninit: MaybeUninit = MaybeUninit::uninit(); + unsafe { + zkvm_u256_clone_impl( + (*uninit.as_mut_ptr()).limbs.as_mut_ptr() as *mut u8, + self.limbs.as_ptr() as *const u8, + ); + } + return unsafe { uninit.assume_init() }; + } + Self { limbs: self.limbs } + } +} + +impl PartialEq for Uint { + fn eq(&self, other: &Self) -> bool { + if BITS == 256 { + unsafe { + zkvm_u256_eq_impl( + self.limbs.as_ptr() as *const u8, + other.limbs.as_ptr() as *const u8, + ) + } + } else { + self.limbs == other.limbs + } + } +} + +impl Eq for Uint {} diff --git a/guest-libs/ruint/src/utils.rs b/guest-libs/ruint/src/utils.rs new file mode 100644 index 0000000000..23e512da32 --- /dev/null +++ b/guest-libs/ruint/src/utils.rs @@ -0,0 +1,71 @@ +#[cfg(feature = "alloc")] +#[allow(unused_imports)] +use alloc::vec::Vec; + +/// Like `a % b` but returns `b` instead of `0`. +#[allow(dead_code)] // This is used by some support features. +#[must_use] +pub(crate) const fn rem_up(a: usize, b: usize) -> usize { + let rem = a % b; + if rem > 0 { + rem + } else { + b + } +} + +#[allow(dead_code)] // This is used by some support features. +#[inline] +fn last_idx(x: &[T], value: &T) -> usize { + x.iter().rposition(|b| b != value).map_or(0, |idx| idx + 1) +} + +#[allow(dead_code)] // This is used by some support features. +#[inline] +#[must_use] +pub(crate) fn trim_end_slice<'a, T: PartialEq>(slice: &'a [T], value: &T) -> &'a [T] { + &slice[..last_idx(slice, value)] +} + +#[cfg(feature = "alloc")] +#[inline] +pub(crate) fn trim_end_vec(vec: &mut Vec, value: &T) { + vec.truncate(last_idx(vec, value)); +} + +// Branch prediction hints. +#[cfg(feature = "nightly")] +pub(crate) use core::intrinsics::{likely, unlikely}; + +#[cfg(not(feature = "nightly"))] +pub(crate) use core::convert::identity as likely; +#[cfg(not(feature = "nightly"))] +pub(crate) use core::convert::identity as unlikely; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_trim() { + assert_eq!(trim_end_slice(&[], &0), &[] as &[i32]); + assert_eq!(trim_end_slice(&[0], &0), &[] as &[i32]); + assert_eq!(trim_end_slice(&[0, 1], &0), &[0, 1]); + assert_eq!(trim_end_slice(&[0, 1, 0], &0), &[0, 1]); + assert_eq!(trim_end_slice(&[0, 1, 0, 0], &0), &[0, 1]); + assert_eq!(trim_end_slice(&[0, 1, 0, 0, 0], &0), &[0, 1]); + assert_eq!(trim_end_slice(&[0, 1, 0, 1, 0], &0), &[0, 1, 0, 1]); + + let trim_end_vec = |mut v: Vec, x: &i32| { + trim_end_vec(&mut v, x); + v + }; + assert_eq!(trim_end_vec(vec![], &0), &[] as &[i32]); + assert_eq!(trim_end_vec(vec![0], &0), &[] as &[i32]); + assert_eq!(trim_end_vec(vec![0, 1], &0), &[0, 1]); + assert_eq!(trim_end_vec(vec![0, 1, 0], &0), &[0, 1]); + assert_eq!(trim_end_vec(vec![0, 1, 0, 0], &0), &[0, 1]); + assert_eq!(trim_end_vec(vec![0, 1, 0, 0, 0], &0), &[0, 1]); + assert_eq!(trim_end_vec(vec![0, 1, 0, 1, 0], &0), &[0, 1, 0, 1]); + } +} diff --git a/extensions/bigint/tests/src/lib.rs b/guest-libs/ruint/tests/lib.rs similarity index 61% rename from extensions/bigint/tests/src/lib.rs rename to guest-libs/ruint/tests/lib.rs index 866be2f672..3db2697775 100644 --- a/extensions/bigint/tests/src/lib.rs +++ b/guest-libs/ruint/tests/lib.rs @@ -16,23 +16,12 @@ mod tests { #[test] fn test_matrix_power() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "matrix-power-unsigned")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Int256TranspilerExtension), - )?; let config = Int256Rv32Config::default(); - air_test(config, openvm_exe); - Ok(()) - } - - #[test] - fn test_matrix_power_signed() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "matrix-power-signed")?; + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "matrix_power", + &config, + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -41,7 +30,6 @@ mod tests { .with_extension(Rv32IoTranspilerExtension) .with_extension(Int256TranspilerExtension), )?; - let config = Int256Rv32Config::default(); air_test(config, openvm_exe); Ok(()) } diff --git a/extensions/bigint/tests/programs/Cargo.toml b/guest-libs/ruint/tests/programs/Cargo.toml similarity index 69% rename from extensions/bigint/tests/programs/Cargo.toml rename to guest-libs/ruint/tests/programs/Cargo.toml index 5b7ee53bf7..dcf4c54189 100644 --- a/extensions/bigint/tests/programs/Cargo.toml +++ b/guest-libs/ruint/tests/programs/Cargo.toml @@ -1,19 +1,20 @@ [workspace] [package] -name = "openvm-bigint-test-programs" +name = "openvm-ruint-test-programs" version = "0.0.0" edition = "2021" [dependencies] openvm = { path = "../../../../crates/toolchain/openvm" } -openvm-platform = { path = "../../../../crates/toolchain/platform" } +openvm-ruint = { path = "../../", package = "ruint", default-features = false, features = [ + "num-traits", +] } -openvm-bigint-guest = { path = "../../guest" } serde = { version = "1.0", default-features = false, features = [ "alloc", "derive", ] } - +num-traits = { version = "0.2.19", default-features = false } [features] default = [] diff --git a/extensions/bigint/tests/programs/examples/matrix-power-unsigned.rs b/guest-libs/ruint/tests/programs/examples/matrix_power.rs similarity index 66% rename from extensions/bigint/tests/programs/examples/matrix-power-unsigned.rs rename to guest-libs/ruint/tests/programs/examples/matrix_power.rs index b128dbe4f9..95826d32de 100644 --- a/extensions/bigint/tests/programs/examples/matrix-power-unsigned.rs +++ b/guest-libs/ruint/tests/programs/examples/matrix_power.rs @@ -6,14 +6,15 @@ openvm::entry!(main); use core::array; +use num_traits::cast::FromPrimitive; use openvm::io::print; -use openvm_bigint_guest::U256; +use openvm_ruint::aliases::U256; const N: usize = 16; type Matrix = [[U256; N]; N]; pub fn get_matrix(val: u8) -> Matrix { - array::from_fn(|_| array::from_fn(|_| U256::from_u8(val))) + array::from_fn(|_| array::from_fn(|_| U256::from_u8(val).unwrap())) } pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { @@ -21,7 +22,7 @@ pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { for i in 0..N { for j in 0..N { for k in 0..N { - c[i][j] += &a[i][k] * &b[k][j]; + c[i][j] += a[i][k] * b[k][j]; } } } @@ -31,7 +32,7 @@ pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { pub fn get_identity_matrix() -> Matrix { let mut res = get_matrix(0); for i in 0..N { - res[i][i] = U256::from_u8(1); + res[i][i] = U256::from_u8(1).unwrap(); } res } @@ -39,31 +40,31 @@ pub fn get_identity_matrix() -> Matrix { /// Computes base^exp using binary exponentiation. pub fn bin_exp(mut base: Matrix, mut exp: U256) -> Matrix { let mut result = get_identity_matrix(); - let one = U256::from_u8(1); - while exp > U256::from_u8(0) { - if (&exp & &one) == one { + let one = U256::from_u8(1).unwrap(); + while exp > U256::from_u8(0).unwrap() { + if (exp & one) == one { result = mult(&result, &base); } base = mult(&base, &base); - exp >>= &one; + exp >>= one; } result } pub fn main() { let a: Matrix = get_identity_matrix(); - let c = bin_exp(a, U256::from_u32(1234567)); + let c = bin_exp(a, U256::from_u32(1234567).unwrap()); if c != get_identity_matrix() { print("FAIL: the resulting matrix should have been the identity matrix"); panic!(); } - let one = U256::from_u8(1); - let zero = U256::from_u8(0); + let one = U256::from_u8(1).unwrap(); + let zero = U256::from_u8(0).unwrap(); let a: Matrix = get_matrix(1); - let c = bin_exp(a, U256::from_u8(51)); - let two_to_200 = &one << &U256::from_u8(200); + let c = bin_exp(a, U256::from_u8(51).unwrap()); + let two_to_200 = one << U256::from_u8(200).unwrap(); for i in 0..N { for j in 0..N { @@ -75,59 +76,54 @@ pub fn main() { } // Shift right tests - if &two_to_200 >> &U256::from_u8(200) != one { + if two_to_200 >> U256::from_u8(200).unwrap() != one { print("FAIL: 2^200 >> 200 == 1 test failed"); panic!(); } - if &two_to_200 >> &U256::from_u8(201) != zero { + if two_to_200 >> U256::from_u8(201).unwrap() != zero { print("FAIL: 2^200 >> 201 == 0 test failed"); panic!(); } // Xor tests - if &two_to_200 ^ &two_to_200 != zero { + if two_to_200 ^ two_to_200 != zero { print("FAIL: 2^200 ^ 2^200 == 0 test failed"); panic!(); } - if &two_to_200 ^ &one != &two_to_200 + &one { + if two_to_200 ^ one != two_to_200 + one { print("FAIL: 2^200 ^ 1 == 2^200 + 1 test failed"); panic!(); } // Or tests - if &one | &one != one { + if one | one != one { print("FAIL: 1 | 1 == 1 test failed"); panic!(); } - if &two_to_200 | &one != &two_to_200 + &one { + if two_to_200 | one != two_to_200 + one { print("FAIL: 2^200 | 1 = 2^200 + 1 test failed"); panic!(); } // Other tests - if &zero - &one <= zero { + if zero - one <= zero { print("FAIL: 0 - 1 > 0 test failed (should have wrapped)"); panic!(); } - if &zero - &one + &one != zero { + if zero - one + one != zero { print("FAIL: 0 - 1 + 1 == 0 test failed (should have wrapped)"); panic!(); } - if &one << &U256::from_u32(256) != one { - print("FAIL: 1 << 256 == 1 test failed"); + if one << U256::from_u32(256).unwrap() != zero { + print("FAIL: 1 << 256 == 0 test failed"); panic!(); } - if &one << &U256::from_u32(261) != U256::from_u8(32) { - print("FAIL: 1 << 261 == 32 test failed"); - panic!(); - } - - if two_to_200.clone() != two_to_200 { + if two_to_200 != two_to_200 { print("FAIL: 2^200 clone test failed"); panic!(); } diff --git a/guest-libs/sha2/Cargo.toml b/guest-libs/sha2/Cargo.toml new file mode 100644 index 0000000000..f8bf7b545e --- /dev/null +++ b/guest-libs/sha2/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "openvm-sha2" +description = "OpenVM library for sha2" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +openvm-sha256-guest = { workspace = true } + +[dev-dependencies] +openvm-instructions = { workspace = true } +openvm-stark-sdk = { workspace = true } +openvm-circuit = { workspace = true, features = ["test-utils", "parallel"] } +openvm-transpiler = { workspace = true } +openvm-sha256-transpiler = { workspace = true } +openvm-sha256-circuit = { workspace = true } +openvm-rv32im-transpiler = { workspace = true } +openvm-toolchain-tests = { workspace = true } +eyre = { workspace = true } + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +sha2 = { workspace = true } diff --git a/guest-libs/sha2/src/lib.rs b/guest-libs/sha2/src/lib.rs new file mode 100644 index 0000000000..43d90ba822 --- /dev/null +++ b/guest-libs/sha2/src/lib.rs @@ -0,0 +1,28 @@ +#![no_std] + +/// The sha256 cryptographic hash function. +#[inline(always)] +pub fn sha256(input: &[u8]) -> [u8; 32] { + let mut output = [0u8; 32]; + set_sha256(input, &mut output); + output +} + +/// Sets `output` to the sha256 hash of `input`. +pub fn set_sha256(input: &[u8], output: &mut [u8; 32]) { + #[cfg(not(target_os = "zkvm"))] + { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(input); + output.copy_from_slice(hasher.finalize().as_ref()); + } + #[cfg(target_os = "zkvm")] + { + openvm_sha256_guest::zkvm_sha256_impl( + input.as_ptr(), + input.len(), + output.as_mut_ptr() as *mut u8, + ); + } +} diff --git a/extensions/sha256/tests/src/lib.rs b/guest-libs/sha2/tests/lib.rs similarity index 83% rename from extensions/sha256/tests/src/lib.rs rename to guest-libs/sha2/tests/lib.rs index 41ea37e8ae..9ebab5ac02 100644 --- a/extensions/sha256/tests/src/lib.rs +++ b/guest-libs/sha2/tests/lib.rs @@ -16,7 +16,9 @@ mod tests { #[test] fn test_sha256() -> Result<()> { - let elf = build_example_program_at_path(get_programs_dir!(), "sha")?; + let config = Sha256Rv32Config::default(); + let elf = + build_example_program_at_path(get_programs_dir!("tests/programs"), "sha", &config)?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -25,7 +27,7 @@ mod tests { .with_extension(Rv32IoTranspilerExtension) .with_extension(Sha256TranspilerExtension), )?; - air_test(Sha256Rv32Config::default(), openvm_exe); + air_test(config, openvm_exe); Ok(()) } } diff --git a/extensions/sha256/tests/programs/Cargo.toml b/guest-libs/sha2/tests/programs/Cargo.toml similarity index 87% rename from extensions/sha256/tests/programs/Cargo.toml rename to guest-libs/sha2/tests/programs/Cargo.toml index dad3842cea..df13f8dfc7 100644 --- a/extensions/sha256/tests/programs/Cargo.toml +++ b/guest-libs/sha2/tests/programs/Cargo.toml @@ -1,20 +1,20 @@ [workspace] [package] -name = "openvm-keccak256-test-programs" +name = "openvm-sha2-test-programs" version = "0.0.0" edition = "2021" [dependencies] openvm = { path = "../../../../crates/toolchain/openvm" } openvm-platform = { path = "../../../../crates/toolchain/platform" } -openvm-sha256-guest = { path = "../../guest" } +openvm-sha2 = { path = "../../" } + hex = { version = "0.4.3", default-features = false, features = ["alloc"] } serde = { version = "1.0", default-features = false, features = [ "alloc", "derive", ] } - [features] default = [] std = ["serde/std", "openvm/std"] diff --git a/extensions/sha256/tests/programs/examples/sha.rs b/guest-libs/sha2/tests/programs/examples/sha.rs similarity index 98% rename from extensions/sha256/tests/programs/examples/sha.rs rename to guest-libs/sha2/tests/programs/examples/sha.rs index fffbc677a7..ebfd50cbee 100644 --- a/extensions/sha256/tests/programs/examples/sha.rs +++ b/guest-libs/sha2/tests/programs/examples/sha.rs @@ -7,7 +7,7 @@ use alloc::vec::Vec; use core::hint::black_box; use hex::FromHex; -use openvm_sha256_guest::sha256; +use openvm_sha2::sha256; openvm::entry!(main); @@ -17,7 +17,6 @@ pub fn main() { ("98c1c0bdb7d5fea9a88859f06c6c439f", "b6b2c9c9b6f30e5c66c977f1bd7ad97071bee739524aecf793384890619f2b05"), ("5b58f4163e248467cc1cd3eecafe749e8e2baaf82c0f63af06df0526347d7a11327463c115210a46b6740244eddf370be89c", "ac0e25049870b91d78ef6807bb87fce4603c81abd3c097fba2403fd18b6ce0b7"), ("9ad198539e3160194f38ac076a782bd5210a007560d1fce9ef78f8a4a5e4d78c6b96c250cff3520009036e9c6087d5dab587394edda862862013de49a12072485a6c01165ec0f28ffddf1873fbd53e47fcd02fb6a5ccc9622d5588a92429c663ce298cb71b50022fc2ec4ba9f5bbd250974e1a607b165fee16e8f3f2be20d7348b91a2f518ce928491900d56d9f86970611580350cee08daea7717fe28a73b8dcfdea22a65ed9f5a09198de38e4e4f2cc05b0ba3dd787a5363ab6c9f39dcb66c1a29209b1d6b1152769395df8150b4316658ea6ab19af94903d643fcb0ae4d598035ebe73c8b1b687df1ab16504f633c929569c6d0e5fae6eea43838fbc8ce2c2b43161d0addc8ccf945a9c4e06294e56a67df0000f561f61b630b1983ba403e775aaeefa8d339f669d1e09ead7eae979383eda983321e1743e5404b4b328da656de79ff52d179833a6bd5129f49432d74d001996c37c68d9ab49fcff8061d193576f396c20e1f0d9ee83a51290ba60efa9c3cb2e15b756321a7ca668cdbf63f95ec33b1c450aa100101be059dc00077245b25a6a66698dee81953ed4a606944076e2858b1420de0095a7f60b08194d6d9a997009d345c71f63a7034b976e409af8a9a040ac7113664609a7adedb76b2fadf04b0348392a1650526eb2a4d6ed5e4bbcda8aabc8488b38f4f5d9a398103536bb8250ed82a9b9825f7703c263f9e", "080ad71239852124fc26758982090611b9b19abf22d22db3a57f67a06e984a23") - ]; for (input, expected_output) in test_vectors.iter() { let input = Vec::from_hex(input).unwrap(); diff --git a/guest-libs/verify_stark/Cargo.toml b/guest-libs/verify_stark/Cargo.toml new file mode 100644 index 0000000000..070083edad --- /dev/null +++ b/guest-libs/verify_stark/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "openvm-verify-stark" +description = "OpenVM guest library for verifying STARKs" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +openvm-native-recursion.workspace = true +openvm-rv32im-guest.workspace = true +openvm-sdk = { workspace = true } +openvm-stark-sdk = { workspace = true } + +[dev-dependencies] +openvm-sdk = { workspace = true } +openvm-circuit = { workspace = true, features = ["parallel"] } +openvm-stark-sdk = { workspace = true } +openvm-native-compiler.workspace = true +openvm-verify-stark.workspace = true +eyre.workspace = true \ No newline at end of file diff --git a/guest-libs/verify_stark/examples/verify_openvm_stark/.gitignore b/guest-libs/verify_stark/examples/verify_openvm_stark/.gitignore new file mode 100644 index 0000000000..0e52c45d5d --- /dev/null +++ b/guest-libs/verify_stark/examples/verify_openvm_stark/.gitignore @@ -0,0 +1,4 @@ +*.asm +Cargo.lock +target/ +openvm/ \ No newline at end of file diff --git a/guest-libs/verify_stark/examples/verify_openvm_stark/Cargo.toml b/guest-libs/verify_stark/examples/verify_openvm_stark/Cargo.toml new file mode 100644 index 0000000000..772d00014c --- /dev/null +++ b/guest-libs/verify_stark/examples/verify_openvm_stark/Cargo.toml @@ -0,0 +1,17 @@ +[workspace] +[package] +name = "openvm-verify-stark-program" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../../crates/toolchain/openvm", features = ["std"] } +openvm-verify-stark = { path = "../../../guest" } + +[features] +default = [] + +[profile.profiling] +inherits = "release" +debug = 2 +strip = false diff --git a/guest-libs/verify_stark/examples/verify_openvm_stark/openvm.toml b/guest-libs/verify_stark/examples/verify_openvm_stark/openvm.toml new file mode 100644 index 0000000000..cb8c41f306 --- /dev/null +++ b/guest-libs/verify_stark/examples/verify_openvm_stark/openvm.toml @@ -0,0 +1,4 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +[app_vm_config.native] diff --git a/guest-libs/verify_stark/examples/verify_openvm_stark/src/main.rs b/guest-libs/verify_stark/examples/verify_openvm_stark/src/main.rs new file mode 100644 index 0000000000..bb8f8c18e4 --- /dev/null +++ b/guest-libs/verify_stark/examples/verify_openvm_stark/src/main.rs @@ -0,0 +1,18 @@ +extern crate alloc; +use alloc::vec::Vec; + +use openvm::io::read; +use openvm_verify_stark::define_verify_openvm_stark; + +define_verify_openvm_stark!( + verify_openvm_stark, + env!("CARGO_MANIFEST_DIR"), + "root_verifier.asm" +); + +pub fn main() { + let app_exe_commit: [u32; 8] = read(); + let app_vm_commit: [u32; 8] = read(); + let pvs: Vec = read(); + verify_openvm_stark(&app_exe_commit, &app_vm_commit, &pvs); +} diff --git a/guest-libs/verify_stark/src/host.rs b/guest-libs/verify_stark/src/host.rs new file mode 100644 index 0000000000..92e3b53969 --- /dev/null +++ b/guest-libs/verify_stark/src/host.rs @@ -0,0 +1,28 @@ +use openvm_native_recursion::hints::Hintable; +use openvm_rv32im_guest::hint_load_by_key_encode; +use openvm_sdk::SC; +use openvm_stark_sdk::{openvm_stark_backend::proof::Proof, p3_baby_bear::BabyBear}; + +/// Compute the hint key for `verify_openvm_stark` function, which reads a stark proof from stream +/// `kv_store`. +pub fn compute_hint_key_for_verify_openvm_stark( + asm_filename: &str, + exe_commit_u32: &[u32; 8], + vm_commit_u32: &[u32; 8], + pvs: &[u8], +) -> Vec { + asm_filename + .as_bytes() + .iter() + .cloned() + .chain(exe_commit_u32.iter().flat_map(|x| x.to_le_bytes())) + .chain(vm_commit_u32.iter().flat_map(|x| x.to_le_bytes())) + .chain(pvs.iter().cloned()) + .collect() +} + +/// Encode a proof into a KV store value so `verify_openvm_stark` can hint it. +pub fn encode_proof_to_kv_store_value(proof: &Proof) -> Vec { + let to_encode: Vec> = proof.write(); + hint_load_by_key_encode(&to_encode) +} diff --git a/guest-libs/verify_stark/src/lib.rs b/guest-libs/verify_stark/src/lib.rs new file mode 100644 index 0000000000..02ce76748b --- /dev/null +++ b/guest-libs/verify_stark/src/lib.rs @@ -0,0 +1,58 @@ +#[cfg(not(target_os = "zkvm"))] +pub mod host; + +/// Define a function that verifies an OpenVM Stark proof. +/// To define this function, users need to specify the function name and an ASM file containing the +/// assembly code for the verification(this ASM file can be generated by +/// `Sdk.generate_root_verifier_asm` function). To specify the ASM file, users need to provide the +/// parent folder and filename of the ASM file. +/// To call this function: +/// 1. users need to provide `app_exe_commit`/`app_vm_commit`/`user_pvs`(user public values) as the +/// arguments. CAREFUL: `app_exe_commit`/`app_vm_commit` are in u32 and are interpreted as native +/// fields. `user_pvs` are the exact public values of that proof. Here we assume all the public +/// values are `u8`s. +/// 2. Provide the corresponding stark proof in the key-value store in OpenVM streams. The key +/// should be the concatenation of the filename, `app_exe_commit`, `app_vm_commit`, and +/// `user_pvs` in little-endian bytes. Users can use +/// `openvm::host::compute_hint_key_for_verify_openvm_stark` to compute the hint key. +#[macro_export] +macro_rules! define_verify_openvm_stark { + ($fn_name: ident, $asm_folder: expr, $asm_filename: literal) => { + pub fn $fn_name(app_exe_commit: &[u32; 8], app_vm_commit: &[u32; 8], user_pvs: &[u8]) { + // The memory location for the start of the heap. + const HEAP_START_ADDRESS: u32 = 1 << 24; + const FIELDS_PER_U32: u32 = 4; + const FILENAME: &str = $asm_filename; + // Construct the hint key + let hint_key: alloc::vec::Vec = FILENAME + .as_bytes() + .iter() + .cloned() + .chain(app_exe_commit.iter().flat_map(|x| x.to_le_bytes())) + .chain(app_vm_commit.iter().flat_map(|x| x.to_le_bytes())) + .chain(user_pvs.iter().cloned()) + .collect(); + openvm::io::hint_load_by_key(&hint_key); + // Store the expected public values into the beginning of the native heap. + let mut native_addr = HEAP_START_ADDRESS; + for &x in app_exe_commit { + openvm::io::store_u32_to_native(native_addr, x); + native_addr += FIELDS_PER_U32; + } + for &x in app_vm_commit { + openvm::io::store_u32_to_native(native_addr, x); + native_addr += FIELDS_PER_U32; + } + for &x in user_pvs { + openvm::io::store_u32_to_native(native_addr, x as u32); + native_addr += FIELDS_PER_U32; + } + // Assumption: the asm file should be generated by SDK. The code block should: + // 1. Increase the heap pointer in order to avoid overwriting expected public values. + // 2. Hint a stark proof from the input stream + // 3. Verify the proof + // 4. Compare the public values with the expected ones. Panic if not equal. + unsafe { core::arch::asm!(include_str!(concat!($asm_folder, "/", $asm_filename)),) } + } + }; +} diff --git a/guest-libs/verify_stark/tests/integration_test.rs b/guest-libs/verify_stark/tests/integration_test.rs new file mode 100644 index 0000000000..a93509866c --- /dev/null +++ b/guest-libs/verify_stark/tests/integration_test.rs @@ -0,0 +1,124 @@ +#[cfg(test)] +mod tests { + use std::{path::PathBuf, sync::Arc}; + + use eyre::Result; + use openvm_circuit::arch::{SystemConfig, DEFAULT_MAX_NUM_PUBLIC_VALUES}; + use openvm_native_compiler::conversion::CompilerOptions; + use openvm_sdk::{ + commit::AppExecutionCommit, + config::{AggStarkConfig, AppConfig, SdkSystemConfig, SdkVmConfig}, + keygen::AggStarkProvingKey, + Sdk, StdIn, + }; + use openvm_stark_sdk::config::FriParameters; + use openvm_verify_stark::host::{ + compute_hint_key_for_verify_openvm_stark, encode_proof_to_kv_store_value, + }; + + const LEAF_LOG_BLOWUP: usize = 2; + const INTERNAL_LOG_BLOWUP: usize = 3; + const ROOT_LOG_BLOWUP: usize = 4; + + #[test] + fn test_verify_openvm_stark_e2e() -> Result<()> { + const ASM_FILENAME: &str = "root_verifier.asm"; + let sdk = Sdk::new(); + let mut pkg_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); + pkg_dir.pop(); + pkg_dir.pop(); + pkg_dir.pop(); + pkg_dir.push("crates/sdk/guest/fib"); + + let vm_config = SdkVmConfig::builder() + .system(SdkSystemConfig { + config: SystemConfig::default().with_continuations(), + }) + .rv32i(Default::default()) + .rv32m(Default::default()) + .io(Default::default()) + .native(Default::default()) + .build(); + assert!(vm_config.system.config.continuation_enabled); + let elf = sdk.build( + Default::default(), + &vm_config, + pkg_dir, + &Default::default(), + None, + )?; + + let app_exe = sdk.transpile(elf, vm_config.transpiler())?; + let fri_params = FriParameters::new_for_testing(LEAF_LOG_BLOWUP); + let app_config = + AppConfig::new_with_leaf_fri_params(fri_params, vm_config.clone(), fri_params); + + let app_pk = sdk.app_keygen(app_config.clone())?; + let committed_app_exe = sdk.commit_app_exe(fri_params, app_exe.clone())?; + + let commits = + AppExecutionCommit::compute(&vm_config, &committed_app_exe, &app_pk.leaf_committed_exe); + let exe_commit = commits.app_exe_commit.to_u32_digest(); + let vm_commit = commits.app_vm_commit.to_u32_digest(); + + let agg_pk = AggStarkProvingKey::keygen(AggStarkConfig { + max_num_user_public_values: DEFAULT_MAX_NUM_PUBLIC_VALUES, + leaf_fri_params: FriParameters::new_for_testing(LEAF_LOG_BLOWUP), + internal_fri_params: FriParameters::new_for_testing(INTERNAL_LOG_BLOWUP), + root_fri_params: FriParameters::new_for_testing(ROOT_LOG_BLOWUP), + profiling: false, + compiler_options: CompilerOptions { + enable_cycle_tracker: true, + ..Default::default() + }, + root_max_constraint_degree: (1 << ROOT_LOG_BLOWUP) + 1, + }); + let asm = sdk.generate_root_verifier_asm(&agg_pk); + let asm_path = format!( + "{}/examples/verify_openvm_stark/{}", + env!("CARGO_MANIFEST_DIR"), + ASM_FILENAME + ); + std::fs::write(asm_path, asm)?; + + let e2e_stark_proof = sdk.generate_e2e_stark_proof( + Arc::new(app_pk), + committed_app_exe, + agg_pk, + StdIn::default(), + )?; + + let verify_exe = { + let mut pkg_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); + pkg_dir.push("examples/verify_openvm_stark"); + let elf = sdk.build( + Default::default(), + &vm_config, + pkg_dir, + &Default::default(), + None, + )?; + sdk.transpile(elf, vm_config.transpiler())? + }; + + // app_exe publishes 7th and 8th fibonacci numbers. + let pvs: Vec = [13u32, 21, 0, 0, 0, 0, 0, 0] + .iter() + .flat_map(|x| x.to_le_bytes()) + .collect(); + + let mut stdin = StdIn::default(); + let key = + compute_hint_key_for_verify_openvm_stark(ASM_FILENAME, &exe_commit, &vm_commit, &pvs); + let value = encode_proof_to_kv_store_value(&e2e_stark_proof.proof); + stdin.add_key_value(key, value); + + stdin.write(&exe_commit); + stdin.write(&vm_commit); + stdin.write(&pvs); + + sdk.execute(verify_exe, vm_config, stdin)?; + + Ok(()) + } +} diff --git a/rustfmt.toml b/rustfmt.toml index 7d773d6025..9ca68b5778 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -7,3 +7,5 @@ wrap_comments = true format_code_in_doc_comments = true doc_comment_code_block_width = 100 use_field_init_shorthand = true + +ignore = ["guest-libs/ruint"]