diff --git a/.github/workflows/guest-lib-tests.yml b/.github/workflows/guest-lib-tests.yml new file mode 100644 index 0000000000..f436752201 --- /dev/null +++ b/.github/workflows/guest-lib-tests.yml @@ -0,0 +1,72 @@ +name: Guest Library Tests + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + paths: + - "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: | + - "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 + if: hashFiles(format('guest-libs/{0}/', matrix.crate.path)) != '' + 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 e26cecf7bf..091fe94a34 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 @@ -60,13 +60,13 @@ jobs: 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 + elif [[ "$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/Cargo.lock b/Cargo.lock deleted file mode 100644 index d6616b7788..0000000000 --- a/Cargo.lock +++ /dev/null @@ -1,8183 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "addchain" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2e69442aa5628ea6951fa33e24efe8313f4321a91bd729fc2f75bdfc858570" -dependencies = [ - "num-bigint 0.3.3", - "num-integer", - "num-traits", -] - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "alloy-eip2930" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" -dependencies = [ - "alloy-primitives 0.8.25", - "alloy-rlp", - "serde", -] - -[[package]] -name = "alloy-eip7702" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" -dependencies = [ - "alloy-primitives 0.8.25", - "alloy-rlp", - "derive_more 1.0.0", - "k256", - "serde", -] - -[[package]] -name = "alloy-json-abi" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6beff64ad0aa6ad1019a3db26fef565aefeb011736150ab73ed3366c3cfd1b" -dependencies = [ - "alloy-primitives 0.8.25", - "alloy-sol-type-parser", - "serde", - "serde_json", -] - -[[package]] -name = "alloy-primitives" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" -dependencies = [ - "bytes", - "cfg-if", - "const-hex", - "derive_more 0.99.19", - "hex-literal", - "itoa", - "ruint", - "tiny-keccak", -] - -[[package]] -name = "alloy-primitives" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0628ec0ba5b98b3370bb6be17b12f23bfce8ee4ad83823325a20546d9b03b78" -dependencies = [ - "alloy-rlp", - "bytes", - "cfg-if", - "const-hex", - "derive_more 0.99.19", - "hex-literal", - "itoa", - "ruint", - "tiny-keccak", -] - -[[package]] -name = "alloy-primitives" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c77490fe91a0ce933a1f219029521f20fc28c2c0ca95d53fa4da9c00b8d9d4e" -dependencies = [ - "alloy-rlp", - "bytes", - "cfg-if", - "const-hex", - "derive_more 2.0.1", - "foldhash", - "hashbrown 0.15.2", - "indexmap 2.7.1", - "itoa", - "k256", - "keccak-asm", - "paste", - "proptest", - "rand", - "ruint", - "rustc-hash 2.1.1", - "serde", - "sha3", - "tiny-keccak", -] - -[[package]] -name = "alloy-rlp" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" -dependencies = [ - "alloy-rlp-derive", - "arrayvec", - "bytes", -] - -[[package]] -name = "alloy-rlp-derive" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "alloy-sol-macro" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2" -dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "alloy-sol-macro-expander" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26" -dependencies = [ - "alloy-json-abi", - "alloy-sol-macro-input", - "const-hex", - "heck", - "indexmap 2.7.1", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.98", - "syn-solidity", - "tiny-keccak", -] - -[[package]] -name = "alloy-sol-macro-input" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e" -dependencies = [ - "alloy-json-abi", - "const-hex", - "dunce", - "heck", - "macro-string", - "proc-macro2", - "quote", - "serde_json", - "syn 2.0.98", - "syn-solidity", -] - -[[package]] -name = "alloy-sol-type-parser" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d162f8524adfdfb0e4bd0505c734c985f3e2474eb022af32eef0d52a4f3935c" -dependencies = [ - "serde", - "winnow 0.7.3", -] - -[[package]] -name = "alloy-sol-types" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43d5e60466a440230c07761aa67671d4719d46f43be8ea6e7ed334d8db4a9ab" -dependencies = [ - "alloy-json-abi", - "alloy-primitives 0.8.25", - "alloy-sol-macro", - "const-hex", - "serde", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" -dependencies = [ - "anstyle", - "once_cell", - "windows-sys 0.59.0", -] - -[[package]] -name = "anyhow" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" - -[[package]] -name = "ariadne" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367fd0ad87307588d087544707bc5fbf4805ded96c7db922b70d368fa1cb5702" -dependencies = [ - "unicode-width", - "yansi 0.5.1", -] - -[[package]] -name = "ark-ff" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" -dependencies = [ - "ark-ff-asm 0.3.0", - "ark-ff-macros 0.3.0", - "ark-serialize 0.3.0", - "ark-std 0.3.0", - "derivative", - "num-bigint 0.4.6", - "num-traits", - "paste", - "rustc_version 0.3.3", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" -dependencies = [ - "ark-ff-asm 0.4.2", - "ark-ff-macros 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "derivative", - "digest 0.10.7", - "itertools 0.10.5", - "num-bigint 0.4.6", - "num-traits", - "paste", - "rustc_version 0.4.1", - "zeroize", -] - -[[package]] -name = "ark-ff-asm" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-asm" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" -dependencies = [ - "num-bigint 0.4.6", - "num-traits", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" -dependencies = [ - "num-bigint 0.4.6", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-serialize" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" -dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", -] - -[[package]] -name = "ark-serialize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" -dependencies = [ - "ark-std 0.4.0", - "digest 0.10.7", - "num-bigint 0.4.6", -] - -[[package]] -name = "ark-std" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" -dependencies = [ - "num-traits", - "rand", -] - -[[package]] -name = "ark-std" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" -dependencies = [ - "num-traits", - "rand", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "async-trait" -version = "0.1.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "atomic" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "aurora-engine-modexp" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" -dependencies = [ - "hex", - "num", -] - -[[package]] -name = "auto_impl" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "aws-config" -version = "1.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90aff65e86db5fe300752551c1b015ef72b708ac54bded8ef43d0d53cb7cb0b1" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-sdk-sso", - "aws-sdk-ssooidc", - "aws-sdk-sts", - "aws-smithy-async", - "aws-smithy-http 0.61.1", - "aws-smithy-json", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "hex", - "http 0.2.12", - "ring", - "time", - "tokio", - "tracing", - "url", - "zeroize", -] - -[[package]] -name = "aws-credential-types" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" -dependencies = [ - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "zeroize", -] - -[[package]] -name = "aws-runtime" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76dd04d39cc12844c0994f2c9c5a6f5184c22e9188ec1ff723de41910a21dcad" -dependencies = [ - "aws-credential-types", - "aws-sigv4", - "aws-smithy-async", - "aws-smithy-eventstream", - "aws-smithy-http 0.60.12", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http 0.2.12", - "http-body 0.4.6", - "once_cell", - "percent-encoding", - "pin-project-lite", - "tracing", - "uuid", -] - -[[package]] -name = "aws-sdk-s3" -version = "1.78.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038614b6cf7dd68d9a7b5b39563d04337eb3678d1d4173e356e927b0356158a" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-sigv4", - "aws-smithy-async", - "aws-smithy-checksums", - "aws-smithy-eventstream", - "aws-smithy-http 0.61.1", - "aws-smithy-json", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-smithy-xml", - "aws-types", - "bytes", - "fastrand", - "hex", - "hmac", - "http 0.2.12", - "http-body 0.4.6", - "lru", - "once_cell", - "percent-encoding", - "regex-lite", - "sha2", - "tracing", - "url", -] - -[[package]] -name = "aws-sdk-sso" -version = "1.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e65ff295979977039a25f5a0bf067a64bc5e6aa38f3cef4037cf42516265553c" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http 0.61.1", - "aws-smithy-json", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "http 0.2.12", - "once_cell", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-ssooidc" -version = "1.62.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91430a60f754f235688387b75ee798ef00cfd09709a582be2b7525ebb5306d4f" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http 0.61.1", - "aws-smithy-json", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "http 0.2.12", - "once_cell", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-sts" -version = "1.62.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9276e139d39fff5a0b0c984fc2d30f970f9a202da67234f948fda02e5bea1dbe" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http 0.61.1", - "aws-smithy-json", - "aws-smithy-query", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-smithy-xml", - "aws-types", - "http 0.2.12", - "once_cell", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sigv4" -version = "1.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bfe75fad52793ce6dec0dc3d4b1f388f038b5eb866c8d4d7f3a8e21b5ea5051" -dependencies = [ - "aws-credential-types", - "aws-smithy-eventstream", - "aws-smithy-http 0.60.12", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "crypto-bigint 0.5.5", - "form_urlencoded", - "hex", - "hmac", - "http 0.2.12", - "http 1.2.0", - "once_cell", - "p256 0.11.1", - "percent-encoding", - "ring", - "sha2", - "subtle", - "time", - "tracing", - "zeroize", -] - -[[package]] -name = "aws-smithy-async" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa59d1327d8b5053c54bf2eaae63bf629ba9e904434d0835a28ed3c0ed0a614e" -dependencies = [ - "futures-util", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "aws-smithy-checksums" -version = "0.63.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2dc8d842d872529355c72632de49ef8c5a2949a4472f10e802f28cf925770c" -dependencies = [ - "aws-smithy-http 0.60.12", - "aws-smithy-types", - "bytes", - "crc32c", - "crc32fast", - "crc64fast-nvme", - "hex", - "http 0.2.12", - "http-body 0.4.6", - "md-5", - "pin-project-lite", - "sha1", - "sha2", - "tracing", -] - -[[package]] -name = "aws-smithy-eventstream" -version = "0.60.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "461e5e02f9864cba17cff30f007c2e37ade94d01e87cdb5204e44a84e6d38c17" -dependencies = [ - "aws-smithy-types", - "bytes", - "crc32fast", -] - -[[package]] -name = "aws-smithy-http" -version = "0.60.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7809c27ad8da6a6a68c454e651d4962479e81472aa19ae99e59f9aba1f9713cc" -dependencies = [ - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http-body 0.4.6", - "once_cell", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tracing", -] - -[[package]] -name = "aws-smithy-http" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f276f21c7921fe902826618d1423ae5bf74cf8c1b8472aee8434f3dfd31824" -dependencies = [ - "aws-smithy-eventstream", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http-body 0.4.6", - "once_cell", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tracing", -] - -[[package]] -name = "aws-smithy-json" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623a51127f24c30776c8b374295f2df78d92517386f77ba30773f15a30ce1422" -dependencies = [ - "aws-smithy-types", -] - -[[package]] -name = "aws-smithy-query" -version = "0.60.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" -dependencies = [ - "aws-smithy-types", - "urlencoding", -] - -[[package]] -name = "aws-smithy-runtime" -version = "1.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d526a12d9ed61fadefda24abe2e682892ba288c2018bcb38b1b4c111d13f6d92" -dependencies = [ - "aws-smithy-async", - "aws-smithy-http 0.60.12", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "fastrand", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "http-body 1.0.1", - "httparse", - "hyper", - "hyper-rustls", - "once_cell", - "pin-project-lite", - "pin-utils", - "rustls", - "tokio", - "tracing", -] - -[[package]] -name = "aws-smithy-runtime-api" -version = "1.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" -dependencies = [ - "aws-smithy-async", - "aws-smithy-types", - "bytes", - "http 0.2.12", - "http 1.2.0", - "pin-project-lite", - "tokio", - "tracing", - "zeroize", -] - -[[package]] -name = "aws-smithy-types" -version = "1.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b8a53819e42f10d0821f56da995e1470b199686a1809168db6ca485665f042" -dependencies = [ - "base64-simd", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http 1.2.0", - "http-body 0.4.6", - "http-body 1.0.1", - "http-body-util", - "itoa", - "num-integer", - "pin-project-lite", - "pin-utils", - "ryu", - "serde", - "time", - "tokio", - "tokio-util", -] - -[[package]] -name = "aws-smithy-xml" -version = "0.60.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" -dependencies = [ - "xmlparser", -] - -[[package]] -name = "aws-types" -version = "1.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbd0a668309ec1f66c0f6bda4840dd6d4796ae26d699ebc266d7cc95c6d040f" -dependencies = [ - "aws-credential-types", - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "rustc_version 0.4.1", - "tracing", -] - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "serde", - "windows-targets 0.52.6", -] - -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64-simd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" -dependencies = [ - "outref", - "vsimd", -] - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitcode" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c1406a27371b2f76232a2259df6ab607b91b5a0a7476a7729ff590df5a969a" -dependencies = [ - "arrayvec", - "bitcode_derive", - "bytemuck", - "glam", - "serde", -] - -[[package]] -name = "bitcode_derive" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b6b4cb608b8282dc3b53d0f4c9ab404655d562674c682db7e6c0458cc83c23" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "blake2b_simd" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq 0.3.1", -] - -[[package]] -name = "blake3" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq 0.3.1", - "memmap2", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bls12_381" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" -dependencies = [ - "ff 0.12.1", - "group 0.12.1", - "pairing 0.22.0", - "rand_core", - "subtle", -] - -[[package]] -name = "blst" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" -dependencies = [ - "cc", - "glob", - "threadpool", - "zeroize", -] - -[[package]] -name = "bon" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7acc34ff59877422326db7d6f2d845a582b16396b6b08194942bf34c6528ab" -dependencies = [ - "bon-macros", - "rustversion", -] - -[[package]] -name = "bon-macros" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4159dd617a7fbc9be6a692fe69dc2954f8e6bb6bb5e4d7578467441390d77fd0" -dependencies = [ - "darling", - "ident_case", - "prettyplease", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.98", -] - -[[package]] -name = "bstr" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "build_const" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "bytemuck" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" -dependencies = [ - "serde", -] - -[[package]] -name = "bytes-utils" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" -dependencies = [ - "bytes", - "either", -] - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "c-kzg" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" -dependencies = [ - "blst", - "cc", - "glob", - "hex", - "libc", - "once_cell", - "serde", -] - -[[package]] -name = "camino" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-openvm" -version = "1.1.2" -dependencies = [ - "aws-config", - "aws-sdk-s3", - "bitcode", - "clap", - "eyre", - "hex", - "openvm-build", - "openvm-circuit", - "openvm-native-recursion", - "openvm-sdk", - "openvm-stark-backend", - "openvm-stark-sdk", - "openvm-transpiler", - "serde", - "serde_json", - "target-lexicon", - "tempfile", - "tokio", - "toml 0.8.20", - "tracing", - "vergen", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.25", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" -dependencies = [ - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-targets 0.52.6", -] - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clap" -version = "4.5.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "const-default" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" - -[[package]] -name = "const-hex" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" -dependencies = [ - "cfg-if", - "cpufeatures", - "hex", - "proptest", - "serde", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const_format" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32c" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" -dependencies = [ - "rustc_version 0.4.1", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crc64fast-nvme" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3" -dependencies = [ - "crc", -] - -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" - -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.98", -] - -[[package]] -name = "darling_macro" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "der" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive-new" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "derive-new" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "derive_more" -version = "0.99.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.1", - "syn 2.0.98", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", -] - -[[package]] -name = "derive_more" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" -dependencies = [ - "derive_more-impl 2.0.1", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "unicode-xid", -] - -[[package]] -name = "derive_more-impl" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "unicode-xid", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[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.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" - -[[package]] -name = "ecdsa" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" -dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der 0.7.9", - "digest 0.10.7", - "elliptic-curve 0.13.8", - "rfc6979 0.4.0", - "signature 2.2.0", - "spki 0.7.3", -] - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "elf" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" - -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.7", - "ff 0.12.1", - "generic-array", - "group 0.12.1", - "pkcs8 0.9.0", - "rand_core", - "sec1 0.3.0", - "subtle", - "zeroize", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.5", - "digest 0.10.7", - "ff 0.13.1", - "generic-array", - "group 0.13.0", - "pkcs8 0.10.2", - "rand_core", - "sec1 0.7.3", - "subtle", - "zeroize", -] - -[[package]] -name = "embedded-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" -dependencies = [ - "const-default", - "critical-section", - "linked_list_allocator", - "rlsf", -] - -[[package]] -name = "ena" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" -dependencies = [ - "log", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "enum_dispatch" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "enumn" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3", - "thiserror 1.0.69", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", -] - -[[package]] -name = "ethers-core" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" -dependencies = [ - "arrayvec", - "bytes", - "chrono", - "const-hex", - "elliptic-curve 0.13.8", - "ethabi", - "generic-array", - "k256", - "num_enum", - "open-fastrlp", - "rand", - "rlp", - "serde", - "serde_json", - "strum", - "tempfile", - "thiserror 1.0.69", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "ethers-etherscan" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" -dependencies = [ - "chrono", - "ethers-core", - "reqwest", - "semver 1.0.25", - "serde", - "serde_json", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "ethers-solc" -version = "2.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de34e484e7ae3cab99fbfd013d6c5dc7f9013676a4e0e414d8b12e1213e8b3ba" -dependencies = [ - "cfg-if", - "const-hex", - "dirs", - "dunce", - "ethers-core", - "futures-util", - "glob", - "home", - "md-5", - "num_cpus", - "once_cell", - "path-slash", - "rayon", - "regex", - "semver 1.0.25", - "serde", - "serde_json", - "sha2", - "solang-parser", - "svm-rs", - "svm-rs-builds", - "thiserror 1.0.69", - "tiny-keccak", - "tokio", - "tracing", - "walkdir", - "yansi 0.5.1", -] - -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fastrlp" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] - -[[package]] -name = "fastrlp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] - -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "bitvec", - "rand_core", - "subtle", -] - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "bitvec", - "byteorder", - "ff_derive", - "rand_core", - "subtle", -] - -[[package]] -name = "ff_derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" -dependencies = [ - "addchain", - "num-bigint 0.3.3", - "num-integer", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "figment" -version = "0.10.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" -dependencies = [ - "atomic", - "pear", - "serde", - "toml 0.8.20", - "uncased", - "version_check", -] - -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" - -[[package]] -name = "forge-fmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac0b82597ff80bc12b3b001dfb968769c345e353650dd32d337f2c64d2167c88" -dependencies = [ - "alloy-primitives 0.3.3", - "ariadne", - "foundry-config", - "itertools 0.11.0", - "solang-parser", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "foundry-config" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a64a9bdad47eb4d950523b8ff14e675db8f2226a2aef79063d9344449b3abd5" -dependencies = [ - "Inflector", - "dirs-next", - "ethers-core", - "ethers-etherscan", - "ethers-solc", - "eyre", - "figment", - "globset", - "number_prefix", - "once_cell", - "open-fastrlp", - "path-slash", - "regex", - "reqwest", - "revm-primitives 1.3.0", - "semver 1.0.25", - "serde", - "serde_json", - "serde_regex", - "thiserror 1.0.69", - "toml 0.7.8", - "toml_edit 0.19.15", - "tracing", - "walkdir", -] - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "gcd" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "getset" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded738faa0e88d3abc9d1a13cb11adc2073c400969eeb8793cf7132589959fc" -dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "git2" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" -dependencies = [ - "bitflags 2.8.0", - "libc", - "libgit2-sys", - "log", - "url", -] - -[[package]] -name = "glam" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17fcdf9683c406c2fc4d124afd29c0d595e22210d633cbdb8695ba9935ab1dc6" - -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "globset" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "memuse", - "rand_core", - "subtle", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff 0.13.1", - "rand_core", - "subtle", -] - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.7.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] - -[[package]] -name = "halo2" -version = "0.1.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a23c779b38253fe1538102da44ad5bd5378495a61d2c4ee18d64eaa61ae5995" -dependencies = [ - "halo2_proofs", -] - -[[package]] -name = "halo2-axiom" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f0ca78d12ac5c893f286d7cdfe3869290305ab8cac376e2592cdc8396da102" -dependencies = [ - "blake2b_simd", - "crossbeam", - "ff 0.13.1", - "group 0.13.0", - "halo2curves-axiom", - "itertools 0.11.0", - "maybe-rayon", - "pairing 0.23.0", - "rand", - "rand_core", - "rayon", - "rustc-hash 1.1.0", - "sha3", - "tracing", -] - -[[package]] -name = "halo2-base" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678cf3adc0a39d7b4d9b82315a655201aa24a430dd1902b162c508047f56ac69" -dependencies = [ - "getset", - "halo2-axiom", - "itertools 0.11.0", - "log", - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "poseidon-primitives", - "rand_chacha", - "rayon", - "rustc-hash 1.1.0", - "serde", - "serde_json", -] - -[[package]] -name = "halo2-ecc" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c00681fdd1febaf552d8814e9f5a6a142d81a1514102190da07039588b366" -dependencies = [ - "halo2-base", - "itertools 0.11.0", - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "rand", - "rand_chacha", - "rand_core", - "rayon", - "serde", - "serde_json", - "test-case", -] - -[[package]] -name = "halo2_proofs" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e925780549adee8364c7f2b685c753f6f3df23bde520c67416e93bf615933760" -dependencies = [ - "blake2b_simd", - "ff 0.12.1", - "group 0.12.1", - "pasta_curves 0.4.1", - "rand_core", - "rayon", -] - -[[package]] -name = "halo2curves" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b756596082144af6e57105a20403b7b80fe9dccd085700b74fae3af523b74dba" -dependencies = [ - "blake2", - "digest 0.10.7", - "ff 0.13.1", - "group 0.13.0", - "halo2derive", - "hex", - "lazy_static", - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "pairing 0.23.0", - "paste", - "rand", - "rand_core", - "rayon", - "serde", - "serde_arrays", - "sha2", - "static_assertions", - "subtle", - "unroll", -] - -[[package]] -name = "halo2curves-axiom" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8309e4638b4f1bcf6613d72265a84074d26034c35edc5d605b5688e580b8b8" -dependencies = [ - "blake2b_simd", - "digest 0.10.7", - "ff 0.13.1", - "group 0.13.0", - "hex", - "lazy_static", - "num-bigint 0.4.6", - "num-traits", - "pairing 0.23.0", - "pasta_curves 0.5.1", - "paste", - "rand", - "rand_core", - "rayon", - "serde", - "serde_arrays", - "sha2", - "static_assertions", - "subtle", - "unroll", -] - -[[package]] -name = "halo2derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb99e7492b4f5ff469d238db464131b86c2eaac814a78715acba369f64d2c76" -dependencies = [ - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", - "serde", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.2.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper", - "log", - "rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown 0.15.2", - "serde", -] - -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "is-terminal" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "jubjub" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" -dependencies = [ - "bitvec", - "bls12_381", - "ff 0.12.1", - "group 0.12.1", - "rand_core", - "subtle", -] - -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "once_cell", - "sha2", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "keccak-asm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" -dependencies = [ - "digest 0.10.7", - "sha3-asm", -] - -[[package]] -name = "lalrpop" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.11.0", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax 0.8.5", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" -dependencies = [ - "regex-automata 0.4.9", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "libgit2-sys" -version = "0.17.0+1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - -[[package]] -name = "libm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" - -[[package]] -name = "libmimalloc-sys" -version = "0.1.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23aa6811d3bd4deb8a84dde645f943476d13b248d818edcf8ce0b2f37f036b44" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.8.0", - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linked_list_allocator" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - -[[package]] -name = "log" -version = "0.4.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" - -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.2", -] - -[[package]] -name = "macro-string" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest 0.10.7", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - -[[package]] -name = "memuse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" - -[[package]] -name = "metrics" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884adb57038347dfbaf2d5065887b6cf4312330dc8e94bc30a1a839bd79d3261" -dependencies = [ - "ahash", - "portable-atomic", -] - -[[package]] -name = "metrics-tracing-context" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62a6a1f7141f1d9bc7a886b87536bbfc97752e08b369e1e0453a9acfab5f5da4" -dependencies = [ - "indexmap 2.7.1", - "itoa", - "lockfree-object-pool", - "metrics", - "metrics-util", - "once_cell", - "tracing", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "metrics-util" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" -dependencies = [ - "aho-corasick", - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.14.5", - "indexmap 2.7.1", - "metrics", - "num_cpus", - "ordered-float", - "quanta", - "radix_trie", - "sketches-ddsketch", -] - -[[package]] -name = "mimalloc" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68914350ae34959d83f732418d51e2427a794055d0b9529f48259ac07af65633" -dependencies = [ - "libmimalloc-sys", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint 0.4.6", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", - "rand", - "serde", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-format" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" -dependencies = [ - "arrayvec", - "itoa", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "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", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint 0.4.6", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - -[[package]] -name = "nums" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3c74f925fb8cfc49a8022f2afce48a0683b70f9e439885594e84c5edbf5b01" -dependencies = [ - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "rand", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" - -[[package]] -name = "oorandom" -version = "11.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" - -[[package]] -name = "open-fastrlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", -] - -[[package]] -name = "open-fastrlp-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" -dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openvm" -version = "1.1.2" -dependencies = [ - "bytemuck", - "chrono", - "getrandom 0.3.1", - "num-bigint 0.4.6", - "openvm-custom-insn", - "openvm-platform", - "openvm-rv32im-guest", - "serde", -] - -[[package]] -name = "openvm-algebra-circuit" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "derive_more 1.0.0", - "eyre", - "halo2curves-axiom", - "itertools 0.14.0", - "num-bigint 0.4.6", - "num-traits", - "openvm-algebra-transpiler", - "openvm-circuit", - "openvm-circuit-derive", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-instructions", - "openvm-mod-circuit-builder", - "openvm-pairing-guest", - "openvm-rv32-adapters", - "openvm-rv32im-circuit", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", - "serde-big-array", - "serde_with", - "strum", -] - -[[package]] -name = "openvm-algebra-complex-macros" -version = "0.1.0" -dependencies = [ - "openvm-macros-common", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "openvm-algebra-guest" -version = "1.1.2" -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.1.2" -dependencies = [ - "num-bigint 0.4.6", - "num-prime", - "openvm-macros-common", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "openvm-algebra-tests" -version = "1.1.2" -dependencies = [ - "eyre", - "num-bigint 0.4.6", - "openvm-algebra-circuit", - "openvm-algebra-transpiler", - "openvm-circuit", - "openvm-ecc-circuit", - "openvm-instructions", - "openvm-rv32im-transpiler", - "openvm-stark-sdk", - "openvm-toolchain-tests", - "openvm-transpiler", -] - -[[package]] -name = "openvm-algebra-transpiler" -version = "1.1.2" -dependencies = [ - "openvm-algebra-guest", - "openvm-instructions", - "openvm-instructions-derive", - "openvm-stark-backend", - "openvm-transpiler", - "rrs-lib", - "strum", -] - -[[package]] -name = "openvm-benchmarks-execute" -version = "1.1.2" -dependencies = [ - "cargo-openvm", - "clap", - "criterion", - "derive_more 1.0.0", - "eyre", - "openvm-benchmarks-utils", - "openvm-circuit", - "openvm-keccak256-circuit", - "openvm-keccak256-transpiler", - "openvm-rv32im-circuit", - "openvm-rv32im-transpiler", - "openvm-sdk", - "openvm-stark-sdk", - "openvm-transpiler", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "openvm-benchmarks-prove" -version = "1.1.2" -dependencies = [ - "clap", - "derive-new 0.6.0", - "derive_more 1.0.0", - "eyre", - "k256", - "num-bigint 0.4.6", - "openvm-algebra-circuit", - "openvm-algebra-transpiler", - "openvm-benchmarks-utils", - "openvm-circuit", - "openvm-ecc-circuit", - "openvm-ecc-transpiler", - "openvm-keccak256-circuit", - "openvm-keccak256-transpiler", - "openvm-native-circuit", - "openvm-native-compiler", - "openvm-native-recursion", - "openvm-pairing-circuit", - "openvm-pairing-guest", - "openvm-rv32im-circuit", - "openvm-rv32im-transpiler", - "openvm-sdk", - "openvm-stark-backend", - "openvm-stark-sdk", - "openvm-transpiler", - "rand_chacha", - "serde", - "tiny-keccak", - "tokio", - "tracing", -] - -[[package]] -name = "openvm-benchmarks-utils" -version = "1.1.2" -dependencies = [ - "cargo_metadata", - "clap", - "eyre", - "openvm-build", - "openvm-transpiler", - "tempfile", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "openvm-bigint-circuit" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "derive_more 1.0.0", - "openvm-bigint-transpiler", - "openvm-circuit", - "openvm-circuit-derive", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-instructions", - "openvm-rv32-adapters", - "openvm-rv32im-circuit", - "openvm-rv32im-transpiler", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", -] - -[[package]] -name = "openvm-bigint-guest" -version = "1.1.2" -dependencies = [ - "num-bigint 0.4.6", - "num-traits", - "openvm", - "openvm-platform", - "serde", - "serde-big-array", - "strum_macros", -] - -[[package]] -name = "openvm-bigint-transpiler" -version = "1.1.2" -dependencies = [ - "openvm-bigint-guest", - "openvm-instructions", - "openvm-instructions-derive", - "openvm-rv32im-transpiler", - "openvm-stark-backend", - "openvm-transpiler", - "rrs-lib", - "strum", -] - -[[package]] -name = "openvm-build" -version = "1.1.2" -dependencies = [ - "cargo_metadata", - "eyre", - "openvm-platform", - "serde", - "serde_json", -] - -[[package]] -name = "openvm-circuit" -version = "1.1.2" -dependencies = [ - "backtrace", - "cfg-if", - "derivative", - "derive-new 0.6.0", - "derive_more 1.0.0", - "enum_dispatch", - "eyre", - "getset", - "itertools 0.14.0", - "metrics", - "openvm-circuit", - "openvm-circuit-derive", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-instructions", - "openvm-native-circuit", - "openvm-native-compiler", - "openvm-poseidon2-air", - "openvm-rv32im-transpiler", - "openvm-stark-backend", - "openvm-stark-sdk", - "p3-baby-bear", - "rand", - "rustc-hash 2.1.1", - "serde", - "serde-big-array", - "static_assertions", - "test-log", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "openvm-circuit-derive" -version = "1.1.2" -dependencies = [ - "itertools 0.14.0", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "openvm-circuit-primitives" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "itertools 0.14.0", - "num-bigint 0.4.6", - "num-traits", - "openvm-circuit-primitives-derive", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "test-case", - "tracing", -] - -[[package]] -name = "openvm-circuit-primitives-derive" -version = "1.1.2" -dependencies = [ - "itertools 0.14.0", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "openvm-continuations" -version = "1.1.2" -dependencies = [ - "derivative", - "openvm-circuit", - "openvm-native-compiler", - "openvm-native-recursion", - "openvm-stark-backend", - "openvm-stark-sdk", - "serde", - "static_assertions", -] - -[[package]] -name = "openvm-custom-insn" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "openvm-ecc-circuit" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "derive_more 1.0.0", - "eyre", - "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", - "openvm-rv32-adapters", - "openvm-rv32im-circuit", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", - "serde_with", - "strum", -] - -[[package]] -name = "openvm-ecc-guest" -version = "1.1.2" -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.1.2" -dependencies = [ - "eyre", - "halo2curves-axiom", - "hex-literal", - "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", - "openvm-sdk", - "openvm-stark-sdk", - "openvm-toolchain-tests", - "openvm-transpiler", -] - -[[package]] -name = "openvm-ecc-sw-macros" -version = "1.1.2" -dependencies = [ - "openvm-macros-common", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "openvm-ecc-transpiler" -version = "1.1.2" -dependencies = [ - "openvm-ecc-guest", - "openvm-instructions", - "openvm-instructions-derive", - "openvm-stark-backend", - "openvm-transpiler", - "rrs-lib", - "strum", -] - -[[package]] -name = "openvm-instructions" -version = "1.1.2" -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", - "serde", - "strum", - "strum_macros", -] - -[[package]] -name = "openvm-instructions-derive" -version = "1.1.2" -dependencies = [ - "openvm-instructions", - "quote", - "strum", - "strum_macros", - "syn 2.0.98", -] - -[[package]] -name = "openvm-keccak256-circuit" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "derive_more 1.0.0", - "hex", - "itertools 0.14.0", - "openvm-circuit", - "openvm-circuit-derive", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-instructions", - "openvm-keccak256-transpiler", - "openvm-rv32im-circuit", - "openvm-stark-backend", - "openvm-stark-sdk", - "p3-keccak-air", - "rand", - "serde", - "serde-big-array", - "strum", - "tiny-keccak", - "tracing", -] - -[[package]] -name = "openvm-keccak256-guest" -version = "1.1.2" -dependencies = [ - "openvm-platform", - "tiny-keccak", -] - -[[package]] -name = "openvm-keccak256-transpiler" -version = "1.1.2" -dependencies = [ - "openvm-instructions", - "openvm-instructions-derive", - "openvm-keccak256-guest", - "openvm-stark-backend", - "openvm-transpiler", - "rrs-lib", - "strum", -] - -[[package]] -name = "openvm-macros-common" -version = "1.1.2" -dependencies = [ - "syn 2.0.98", -] - -[[package]] -name = "openvm-mod-circuit-builder" -version = "1.1.2" -dependencies = [ - "halo2curves-axiom", - "itertools 0.14.0", - "num-bigint 0.4.6", - "num-traits", - "openvm-circuit", - "openvm-circuit-primitives", - "openvm-instructions", - "openvm-pairing-guest", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", - "serde_with", - "tracing", -] - -[[package]] -name = "openvm-native-circuit" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "derive_more 1.0.0", - "eyre", - "itertools 0.14.0", - "openvm-circuit", - "openvm-circuit-derive", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-instructions", - "openvm-native-compiler", - "openvm-poseidon2-air", - "openvm-rv32im-circuit", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", - "serde-big-array", - "static_assertions", - "strum", - "tracing", -] - -[[package]] -name = "openvm-native-compiler" -version = "1.1.2" -dependencies = [ - "backtrace", - "itertools 0.14.0", - "metrics", - "num-bigint 0.4.6", - "num-integer", - "openvm-circuit", - "openvm-instructions", - "openvm-instructions-derive", - "openvm-native-circuit", - "openvm-native-compiler-derive", - "openvm-rv32im-transpiler", - "openvm-stark-backend", - "openvm-stark-sdk", - "p3-symmetric", - "rand", - "serde", - "snark-verifier-sdk", - "strum", - "strum_macros", - "zkhash", -] - -[[package]] -name = "openvm-native-compiler-derive" -version = "1.1.2" -dependencies = [ - "quote", - "syn 2.0.98", -] - -[[package]] -name = "openvm-native-recursion" -version = "1.1.2" -dependencies = [ - "bitcode", - "cfg-if", - "itertools 0.14.0", - "lazy_static", - "metrics", - "once_cell", - "openvm-circuit", - "openvm-native-circuit", - "openvm-native-compiler", - "openvm-native-compiler-derive", - "openvm-native-recursion", - "openvm-stark-backend", - "openvm-stark-sdk", - "p3-dft", - "p3-fri", - "p3-merkle-tree", - "p3-symmetric", - "rand", - "serde", - "serde_json", - "serde_with", - "snark-verifier-sdk", - "tempfile", - "tracing", -] - -[[package]] -name = "openvm-native-transpiler" -version = "1.1.2" -dependencies = [ - "openvm-instructions", - "openvm-transpiler", - "p3-field", -] - -[[package]] -name = "openvm-pairing-circuit" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "derive_more 1.0.0", - "eyre", - "halo2curves-axiom", - "itertools 0.14.0", - "num-bigint 0.4.6", - "num-traits", - "openvm-algebra-circuit", - "openvm-circuit", - "openvm-circuit-derive", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-ecc-circuit", - "openvm-ecc-guest", - "openvm-instructions", - "openvm-mod-circuit-builder", - "openvm-pairing-guest", - "openvm-pairing-transpiler", - "openvm-rv32-adapters", - "openvm-rv32im-circuit", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", - "strum", -] - -[[package]] -name = "openvm-pairing-guest" -version = "1.1.2" -dependencies = [ - "group 0.13.0", - "halo2curves-axiom", - "hex-literal", - "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", - "serde", - "strum_macros", - "subtle", -] - -[[package]] -name = "openvm-pairing-transpiler" -version = "1.1.2" -dependencies = [ - "openvm-instructions", - "openvm-instructions-derive", - "openvm-pairing-guest", - "openvm-stark-backend", - "openvm-transpiler", - "rrs-lib", - "strum", -] - -[[package]] -name = "openvm-platform" -version = "1.1.2" -dependencies = [ - "critical-section", - "embedded-alloc", - "libm", - "openvm-custom-insn", - "openvm-rv32im-guest", -] - -[[package]] -name = "openvm-poseidon2-air" -version = "1.1.2" -dependencies = [ - "derivative", - "lazy_static", - "openvm-stark-backend", - "openvm-stark-sdk", - "p3-monty-31", - "p3-poseidon2", - "p3-poseidon2-air", - "p3-symmetric", - "rand", - "zkhash", -] - -[[package]] -name = "openvm-prof" -version = "1.1.2" -dependencies = [ - "clap", - "eyre", - "itertools 0.14.0", - "memmap2", - "num-format", - "serde", - "serde_json", -] - -[[package]] -name = "openvm-rv32-adapters" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "itertools 0.14.0", - "openvm-circuit", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-instructions", - "openvm-rv32im-circuit", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", - "serde-big-array", - "serde_with", -] - -[[package]] -name = "openvm-rv32im-circuit" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "derive_more 1.0.0", - "eyre", - "num-bigint 0.4.6", - "num-integer", - "openvm-circuit", - "openvm-circuit-derive", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-instructions", - "openvm-rv32im-transpiler", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", - "serde-big-array", - "strum", -] - -[[package]] -name = "openvm-rv32im-guest" -version = "1.1.2" -dependencies = [ - "openvm-custom-insn", - "strum_macros", -] - -[[package]] -name = "openvm-rv32im-integration-tests" -version = "1.1.2" -dependencies = [ - "eyre", - "openvm", - "openvm-circuit", - "openvm-instructions", - "openvm-rv32im-circuit", - "openvm-rv32im-transpiler", - "openvm-stark-sdk", - "openvm-toolchain-tests", - "openvm-transpiler", - "serde", - "test-case", -] - -[[package]] -name = "openvm-rv32im-transpiler" -version = "1.1.2" -dependencies = [ - "openvm-instructions", - "openvm-instructions-derive", - "openvm-rv32im-guest", - "openvm-stark-backend", - "openvm-transpiler", - "rrs-lib", - "serde", - "strum", - "tracing", -] - -[[package]] -name = "openvm-sdk" -version = "1.1.2" -dependencies = [ - "alloy-sol-types", - "async-trait", - "bitcode", - "bon", - "clap", - "derivative", - "derive_more 1.0.0", - "eyre", - "forge-fmt", - "getset", - "hex", - "itertools 0.14.0", - "metrics", - "openvm", - "openvm-algebra-circuit", - "openvm-algebra-transpiler", - "openvm-bigint-circuit", - "openvm-bigint-transpiler", - "openvm-build", - "openvm-circuit", - "openvm-continuations", - "openvm-ecc-circuit", - "openvm-ecc-transpiler", - "openvm-keccak256-circuit", - "openvm-keccak256-transpiler", - "openvm-native-circuit", - "openvm-native-compiler", - "openvm-native-recursion", - "openvm-pairing-circuit", - "openvm-pairing-transpiler", - "openvm-rv32im-circuit", - "openvm-rv32im-transpiler", - "openvm-sha256-circuit", - "openvm-sha256-transpiler", - "openvm-stark-backend", - "openvm-stark-sdk", - "openvm-transpiler", - "p3-fri", - "serde", - "serde_json", - "serde_with", - "snark-verifier", - "snark-verifier-sdk", - "tempfile", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "openvm-sha256-air" -version = "1.1.2" -dependencies = [ - "openvm-circuit", - "openvm-circuit-primitives", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "sha2", -] - -[[package]] -name = "openvm-sha256-circuit" -version = "1.1.2" -dependencies = [ - "derive-new 0.6.0", - "derive_more 1.0.0", - "openvm-circuit", - "openvm-circuit-derive", - "openvm-circuit-primitives", - "openvm-circuit-primitives-derive", - "openvm-instructions", - "openvm-rv32im-circuit", - "openvm-sha256-air", - "openvm-sha256-transpiler", - "openvm-stark-backend", - "openvm-stark-sdk", - "rand", - "serde", - "sha2", - "strum", -] - -[[package]] -name = "openvm-sha256-guest" -version = "1.1.2" -dependencies = [ - "openvm-platform", - "sha2", -] - -[[package]] -name = "openvm-sha256-transpiler" -version = "1.1.2" -dependencies = [ - "openvm-instructions", - "openvm-instructions-derive", - "openvm-sha256-guest", - "openvm-stark-backend", - "openvm-transpiler", - "rrs-lib", - "strum", -] - -[[package]] -name = "openvm-stark-backend" -version = "1.0.0" -source = "git+https://github.com/openvm-org/stark-backend.git?tag=v1.0.1#e540dbdd09ef20db7207ad7f2674bece75a2b803" -dependencies = [ - "bitcode", - "cfg-if", - "derivative", - "derive-new 0.7.0", - "itertools 0.14.0", - "metrics", - "mimalloc", - "p3-air", - "p3-challenger", - "p3-commit", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-uni-stark", - "p3-util", - "rayon", - "rustc-hash 2.1.1", - "serde", - "thiserror 1.0.69", - "tikv-jemallocator", - "tracing", -] - -[[package]] -name = "openvm-stark-sdk" -version = "1.0.0" -source = "git+https://github.com/openvm-org/stark-backend.git?tag=v1.0.1#e540dbdd09ef20db7207ad7f2674bece75a2b803" -dependencies = [ - "derivative", - "derive_more 0.99.19", - "ff 0.13.1", - "itertools 0.14.0", - "metrics", - "metrics-tracing-context", - "metrics-util", - "openvm-stark-backend", - "p3-baby-bear", - "p3-blake3", - "p3-bn254-fr", - "p3-dft", - "p3-fri", - "p3-goldilocks", - "p3-keccak", - "p3-koala-bear", - "p3-merkle-tree", - "p3-poseidon", - "p3-poseidon2", - "p3-symmetric", - "rand", - "serde", - "serde_json", - "static_assertions", - "toml 0.8.20", - "tracing", - "tracing-forest", - "tracing-subscriber", - "zkhash", -] - -[[package]] -name = "openvm-toolchain-tests" -version = "1.1.2" -dependencies = [ - "derive_more 1.0.0", - "eyre", - "num-bigint 0.4.6", - "openvm-algebra-circuit", - "openvm-algebra-transpiler", - "openvm-bigint-circuit", - "openvm-build", - "openvm-circuit", - "openvm-ecc-guest", - "openvm-instructions", - "openvm-platform", - "openvm-rv32im-circuit", - "openvm-rv32im-transpiler", - "openvm-stark-backend", - "openvm-stark-sdk", - "openvm-transpiler", - "serde", - "tempfile", - "test-case", -] - -[[package]] -name = "openvm-transpiler" -version = "1.1.2" -dependencies = [ - "elf", - "eyre", - "openvm-instructions", - "openvm-platform", - "openvm-stark-backend", - "rrs-lib", - "rustc-demangle", - "thiserror 1.0.69", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - -[[package]] -name = "outref" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "p256" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" -dependencies = [ - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "elliptic-curve 0.13.8", -] - -[[package]] -name = "p3-air" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "p3-field", - "p3-matrix", -] - -[[package]] -name = "p3-baby-bear" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "p3-field", - "p3-mds", - "p3-monty-31", - "p3-poseidon2", - "p3-symmetric", - "rand", - "serde", -] - -[[package]] -name = "p3-blake3" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "blake3", - "p3-symmetric", - "p3-util", -] - -[[package]] -name = "p3-bn254-fr" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "ff 0.13.1", - "halo2curves", - "num-bigint 0.4.6", - "p3-field", - "p3-poseidon2", - "p3-symmetric", - "rand", - "serde", -] - -[[package]] -name = "p3-challenger" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "p3-field", - "p3-maybe-rayon", - "p3-symmetric", - "p3-util", - "tracing", -] - -[[package]] -name = "p3-commit" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-challenger", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-util", - "serde", -] - -[[package]] -name = "p3-dft" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", - "tracing", -] - -[[package]] -name = "p3-field" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "nums", - "p3-maybe-rayon", - "p3-util", - "rand", - "serde", - "tracing", -] - -[[package]] -name = "p3-fri" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-challenger", - "p3-commit", - "p3-dft", - "p3-field", - "p3-interpolation", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", - "rand", - "serde", - "tracing", -] - -[[package]] -name = "p3-goldilocks" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "num-bigint 0.4.6", - "p3-dft", - "p3-field", - "p3-mds", - "p3-poseidon", - "p3-poseidon2", - "p3-symmetric", - "p3-util", - "rand", - "serde", -] - -[[package]] -name = "p3-interpolation" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", -] - -[[package]] -name = "p3-keccak" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-field", - "p3-symmetric", - "p3-util", - "tiny-keccak", -] - -[[package]] -name = "p3-keccak-air" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "p3-air", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", - "rand", - "tracing", -] - -[[package]] -name = "p3-koala-bear" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "p3-field", - "p3-mds", - "p3-monty-31", - "p3-poseidon2", - "p3-symmetric", - "rand", - "serde", -] - -[[package]] -name = "p3-matrix" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-field", - "p3-maybe-rayon", - "p3-util", - "rand", - "serde", - "tracing", - "transpose", -] - -[[package]] -name = "p3-maybe-rayon" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "rayon", -] - -[[package]] -name = "p3-mds" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-symmetric", - "p3-util", - "rand", -] - -[[package]] -name = "p3-merkle-tree" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-commit", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-symmetric", - "p3-util", - "rand", - "serde", - "tracing", -] - -[[package]] -name = "p3-monty-31" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "num-bigint 0.4.6", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-mds", - "p3-poseidon2", - "p3-symmetric", - "p3-util", - "rand", - "serde", - "tracing", - "transpose", -] - -[[package]] -name = "p3-poseidon" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "p3-field", - "p3-mds", - "p3-symmetric", - "rand", -] - -[[package]] -name = "p3-poseidon2" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "gcd", - "p3-field", - "p3-mds", - "p3-symmetric", - "rand", -] - -[[package]] -name = "p3-poseidon2-air" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "p3-air", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-poseidon2", - "p3-util", - "rand", - "tikv-jemallocator", - "tracing", -] - -[[package]] -name = "p3-symmetric" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-field", - "serde", -] - -[[package]] -name = "p3-uni-stark" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "itertools 0.14.0", - "p3-air", - "p3-challenger", - "p3-commit", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", - "serde", - "tracing", -] - -[[package]] -name = "p3-util" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c#1ba4e5c40417f4f7aae86bcca56b6484b4b2490b" -dependencies = [ - "serde", -] - -[[package]] -name = "pairing" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" -dependencies = [ - "group 0.12.1", -] - -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group 0.13.0", -] - -[[package]] -name = "parity-scale-codec" -version = "3.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "const_format", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "rustversion", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - -[[package]] -name = "pasta_curves" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" -dependencies = [ - "blake2b_simd", - "ff 0.12.1", - "group 0.12.1", - "lazy_static", - "rand", - "static_assertions", - "subtle", -] - -[[package]] -name = "pasta_curves" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" -dependencies = [ - "blake2b_simd", - "ff 0.13.1", - "group 0.13.0", - "lazy_static", - "rand", - "static_assertions", - "subtle", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "path-slash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest 0.10.7", - "hmac", - "password-hash", - "sha2", -] - -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi 1.0.1", -] - -[[package]] -name = "pear_codegen" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" -dependencies = [ - "memchr", - "thiserror 2.0.11", - "ucd-trie", -] - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap 2.7.1", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.9", - "spki 0.7.3", -] - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "portable-atomic" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" - -[[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", - "rand_xorshift", - "thiserror 1.0.69", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" -dependencies = [ - "proc-macro2", - "syn 2.0.98", -] - -[[package]] -name = "primitive-types" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" -dependencies = [ - "toml_edit 0.22.24", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "proc-macro2" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "version_check", - "yansi 1.0.1", -] - -[[package]] -name = "proptest" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.8.0", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax 0.8.5", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "quanta" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" -dependencies = [ - "crossbeam-utils", - "libc", - "once_cell", - "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", - "web-sys", - "winapi", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quote" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "serde", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - -[[package]] -name = "raw-cpuid" -version = "11.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529468c1335c1c03919960dfefdb1b3648858c20d7ec2d0663e728e4a717efbc" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-lite" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper", - "hyper-rustls", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "revm" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15689a3c6a8d14b647b4666f2e236ef47b5a5133cdfd423f545947986fff7013" -dependencies = [ - "auto_impl", - "cfg-if", - "dyn-clone", - "revm-interpreter", - "revm-precompile", - "serde", - "serde_json", -] - -[[package]] -name = "revm-interpreter" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e3f11d0fed049a4a10f79820c59113a79b38aed4ebec786a79d5c667bfeb51" -dependencies = [ - "revm-primitives 14.0.0", - "serde", -] - -[[package]] -name = "revm-precompile" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e381060af24b750069a2b2d2c54bba273d84e8f5f9e8026fc9262298e26cc336" -dependencies = [ - "aurora-engine-modexp", - "blst", - "c-kzg", - "cfg-if", - "k256", - "once_cell", - "revm-primitives 14.0.0", - "ripemd", - "secp256k1", - "sha2", - "substrate-bn", -] - -[[package]] -name = "revm-primitives" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51187b852d9e458816a2e19c81f1dd6c924077e1a8fccd16e4f044f865f299d7" -dependencies = [ - "alloy-primitives 0.4.2", - "alloy-rlp", - "auto_impl", - "bitflags 2.8.0", - "bitvec", - "enumn", - "hashbrown 0.14.5", - "hex", -] - -[[package]] -name = "revm-primitives" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3702f132bb484f4f0d0ca4f6fbde3c82cfd745041abbedd6eda67730e1868ef0" -dependencies = [ - "alloy-eip2930", - "alloy-eip7702", - "alloy-primitives 0.8.25", - "auto_impl", - "bitflags 2.8.0", - "bitvec", - "c-kzg", - "cfg-if", - "dyn-clone", - "enumn", - "hex", - "serde", -] - -[[package]] -name = "rfc6979" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -dependencies = [ - "crypto-bigint 0.4.9", - "hmac", - "zeroize", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "ring" -version = "0.17.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "rlp" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" -dependencies = [ - "bytes", - "rlp-derive", - "rustc-hex", -] - -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rlsf" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" -dependencies = [ - "cfg-if", - "const-default", - "libc", - "svgbobdoc", -] - -[[package]] -name = "rrs-lib" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001" -dependencies = [ - "downcast-rs", - "paste", -] - -[[package]] -name = "ruint" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" -dependencies = [ - "alloy-rlp", - "ark-ff 0.3.0", - "ark-ff 0.4.2", - "bytes", - "fastrlp 0.3.1", - "fastrlp 0.4.0", - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "parity-scale-codec", - "primitive-types", - "proptest", - "rand", - "rlp", - "ruint-macro", - "serde", - "valuable", - "zeroize", -] - -[[package]] -name = "ruint-macro" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver 1.0.25", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.8.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" - -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] - -[[package]] -name = "ryu" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scale-info" -version = "2.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" -dependencies = [ - "cfg-if", - "derive_more 1.0.0", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array", - "pkcs8 0.9.0", - "subtle", - "zeroize", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct 0.2.0", - "der 0.7.9", - "generic-array", - "pkcs8 0.10.2", - "subtle", - "zeroize", -] - -[[package]] -name = "secp256k1" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" -dependencies = [ - "rand", - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" -dependencies = [ - "cc", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.8.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" -dependencies = [ - "serde", -] - -[[package]] -name = "semver-parser" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" -dependencies = [ - "pest", -] - -[[package]] -name = "serde" -version = "1.0.218" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-big-array" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_arrays" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.218" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_json" -version = "1.0.139" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" -dependencies = [ - "indexmap 2.7.1", - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_regex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" -dependencies = [ - "regex", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.7.1", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest 0.10.7", - "keccak", -] - -[[package]] -name = "sha3-asm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" -dependencies = [ - "cc", - "cfg-if", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.7", - "rand_core", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "sketches-ddsketch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" - -[[package]] -name = "snark-verifier" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e4c4ed1edca41687fe2d8a09ba30badb0a5cc7fa56dd1159d62aeab7c99ace" -dependencies = [ - "halo2-base", - "halo2-ecc", - "hex", - "itertools 0.11.0", - "lazy_static", - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "pairing 0.23.0", - "rand", - "revm", - "ruint", - "serde", - "sha3", -] - -[[package]] -name = "snark-verifier-sdk" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "babff70ce6292fce03f692d68569f76b8f6710dbac7be7fe5f32c915909c9065" -dependencies = [ - "bincode", - "ethereum-types", - "getset", - "halo2-base", - "hex", - "itertools 0.11.0", - "lazy_static", - "num-bigint 0.4.6", - "num-integer", - "num-traits", - "rand", - "rand_chacha", - "serde", - "serde_json", - "snark-verifier", -] - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "solang-parser" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb9fa2fa2fa6837be8a2495486ff92e3ffe68a99b6eeba288e139efdd842457" -dependencies = [ - "itertools 0.11.0", - "lalrpop", - "lalrpop-util", - "phf", - "thiserror 1.0.69", - "unicode-xid", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der 0.7.9", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strength_reduce" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.98", -] - -[[package]] -name = "substrate-bn" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" -dependencies = [ - "byteorder", - "crunchy", - "lazy_static", - "rand", - "rustc-hex", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "svgbobdoc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" -dependencies = [ - "base64 0.13.1", - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-width", -] - -[[package]] -name = "svm-rs" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11297baafe5fa0c99d5722458eac6a5e25c01eb1b8e5cd137f54079093daa7a4" -dependencies = [ - "dirs", - "fs2", - "hex", - "once_cell", - "reqwest", - "semver 1.0.25", - "serde", - "serde_json", - "sha2", - "thiserror 1.0.69", - "url", - "zip", -] - -[[package]] -name = "svm-rs-builds" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa64b5e8eecd3a8af7cfc311e29db31a268a62d5953233d3e8243ec77a71c4e3" -dependencies = [ - "build_const", - "hex", - "semver 1.0.25", - "serde_json", - "svm-rs", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn-solidity" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560533fbd6914b94a8fb5cc803ed6801c3455668db3b810702c57612bac9412" -dependencies = [ - "paste", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tempfile" -version = "3.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" -dependencies = [ - "cfg-if", - "fastrand", - "getrandom 0.3.1", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "test-case" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" -dependencies = [ - "test-case-macros", -] - -[[package]] -name = "test-case-core" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "test-case-macros" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "test-case-core", -] - -[[package]] -name = "test-log" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f" -dependencies = [ - "env_logger", - "test-log-macros", - "tracing-subscriber", -] - -[[package]] -name = "test-log-macros" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" -dependencies = [ - "thiserror-impl 2.0.11", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "tikv-jemalloc-sys" -version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "tikv-jemallocator" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" -dependencies = [ - "libc", - "tikv-jemalloc-sys", -] - -[[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "itoa", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "tokio" -version = "1.43.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.24", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.7.3", -] - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-forest" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee40835db14ddd1e3ba414292272eddde9dad04d3d4b65509656414d1c42592f" -dependencies = [ - "ansi_term", - "smallvec", - "thiserror 1.0.69", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "transpose" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" -dependencies = [ - "num-integer", - "strength_reduce", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-ident" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "unroll" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "vergen" -version = "8.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" -dependencies = [ - "anyhow", - "cfg-if", - "git2", - "rustversion", - "time", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vsimd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" - -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.98", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "xmlparser" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq 0.1.5", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2", - "sha1", - "time", - "zstd", -] - -[[package]] -name = "zkhash" -version = "0.2.0" -source = "git+https://github.com/HorizenLabs/poseidon2.git?rev=bb476b9#bb476b9ca38198cf5092487283c8b8c5d4317c4e" -dependencies = [ - "ark-ff 0.4.2", - "ark-std 0.4.0", - "bitvec", - "blake2", - "bls12_381", - "byteorder", - "cfg-if", - "group 0.12.1", - "group 0.13.0", - "halo2", - "hex", - "jubjub", - "lazy_static", - "pasta_curves 0.5.1", - "rand", - "serde", - "sha2", - "sha3", - "subtle", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 40707aaa20..4243658ba4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,13 @@ members = [ "extensions/pairing/circuit", "extensions/pairing/guest", "extensions/pairing/transpiler", + "guest-libs/ff_derive/", + "guest-libs/k256/", + "guest-libs/p256/", + "guest-libs/keccak256/", + "guest-libs/pairing/", + "guest-libs/ruint/", + "guest-libs/sha2/", ] exclude = ["crates/sdk/example"] resolver = "2" @@ -235,6 +242,8 @@ ecdsa = { version = "0.16.9", 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/benchmarks/guest/ecrecover/Cargo.toml b/benchmarks/guest/ecrecover/Cargo.toml index ce1e632ac5..52b2f253c3 100644 --- a/benchmarks/guest/ecrecover/Cargo.toml +++ b/benchmarks/guest/ecrecover/Cargo.toml @@ -5,14 +5,15 @@ version = "0.0.0" edition = "2021" [dependencies] -k256 = { version = "0.13.3", default-features = false, features = ["ecdsa"] } +ecdsa = { version = "0.16.9", default-features = false } 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 } +openvm-keccak256 = { path = "../../../guest-libs/keccak256/", default-features = false } +k256 = { path = "../../../guest-libs/k256/", default-features = false, features = ["ecdsa"], package = "openvm-k256" } # 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 = [ 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 db91ea1c22..d0dd982571 100644 --- a/benchmarks/guest/ecrecover/src/main.rs +++ b/benchmarks/guest/ecrecover/src/main.rs @@ -1,27 +1,19 @@ -use alloy_primitives::{keccak256, Bytes, B256, B512}; +use alloy_primitives::{Bytes, B256, B512}; +// Be careful not to import k256::ecdsa::{Signature, VerifyingKey} +// because those are type aliases that their (non-zkvm) implementations use k256::{ - ecdsa::{Error, RecoveryId, Signature}, - Secp256k1, + ecdsa::{Error, RecoveryId, Signature, VerifyingKey}, + 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; +use openvm_keccak256::keccak256; // export native keccak use revm_precompile::{ utilities::right_pad, Error as PrecompileError, PrecompileOutput, PrecompileResult, }; -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() { let expected_address = read_vec(); @@ -32,8 +24,7 @@ pub fn main() { } } -// OpenVM version of ecrecover precompile. -pub fn ecrecover(sig: &B512, mut recid: u8, msg: &B256) -> Result { +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() { @@ -42,15 +33,13 @@ pub fn ecrecover(sig: &B512, mut recid: u8, msg: &B256) -> Result { } 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); + let recovered_key = VerifyingKey::recover_from_prehash(&msg[..], &sig, recid)?; + let mut hash = keccak256( + &recovered_key + .to_encoded_point(/* compress = */ false) + .as_bytes()[1..], + ); + // truncate to 20 bytes hash[..12].fill(0); Ok(B256::from(hash)) 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 9bea64b283..5d756d8e41 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,33 +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() { + // hopefully 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) => { + // hopefully 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() { - // TODO: Since we don't explicitly call setup functions anymore, we should rewrite this test - // to use every declared modulus and curve to ensure that every chip is materialized. + // 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); @@ -79,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); @@ -91,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_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 70e9c7683c..819c1e2691 100644 --- a/benchmarks/guest/pairing/src/main.rs +++ b/benchmarks/guest/pairing/src/main.rs @@ -1,23 +1,12 @@ 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) 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 f0a3886f92..23fe2c82af 100644 --- a/benchmarks/prove/src/bin/ecrecover.rs +++ b/benchmarks/prove/src/bin/ecrecover.rs @@ -97,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() @@ -133,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 87b1a464ca..41611d0970 100644 --- a/benchmarks/prove/src/bin/fib_e2e.rs +++ b/benchmarks/prove/src/bin/fib_e2e.rs @@ -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 acf3a7f628..3102c9e3fe 100644 --- a/benchmarks/prove/src/bin/kitchen_sink.rs +++ b/benchmarks/prove/src/bin/kitchen_sink.rs @@ -24,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() @@ -69,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 298169cb60..1db6d1b491 100644 --- a/benchmarks/prove/src/bin/pairing.rs +++ b/benchmarks/prove/src/bin/pairing.rs @@ -12,7 +12,6 @@ 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()) @@ -32,6 +31,7 @@ fn main() -> Result<()> { ])) .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/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/crates/toolchain/tests/Cargo.toml b/crates/toolchain/tests/Cargo.toml index d31d388c32..87ed333889 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-guest = { workspace = true, features = ["k256"] } openvm-instructions = { workspace = true } openvm-platform = { workspace = true } diff --git a/examples/algebra/src/main.rs b/examples/algebra/src/main.rs index de3d49697c..db94b9f4ed 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`, diff --git a/examples/pairing/Cargo.toml b/examples/pairing/Cargo.toml index a5812dd4ca..f156266e40 100644 --- a/examples/pairing/Cargo.toml +++ b/examples/pairing/Cargo.toml @@ -13,6 +13,9 @@ 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-pairing = { path = "../../guest-libs/pairing/", features = [ + "bls12_381", +] } openvm-pairing-guest = { path = "../../extensions/pairing/guest", features = [ "bls12_381", ] } diff --git a/examples/pairing/src/main.rs b/examples/pairing/src/main.rs index afb3828d0a..e317af7897 100644 --- a/examples/pairing/src/main.rs +++ b/examples/pairing/src/main.rs @@ -4,9 +4,9 @@ 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 diff --git a/extensions/algebra/guest/src/halo2curves.rs b/extensions/algebra/guest/src/halo2curves.rs index 7b913e0d42..725bd889dc 100644 --- a/extensions/algebra/guest/src/halo2curves.rs +++ b/extensions/algebra/guest/src/halo2curves.rs @@ -1,10 +1,8 @@ -use core::ops::{Add, Mul, Sub}; - use halo2curves_axiom::ff; use crate::{field::Field, DivAssignUnsafe, DivUnsafe}; -macro_rules! div_unsafe_impl { +macro_rules! field_impls { ($($t:ty),*) => { $( impl DivUnsafe for $t { @@ -42,49 +40,37 @@ macro_rules! div_unsafe_impl { *self *= other.invert().unwrap(); } } + + impl Field for $t { + const ZERO: Self = <$t as ff::Field>::ZERO; + const ONE: Self = <$t as ff::Field>::ONE; + + type SelfRef<'a> = &'a Self; + + fn double_assign(&mut self) { + *self += *self; + } + + fn square_assign(&mut self) { + *self = self.square(); + } + } + )* }; } -div_unsafe_impl!( +field_impls!( halo2curves_axiom::bls12_381::Fq, halo2curves_axiom::bls12_381::Fq12, halo2curves_axiom::bls12_381::Fq2 ); -div_unsafe_impl!( +field_impls!( halo2curves_axiom::bn256::Fq, halo2curves_axiom::bn256::Fq12, halo2curves_axiom::bn256::Fq2 ); -impl Field for F -where - for<'a> &'a F: Add<&'a F, Output = F> - + Sub<&'a F, Output = F> - + Mul<&'a F, Output = F> - + DivUnsafe<&'a F, Output = F>, - for<'a> F: Add<&'a F, Output = F> - + Sub<&'a F, Output = F> - + Mul<&'a F, Output = F> - + DivAssignUnsafe - + DivUnsafe - + DivAssignUnsafe<&'a F> - + DivUnsafe<&'a F, Output = F>, -{ - const ZERO: Self = ::ZERO; - const ONE: Self = ::ONE; - - type SelfRef<'a> = &'a F; - - fn double_assign(&mut self) { - *self += *self; - } - - fn square_assign(&mut self) { - *self = self.square(); - } -} - mod bn254 { use alloc::vec::Vec; diff --git a/extensions/algebra/tests/programs/Cargo.toml b/extensions/algebra/tests/programs/Cargo.toml index 9d067de693..fabf651e1c 100644 --- a/extensions/algebra/tests/programs/Cargo.toml +++ b/extensions/algebra/tests/programs/Cargo.toml @@ -11,7 +11,6 @@ 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 } -#openvm-custom-insn = { path = "../../../../crates/toolchain/custom_insn" } num-bigint = { version = "0.4", default-features = false } serde = { version = "1.0", default-features = false, features = [ diff --git a/extensions/bigint/guest/Cargo.toml b/extensions/bigint/guest/Cargo.toml index 2213c11796..0a3fd64a37 100644 --- a/extensions/bigint/guest/Cargo.toml +++ b/extensions/bigint/guest/Cargo.toml @@ -8,15 +8,8 @@ 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 = [] diff --git a/extensions/bigint/guest/src/externs.rs b/extensions/bigint/guest/src/externs.rs index 462ff21ff7..c20354e6e1 100644 --- a/extensions/bigint/guest/src/externs.rs +++ b/extensions/bigint/guest/src/externs.rs @@ -133,7 +133,7 @@ pub unsafe extern "C" fn zkvm_u256_eq_impl(a: *const u8, b: *const u8) -> bool { #[no_mangle] pub 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, @@ -143,7 +143,7 @@ pub unsafe extern "C" fn zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Order 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!( @@ -154,7 +154,7 @@ pub unsafe extern "C" fn zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Order 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; @@ -162,7 +162,7 @@ pub unsafe extern "C" fn zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Order #[no_mangle] pub 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/ecc/circuit/Cargo.toml b/extensions/ecc/circuit/Cargo.toml index 28ea661248..969a3f99f7 100644 --- a/extensions/ecc/circuit/Cargo.toml +++ b/extensions/ecc/circuit/Cargo.toml @@ -30,7 +30,6 @@ eyre = { workspace = true } num-integer = { workspace = true } serde = { workspace = true } serde_with = { workspace = true } -rand = { workspace = true, features = ["std_rng"] } [dev-dependencies] openvm-stark-sdk = { workspace = true } @@ -41,7 +40,6 @@ lazy_static = { workspace = true } [target.'cfg(not(target_os = "zkvm"))'.dependencies] openvm-ecc-guest = { workspace = true, features = [ - "halo2curves", "k256", "p256", ] } diff --git a/extensions/ecc/guest/Cargo.toml b/extensions/ecc/guest/Cargo.toml index dbc1749fc2..45c69a9635 100644 --- a/extensions/ecc/guest/Cargo.toml +++ b/extensions/ecc/guest/Cargo.toml @@ -25,7 +25,6 @@ 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] @@ -41,4 +40,4 @@ p256 = ["dep:p256"] halo2curves = ["dep:halo2curves-axiom", "openvm-algebra-guest/halo2curves"] [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/tests/Cargo.toml b/extensions/ecc/tests/Cargo.toml index b877277da6..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" } diff --git a/extensions/ecc/tests/programs/Cargo.toml b/extensions/ecc/tests/programs/Cargo.toml index 1adecb552d..c19094c2ef 100644 --- a/extensions/ecc/tests/programs/Cargo.toml +++ b/extensions/ecc/tests/programs/Cargo.toml @@ -15,6 +15,8 @@ openvm-algebra-guest = { path = "../../../algebra/guest", default-features = fal openvm-algebra-moduli-macros = { path = "../../../algebra/moduli-macros", default-features = false } openvm-rv32im-guest = { path = "../../../../extensions/rv32im/guest", default-features = false } +openvm-keccak256 = { path = "../../../../guest-libs/keccak256/" } + serde = { version = "1.0", default-features = false, features = [ "alloc", "derive", diff --git a/extensions/ecc/tests/src/lib.rs b/extensions/ecc/tests/src/lib.rs index 88c9296dec..523973f098 100644 --- a/extensions/ecc/tests/src/lib.rs +++ b/extensions/ecc/tests/src/lib.rs @@ -5,16 +5,21 @@ mod tests { use eyre::Result; use hex_literal::hex; use num_bigint::BigUint; + use openvm_algebra_circuit::ModularExtension; use openvm_algebra_transpiler::ModularTranspilerExtension; use openvm_circuit::{ - arch::instructions::exe::VmExe, + arch::{instructions::exe::VmExe, SystemConfig}, utils::{air_test, air_test_with_min_segments}, }; - use openvm_ecc_circuit::{CurveConfig, Rv32WeierstrassConfig, P256_CONFIG, SECP256K1_CONFIG}; + use openvm_ecc_circuit::{ + CurveConfig, Rv32WeierstrassConfig, WeierstrassExtension, P256_CONFIG, SECP256K1_CONFIG, + }; use openvm_ecc_transpiler::EccTranspilerExtension; + use openvm_keccak256_transpiler::Keccak256TranspilerExtension; use openvm_rv32im_transpiler::{ Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, }; + 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::{ @@ -156,42 +161,40 @@ mod tests { Ok(()) } - // Commenting out for now since ecdsa requires keccak256 which is now removed from openvm - // And openvm-keccak256 depends on openvm-platform which cause weird linking issues - // #[test] - // fn test_ecdsa() -> Result<()> { - // let config = SdkVmConfig::builder() - // .system(SystemConfig::default().with_continuations().into()) - // .rv32i(Default::default()) - // .rv32m(Default::default()) - // .io(Default::default()) - // .modular(ModularExtension::new(vec![ - // SECP256K1_CONFIG.modulus.clone(), - // SECP256K1_CONFIG.scalar.clone(), - // ])) - // .keccak(Default::default()) - // .ecc(WeierstrassExtension::new(vec![SECP256K1_CONFIG.clone()])) - // .build(); + #[test] + fn test_ecdsa() -> Result<()> { + let config = SdkVmConfig::builder() + .system(SystemConfig::default().with_continuations().into()) + .rv32i(Default::default()) + .rv32m(Default::default()) + .io(Default::default()) + .modular(ModularExtension::new(vec![ + SECP256K1_CONFIG.modulus.clone(), + SECP256K1_CONFIG.scalar.clone(), + ])) + .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() - // .with_extension(Rv32ITranspilerExtension) - // .with_extension(Rv32MTranspilerExtension) - // .with_extension(Rv32IoTranspilerExtension) - // .with_extension(Keccak256TranspilerExtension) - // .with_extension(EccTranspilerExtension) - // .with_extension(ModularTranspilerExtension), - // )?; - // air_test(config, openvm_exe); - // Ok(()) - // } + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "ecdsa", + ["k256"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Keccak256TranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } #[test] #[should_panic] diff --git a/extensions/keccak256/guest/Cargo.toml b/extensions/keccak256/guest/Cargo.toml index 4b4b1ea518..f2e86a53fe 100644 --- a/extensions/keccak256/guest/Cargo.toml +++ b/extensions/keccak256/guest/Cargo.toml @@ -8,10 +8,7 @@ homepage.workspace = true repository.workspace = true [dependencies] -openvm-platform = { workspace = true, features = ["rust-runtime"] } - -[target.'cfg(not(target_os = "zkvm"))'.dependencies] -tiny-keccak.workspace = true +openvm-platform = { workspace = true } [features] default = [] 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/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 72951a15be..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,13 +1,7 @@ use alloc::vec::Vec; -use core::mem::transmute; -use halo2curves_axiom::bls12_381::{Fq, Fq12, Fq2, G1Affine, G2Affine, MillerLoopResult}; -use hex_literal::hex; +use halo2curves_axiom::bls12_381::{Fq, Fq2, G1Affine, G2Affine}; use itertools::izip; -use lazy_static::lazy_static; -use num_bigint::BigUint; -use num_traits::Pow; -use openvm_algebra_guest::ExpBytes; use openvm_ecc_guest::AffinePoint; use rand::{rngs::StdRng, SeedableRng}; @@ -19,32 +13,6 @@ mod test_line; mod test_miller_loop; #[cfg(not(target_os = "zkvm"))] - -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)); -} - #[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/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 beecacac45..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,16 +1,7 @@ use alloc::vec::Vec; -use core::mem::transmute; -use halo2curves_axiom::{ - bn256::{Fq, Fq12, Fq2, G1Affine, G2Affine, Gt}, - pairing::MillerLoopResult, -}; -use hex_literal::hex; +use halo2curves_axiom::bn256::{Fq, Fq2, G1Affine, G2Affine}; use itertools::izip; -use lazy_static::lazy_static; -use num_bigint::BigUint; -use num_traits::Pow; -use openvm_algebra_guest::ExpBytes; use openvm_ecc_guest::AffinePoint; use rand::{rngs::StdRng, SeedableRng}; @@ -21,30 +12,6 @@ mod test_line; #[cfg(test)] mod test_miller_loop; -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); -} - #[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/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/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..8b3ea92830 --- /dev/null +++ b/guest-libs/ff_derive/src/lib.rs @@ -0,0 +1,1686 @@ +#![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. +/// This macro removes the struct definition and inserts our own zkvm-compatible struct into the +/// token stream. +// 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..1c48ff1f98 --- /dev/null +++ b/guest-libs/k256/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "openvm-k256" +description = "OpenVM fork of k256" +version.workspace = true +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 } +ecdsa = { workspace = true } +serde = { workspace = true } +hex-literal = { workspace = true } +ff = { workspace = true } +k256 = { workspace = 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"] +ecdsa = ["k256/ecdsa", "ecdsa-core"] +ecdsa-core = ["k256/ecdsa-core"] + +[package.metadata.cargo-shear] +ignored = ["openvm", "num-bigint", "serde", "derive_more"] \ No newline at end of file diff --git a/guest-libs/k256/src/coord.rs b/guest-libs/k256/src/coord.rs new file mode 100644 index 0000000000..ff728ef247 --- /dev/null +++ b/guest-libs/k256/src/coord.rs @@ -0,0 +1,33 @@ +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::>(), + ) + } +} + +// Requires canonical form +impl ConstantTimeEq for Secp256k1Coord { + fn ct_eq(&self, other: &Secp256k1Coord) -> Choice { + self.as_le_bytes().ct_eq(other.as_le_bytes()) + } +} diff --git a/guest-libs/k256/src/ecdsa.rs b/guest-libs/k256/src/ecdsa.rs new file mode 100644 index 0000000000..07cfa466e7 --- /dev/null +++ b/guest-libs/k256/src/ecdsa.rs @@ -0,0 +1,14 @@ +// re-export types that are visible in the k256 crate for API compatibility + +pub use k256::ecdsa::{Error, RecoveryId}; + +/// ECDSA/secp256k1 signature (fixed-size) +pub type Signature = ecdsa::Signature; + +/// ECDSA/secp256k1 signing key +#[cfg(feature = "ecdsa")] +pub type SigningKey = ecdsa::SigningKey; + +/// ECDSA/secp256k1 verification key (i.e. public key) +#[cfg(feature = "ecdsa")] +pub type VerifyingKey = ecdsa::VerifyingKey; diff --git a/guest-libs/k256/src/internal.rs b/guest-libs/k256/src/internal.rs new file mode 100644 index 0000000000..b8f8857dc9 --- /dev/null +++ b/guest-libs/k256/src/internal.rs @@ -0,0 +1,83 @@ +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, WeierstrassPoint}, + CyclicGroup, Group, +}; +use openvm_ecc_sw_macros::sw_declare; + +use crate::Secp256k1; + +// --- Define the OpenVM modular arithmetic and ecc types --- + +const CURVE_B: Secp256k1Coord = Secp256k1Coord::from_const_bytes(seven_le()); +pub const fn seven_le() -> [u8; 32] { + let mut buf = [0u8; 32]; + buf[0] = 7; + buf +} + +moduli_declare! { + Secp256k1Coord { modulus = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" }, + Secp256k1Scalar { modulus = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" }, +} + +sw_declare! { + Secp256k1Point { mod_type = Secp256k1Coord, b = CURVE_B }, +} + +// --- Implement internal traits --- + +impl CyclicGroup for Secp256k1Point { + // The constants are taken from: https://en.bitcoin.it/wiki/Secp256k1 + const GENERATOR: Self = Secp256k1Point { + // from_const_bytes takes a little endian byte string + x: Secp256k1Coord::from_const_bytes(hex!( + "9817F8165B81F259D928CE2DDBFC9B02070B87CE9562A055ACBBDCF97E66BE79" + )), + y: Secp256k1Coord::from_const_bytes(hex!( + "B8D410FB8FD0479C195485A648B417FDA808110EFCFBA45D65C4A32677DA3A48" + )), + }; + const NEG_GENERATOR: Self = Secp256k1Point { + x: Secp256k1Coord::from_const_bytes(hex!( + "9817F8165B81F259D928CE2DDBFC9B02070B87CE9562A055ACBBDCF97E66BE79" + )), + y: Secp256k1Coord::from_const_bytes(hex!( + "7727EF046F2FB863E6AB7A59B74BE80257F7EEF103045BA29A3B5CD98825C5B7" + )), + }; +} + +impl IntrinsicCurve for Secp256k1 { + type Scalar = Secp256k1Scalar; + type Point = Secp256k1Point; + + 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 { + let table = CachedMulTable::::new_with_prime_order(bases, 4); + table.windowed_mul(coeffs) + } else { + 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..f279d13c75 --- /dev/null +++ b/guest-libs/k256/src/lib.rs @@ -0,0 +1,62 @@ +// Fork of RustCrypto's k256 crate https://docs.rs/k256/latest/k256/ +// 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::{ + Secp256k1Point, Secp256k1Point as AffinePoint, Secp256k1Point as ProjectivePoint, + Secp256k1Scalar as Scalar, +}; + +// -- 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..2f38193ff8 --- /dev/null +++ b/guest-libs/k256/src/point.rs @@ -0,0 +1,274 @@ +use core::{ + iter::Sum, + ops::{Mul, MulAssign}, +}; + +use ecdsa::hazmat::VerifyPrimitive; +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 {} + +/// ECDSA/secp256k1 signature (fixed-size) +pub type Signature = ecdsa::Signature; + +impl VerifyPrimitive for Secp256k1Point { + fn verify_prehashed(&self, z: &FieldBytes, sig: &Signature) -> Result<(), ecdsa::Error> { + type PublicKey = openvm_ecc_guest::ecdsa::PublicKey; + type VerifyingKey = openvm_ecc_guest::ecdsa::VerifyingKey; + + let vk = VerifyingKey::new(PublicKey::new(*self)); + vk.verify_prehashed(z.as_slice(), sig.to_bytes().as_slice()) + .map_err(|_| ecdsa::Error::new()) + } +} + +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..4a0f722567 --- /dev/null +++ b/guest-libs/k256/src/scalar.rs @@ -0,0 +1,233 @@ +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, +}; + +// --- 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..a049984cd5 --- /dev/null +++ b/guest-libs/k256/tests/lib.rs @@ -0,0 +1,184 @@ +#[cfg(test)] +mod 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_sjcalar_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(()) + } +} diff --git a/guest-libs/k256/tests/programs/Cargo.toml b/guest-libs/k256/tests/programs/Cargo.toml new file mode 100644 index 0000000000..ce4ae8ebdb --- /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 = "../../" } +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..76c36c9923 --- /dev/null +++ b/guest-libs/k256/tests/programs/examples/ecdsa.rs @@ -0,0 +1,59 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use ecdsa::{RecoveryId, Signature, VerifyingKey}; +use hex_literal::hex; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_k256::Secp256k1Point; +use openvm_k256::{EncodedPoint, Secp256k1}; +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 = Signature::try_from(vector.sig.as_slice()).unwrap(); + let recid = vector.recid; + // this also verifies the signature + let pk = VerifyingKey::recover_from_prehash(digest.as_slice(), &sig, recid).unwrap(); + assert_eq!(&vector.pk[..], EncodedPoint::from(&pk).as_bytes()); + } +} 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/guest-libs/keccak256/tests/lib.rs b/guest-libs/keccak256/tests/lib.rs new file mode 100644 index 0000000000..836d158a4c --- /dev/null +++ b/guest-libs/keccak256/tests/lib.rs @@ -0,0 +1,33 @@ +#[cfg(test)] +mod tests { + use eyre::Result; + use openvm_circuit::utils::air_test; + use openvm_instructions::exe::VmExe; + use openvm_keccak256_circuit::Keccak256Rv32Config; + use openvm_keccak256_transpiler::Keccak256TranspilerExtension; + use openvm_rv32im_transpiler::{ + 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_transpiler::{transpiler::Transpiler, FromElf}; + + type F = BabyBear; + + #[test] + fn test_keccak256() -> Result<()> { + 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() + .with_extension(Keccak256TranspilerExtension) + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } +} diff --git a/guest-libs/keccak256/tests/programs/Cargo.toml b/guest-libs/keccak256/tests/programs/Cargo.toml new file mode 100644 index 0000000000..60c0f64d24 --- /dev/null +++ b/guest-libs/keccak256/tests/programs/Cargo.toml @@ -0,0 +1,24 @@ +[workspace] +[package] +name = "openvm-keccak256-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +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"] + +[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/keccak256/tests/programs/examples/keccak.rs b/guest-libs/keccak256/tests/programs/examples/keccak.rs new file mode 100644 index 0000000000..37955be114 --- /dev/null +++ b/guest-libs/keccak256/tests/programs/examples/keccak.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::hint::black_box; + +use hex::FromHex; +use openvm_keccak256::keccak256; + +openvm::entry!(main); + +pub fn main() { + let test_vectors = [ + ("", "C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470"), // ShortMsgKAT_256 Len = 0 + ("CC", "EEAD6DBFC7340A56CAEDC044696A168870549A6A7F6F56961E84A54BD9970B8A"), // ShortMsgKAT_256 Len = 8 + ("B55C10EAE0EC684C16D13463F29291BF26C82E2FA0422A99C71DB4AF14DD9C7F33EDA52FD73D017CC0F2DBE734D831F0D820D06D5F89DACC485739144F8CFD4799223B1AFF9031A105CB6A029BA71E6E5867D85A554991C38DF3C9EF8C1E1E9A7630BE61CAABCA69280C399C1FB7A12D12AEFC", "0347901965D3635005E75A1095695CCA050BC9ED2D440C0372A31B348514A889"), // ShortMsgKAT_256 Len = 920 + ("2EDC282FFB90B97118DD03AAA03B145F363905E3CBD2D50ECD692B37BF000185C651D3E9726C690D3773EC1E48510E42B17742B0B0377E7DE6B8F55E00A8A4DB4740CEE6DB0830529DD19617501DC1E9359AA3BCF147E0A76B3AB70C4984C13E339E6806BB35E683AF8527093670859F3D8A0FC7D493BCBA6BB12B5F65E71E705CA5D6C948D66ED3D730B26DB395B3447737C26FAD089AA0AD0E306CB28BF0ACF106F89AF3745F0EC72D534968CCA543CD2CA50C94B1456743254E358C1317C07A07BF2B0ECA438A709367FAFC89A57239028FC5FECFD53B8EF958EF10EE0608B7F5CB9923AD97058EC067700CC746C127A61EE3", "DD1D2A92B3F3F3902F064365838E1F5F3468730C343E2974E7A9ECFCD84AA6DB"), // ShortMsgKAT_256 Len = 1952, + ("724627916C50338643E6996F07877EAFD96BDF01DA7E991D4155B9BE1295EA7D21C9391F4C4A41C75F77E5D27389253393725F1427F57914B273AB862B9E31DABCE506E558720520D33352D119F699E784F9E548FF91BC35CA147042128709820D69A8287EA3257857615EB0321270E94B84F446942765CE882B191FAEE7E1C87E0F0BD4E0CD8A927703524B559B769CA4ECE1F6DBF313FDCF67C572EC4185C1A88E86EC11B6454B371980020F19633B6B95BD280E4FBCB0161E1A82470320CEC6ECFA25AC73D09F1536F286D3F9DACAFB2CD1D0CE72D64D197F5C7520B3CCB2FD74EB72664BA93853EF41EABF52F015DD591500D018DD162815CC993595B195", "EA0E416C0F7B4F11E3F00479FDDF954F2539E5E557753BD546F69EE375A5DE29"), // LongMsgKAT_256 Len = 2048 + ("6E1CADFB2A14C5FFB1DD69919C0124ED1B9A414B2BEA1E5E422D53B022BDD13A9C88E162972EBB9852330006B13C5B2F2AFBE754AB7BACF12479D4558D19DDBB1A6289387B3AC084981DF335330D1570850B97203DBA5F20CF7FF21775367A8401B6EBE5B822ED16C39383232003ABC412B0CE0DD7C7DA064E4BB73E8C58F222A1512D5FE6D947316E02F8AA87E7AA7A3AA1C299D92E6414AE3B927DB8FF708AC86A09B24E1884743BC34067BB0412453B4A6A6509504B550F53D518E4BCC3D9C1EFDB33DA2EACCB84C9F1CAEC81057A8508F423B25DB5500E5FC86AB3B5EB10D6D0BF033A716DDE55B09FD53451BBEA644217AE1EF91FAD2B5DCC6515249C96EE7EABFD12F1EF65256BD1CFF2087DABF2F69AD1FFB9CF3BC8CA437C7F18B6095BC08D65DF99CC7F657C418D8EB109FDC91A13DC20A438941726EF24F9738B6552751A320C4EA9C8D7E8E8592A3B69D30A419C55FB6CB0850989C029AAAE66305E2C14530B39EAA86EA3BA2A7DECF4B2848B01FAA8AA91F2440B7CC4334F63061CE78AA1589BEFA38B194711697AE3AADCB15C9FBF06743315E2F97F1A8B52236ACB444069550C2345F4ED12E5B8E881CDD472E803E5DCE63AE485C2713F81BC307F25AC74D39BAF7E3BC5E7617465C2B9C309CB0AC0A570A7E46C6116B2242E1C54F456F6589E20B1C0925BF1CD5F9344E01F63B5BA9D4671ABBF920C7ED32937A074C33836F0E019DFB6B35D865312C6058DFDAFF844C8D58B75071523E79DFBAB2EA37479DF12C474584F4FF40F00F92C6BADA025CE4DF8FAF0AFB2CE75C07773907CA288167D6B011599C3DE0FFF16C1161D31DF1C1DDE217CB574ED5A33751759F8ED2B1E6979C5088B940926B9155C9D250B479948C20ACB5578DC02C97593F646CC5C558A6A0F3D8D273258887CCFF259197CB1A7380622E371FD2EB5376225EC04F9ED1D1F2F08FA2376DB5B790E73086F581064ED1C5F47E989E955D77716B50FB64B853388FBA01DAC2CEAE99642341F2DA64C56BEFC4789C051E5EB79B063F2F084DB4491C3C5AA7B4BCF7DD7A1D7CED1554FA67DCA1F9515746A237547A4A1D22ACF649FA1ED3B9BB52BDE0C6996620F8CFDB293F8BACAD02BCE428363D0BB3D391469461D212769048219220A7ED39D1F9157DFEA3B4394CA8F5F612D9AC162BF0B961BFBC157E5F863CE659EB235CF98E8444BC8C7880BDDCD0B3B389AAA89D5E05F84D0649EEBACAB4F1C75352E89F0E9D91E4ACA264493A50D2F4AED66BD13650D1F18E7199E931C78AEB763E903807499F1CD99AF81276B615BE8EC709B039584B2B57445B014F6162577F3548329FD288B0800F936FC5EA1A412E3142E609FC8E39988CA53DF4D8FB5B5FB5F42C0A01648946AC6864CFB0E92856345B08E5DF0D235261E44CFE776456B40AEF0AC1A0DFA2FE639486666C05EA196B0C1A9D346435E03965E6139B1CE10129F8A53745F80100A94AE04D996C13AC14CF2713E39DFBB19A936CF3861318BD749B1FB82F40D73D714E406CBEB3D920EA037B7DE566455CCA51980F0F53A762D5BF8A4DBB55AAC0EDDB4B1F2AED2AA3D01449D34A57FDE4329E7FF3F6BECE4456207A4225218EE9F174C2DE0FF51CEAF2A07CF84F03D1DF316331E3E725C5421356C40ED25D5ABF9D24C4570FED618CA41000455DBD759E32E2BF0B6C5E61297C20F752C3042394CE840C70943C451DD5598EB0E4953CE26E833E5AF64FC1007C04456D19F87E45636F456B7DC9D31E757622E2739573342DE75497AE181AAE7A5425756C8E2A7EEF918E5C6A968AEFE92E8B261BBFE936B19F9E69A3C90094096DAE896450E1505ED5828EE2A7F0EA3A28E6EC47C0AF711823E7689166EA07ECA00FFC493131D65F93A4E1D03E0354AFC2115CFB8D23DAE8C6F96891031B23226B8BC82F1A73DAA5BB740FC8CC36C0975BEFA0C7895A9BBC261EDB7FD384103968F7A18353D5FE56274E4515768E4353046C785267DE01E816A2873F97AAD3AB4D7234EBFD9832716F43BE8245CF0B4408BA0F0F764CE9D24947AB6ABDD9879F24FCFF10078F5894B0D64F6A8D3EA3DD92A0C38609D3C14FDC0A44064D501926BE84BF8034F1D7A8C5F382E6989BFFA2109D4FBC56D1F091E8B6FABFF04D21BB19656929D19DECB8E8291E6AE5537A169874E0FE9890DFF11FFD159AD23D749FB9E8B676E2C31313C16D1EFA06F4D7BC191280A4EE63049FCEF23042B20303AECDD412A526D7A53F760A089FBDF13F361586F0DCA76BB928EDB41931D11F679619F948A6A9E8DBA919327769006303C6EF841438A7255C806242E2E7FF4621BB0F8AFA0B4A248EAD1A1E946F3E826FBFBBF8013CE5CC814E20FEF21FA5DB19EC7FF0B06C592247B27E500EB4705E6C37D41D09E83CB0A618008CA1AAAE8A215171D817659063C2FA385CFA3C1078D5C2B28CE7312876A276773821BE145785DFF24BBB24D590678158A61EA49F2BE56FDAC8CE7F94B05D62F15ADD351E5930FD4F31B3E7401D5C0FF7FC845B165FB6ABAFD4788A8B0615FEC91092B34B710A68DA518631622BA2AAE5D19010D307E565A161E64A4319A6B261FB2F6A90533997B1AEC32EF89CF1F232696E213DAFE4DBEB1CF1D5BBD12E5FF2EBB2809184E37CD9A0E58A4E0AF099493E6D8CC98B05A2F040A7E39515038F6EE21FC25F8D459A327B83EC1A28A234237ACD52465506942646AC248EC96EBBA6E1B092475F7ADAE4D35E009FD338613C7D4C12E381847310A10E6F02C02392FC32084FBE939689BC6518BE27AF7842DEEA8043828E3DFFE3BBAC4794CA0CC78699722709F2E4B0EAE7287DEB06A27B462423EC3F0DF227ACF589043292685F2C0E73203E8588B62554FF19D6260C7FE48DF301509D33BE0D8B31D3F658C921EF7F55449FF3887D91BFB894116DF57206098E8C5835B", "3C79A3BD824542C20AF71F21D6C28DF2213A041F77DD79A328A0078123954E7B"), // LongMsgKAT_256 Len = 16664 + ("7ADC0B6693E61C269F278E6944A5A2D8300981E40022F839AC644387BFAC9086650085C2CDC585FEA47B9D2E52D65A2B29A7DC370401EF5D60DD0D21F9E2B90FAE919319B14B8C5565B0423CEFB827D5F1203302A9D01523498A4DB10374", "4CC2AFF141987F4C2E683FA2DE30042BACDCD06087D7A7B014996E9CFEAA58CE"), // ShortMsgKAT_256 Len = 752 + ]; + for (input, expected_output) in test_vectors.iter() { + let input = Vec::from_hex(input).unwrap(); + let expected_output = Vec::from_hex(expected_output).unwrap(); + let output = keccak256(&black_box(input)); + if output != *expected_output { + panic!(); + } + } +} diff --git a/guest-libs/p256/Cargo.toml b/guest-libs/p256/Cargo.toml new file mode 100644 index 0000000000..45fe8b2460 --- /dev/null +++ b/guest-libs/p256/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "openvm-p256" +description = "OpenVM fork of p256" +version.workspace = true +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 } +ecdsa = { workspace = true } +serde = { workspace = true } +hex-literal = { workspace = true } +ff = { workspace = true } +p256 = { 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"] +ecdsa = ["p256/ecdsa", "ecdsa-core"] +ecdsa-core = ["p256/ecdsa-core"] + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +num-bigint = { workspace = true } + +[package.metadata.cargo-shear] +ignored = ["openvm", "serde", "num-bigint", "derive_more"] \ No newline at end of file diff --git a/guest-libs/p256/src/coord.rs b/guest-libs/p256/src/coord.rs new file mode 100644 index 0000000000..9f89a62f42 --- /dev/null +++ b/guest-libs/p256/src/coord.rs @@ -0,0 +1,29 @@ +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::>(), + ) + } +} + +// Requires canonical form +impl ConstantTimeEq for P256Coord { + fn ct_eq(&self, other: &P256Coord) -> Choice { + self.as_le_bytes().ct_eq(other.as_le_bytes()) + } +} diff --git a/guest-libs/p256/src/ecdsa.rs b/guest-libs/p256/src/ecdsa.rs new file mode 100644 index 0000000000..71f679ccdd --- /dev/null +++ b/guest-libs/p256/src/ecdsa.rs @@ -0,0 +1,14 @@ +// re-export types that are visible in the p256 crate for API compatibility + +pub use p256::ecdsa::Error; + +/// ECDSA/secp256k1 signature (fixed-size) +pub type Signature = ecdsa::Signature; + +/// ECDSA/secp256k1 signing key +#[cfg(feature = "ecdsa")] +pub type SigningKey = ecdsa::SigningKey; + +/// ECDSA/secp256k1 verification key (i.e. public key) +#[cfg(feature = "ecdsa")] +pub type VerifyingKey = ecdsa::VerifyingKey; diff --git a/guest-libs/p256/src/internal.rs b/guest-libs/p256/src/internal.rs new file mode 100644 index 0000000000..d2d7d1dd89 --- /dev/null +++ b/guest-libs/p256/src/internal.rs @@ -0,0 +1,83 @@ +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, WeierstrassPoint}, + CyclicGroup, Group, +}; +use openvm_ecc_sw_macros::sw_declare; + +use crate::P256; + +// --- Define the OpenVM modular arithmetic and ecc types --- + +moduli_declare! { + P256Coord { modulus = "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff" }, + P256Scalar { modulus = "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551" }, +} + +// from_const_bytes is little endian +pub const CURVE_A: P256Coord = P256Coord::from_const_bytes(hex!( + "fcffffffffffffffffffffff00000000000000000000000001000000ffffffff" +)); +pub const CURVE_B: P256Coord = P256Coord::from_const_bytes(hex!( + "4b60d2273e3cce3bf6b053ccb0061d65bc86987655bdebb3e7933aaad835c65a" +)); + +sw_declare! { + P256Point { mod_type = P256Coord, a = CURVE_A, b = CURVE_B }, +} + +// --- Implement internal traits --- + +impl CyclicGroup for P256Point { + // The constants are taken from: https://neuromancer.sk/std/secg/secp256r1 + const GENERATOR: Self = P256Point { + // from_const_bytes takes a little endian byte string + x: P256Coord::from_const_bytes(hex!( + "96c298d84539a1f4a033eb2d817d0377f240a463e5e6bcf847422ce1f2d1176b" + )), + y: P256Coord::from_const_bytes(hex!( + "f551bf376840b6cbce5e316b5733ce2b169e0f7c4aebe78e9b7f1afee242e34f" + )), + }; + const NEG_GENERATOR: Self = P256Point { + x: P256Coord::from_const_bytes(hex!( + "96c298d84539a1f4a033eb2d817d0377f240a463e5e6bcf847422ce1f2d1176b" + )), + y: P256Coord::from_const_bytes(hex!( + "0aae40c897bf493431a1ce94a9cc31d4e961f083b51418716580e5011cbd1cb0" + )), + }; +} + +impl IntrinsicCurve for P256 { + type Scalar = P256Scalar; + type Point = P256Point; + + fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point + where + for<'a> &'a Self::Point: Add<&'a Self::Point, Output = Self::Point>, + { + if coeffs.len() < 25 { + let table = CachedMulTable::::new_with_prime_order(bases, 4); + table.windowed_mul(coeffs) + } else { + 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..de60902a63 --- /dev/null +++ b/guest-libs/p256/src/lib.rs @@ -0,0 +1,61 @@ +// 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::{ + P256Point, P256Point as AffinePoint, P256Point as ProjectivePoint, P256Scalar as Scalar, +}; + +// -- Define the ZST for implementing the elliptic curve traits -- +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct P256; + +// --- 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 P256 { + /// 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 P256 {} + +impl CurveArithmetic for P256 { + type AffinePoint = AffinePoint; + type ProjectivePoint = ProjectivePoint; + type Scalar = Scalar; +} + +impl PointCompression for P256 { + /// 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..da53da3059 --- /dev/null +++ b/guest-libs/p256/src/point.rs @@ -0,0 +1,269 @@ +use core::{ + iter::Sum, + ops::{Mul, MulAssign}, +}; + +use ecdsa::hazmat::VerifyPrimitive; +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, P256, +}; + +// --- 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 { + P256::msm(&[other], &[self]) + } +} + +impl Mul<&P256Scalar> for &P256Point { + type Output = P256Point; + + fn mul(self, other: &P256Scalar) -> P256Point { + P256::msm(&[*other], &[*self]) + } +} + +impl Mul<&P256Scalar> for P256Point { + type Output = P256Point; + + fn mul(self, other: &P256Scalar) -> P256Point { + P256::msm(&[*other], &[self]) + } +} + +impl MulAssign for P256Point { + fn mul_assign(&mut self, rhs: P256Scalar) { + *self = P256::msm(&[rhs], &[*self]); + } +} + +impl MulAssign<&P256Scalar> for P256Point { + fn mul_assign(&mut self, rhs: &P256Scalar) { + *self = P256::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 { + P256::msm(&[*k, *l], &[*x, *y]) + } +} + +// default implementation +impl MulByGenerator for P256Point {} + +/// ECDSA/secp256k1 signature (fixed-size) +pub type Signature = ecdsa::Signature; + +impl VerifyPrimitive for P256Point { + fn verify_prehashed(&self, z: &FieldBytes, sig: &Signature) -> Result<(), ecdsa::Error> { + type PublicKey = openvm_ecc_guest::ecdsa::PublicKey; + type VerifyingKey = openvm_ecc_guest::ecdsa::VerifyingKey; + + let vk = VerifyingKey::new(PublicKey::new(*self)); + vk.verify_prehashed(z.as_slice(), sig.to_bytes().as_slice()) + .map_err(|_| ecdsa::Error::new()) + } +} + +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..fda4ae578a --- /dev/null +++ b/guest-libs/p256/src/scalar.rs @@ -0,0 +1,231 @@ +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, ORDER_HEX, P256}; + +// --- 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..7c1832272f --- /dev/null +++ b/guest-libs/p256/tests/lib.rs @@ -0,0 +1,184 @@ +#[cfg(test)] +mod 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(()) + } +} diff --git a/guest-libs/p256/tests/programs/Cargo.toml b/guest-libs/p256/tests/programs/Cargo.toml new file mode 100644 index 0000000000..b9396fb331 --- /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 = "../../" } +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..2aed90f873 --- /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}; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_p256::P256Point; +use openvm_p256::P256; + +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..ec4c764d2f --- /dev/null +++ b/guest-libs/p256/tests/programs/examples/ecdsa.rs @@ -0,0 +1,43 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}; +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::{EncodedPoint, P256}; + +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 = 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..5db774a2f0 --- /dev/null +++ b/guest-libs/p256/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_p256::P256Point; +use openvm_p256::{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/p256/tests/programs/examples/mul.rs b/guest-libs/p256/tests/programs/examples/mul.rs new file mode 100644 index 0000000000..a8fa179a24 --- /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}; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_p256::P256Point; +use openvm_p256::P256; + +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..ea707b3032 --- /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}; +// clippy thinks this is unused, but it's used in the init! macro +#[allow(unused)] +use openvm_p256::P256Point; +use openvm_p256::P256; + +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/guest-libs/pairing/README.md b/guest-libs/pairing/README.md new file mode 100644 index 0000000000..30ca7e1888 --- /dev/null +++ b/guest-libs/pairing/README.md @@ -0,0 +1,4 @@ +Optimal Ate Pairing +== + +See [this HackMD](https://hackmd.io/@openvm/BkrrE_un1g) for details. diff --git a/guest-libs/pairing/src/bls12_381/fp12.rs b/guest-libs/pairing/src/bls12_381/fp12.rs new file mode 100644 index 0000000000..0269c7b864 --- /dev/null +++ b/guest-libs/pairing/src/bls12_381/fp12.rs @@ -0,0 +1,217 @@ +extern crate alloc; + +use alloc::vec::Vec; +use core::ops::{Mul, MulAssign, Neg}; + +use openvm_algebra_guest::{ + field::{ComplexConjugate, FieldExtension}, + DivAssignUnsafe, DivUnsafe, Field, +}; +use openvm_pairing_guest::pairing::PairingIntrinsics; + +use super::{Bls12_381, Fp, Fp2}; +use crate::operations::{fp12_invert_assign, SexticExtField}; + +pub type Fp12 = SexticExtField; + +impl Fp12 { + pub fn invert(&self) -> Self { + let mut s = self.clone(); + fp12_invert_assign::(&mut s.c, &Bls12_381::XI); + s + } +} + +impl Field for Fp12 { + type SelfRef<'a> = &'a Self; + const ZERO: Self = Self::new([Fp2::ZERO; 6]); + const ONE: Self = Self::new([ + Fp2::ONE, + Fp2::ZERO, + Fp2::ZERO, + Fp2::ZERO, + Fp2::ZERO, + Fp2::ZERO, + ]); + + fn double_assign(&mut self) { + *self += self.clone(); + } + + fn square_assign(&mut self) { + *self *= self.clone(); + } +} + +impl FieldExtension for Fp12 { + const D: usize = 6; + type Coeffs = [Fp2; 6]; + + fn from_coeffs(coeffs: Self::Coeffs) -> Self { + Self::new(coeffs) + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 576); + Self::from_coeffs([ + Fp2::from_bytes(&bytes[0..96]), + Fp2::from_bytes(&bytes[96..192]), + Fp2::from_bytes(&bytes[192..288]), + Fp2::from_bytes(&bytes[288..384]), + Fp2::from_bytes(&bytes[384..480]), + Fp2::from_bytes(&bytes[480..576]), + ]) + } + + fn to_coeffs(self) -> Self::Coeffs { + self.c + } + + fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(576); + for coeff in self.clone().to_coeffs() { + bytes.extend_from_slice(&coeff.to_bytes()); + } + bytes + } + + fn embed(c0: Fp2) -> Self { + Self::new([c0, Fp2::ZERO, Fp2::ZERO, Fp2::ZERO, Fp2::ZERO, Fp2::ZERO]) + } + + /// We assume that the frobenius map power is < 12 + fn frobenius_map(&self, power: usize) -> Self { + if power & 1 != 0 { + let c0 = self.c[0].clone().conjugate(); + let c1 = self.c[1].clone().conjugate() * &Bls12_381::FROBENIUS_COEFFS[power][0]; + let c2 = self.c[2].clone().conjugate() * &Bls12_381::FROBENIUS_COEFFS[power][1]; + let c3 = self.c[3].clone().conjugate() * &Bls12_381::FROBENIUS_COEFFS[power][2]; + let c4 = self.c[4].clone().conjugate() * &Bls12_381::FROBENIUS_COEFFS[power][3]; + let c5 = self.c[5].clone().conjugate() * &Bls12_381::FROBENIUS_COEFFS[power][4]; + Self::new([c0, c1, c2, c3, c4, c5]) + } else { + let c0 = self.c[0].clone(); + let c1 = &self.c[1] * &Bls12_381::FROBENIUS_COEFFS[power][0]; + let c2 = &self.c[2] * &Bls12_381::FROBENIUS_COEFFS[power][1]; + let c3 = &self.c[3] * &Bls12_381::FROBENIUS_COEFFS[power][2]; + let c4 = &self.c[4] * &Bls12_381::FROBENIUS_COEFFS[power][3]; + let c5 = &self.c[5] * &Bls12_381::FROBENIUS_COEFFS[power][4]; + Self::new([c0, c1, c2, c3, c4, c5]) + } + } + + fn mul_base(&self, rhs: &Fp2) -> Self { + Self::new([ + &self.c[0] * rhs, + &self.c[1] * rhs, + &self.c[2] * rhs, + &self.c[3] * rhs, + &self.c[4] * rhs, + &self.c[5] * rhs, + ]) + } +} + +// This is ambiguous. It is conjugation for Fp12 over Fp6. +impl ComplexConjugate for Fp12 { + fn conjugate(self) -> Self { + let [c0, c1, c2, c3, c4, c5] = self.c; + Self::new([c0, -c1, c2, -c3, c4, -c5]) + } + + fn conjugate_assign(&mut self) { + self.c[1].neg_assign(); + self.c[3].neg_assign(); + self.c[5].neg_assign(); + } +} + +impl<'a> MulAssign<&'a Fp12> for Fp12 { + #[inline(always)] + fn mul_assign(&mut self, other: &'a Fp12) { + *self = crate::operations::sextic_tower_mul(self, other, &Bls12_381::XI); + } +} + +impl<'a> Mul<&'a Fp12> for &'a Fp12 { + type Output = Fp12; + #[inline(always)] + fn mul(self, other: &'a Fp12) -> Self::Output { + crate::operations::sextic_tower_mul(self, other, &Bls12_381::XI) + } +} + +impl MulAssign for Fp12 { + #[inline(always)] + fn mul_assign(&mut self, other: Self) { + self.mul_assign(&other); + } +} + +impl Mul for Fp12 { + type Output = Self; + #[inline(always)] + fn mul(mut self, other: Self) -> Self::Output { + self *= other; + self + } +} + +impl<'a> Mul<&'a Fp12> for Fp12 { + type Output = Self; + #[inline(always)] + fn mul(mut self, other: &'a Fp12) -> Self::Output { + self *= other; + self + } +} + +impl<'a> DivAssignUnsafe<&'a Fp12> for Fp12 { + #[inline(always)] + fn div_assign_unsafe(&mut self, other: &'a Fp12) { + *self *= other.invert(); + } +} + +impl<'a> DivUnsafe<&'a Fp12> for &'a Fp12 { + type Output = Fp12; + #[inline(always)] + fn div_unsafe(self, other: &'a Fp12) -> Self::Output { + let mut res = self.clone(); + res.div_assign_unsafe(other); + res + } +} + +impl DivAssignUnsafe for Fp12 { + #[inline(always)] + fn div_assign_unsafe(&mut self, other: Self) { + *self *= other.invert(); + } +} + +impl DivUnsafe for Fp12 { + type Output = Self; + #[inline(always)] + fn div_unsafe(mut self, other: Self) -> Self::Output { + self.div_assign_unsafe(other); + self + } +} + +impl<'a> DivUnsafe<&'a Fp12> for Fp12 { + type Output = Self; + #[inline(always)] + fn div_unsafe(mut self, other: &'a Fp12) -> Self::Output { + self.div_assign_unsafe(other); + self + } +} + +impl Neg for Fp12 { + type Output = Fp12; + #[inline(always)] + fn neg(self) -> Self::Output { + Self::ZERO - &self + } +} diff --git a/guest-libs/pairing/src/bls12_381/fp2.rs b/guest-libs/pairing/src/bls12_381/fp2.rs new file mode 100644 index 0000000000..2e1512878b --- /dev/null +++ b/guest-libs/pairing/src/bls12_381/fp2.rs @@ -0,0 +1,74 @@ +extern crate alloc; + +use alloc::vec::Vec; +use core::ops::Neg; + +use openvm_algebra_complex_macros::{complex_declare, complex_impl_field}; +use openvm_algebra_guest::{field::FieldExtension, Field, IntMod}; + +use super::Fp; + +// The struct name needs to be globally unique for linking purposes. +// The mod_type is a path used only in the struct definition. +complex_declare! { + Bls12_381Fp2 { mod_type = Fp } +} + +complex_impl_field! { + Bls12_381Fp2, +} + +pub type Fp2 = Bls12_381Fp2; + +impl FieldExtension for Fp2 { + const D: usize = 2; + type Coeffs = [Fp; 2]; + + fn from_coeffs([c0, c1]: Self::Coeffs) -> Self { + Self { c0, c1 } + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 96); + Self::from_coeffs([ + Fp::from_const_bytes(bytes[0..48].try_into().unwrap()), + Fp::from_const_bytes(bytes[48..96].try_into().unwrap()), + ]) + } + + fn to_coeffs(self) -> Self::Coeffs { + [self.c0, self.c1] + } + + fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(96); + bytes.extend_from_slice(self.c0.as_le_bytes()); + bytes.extend_from_slice(self.c1.as_le_bytes()); + bytes + } + + fn embed(base_elem: Fp) -> Self { + Self { + c0: base_elem, + c1: ::ZERO, + } + } + + fn frobenius_map(&self, power: usize) -> Self { + if power % 2 == 0 { + self.clone() + } else { + Self { + c0: self.c0.clone(), + c1: (&self.c1).neg(), + } + } + } + + fn mul_base(&self, rhs: &Fp) -> Self { + Self { + c0: &self.c0 * rhs, + c1: &self.c1 * rhs, + } + } +} 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/guest-libs/pairing/src/bls12_381/pairing.rs b/guest-libs/pairing/src/bls12_381/pairing.rs new file mode 100644 index 0000000000..db13c785e1 --- /dev/null +++ b/guest-libs/pairing/src/bls12_381/pairing.rs @@ -0,0 +1,352 @@ +extern crate alloc; + +use alloc::vec::Vec; + +use itertools::izip; +use openvm_algebra_guest::{ + field::{ComplexConjugate, FieldExtension}, + 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 { + 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}; +#[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, +}; + +impl Evaluatable for UnevaluatedLine { + fn evaluate(&self, xy_frac: &(Fp, Fp)) -> EvaluatedLine { + let (x_over_y, y_inv) = xy_frac; + // Represents the line L(x,y) = 1 + b (x/y) w^-1 + c (1/y) w^-3 + EvaluatedLine { + b: self.b.mul_base(x_over_y), + c: self.c.mul_base(y_inv), + } + } +} + +impl FromLineMType for Fp12 { + // Since multiplying by w^3 doesn't change the miller loop result, we transform the line + // into L_new(x,y) = w^3 L(x,y) = w^3 + b (x/y) w^2 + c (1/y) + fn from_evaluated_line_m_type(line: EvaluatedLine) -> Fp12 { + Fp12::from_coeffs([line.c, Fp2::ZERO, line.b, Fp2::ONE, Fp2::ZERO, Fp2::ZERO]) + } +} + +// TODO[jpw]: make this into a macro depending on P::PAIRING_IDX when we have more curves +impl LineMulMType for Bls12_381 { + /// Multiplies two lines in 023-form to get an element in 02345-form + fn mul_023_by_023(l0: &EvaluatedLine, l1: &EvaluatedLine) -> [Fp2; 5] { + // l0 = c0 + b0 w^2 + w^3 + let b0 = &l0.b; + let c0 = &l0.c; + // l1 = c1 + b1 w^2 + w^3 + let b1 = &l1.b; + let c1 = &l1.c; + + // where w⁶ = xi + // l0 * l1 = c0c1 + (c0b1 + c1b0)w² + (c0 + c1)w³ + (b0b1)w⁴ + (b0 +b1)w⁵ + w⁶ + // = (c0c1 + xi) + (c0b1 + c1b0)w² + (c0 + c1)w³ + (b0b1)w⁴ + (b0 + b1)w⁵ + let x0 = c0 * c1 + Bls12_381::XI; + let x2 = c0 * b1 + c1 * b0; + let x3 = c0 + c1; + let x4 = b0 * b1; + let x5 = b0 + b1; + + [x0, x2, x3, x4, x5] + } + + /// Multiplies a line in 02345-form with a Fp12 element to get an Fp12 element + fn mul_by_023(f: &Fp12, l: &EvaluatedLine) -> Fp12 { + // this is only used if the number of lines is odd, which doesn't happen for our + // applications right now, so we can use this suboptimal implementation + Fp12::from_evaluated_line_m_type(l.clone()) * f + } + + /// Multiplies a line in 02345-form with a Fp12 element to get an Fp12 element + fn mul_by_02345(f: &Fp12, x: &[Fp2; 5]) -> Fp12 { + // we update the order of the coefficients to match the Fp12 coefficient ordering: + // Fp12 { + // c0: Fp6 { + // c0: x0, + // c1: x2, + // c2: x4, + // }, + // c1: Fp6 { + // c0: x1, + // c1: x3, + // c2: x5, + // }, + // } + let o0 = &x[0]; // coeff x0 + let o1 = &x[1]; // coeff x2 + let o2 = &x[3]; // coeff x4 + let o4 = &x[2]; // coeff x3 + let o5 = &x[4]; // coeff x5 + + let xi = &Bls12_381::XI; + + let self_coeffs = &f.c; + let s0 = &self_coeffs[0]; + let s1 = &self_coeffs[2]; + let s2 = &self_coeffs[4]; + let s3 = &self_coeffs[1]; + let s4 = &self_coeffs[3]; + let s5 = &self_coeffs[5]; + + // NOTE[yj]: Hand-calculated multiplication for Fp12 * 02345 ∈ Fp2; this is likely not the + // most efficient implementation c00 = cs0co0 + xi(cs1co2 + cs2co1 + cs3co5 + + // cs4co4) c01 = cs0co1 + cs1co0 + xi(cs2co2 + cs4co5 + cs5co4) + // c02 = cs0co2 + cs1co1 + cs2co0 + cs3co4 + xi(cs5co5) + // c10 = cs3co0 + xi(cs1co5 + cs2co4 + cs4co2 + cs5co1) + // c11 = cs0co4 + cs3co1 + cs4co0 + xi(cs2co5 + cs5co2) + // c12 = cs0co5 + cs1co4 + cs3co2 + cs4co1 + cs5co0 + // where cs*: self.c* + let c00 = s0 * o0 + xi * &(s1 * o2 + s2 * o1 + s3 * o5 + s4 * o4); + let c01 = s0 * o1 + s1 * o0 + xi * &(s2 * o2 + s4 * o5 + s5 * o4); + let c02 = s0 * o2 + s1 * o1 + s2 * o0 + s3 * o4 + xi * &(s5 * o5); + let c10 = s3 * o0 + xi * &(s1 * o5 + s2 * o4 + s4 * o2 + s5 * o1); + let c11 = s0 * o4 + s3 * o1 + s4 * o0 + xi * &(s2 * o5 + s5 * o2); + let c12 = s0 * o5 + s1 * o4 + s3 * o2 + s4 * o1 + s5 * o0; + + Fp12::from_coeffs([c00, c10, c01, c11, c02, c12]) + } +} + +#[allow(non_snake_case)] +impl MultiMillerLoop for Bls12_381 { + type Fp = Fp; + type Fp12 = Fp12; + + const SEED_ABS: u64 = BLS12_381_SEED_ABS; + const PSEUDO_BINARY_ENCODING: &[i8] = &BLS12_381_PSEUDO_BINARY_ENCODING; + + fn evaluate_lines_vec(f: Self::Fp12, lines: Vec>) -> Self::Fp12 { + let mut f = f; + let mut lines = lines; + if lines.len() % 2 == 1 { + f = Self::mul_by_023(&f, &lines.pop().unwrap()); + } + for chunk in lines.chunks(2) { + if let [line0, line1] = chunk { + let prod = Self::mul_023_by_023(line0, line1); + f = Self::mul_by_02345(&f, &prod); + } else { + panic!("lines.len() % 2 should be 0 at this point"); + } + } + f + } + + /// The expected output of this function when running the Miller loop with embedded exponent is + /// c^3 * l_{3Q} + fn pre_loop( + Q_acc: Vec>, + Q: &[AffinePoint], + c: Option, + xy_fracs: &[(Self::Fp, Self::Fp)], + ) -> (Self::Fp12, Vec>) { + let mut f = if let Some(mut c) = c { + // for the miller loop with embedded exponent, f will be set to c at the beginning of + // the function, and we will multiply by c again due to the last two values + // of the pseudo-binary encoding (BLS12_381_PSEUDO_BINARY_ENCODING) being 1. + // Therefore, the final value of f at the end of this block is c^3. + let mut c3 = c.clone(); + c.square_assign(); + c3 *= &c; + c3 + } else { + Self::Fp12::ONE + }; + + let mut Q_acc = Q_acc; + + // Special case the first iteration of the Miller loop with pseudo_binary_encoding = 1: + // this means that the first step is a double and add, but we need to separate the two steps + // since the optimized `miller_double_and_add_step` will fail because Q_acc is equal + // to Q_signed on the first iteration + let (Q_out_double, lines_2S) = Q_acc + .into_iter() + .map(|Q| Self::miller_double_step(&Q)) + .unzip::<_, _, Vec<_>, Vec<_>>(); + Q_acc = Q_out_double; + + let mut initial_lines = Vec::>::new(); + + let lines_iter = izip!(lines_2S.iter(), xy_fracs.iter()); + for (line_2S, xy_frac) in lines_iter { + let line = line_2S.evaluate(xy_frac); + initial_lines.push(line); + } + + let (Q_out_add, lines_S_plus_Q) = Q_acc + .iter() + .zip(Q.iter()) + .map(|(Q_acc, Q)| Self::miller_add_step(Q_acc, Q)) + .unzip::<_, _, Vec<_>, Vec<_>>(); + Q_acc = Q_out_add; + + let lines_iter = izip!(lines_S_plus_Q.iter(), xy_fracs.iter()); + for (lines_S_plus_Q, xy_frac) in lines_iter { + let line = lines_S_plus_Q.evaluate(xy_frac); + initial_lines.push(line); + } + + f = Self::evaluate_lines_vec(f, initial_lines); + + (f, Q_acc) + } + + /// After running the main body of the Miller loop, we conjugate f due to the curve seed x being + /// negative. + fn post_loop( + f: &Self::Fp12, + Q_acc: Vec>, + _Q: &[AffinePoint], + _c: Option, + _xy_fracs: &[(Self::Fp, Self::Fp)], + ) -> (Self::Fp12, Vec>) { + // Conjugate for negative component of the seed + // By Lemma 1 from https://www.iacr.org/archive/eurocrypt2011/66320047/66320047.pdf f_{x,Q} = conjugate( f_{|x|,Q} ) + let mut f = f.clone(); + f.conjugate_assign(); + (f, Q_acc) + } +} + +#[allow(non_snake_case)] +impl PairingCheck for Bls12_381 { + type Fp = Fp; + type Fp2 = Fp2; + type Fp12 = Fp12; + + #[allow(unused_variables)] + fn pairing_check_hint( + P: &[AffinePoint], + Q: &[AffinePoint], + ) -> (Self::Fp12, Self::Fp12) { + #[cfg(not(target_os = "zkvm"))] + { + #[cfg(not(feature = "halo2curves"))] + panic!("`halo2curves` feature must be enabled to use pairing check hint on host"); + + #[cfg(feature = "halo2curves")] + { + let p_halo2 = P + .iter() + .map(|p| { + AffinePoint::new( + convert_bls12381_fp_to_halo2_fq(p.x.clone()), + convert_bls12381_fp_to_halo2_fq(p.y.clone()), + ) + }) + .collect::>(); + let q_halo2 = Q + .iter() + .map(|q| { + AffinePoint::new( + convert_bls12381_fp2_to_halo2_fq2(q.x.clone()), + convert_bls12381_fp2_to_halo2_fq2(q.y.clone()), + ) + }) + .collect::>(); + let fq12 = Halo2CurvesBls12_381::multi_miller_loop(&p_halo2, &q_halo2); + let (c_fq12, s_fq12) = Halo2CurvesBls12_381::final_exp_hint(&fq12); + let c = convert_bls12381_halo2_fq12_to_fp12(c_fq12); + let s = convert_bls12381_halo2_fq12_to_fp12(s_fq12); + (c, s) + } + } + #[cfg(target_os = "zkvm")] + { + let hint = MaybeUninit::<(Fp12, Fp12)>::uninit(); + // We do not rely on the slice P's memory layout since rust does not guarantee it across + // compiler versions. + let p_fat_ptr = (P.as_ptr() as u32, P.len() as u32); + let q_fat_ptr = (Q.as_ptr() as u32, Q.len() as u32); + unsafe { + custom_insn_r!( + opcode = OPCODE, + funct3 = PAIRING_FUNCT3, + funct7 = ((Bls12_381::PAIRING_IDX as u8) * PairingBaseFunct7::PAIRING_MAX_KINDS + PairingBaseFunct7::HintFinalExp as u8), + rd = Const "x0", + rs1 = In &p_fat_ptr, + rs2 = In &q_fat_ptr + ); + let ptr = hint.as_ptr() as *const u8; + hint_buffer_u32!(ptr, (48 * 12 * 2) / 4); + hint.assume_init() + } + } + } + + fn pairing_check( + P: &[AffinePoint], + Q: &[AffinePoint], + ) -> Result<(), PairingCheckError> { + Self::try_honest_pairing_check(P, Q).unwrap_or_else(|| { + let f = Self::multi_miller_loop(P, Q); + exp_check_fallback(&f, &Self::FINAL_EXPONENT) + }) + } +} + +#[allow(non_snake_case)] +impl Bls12_381 { + // The paper only describes the implementation for Bn254, so we use the gnark implementation for + // Bls12_381. Adapted from the gnark implementation: + // https://github.com/Consensys/gnark/blob/af754dd1c47a92be375930ae1abfbd134c5310d8/std/algebra/emulated/fields_bls12381/e12_pairing.go#L394C1-L395C1 + fn try_honest_pairing_check( + P: &[AffinePoint<::Fp>], + Q: &[AffinePoint<::Fp2>], + ) -> Option> { + let (c, s) = Self::pairing_check_hint(P, Q); + + // The gnark implementation checks that f * s = c^{q - x} where x is the curve seed. + // We check an equivalent condition: f * c^x * s = c^q. + // This is because we can compute f * c^x by embedding the c^x computation in the miller + // loop. + + // We compute c^q before c is consumed by conjugate() below + let c_q = FieldExtension::frobenius_map(&c, 1); + + // Since the Bls12_381 curve has a negative seed, the miller loop for Bls12_381 is computed + // as f_{Miller,x,Q}(P) = conjugate( f_{Miller,-x,Q}(P) * c^{-x} ). + // We will pass in the conjugate inverse of c into the miller loop so that we compute + // fc = conjugate( f_{Miller,-x,Q}(P) * c'^{-x} ) (where c' is the conjugate inverse of c) + // = f_{Miller,x,Q}(P) * c^x + let c_conj = c.conjugate(); + if c_conj == Fp12::ZERO { + return None; + } + let c_conj_inv = Fp12::ONE.div_unsafe(&c_conj); + let fc = Self::multi_miller_loop_embedded_exp(P, Q, Some(c_conj_inv)); + + if fc * s == c_q { + Some(Ok(())) + } else { + None + } + } +} 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/guest-libs/pairing/src/bls12_381/utils.rs b/guest-libs/pairing/src/bls12_381/utils.rs new file mode 100644 index 0000000000..51c749c596 --- /dev/null +++ b/guest-libs/pairing/src/bls12_381/utils.rs @@ -0,0 +1,49 @@ +use halo2curves_axiom::bls12_381::{Fq, Fq12, Fq2, G2Affine}; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; +use openvm_ecc_guest::weierstrass::WeierstrassPoint; + +use super::{Fp, Fp12, Fp2}; +use crate::bls12_381::G2Affine as OpenVmG2Affine; + +pub(crate) fn convert_bls12381_halo2_fq_to_fp(x: Fq) -> Fp { + let bytes = x.to_bytes(); + Fp::from_le_bytes(&bytes) +} + +pub(crate) fn convert_bls12381_halo2_fq2_to_fp2(x: Fq2) -> Fp2 { + Fp2::new( + convert_bls12381_halo2_fq_to_fp(x.c0), + convert_bls12381_halo2_fq_to_fp(x.c1), + ) +} + +pub(crate) fn convert_bls12381_halo2_fq12_to_fp12(x: Fq12) -> Fp12 { + Fp12 { + c: x.to_coeffs().map(convert_bls12381_halo2_fq2_to_fp2), + } +} + +pub(crate) fn convert_bls12381_fp_to_halo2_fq(x: Fp) -> Fq { + Fq::from_bytes(&x.0).unwrap() +} + +pub(crate) fn convert_bls12381_fp2_to_halo2_fq2(x: Fp2) -> Fq2 { + Fq2 { + c0: convert_bls12381_fp_to_halo2_fq(x.c0.clone()), + c1: convert_bls12381_fp_to_halo2_fq(x.c1.clone()), + } +} + +#[allow(unused)] +pub(crate) fn convert_bls12381_fp12_to_halo2_fq12(x: Fp12) -> Fq12 { + let c = x.to_coeffs(); + Fq12::from_coeffs(c.map(convert_bls12381_fp2_to_halo2_fq2)) +} + +#[allow(unused)] +pub(crate) fn convert_g2_affine_halo2_to_openvm(p: G2Affine) -> OpenVmG2Affine { + OpenVmG2Affine::from_xy_unchecked( + convert_bls12381_halo2_fq2_to_fp2(p.x), + convert_bls12381_halo2_fq2_to_fp2(p.y), + ) +} diff --git a/guest-libs/pairing/src/bn254/fp12.rs b/guest-libs/pairing/src/bn254/fp12.rs new file mode 100644 index 0000000000..66fb188421 --- /dev/null +++ b/guest-libs/pairing/src/bn254/fp12.rs @@ -0,0 +1,218 @@ +extern crate alloc; + +use alloc::vec::Vec; +use core::ops::{Mul, MulAssign, Neg}; + +use openvm_algebra_guest::{ + field::{ComplexConjugate, FieldExtension}, + DivAssignUnsafe, DivUnsafe, Field, +}; +use openvm_pairing_guest::pairing::PairingIntrinsics; + +use super::{Bn254, Fp, Fp2}; +use crate::operations::{fp12_invert_assign, SexticExtField}; + +pub type Fp12 = SexticExtField; + +impl Fp12 { + pub fn invert(&self) -> Self { + let mut s = self.clone(); + fp12_invert_assign::(&mut s.c, &Bn254::XI); + s + } +} + +impl Field for Fp12 { + type SelfRef<'a> = &'a Self; + const ZERO: Self = Self::new([Fp2::ZERO; 6]); + const ONE: Self = Self::new([ + Fp2::ONE, + Fp2::ZERO, + Fp2::ZERO, + Fp2::ZERO, + Fp2::ZERO, + Fp2::ZERO, + ]); + + fn double_assign(&mut self) { + *self += self.clone(); + } + + fn square_assign(&mut self) { + *self *= self.clone(); + } +} + +impl FieldExtension for Fp12 { + const D: usize = 6; + type Coeffs = [Fp2; 6]; + + fn from_coeffs(coeffs: Self::Coeffs) -> Self { + Self::new(coeffs) + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 384); + Self::from_coeffs([ + Fp2::from_bytes(&bytes[0..64]), + Fp2::from_bytes(&bytes[64..128]), + Fp2::from_bytes(&bytes[128..192]), + Fp2::from_bytes(&bytes[192..256]), + Fp2::from_bytes(&bytes[256..320]), + Fp2::from_bytes(&bytes[320..384]), + ]) + } + + fn to_coeffs(self) -> Self::Coeffs { + self.c + } + + fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(384); + for coeff in self.clone().to_coeffs() { + bytes.extend_from_slice(&coeff.to_bytes()); + } + bytes + } + + fn embed(c0: Fp2) -> Self { + Self::new([c0, Fp2::ZERO, Fp2::ZERO, Fp2::ZERO, Fp2::ZERO, Fp2::ZERO]) + } + + /// We assume that the frobenius map power is < 12 + fn frobenius_map(&self, power: usize) -> Self { + if power & 1 != 0 { + let c0 = self.c[0].clone().conjugate(); + let c1 = self.c[1].clone().conjugate() * &Bn254::FROBENIUS_COEFFS[power][0]; + let c2 = self.c[2].clone().conjugate() * &Bn254::FROBENIUS_COEFFS[power][1]; + let c3 = self.c[3].clone().conjugate() * &Bn254::FROBENIUS_COEFFS[power][2]; + let c4 = self.c[4].clone().conjugate() * &Bn254::FROBENIUS_COEFFS[power][3]; + let c5 = self.c[5].clone().conjugate() * &Bn254::FROBENIUS_COEFFS[power][4]; + Self::new([c0, c1, c2, c3, c4, c5]) + } else { + let c0 = self.c[0].clone(); + let c1 = &self.c[1] * &Bn254::FROBENIUS_COEFFS[power][0]; + let c2 = &self.c[2] * &Bn254::FROBENIUS_COEFFS[power][1]; + let c3 = &self.c[3] * &Bn254::FROBENIUS_COEFFS[power][2]; + let c4 = &self.c[4] * &Bn254::FROBENIUS_COEFFS[power][3]; + let c5 = &self.c[5] * &Bn254::FROBENIUS_COEFFS[power][4]; + Self::new([c0, c1, c2, c3, c4, c5]) + } + } + + fn mul_base(&self, rhs: &Fp2) -> Self { + Self::new([ + &self.c[0] * rhs, + &self.c[1] * rhs, + &self.c[2] * rhs, + &self.c[3] * rhs, + &self.c[4] * rhs, + &self.c[5] * rhs, + ]) + } +} + +// This is ambiguous. It is conjugation for Fp12 over Fp6. +impl ComplexConjugate for Fp12 { + #[inline(always)] + fn conjugate(self) -> Self { + let [c0, c1, c2, c3, c4, c5] = self.c; + Self::new([c0, -c1, c2, -c3, c4, -c5]) + } + + fn conjugate_assign(&mut self) { + self.c[1].neg_assign(); + self.c[3].neg_assign(); + self.c[5].neg_assign(); + } +} + +impl<'a> MulAssign<&'a Fp12> for Fp12 { + #[inline(always)] + fn mul_assign(&mut self, other: &'a Fp12) { + *self = crate::operations::sextic_tower_mul(self, other, &Bn254::XI); + } +} + +impl<'a> Mul<&'a Fp12> for &'a Fp12 { + type Output = Fp12; + #[inline(always)] + fn mul(self, other: &'a Fp12) -> Self::Output { + crate::operations::sextic_tower_mul(self, other, &Bn254::XI) + } +} + +impl MulAssign for Fp12 { + #[inline(always)] + fn mul_assign(&mut self, other: Self) { + self.mul_assign(&other); + } +} + +impl Mul for Fp12 { + type Output = Self; + #[inline(always)] + fn mul(mut self, other: Self) -> Self::Output { + self *= other; + self + } +} + +impl<'a> Mul<&'a Fp12> for Fp12 { + type Output = Self; + #[inline(always)] + fn mul(mut self, other: &'a Fp12) -> Fp12 { + self *= other; + self + } +} + +impl<'a> DivAssignUnsafe<&'a Fp12> for Fp12 { + #[inline(always)] + fn div_assign_unsafe(&mut self, other: &'a Fp12) { + *self *= other.invert(); + } +} + +impl<'a> DivUnsafe<&'a Fp12> for &'a Fp12 { + type Output = Fp12; + #[inline(always)] + fn div_unsafe(self, other: &'a Fp12) -> Self::Output { + let mut res = self.clone(); + res.div_assign_unsafe(other); + res + } +} + +impl DivAssignUnsafe for Fp12 { + #[inline(always)] + fn div_assign_unsafe(&mut self, other: Self) { + *self *= other.invert(); + } +} + +impl DivUnsafe for Fp12 { + type Output = Self; + #[inline(always)] + fn div_unsafe(mut self, other: Self) -> Self::Output { + self.div_assign_unsafe(other); + self + } +} + +impl<'a> DivUnsafe<&'a Fp12> for Fp12 { + type Output = Self; + #[inline(always)] + fn div_unsafe(mut self, other: &'a Fp12) -> Self::Output { + self.div_assign_unsafe(other); + self + } +} + +impl Neg for Fp12 { + type Output = Fp12; + #[inline(always)] + fn neg(self) -> Self::Output { + Self::ZERO - &self + } +} diff --git a/guest-libs/pairing/src/bn254/fp2.rs b/guest-libs/pairing/src/bn254/fp2.rs new file mode 100644 index 0000000000..0df1f9525c --- /dev/null +++ b/guest-libs/pairing/src/bn254/fp2.rs @@ -0,0 +1,74 @@ +extern crate alloc; + +use alloc::vec::Vec; +use core::ops::Neg; + +use openvm_algebra_complex_macros::{complex_declare, complex_impl_field}; +use openvm_algebra_guest::{field::FieldExtension, Field, IntMod}; + +use super::Fp; + +// The struct name needs to be globally unique for linking purposes. +// The mod_type is a path used only in the struct definition. +complex_declare! { + Bn254Fp2 { mod_type = Fp } +} + +complex_impl_field! { + Bn254Fp2, +} + +pub type Fp2 = Bn254Fp2; + +impl FieldExtension for Fp2 { + const D: usize = 2; + type Coeffs = [Fp; 2]; + + fn from_coeffs([c0, c1]: Self::Coeffs) -> Self { + Self { c0, c1 } + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 64); + Self::from_coeffs([ + Fp::from_const_bytes(bytes[0..32].try_into().unwrap()), + Fp::from_const_bytes(bytes[32..64].try_into().unwrap()), + ]) + } + + fn to_coeffs(self) -> Self::Coeffs { + [self.c0, self.c1] + } + + fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(64); + bytes.extend_from_slice(self.c0.as_le_bytes()); + bytes.extend_from_slice(self.c1.as_le_bytes()); + bytes + } + + fn embed(c0: Fp) -> Self { + Self { + c0, + c1: ::ZERO, + } + } + + fn frobenius_map(&self, power: usize) -> Self { + if power % 2 == 0 { + self.clone() + } else { + Self { + c0: self.c0.clone(), + c1: (&self.c1).neg(), + } + } + } + + fn mul_base(&self, rhs: &Fp) -> Self { + Self { + c0: &self.c0 * rhs, + c1: &self.c1 * rhs, + } + } +} 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/guest-libs/pairing/src/bn254/pairing.rs b/guest-libs/pairing/src/bn254/pairing.rs new file mode 100644 index 0000000000..c0f1cc35f2 --- /dev/null +++ b/guest-libs/pairing/src/bn254/pairing.rs @@ -0,0 +1,383 @@ +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 { + 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}; +#[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, +}; + +impl Evaluatable for UnevaluatedLine { + fn evaluate(&self, xy_frac: &(Fp, Fp)) -> EvaluatedLine { + let (x_over_y, y_inv) = xy_frac; + // Represents the line L(x,y) = 1 + b (x/y) w^1 + c (1/y) w^3 + EvaluatedLine { + b: self.b.mul_base(x_over_y), + c: self.c.mul_base(y_inv), + } + } +} + +impl FromLineDType for Fp12 { + fn from_evaluated_line_d_type(line: EvaluatedLine) -> Fp12 { + FieldExtension::::from_coeffs([ + Fp2::ONE, + line.b, + Fp2::ZERO, + line.c, + Fp2::ZERO, + Fp2::ZERO, + ]) + } +} + +// TODO[jpw]: make this into a macro depending on P::PAIRING_IDX when we have more curves +impl LineMulDType for Bn254 { + /// Multiplies two lines in 013-form to get an element in 01234-form + fn mul_013_by_013(l0: &EvaluatedLine, l1: &EvaluatedLine) -> [Fp2; 5] { + let b0 = &l0.b; + let c0 = &l0.c; + let b1 = &l1.b; + let c1 = &l1.c; + + // where w⁶ = xi + // l0 * l1 = 1 + (b0 + b1)w + (b0b1)w² + (c0 + c1)w³ + (b0c1 + b1c0)w⁴ + (c0c1)w⁶ + // = (1 + c0c1 * xi) + (b0 + b1)w + (b0b1)w² + (c0 + c1)w³ + (b0c1 + b1c0)w⁴ + let x0 = Fp2::ONE + c0 * c1 * &Bn254::XI; + let x1 = b0 + b1; + let x2 = b0 * b1; + let x3 = c0 + c1; + let x4 = b0 * c1 + b1 * c0; + + [x0, x1, x2, x3, x4] + } + + /// Multiplies a line in 013-form with a Fp12 element to get an Fp12 element + fn mul_by_013(f: &Fp12, l: &EvaluatedLine) -> Fp12 { + Fp12::from_evaluated_line_d_type(l.clone()) * f + } + + /// Multiplies a line in 01234-form with a Fp12 element to get an Fp12 element + fn mul_by_01234(f: &Fp12, x: &[Fp2; 5]) -> Fp12 { + // we update the order of the coefficients to match the Fp12 coefficient ordering: + // Fp12 { + // c0: Fp6 { + // c0: x0, + // c1: x2, + // c2: x4, + // }, + // c1: Fp6 { + // c0: x1, + // c1: x3, + // c2: x5, + // }, + // } + let o0 = &x[0]; + let o1 = &x[2]; + let o2 = &x[4]; + let o3 = &x[1]; + let o4 = &x[3]; + + let xi = &Bn254::XI; + + let self_coeffs = &f.c; + let s0 = &self_coeffs[0]; + let s1 = &self_coeffs[2]; + let s2 = &self_coeffs[4]; + let s3 = &self_coeffs[1]; + let s4 = &self_coeffs[3]; + let s5 = &self_coeffs[5]; + + // NOTE[yj]: Hand-calculated multiplication for Fp12 * 01234 ∈ Fp2; this is likely not the + // most efficient implementation c00 = cs0co0 + xi(cs1co2 + cs2co1 + cs4co4 + + // cs5co3) c01 = cs0co1 + cs1co0 + cs3co3 + xi(cs2co2 + cs5co4) + // c02 = cs0co2 + cs1co1 + cs2co0 + cs3co4 + cs4co3 + // c10 = cs0co3 + cs3co0 + xi(cs2co4 + cs4co2 + cs5co1) + // c11 = cs0co4 + cs1co3 + cs3co1 + cs4co0 + xi(cs5co2) + // c12 = cs1co4 + cs2co3 + cs3co2 + cs4co1 + cs5co0 + let c00 = s0 * o0 + xi * &(s1 * o2 + s2 * o1 + s4 * o4 + s5 * o3); + let c01 = s0 * o1 + s1 * o0 + s3 * o3 + xi * &(s2 * o2 + s5 * o4); + let c02 = s0 * o2 + s1 * o1 + s2 * o0 + s3 * o4 + s4 * o3; + let c10 = s0 * o3 + s3 * o0 + xi * &(s2 * o4 + s4 * o2 + s5 * o1); + let c11 = s0 * o4 + s1 * o3 + s3 * o1 + s4 * o0 + xi * &(s5 * o2); + let c12 = s1 * o4 + s2 * o3 + s3 * o2 + s4 * o1 + s5 * o0; + + Fp12::from_coeffs([c00, c10, c01, c11, c02, c12]) + } +} + +#[allow(non_snake_case)] +impl MultiMillerLoop for Bn254 { + type Fp = Fp; + type Fp12 = Fp12; + + const SEED_ABS: u64 = BN254_SEED; + const PSEUDO_BINARY_ENCODING: &[i8] = &BN254_PSEUDO_BINARY_ENCODING; + + fn evaluate_lines_vec(f: Self::Fp12, lines: Vec>) -> Self::Fp12 { + let mut f = f; + let mut lines = lines; + if lines.len() % 2 == 1 { + f = Self::mul_by_013(&f, &lines.pop().unwrap()); + } + for chunk in lines.chunks(2) { + if let [line0, line1] = chunk { + let prod = Self::mul_013_by_013(line0, line1); + f = Self::mul_by_01234(&f, &prod); + } else { + panic!("lines.len() % 2 should be 0 at this point"); + } + } + f + } + + /// The expected output of this function when running the Miller loop with embedded exponent is + /// c^2 * l_{2Q} + fn pre_loop( + Q_acc: Vec>, + _Q: &[AffinePoint], + c: Option, + xy_fracs: &[(Self::Fp, Self::Fp)], + ) -> (Self::Fp12, Vec>) { + let mut f = if let Some(mut c) = c { + // for the miller loop with embedded exponent, f will be set to c at the beginning of + // the function, and we will square c due to the last two values of the + // pseudo-binary encoding (BN254_PSEUDO_BINARY_ENCODING) being 0 and 1. + // Therefore, the final value of f at the end of this block is c^2. + c.square_assign(); + c + } else { + Self::Fp12::ONE + }; + + let mut Q_acc = Q_acc; + let mut initial_lines = Vec::>::new(); + + // We don't need to special case the first iteration for Bn254, but since we are using the + // same Miller loop implementation for both Bn254 and Bls12_381, we need to do the + // first iteration separately here. + let (Q_out_double, lines_2S) = Q_acc + .into_iter() + .map(|Q| Self::miller_double_step(&Q)) + .unzip::<_, _, Vec<_>, Vec<_>>(); + Q_acc = Q_out_double; + + let lines_iter = izip!(lines_2S.iter(), xy_fracs.iter()); + for (line_2S, xy_frac) in lines_iter { + let line = line_2S.evaluate(xy_frac); + initial_lines.push(line); + } + + f = Self::evaluate_lines_vec(f, initial_lines); + + (f, Q_acc) + } + + /// Compute f_{Miller,Q}(P) from f_{6x+2,Q}(P) + fn post_loop( + f: &Self::Fp12, + Q_acc: Vec>, // at this point, Q_acc = (6x+2)Q + Q: &[AffinePoint], + _c: Option, + xy_fracs: &[(Self::Fp, Self::Fp)], + ) -> (Self::Fp12, Vec>) { + let mut Q_acc = Q_acc; + let mut lines = Vec::>::new(); + + let x_to_q_minus_1_over_3 = &Self::FROBENIUS_COEFF_FQ6_C1[1]; + let x_to_q_sq_minus_1_over_3 = &Self::FROBENIUS_COEFF_FQ6_C1[2]; + + // For each q, compute q1 such that `frob_p(twist(q)) = twist(q1)` + let q1_vec = Q + .iter() + .map(|Q| { + let x = Q.x.frobenius_map(1); + let x = x * x_to_q_minus_1_over_3; + let y = Q.y.frobenius_map(1); + let y = y * &Self::XI_TO_Q_MINUS_1_OVER_2; + AffinePoint { x, y } + }) + .collect::>(); + + // compute l_{(6x+2)\Psi(Q), \phi_p(\Psi(Q))} where \phi_p is the Frobenius map + let (Q_out_add, lines_S_plus_Q) = Q_acc + .iter() + .zip(q1_vec.iter()) + .map(|(Q_acc, q1)| Self::miller_add_step(Q_acc, q1)) + .unzip::<_, _, Vec<_>, Vec<_>>(); + Q_acc = Q_out_add; + + let lines_iter = izip!(lines_S_plus_Q.iter(), xy_fracs.iter()); + for (lines_S_plus_Q, xy_frac) in lines_iter { + let line = lines_S_plus_Q.evaluate(xy_frac); + lines.push(line); + } + + // For each q, compute q2 such that `-frob_p^2(twist(q)) = twist(q2)` + let q2_vec = Q + .iter() + .map(|Q| { + // There is a frobenius mapping π²(Q) that we skip here since it is equivalent to + // the identity mapping + let x = &Q.x * x_to_q_sq_minus_1_over_3; + AffinePoint { x, y: Q.y.clone() } + }) + .collect::>(); + + // compute l_{(6x+2)\Psi(Q) + \phi_p(\Psi(Q)), -(\phi_p)^2(\Psi(Q))} where \phi_p is the + // Frobenius map + let (Q_out_add, lines_S_plus_Q) = Q_acc + .iter() + .zip(q2_vec.iter()) + .map(|(Q_acc, q2)| Self::miller_add_step(Q_acc, q2)) + .unzip::<_, _, Vec<_>, Vec<_>>(); + Q_acc = Q_out_add; + + let lines_iter = izip!(lines_S_plus_Q.iter(), xy_fracs.iter()); + for (lines_S_plus_Q, xy_frac) in lines_iter { + let line = lines_S_plus_Q.evaluate(xy_frac); + lines.push(line); + } + + let mut f = f.clone(); + f = Self::evaluate_lines_vec(f, lines); + + (f, Q_acc) + } +} + +#[allow(non_snake_case)] +impl PairingCheck for Bn254 { + type Fp = Fp; + type Fp2 = Fp2; + type Fp12 = Fp12; + + #[allow(unused_variables)] + fn pairing_check_hint( + P: &[AffinePoint], + Q: &[AffinePoint], + ) -> (Self::Fp12, Self::Fp12) { + #[cfg(not(target_os = "zkvm"))] + { + #[cfg(not(feature = "halo2curves"))] + panic!("`halo2curves` feature must be enabled to use pairing check hint on host"); + + #[cfg(feature = "halo2curves")] + { + let p_halo2 = P + .iter() + .map(|p| { + AffinePoint::new( + convert_bn254_fp_to_halo2_fq(p.x.clone()), + convert_bn254_fp_to_halo2_fq(p.y.clone()), + ) + }) + .collect::>(); + let q_halo2 = Q + .iter() + .map(|q| { + AffinePoint::new( + convert_bn254_fp2_to_halo2_fq2(q.x.clone()), + convert_bn254_fp2_to_halo2_fq2(q.y.clone()), + ) + }) + .collect::>(); + let fq12 = Halo2CurvesBn254::multi_miller_loop(&p_halo2, &q_halo2); + let (c_fq12, s_fq12) = Halo2CurvesBn254::final_exp_hint(&fq12); + let c = convert_bn254_halo2_fq12_to_fp12(c_fq12); + let s = convert_bn254_halo2_fq12_to_fp12(s_fq12); + (c, s) + } + } + #[cfg(target_os = "zkvm")] + { + let hint = MaybeUninit::<(Fp12, Fp12)>::uninit(); + // We do not rely on the slice P's memory layout since rust does not guarantee it across + // compiler versions. + let p_fat_ptr = (P.as_ptr() as u32, P.len() as u32); + let q_fat_ptr = (Q.as_ptr() as u32, Q.len() as u32); + unsafe { + custom_insn_r!( + opcode = OPCODE, + funct3 = PAIRING_FUNCT3, + funct7 = ((Bn254::PAIRING_IDX as u8) * PairingBaseFunct7::PAIRING_MAX_KINDS + PairingBaseFunct7::HintFinalExp as u8), + rd = Const "x0", + rs1 = In &p_fat_ptr, + rs2 = In &q_fat_ptr + ); + let ptr = hint.as_ptr() as *const u8; + hint_buffer_u32!(ptr, (32 * 12 * 2) / 4); + hint.assume_init() + } + } + } + + fn pairing_check( + P: &[AffinePoint], + Q: &[AffinePoint], + ) -> Result<(), PairingCheckError> { + Self::try_honest_pairing_check(P, Q).unwrap_or_else(|| { + let f = Self::multi_miller_loop(P, Q); + exp_check_fallback(&f, &Self::FINAL_EXPONENT) + }) + } +} + +#[allow(non_snake_case)] +impl Bn254 { + fn try_honest_pairing_check( + P: &[AffinePoint<::Fp>], + Q: &[AffinePoint<::Fp2>], + ) -> Option> { + let (c, u) = Self::pairing_check_hint(P, Q); + if c == Fp12::ZERO { + return None; + } + let c_inv = Fp12::ONE.div_unsafe(&c); + + // We follow Theorem 3 of https://eprint.iacr.org/2024/640.pdf to check that the pairing equals 1 + // By the theorem, it suffices to provide c and u such that f * u == c^λ. + // Since λ = 6x + 2 + q^3 - q^2 + q, we will check the equivalent condition: + // f * c^-{6x + 2} * u * c^-{q^3 - q^2 + q} == 1 + // This is because we can compute f * c^-{6x+2} by embedding the c^-{6x+2} computation in + // the miller loop. + + // c_mul = c^-{q^3 - q^2 + q} + let c_q3_inv = FieldExtension::frobenius_map(&c_inv, 3); + let c_q2 = FieldExtension::frobenius_map(&c, 2); + let c_q_inv = FieldExtension::frobenius_map(&c_inv, 1); + let c_mul = c_q3_inv * c_q2 * c_q_inv; + + // Pass c inverse into the miller loop so that we compute fc == f * c^-{6x + 2} + let fc = Self::multi_miller_loop_embedded_exp(P, Q, Some(c_inv)); + + if fc * c_mul * u == Fp12::ONE { + Some(Ok(())) + } else { + None + } + } +} 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/guest-libs/pairing/src/bn254/utils.rs b/guest-libs/pairing/src/bn254/utils.rs new file mode 100644 index 0000000000..9102e980db --- /dev/null +++ b/guest-libs/pairing/src/bn254/utils.rs @@ -0,0 +1,49 @@ +use halo2curves_axiom::bn256::{Fq, Fq12, Fq2, G2Affine}; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; +use openvm_ecc_guest::weierstrass::WeierstrassPoint; + +use super::{Fp, Fp12, Fp2}; +use crate::bn254::G2Affine as OpenVmG2Affine; + +pub(crate) fn convert_bn254_halo2_fq_to_fp(x: Fq) -> Fp { + let bytes = x.to_bytes(); + Fp::from_le_bytes(&bytes) +} + +pub(crate) fn convert_bn254_halo2_fq2_to_fp2(x: Fq2) -> Fp2 { + Fp2::new( + convert_bn254_halo2_fq_to_fp(x.c0), + convert_bn254_halo2_fq_to_fp(x.c1), + ) +} + +pub(crate) fn convert_bn254_halo2_fq12_to_fp12(x: Fq12) -> Fp12 { + Fp12 { + c: x.to_coeffs().map(convert_bn254_halo2_fq2_to_fp2), + } +} + +pub(crate) fn convert_bn254_fp_to_halo2_fq(x: Fp) -> Fq { + Fq::from_bytes(&x.0).unwrap() +} + +pub(crate) fn convert_bn254_fp2_to_halo2_fq2(x: Fp2) -> Fq2 { + Fq2 { + c0: convert_bn254_fp_to_halo2_fq(x.c0.clone()), + c1: convert_bn254_fp_to_halo2_fq(x.c1.clone()), + } +} + +#[allow(unused)] +pub(crate) fn convert_bn254_fp12_to_halo2_fq12(x: Fp12) -> Fq12 { + let c = x.to_coeffs(); + Fq12::from_coeffs(c.map(convert_bn254_fp2_to_halo2_fq2)) +} + +#[allow(unused)] +pub(crate) fn convert_g2_affine_halo2_to_openvm(p: G2Affine) -> OpenVmG2Affine { + OpenVmG2Affine::from_xy_unchecked( + convert_bn254_halo2_fq2_to_fp2(p.x), + convert_bn254_halo2_fq2_to_fp2(p.y), + ) +} diff --git a/guest-libs/pairing/src/lib.rs b/guest-libs/pairing/src/lib.rs new file mode 100644 index 0000000000..8e9e8b2436 --- /dev/null +++ b/guest-libs/pairing/src/lib.rs @@ -0,0 +1,15 @@ +#![no_std] + +mod operations; + +#[allow(unused_imports)] +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/guest-libs/pairing/src/operations/fp12.rs b/guest-libs/pairing/src/operations/fp12.rs new file mode 100644 index 0000000000..de72db0c0d --- /dev/null +++ b/guest-libs/pairing/src/operations/fp12.rs @@ -0,0 +1,34 @@ +use openvm_algebra_guest::{field::FieldExtension, Field, IntMod}; + +use super::{ + fp6_invert_assign, fp6_mul_assign, fp6_mul_by_nonresidue_assign, fp6_square_assign, + fp6_sub_assign, +}; + +pub(crate) fn fp12_invert_assign< + Fp: IntMod + Field, + Fp2: Field + FieldExtension, +>( + c: &mut [Fp2; 6], + xi: &Fp2, +) { + let mut c0s = [c[0].clone(), c[2].clone(), c[4].clone()]; + let mut c1s = [c[1].clone(), c[3].clone(), c[5].clone()]; + + fp6_square_assign(&mut c0s, xi); + fp6_square_assign(&mut c1s, xi); + fp6_mul_by_nonresidue_assign(&mut c1s, xi); + fp6_sub_assign(&mut c0s, &c1s); + + fp6_invert_assign(&mut c0s, xi); + let mut t0 = c0s.clone(); + let mut t1 = c0s; + fp6_mul_assign(&mut t0, &[c[0].clone(), c[2].clone(), c[4].clone()], xi); + fp6_mul_assign(&mut t1, &[c[1].clone(), c[3].clone(), c[5].clone()], xi); + c[0] = t0[0].clone(); + c[2] = t0[1].clone(); + c[4] = t0[2].clone(); + c[1] = t1[0].clone().neg(); + c[3] = t1[1].clone().neg(); + c[5] = t1[2].clone().neg(); +} diff --git a/guest-libs/pairing/src/operations/fp2.rs b/guest-libs/pairing/src/operations/fp2.rs new file mode 100644 index 0000000000..38a648b88b --- /dev/null +++ b/guest-libs/pairing/src/operations/fp2.rs @@ -0,0 +1,16 @@ +use openvm_algebra_guest::{Field, IntMod}; + +pub(crate) fn fp2_invert_assign(c: &mut [F; 2]) { + let mut t1 = c[1].clone(); + ::square_assign(&mut t1); + let mut t0 = c[0].clone(); + ::square_assign(&mut t0); + t0 += &t1; + t0 = ::ONE.div_unsafe(&t0); + let mut tmp = [c[0].clone(), c[1].clone()]; + tmp[0] *= &t0; + tmp[1] *= &t0; + tmp[1].neg_assign(); + + *c = tmp; +} diff --git a/guest-libs/pairing/src/operations/fp6.rs b/guest-libs/pairing/src/operations/fp6.rs new file mode 100644 index 0000000000..1896d1d1ee --- /dev/null +++ b/guest-libs/pairing/src/operations/fp6.rs @@ -0,0 +1,187 @@ +use openvm_algebra_guest::{field::FieldExtension, Field, IntMod}; + +use super::fp2_invert_assign; + +pub(crate) fn fp6_invert_assign< + Fp: IntMod + Field, + Fp2: Field + FieldExtension, +>( + c: &mut [Fp2; 3], + xi: &Fp2, +) { + let mut c0 = c[2].clone(); + c0 *= xi; + c0 *= &c[1]; + c0 = c0.neg(); + { + let mut c0s = c[0].clone(); + ::square_assign(&mut c0s); + c0 += &c0s; + } + let mut c1 = c[2].clone(); + ::square_assign(&mut c1); + c1 *= xi; + { + let mut c01 = c[0].clone(); + c01 *= &c[1]; + c1 -= &c01; + } + let mut c2 = c[1].clone(); + ::square_assign(&mut c2); + { + let mut c02 = c[0].clone(); + c02 *= &c[2]; + c2 -= &c02; + } + + let mut tmp1 = c[2].clone(); + tmp1 *= &c1; + let mut tmp2 = c[1].clone(); + tmp2 *= &c2; + tmp1 += &tmp2; + tmp1 *= xi; + tmp2 = c[0].clone(); + tmp2 *= &c0; + tmp1 += &tmp2; + + let mut coeffs = tmp1.clone().to_coeffs(); + fp2_invert_assign::(&mut coeffs); + let tmp = Fp2::from_coeffs(coeffs); + let mut tmp = [tmp.clone(), tmp.clone(), tmp.clone()]; + tmp[0] *= &c0; + tmp[1] *= &c1; + tmp[2] *= &c2; + + *c = tmp; +} + +pub(crate) fn fp6_mul_by_nonresidue_assign< + Fp: IntMod + Field, + Fp2: Field + FieldExtension, +>( + c: &mut [Fp2; 3], + xi: &Fp2, +) { + // c0, c1, c2 -> c2, c0, c1 + c.swap(0, 1); + c.swap(0, 2); + c[0] *= xi; +} + +pub(crate) fn fp6_sub_assign< + Fp: IntMod + Field, + Fp2: Field + FieldExtension, +>( + a: &mut [Fp2; 3], + b: &[Fp2; 3], +) { + a.iter_mut().zip(b).for_each(|(a, b)| *a -= b); +} + +/// Squares 3 elements of `Fp2`, which represents as a single Fp6 element, in place +pub(crate) fn fp6_square_assign< + Fp: IntMod + Field, + Fp2: Field + FieldExtension, +>( + c: &mut [Fp2; 3], + xi: &Fp2, +) { + // s0 = a^2 + let mut s0 = c[0].clone(); + ::square_assign(&mut s0); + // s1 = 2ab + let mut ab = c[0].clone(); + ab *= &c[1]; + let mut s1 = ab; + ::double_assign(&mut s1); + // s2 = (a - b + c)^2 + let mut s2 = c[0].clone(); + s2 -= &c[1]; + s2 += &c[2]; + ::square_assign(&mut s2); + // bc + let mut bc = c[1].clone(); + bc *= &c[2]; + // s3 = 2bc + let mut s3 = bc; + ::double_assign(&mut s3); + // s4 = c^2 + let mut s4 = c[2].clone(); + ::square_assign(&mut s4); + + // new c0 = 2bc.mul_by_xi + a^2 + c[0] = s3.clone(); + c[0] *= xi; + c[0] += &s0; + + // new c1 = (c^2).mul_by_xi + 2ab + c[1] = s4.clone(); + c[1] *= xi; + c[1] += &s1; + + // new c2 = 2ab + (a - b + c)^2 + 2bc - a^2 - c^2 = b^2 + 2ac + c[2] = s1; + c[2] += &s2; + c[2] += &s3; + c[2] -= &s0; + c[2] -= &s4; +} + +pub(crate) fn fp6_mul_assign< + Fp: IntMod + Field, + Fp2: Field + FieldExtension, +>( + a: &mut [Fp2; 3], + b: &[Fp2; 3], + xi: &Fp2, +) { + let mut a_a = a[0].clone(); + let mut b_b = a[1].clone(); + let mut c_c = a[2].clone(); + + a_a *= &b[0]; + b_b *= &b[1]; + c_c *= &b[2]; + + let mut t1 = b[1].clone(); + t1 += &b[2]; + { + let mut tmp = a[1].clone(); + tmp += &a[2]; + + t1 *= &tmp; + t1 -= &b_b; + t1 -= &c_c; + t1 *= xi; + t1 += &a_a; + } + + let mut t3 = b[0].clone(); + t3 += &b[2]; + { + let mut tmp = a[0].clone(); + tmp += &a[2]; + + t3 *= &tmp; + t3 -= &a_a; + t3 += &b_b; + t3 -= &c_c; + } + + let mut t2 = b[0].clone(); + t2 += &b[1]; + { + let mut tmp = a[0].clone(); + tmp += &a[1]; + + t2 *= &tmp; + t2 -= &a_a; + t2 -= &b_b; + c_c *= xi; + t2 += &c_c; + } + + a[0] = t1; + a[1] = t2; + a[2] = t3; +} diff --git a/guest-libs/pairing/src/operations/mod.rs b/guest-libs/pairing/src/operations/mod.rs new file mode 100644 index 0000000000..a709784670 --- /dev/null +++ b/guest-libs/pairing/src/operations/mod.rs @@ -0,0 +1,11 @@ +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::*; +#[allow(unused_imports)] +pub(crate) use sextic_ext_field::*; diff --git a/guest-libs/pairing/src/operations/sextic_ext_field.rs b/guest-libs/pairing/src/operations/sextic_ext_field.rs new file mode 100644 index 0000000000..7f75815e05 --- /dev/null +++ b/guest-libs/pairing/src/operations/sextic_ext_field.rs @@ -0,0 +1,158 @@ +use core::{ + fmt::{Debug, Formatter, Result}, + ops::{Add, AddAssign, Sub, SubAssign}, +}; + +use openvm_algebra_guest::field::Field; + +/// Sextic extension field of `F` with irreducible polynomial `X^6 - \xi`. +/// Elements are represented as `c0 + c1 * w + ... + c5 * w^5` where `w^6 = \xi`, where `\xi in F`. +/// +/// Memory alignment follows alignment of `F`. +#[derive(Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[repr(C)] +pub struct SexticExtField { + pub c: [F; 6], +} + +impl SexticExtField { + pub const fn new(c: [F; 6]) -> Self { + Self { c } + } +} + +impl<'a, F: Field> AddAssign<&'a SexticExtField> for SexticExtField { + #[inline(always)] + fn add_assign(&mut self, other: &'a SexticExtField) { + for i in 0..6 { + self.c[i] += &other.c[i]; + } + } +} + +impl<'a, F: Field> Add<&'a SexticExtField> for &SexticExtField { + type Output = SexticExtField; + #[inline(always)] + fn add(self, other: &'a SexticExtField) -> Self::Output { + let mut res = self.clone(); + res += other; + res + } +} + +impl<'a, F: Field> SubAssign<&'a SexticExtField> for SexticExtField { + #[inline(always)] + fn sub_assign(&mut self, other: &'a SexticExtField) { + for i in 0..6 { + self.c[i] -= &other.c[i]; + } + } +} + +impl<'a, F: Field> Sub<&'a SexticExtField> for &SexticExtField { + type Output = SexticExtField; + #[inline(always)] + fn sub(self, other: &'a SexticExtField) -> Self::Output { + let mut res = self.clone(); + res -= other; + res + } +} + +pub(crate) fn sextic_tower_mul( + lhs: &SexticExtField, + rhs: &SexticExtField, + xi: &F, +) -> SexticExtField +where + for<'a> &'a F: core::ops::Mul<&'a F, Output = F>, +{ + // The following multiplication is hand-derived with respect to the basis where degree 6 + // extension is composed of degree 3 extension followed by degree 2 extension. + + // c0 = cs0co0 + xi(cs1co2 + cs2co1 + cs3co5 + cs4co4 + cs5co3) + // c1 = cs0co1 + cs1co0 + cs3co3 + xi(cs2co2 + cs4co5 + cs5co4) + // c2 = cs0co2 + cs1co1 + cs2co0 + cs3co4 + cs4co3 + xi(cs5co5) + // c3 = cs0co3 + cs3co0 + xi(cs1co5 + cs2co4 + cs4co2 + cs5co1) + // c4 = cs0co4 + cs1co3 + cs3co1 + cs4co0 + xi(cs2co5 + cs5co2) + // c5 = cs0co5 + cs1co4 + cs2co3 + cs3co2 + cs4co1 + cs5co0 + // where cs*: lhs.c*, co*: rhs.c* + + let (s0, s1, s2, s3, s4, s5) = ( + &lhs.c[0], &lhs.c[2], &lhs.c[4], &lhs.c[1], &lhs.c[3], &lhs.c[5], + ); + let (o0, o1, o2, o3, o4, o5) = ( + &rhs.c[0], &rhs.c[2], &rhs.c[4], &rhs.c[1], &rhs.c[3], &rhs.c[5], + ); + + let c0 = s0 * o0 + xi * &(s1 * o2 + s2 * o1 + s3 * o5 + s4 * o4 + s5 * o3); + let c1 = s0 * o1 + s1 * o0 + s3 * o3 + xi * &(s2 * o2 + s4 * o5 + s5 * o4); + let c2 = s0 * o2 + s1 * o1 + s2 * o0 + s3 * o4 + s4 * o3 + xi * &(s5 * o5); + let c3 = s0 * o3 + s3 * o0 + xi * &(s1 * o5 + s2 * o4 + s4 * o2 + s5 * o1); + let c4 = s0 * o4 + s1 * o3 + s3 * o1 + s4 * o0 + xi * &(s2 * o5 + s5 * o2); + let c5 = s0 * o5 + s1 * o4 + s2 * o3 + s3 * o2 + s4 * o1 + s5 * o0; + + SexticExtField::new([c0, c3, c1, c4, c2, c5]) +} + +// Auto-derived implementations: + +impl AddAssign for SexticExtField { + #[inline(always)] + fn add_assign(&mut self, other: Self) { + self.add_assign(&other); + } +} + +impl Add for SexticExtField { + type Output = Self; + #[inline(always)] + fn add(mut self, other: Self) -> Self::Output { + self += other; + self + } +} + +impl<'a, F: Field> Add<&'a SexticExtField> for SexticExtField { + type Output = Self; + #[inline(always)] + fn add(mut self, other: &'a SexticExtField) -> Self::Output { + self += other; + self + } +} + +impl SubAssign for SexticExtField { + #[inline(always)] + fn sub_assign(&mut self, other: Self) { + self.sub_assign(&other); + } +} + +impl Sub for SexticExtField { + type Output = Self; + #[inline(always)] + fn sub(mut self, other: Self) -> Self::Output { + self -= other; + self + } +} + +impl<'a, F: Field> Sub<&'a SexticExtField> for SexticExtField { + type Output = Self; + #[inline(always)] + fn sub(mut self, other: &'a SexticExtField) -> Self::Output { + self -= other; + self + } +} + +impl Debug for SexticExtField { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!( + f, + "{:?}, {:?}, {:?}, {:?}, {:?}, {:?}", + self.c[0], self.c[1], self.c[2], self.c[3], self.c[4], self.c[5] + ) + } +} diff --git a/guest-libs/pairing/tests/lib.rs b/guest-libs/pairing/tests/lib.rs new file mode 100644 index 0000000000..e7c189b550 --- /dev/null +++ b/guest-libs/pairing/tests/lib.rs @@ -0,0 +1,909 @@ +#![allow(non_snake_case)] + +#[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, air_test_impl, air_test_with_min_segments}, + }; + use openvm_ecc_circuit::{Rv32WeierstrassConfig, WeierstrassExtension}; + use openvm_ecc_guest::{ + algebra::{field::FieldExtension, IntMod}, + AffinePoint, + }; + use openvm_ecc_transpiler::EccTranspilerExtension; + use openvm_instructions::exe::VmExe; + use openvm_pairing_circuit::{PairingCurve, PairingExtension, Rv32PairingConfig}; + use openvm_pairing_guest::{ + bn254::{BN254_COMPLEX_STRUCT_NAME, BN254_MODULUS}, + halo2curves_shims::bn254::Bn254, + pairing::{EvaluatedLine, FinalExp, LineMulDType, MillerStep, MultiMillerLoop}, + }; + use openvm_pairing_transpiler::PairingTranspilerExtension; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_stark_sdk::{openvm_stark_backend::p3_field::FieldAlgebra, p3_baby_bear::BabyBear}; + use openvm_toolchain_tests::{build_example_program_at_path_with_features, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + use rand::SeedableRng; + + 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_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!("tests/programs"), + "fp12_mul", + ["bn254"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let mut rng = rand::rngs::StdRng::seed_from_u64(2); + let f0 = Fq12::random(&mut rng); + let f1 = Fq12::random(&mut rng); + let r = f0 * f1; + + let io = [f0, f1, r] + .into_iter() + .flat_map(|fp12| fp12.to_coeffs()) + .flat_map(|fp2| fp2.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + 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!("tests/programs"), + "pairing_line", + ["bn254"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let mut rng = rand::rngs::StdRng::seed_from_u64(2); + let a = G2Affine::random(&mut rng); + let b = G2Affine::random(&mut rng); + let c = G2Affine::random(&mut rng); + + let f = Fq12::random(&mut rng); + let l0 = EvaluatedLine:: { b: a.x, c: a.y }; + let l1 = EvaluatedLine:: { b: b.x, c: b.y }; + + // Test mul_013_by_013 + let r0 = Bn254::mul_013_by_013(&l0, &l1); + let io0 = [l0, l1] + .into_iter() + .flat_map(|fp2| fp2.into_iter()) + .chain(r0) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + // Test mul_by_01234 + let x = [c.x, c.y, b.x, b.y, a.x]; + let r1 = Bn254::mul_by_01234(&f, &x); + let io1 = iter::empty() + .chain(f.to_coeffs()) + .chain(x) + .chain(r1.to_coeffs()) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + + 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!("tests/programs"), + "pairing_miller_step", + ["bn254"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let mut rng = rand::rngs::StdRng::seed_from_u64(20); + let S = G2Affine::random(&mut rng); + let Q = G2Affine::random(&mut rng); + + let s = AffinePoint::new(S.x, S.y); + let q = AffinePoint::new(Q.x, Q.y); + + // Test miller_double_step + let (pt, l) = Bn254::miller_double_step(&s); + let io0 = [s.x, s.y, pt.x, pt.y, l.b, l.c] + .into_iter() + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + // Test miller_double_and_add_step + let (pt, l0, l1) = Bn254::miller_double_and_add_step(&s, &q); + let io1 = [s.x, s.y, q.x, q.y, pt.x, pt.y, l0.b, l0.c, l1.b, l1.c] + .into_iter() + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + + 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!("tests/programs"), + "pairing_miller_loop", + ["bn254"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let S = G1Affine::generator(); + let Q = G2Affine::generator(); + + let mut S_mul = [S * Fr::from(1), S * Fr::from(2)]; + S_mul[1].y = -S_mul[1].y; + let Q_mul = [Q * Fr::from(2), Q * Fr::from(1)]; + + let s = S_mul.map(|s| AffinePoint::new(s.x, s.y)); + let q = Q_mul.map(|p| AffinePoint::new(p.x, p.y)); + + // Test miller_loop + let f = Bn254::multi_miller_loop(&s, &q); + let io0 = s + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter().flat_map(|fp| fp.to_bytes())) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io1 = q + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter()) + .chain(f.to_coeffs()) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + + 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!("tests/programs"), + "pairing_check", + ["bn254"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let S = G1Affine::generator(); + let Q = G2Affine::generator(); + + let mut S_mul = [ + G1Affine::from(S * Fr::from(1)), + G1Affine::from(S * Fr::from(2)), + ]; + S_mul[1].y = -S_mul[1].y; + let Q_mul = [ + G2Affine::from(Q * Fr::from(2)), + G2Affine::from(Q * Fr::from(1)), + ]; + + let s = S_mul.map(|s| AffinePoint::new(s.x, s.y)); + let q = Q_mul.map(|p| AffinePoint::new(p.x, p.y)); + + // Gather inputs + let io0 = s + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter().flat_map(|fp| fp.to_bytes())) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io1 = q + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter()) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + + 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!("tests/programs"), + "pairing_check_fallback", + ["bn254"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let S = G1Affine::generator(); + let Q = G2Affine::generator(); + + let mut S_mul = [ + G1Affine::from(S * Fr::from(1)), + G1Affine::from(S * Fr::from(2)), + ]; + S_mul[1].y = -S_mul[1].y; + let Q_mul = [ + G2Affine::from(Q * Fr::from(2)), + G2Affine::from(Q * Fr::from(1)), + ]; + + let s = S_mul.map(|s| AffinePoint::new(s.x, s.y)); + let q = Q_mul.map(|p| AffinePoint::new(p.x, p.y)); + + // Gather inputs + let io0 = s + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter().flat_map(|fp| fp.to_bytes())) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io1 = q + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter()) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + // Don't run debugger because it's slow + air_test_impl(get_testing_config(), openvm_exe, vec![io_all], 1, false); + Ok(()) + } + + #[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!("tests/programs"), + "bn_final_exp_hint", + ["bn254"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let P = G1Affine::generator(); + let Q = G2Affine::generator(); + let ps = vec![AffinePoint::new(P.x, P.y), AffinePoint::new(P.x, -P.y)]; + let qs = vec![AffinePoint::new(Q.x, Q.y), AffinePoint::new(Q.x, Q.y)]; + let f = Bn254::multi_miller_loop(&ps, &qs); + let (c, s) = Bn254::final_exp_hint(&f); + let ps = ps + .into_iter() + .map(|pt| { + 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::bn254::Fp2::from_bytes(&x.to_bytes())); + AffinePoint::new(x, y) + }) + .collect::>(); + 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 + .into_iter() + .flat_map(|w| w.to_le_bytes()) + .map(F::from_canonical_u8) + .collect(); + air_test_with_min_segments(config, openvm_exe, vec![io], 1); + Ok(()) + } +} + +#[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}; + use openvm_algebra_transpiler::{Fp2TranspilerExtension, ModularTranspilerExtension}; + use openvm_circuit::{ + arch::{instructions::exe::VmExe, SystemConfig}, + utils::{air_test, air_test_impl, air_test_with_min_segments}, + }; + use openvm_ecc_circuit::{CurveConfig, Rv32WeierstrassConfig, WeierstrassExtension}; + use openvm_ecc_guest::{ + algebra::{field::FieldExtension, IntMod}, + AffinePoint, + }; + use openvm_ecc_transpiler::EccTranspilerExtension; + use openvm_pairing_circuit::{PairingCurve, PairingExtension, Rv32PairingConfig}; + use openvm_pairing_guest::{ + 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}, + }; + use openvm_pairing_transpiler::PairingTranspilerExtension; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_stark_sdk::{openvm_stark_backend::p3_field::FieldAlgebra, p3_baby_bear::BabyBear}; + use openvm_toolchain_tests::{build_example_program_at_path_with_features, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + use rand::SeedableRng; + + 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_with_names), + weierstrass: WeierstrassExtension::new(vec![]), + pairing: PairingExtension::new(vec![PairingCurve::Bls12_381]), + } + } + + #[test] + fn test_bls_ec() -> Result<()> { + 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() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + 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!("tests/programs"), + "fp12_mul", + ["bls12_381"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let mut rng = rand::rngs::StdRng::seed_from_u64(50); + let f0 = Fq12::random(&mut rng); + let f1 = Fq12::random(&mut rng); + let r = f0 * f1; + + let io = [f0, f1, r] + .into_iter() + .flat_map(|fp12| fp12.to_coeffs()) + .flat_map(|fp2| fp2.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + 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!("tests/programs"), + "pairing_line", + ["bls12_381"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let mut rng = rand::rngs::StdRng::seed_from_u64(5); + let a = G2Affine::random(&mut rng); + let b = G2Affine::random(&mut rng); + let c = G2Affine::random(&mut rng); + + let f = Fq12::random(&mut rng); + let l0 = EvaluatedLine:: { b: a.x, c: a.y }; + let l1 = EvaluatedLine:: { b: b.x, c: b.y }; + + // Test mul_023_by_023 + let r0 = Bls12_381::mul_023_by_023(&l0, &l1); + let io0 = [l0, l1] + .into_iter() + .flat_map(|fp2| fp2.into_iter()) + .chain(r0) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + // Test mul_by_02345 + let x = [c.x, c.y, b.x, b.y, a.x]; + let r1 = Bls12_381::mul_by_02345(&f, &x); + let io1 = f + .to_coeffs() + .into_iter() + .chain(x) + .chain(r1.to_coeffs()) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + + 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!("tests/programs"), + "pairing_miller_step", + ["bls12_381"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let mut rng = rand::rngs::StdRng::seed_from_u64(88); + let S = G2Affine::random(&mut rng); + let Q = G2Affine::random(&mut rng); + + let s = AffinePoint::new(S.x, S.y); + let q = AffinePoint::new(Q.x, Q.y); + + // Test miller_double_step + let (pt, l) = Bls12_381::miller_double_step(&s); + let io0 = [s.x, s.y, pt.x, pt.y, l.b, l.c] + .into_iter() + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + // Test miller_double_and_add_step + let (pt, l0, l1) = Bls12_381::miller_double_and_add_step(&s, &q); + let io1 = [s.x, s.y, q.x, q.y, pt.x, pt.y, l0.b, l0.c, l1.b, l1.c] + .into_iter() + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + + 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!("tests/programs"), + "pairing_miller_loop", + ["bls12_381"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let S = G1Affine::generator(); + let Q = G2Affine::generator(); + + let mut S_mul = [ + G1Affine::from(S * Fr::from(1)), + G1Affine::from(S * Fr::from(2)), + ]; + S_mul[1].y = -S_mul[1].y; + let Q_mul = [ + G2Affine::from(Q * Fr::from(2)), + G2Affine::from(Q * Fr::from(1)), + ]; + + let s = S_mul.map(|s| AffinePoint::new(s.x, s.y)); + let q = Q_mul.map(|p| AffinePoint::new(p.x, p.y)); + + // Test miller_loop + let f = Bls12_381::multi_miller_loop(&s, &q); + let io0 = s + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter().flat_map(|fp| fp.to_bytes())) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io1 = q + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter()) + .chain(f.to_coeffs()) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + + 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!("tests/programs"), + "pairing_check", + ["bls12_381"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let S = G1Affine::generator(); + let Q = G2Affine::generator(); + + let mut S_mul = [ + G1Affine::from(S * Fr::from(1)), + G1Affine::from(S * Fr::from(2)), + ]; + S_mul[1].y = -S_mul[1].y; + let Q_mul = [ + G2Affine::from(Q * Fr::from(2)), + G2Affine::from(Q * Fr::from(1)), + ]; + let s = S_mul.map(|s| AffinePoint::new(s.x, s.y)); + let q = Q_mul.map(|p| AffinePoint::new(p.x, p.y)); + + // Gather inputs + let io0 = s + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter().flat_map(|fp| fp.to_bytes())) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io1 = q + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter()) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + + 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!("tests/programs"), + "pairing_check_fallback", + ["bls12_381"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let S = G1Affine::generator(); + let Q = G2Affine::generator(); + + let mut S_mul = [ + G1Affine::from(S * Fr::from(1)), + G1Affine::from(S * Fr::from(2)), + ]; + S_mul[1].y = -S_mul[1].y; + let Q_mul = [ + G2Affine::from(Q * Fr::from(2)), + G2Affine::from(Q * Fr::from(1)), + ]; + let s = S_mul.map(|s| AffinePoint::new(s.x, s.y)); + let q = Q_mul.map(|p| AffinePoint::new(p.x, p.y)); + + // Gather inputs + let io0 = s + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter().flat_map(|fp| fp.to_bytes())) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io1 = q + .into_iter() + .flat_map(|pt| [pt.x, pt.y].into_iter()) + .flat_map(|fp2| fp2.to_coeffs()) + .flat_map(|fp| fp.to_bytes()) + .map(FieldAlgebra::from_canonical_u8) + .collect::>(); + + let io_all = io0.into_iter().chain(io1).collect::>(); + // Don't run debugger because it's slow + air_test_impl(get_testing_config(), openvm_exe, vec![io_all], 1, false); + Ok(()) + } + + #[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!("tests/programs"), + "bls_final_exp_hint", + ["bls12_381"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(PairingTranspilerExtension) + .with_extension(ModularTranspilerExtension) + .with_extension(Fp2TranspilerExtension), + )?; + + let P = G1Affine::generator(); + let Q = G2Affine::generator(); + let ps = vec![AffinePoint::new(P.x, P.y), AffinePoint::new(P.x, -P.y)]; + let qs = vec![AffinePoint::new(Q.x, Q.y), AffinePoint::new(Q.x, Q.y)]; + let f = Bls12_381::multi_miller_loop(&ps, &qs); + let (c, s) = Bls12_381::final_exp_hint(&f); + let ps = ps + .into_iter() + .map(|pt| { + let [x, y] = [pt.x, pt.y] + .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::bls12_381::Fp2::from_bytes(&x.to_bytes())); + AffinePoint::new(x, y) + }) + .collect::>(); + 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 + .into_iter() + .flat_map(|w| w.to_le_bytes()) + .map(F::from_canonical_u8) + .collect(); + air_test_with_min_segments(config, openvm_exe, vec![io], 1); + Ok(()) + } +} diff --git a/guest-libs/pairing/tests/programs/Cargo.toml b/guest-libs/pairing/tests/programs/Cargo.toml new file mode 100644 index 0000000000..ce4229af68 --- /dev/null +++ b/guest-libs/pairing/tests/programs/Cargo.toml @@ -0,0 +1,50 @@ +[workspace] +[package] +name = "openvm-pairing-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-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", + "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 } + +[features] +default = [] +std = ["serde/std", "openvm/std"] + +bn254 = ["openvm-pairing/bn254"] +bls12_381 = ["openvm-pairing/bls12_381"] + +[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 = "bn_final_exp_hint" +required-features = ["bn254"] + +[[example]] +name = "bls_final_exp_hint" +required-features = ["bls12_381"] + +[[example]] +name = "bls_ec" +required-features = ["bls12_381"] 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/guest-libs/pairing/tests/programs/examples/bls_final_exp_hint.rs b/guest-libs/pairing/tests/programs/examples/bls_final_exp_hint.rs new file mode 100644 index 0000000000..25945a0780 --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/bls_final_exp_hint.rs @@ -0,0 +1,24 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; + +use openvm::io::read; +use openvm_ecc_guest::AffinePoint; +use openvm_pairing::{ + bls12_381::{Bls12_381, Fp, Fp12, Fp2}, + PairingCheck, +}; + +openvm::entry!(main); + +openvm::init!("openvm_init_bls_final_exp_hint_bls12_381.rs"); + +pub fn main() { + #[allow(clippy::type_complexity)] + let (p, q, expected): (Vec>, Vec>, (Fp12, Fp12)) = read(); + let actual = Bls12_381::pairing_check_hint(&p, &q); + assert_eq!(actual, expected); +} 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/guest-libs/pairing/tests/programs/examples/bn_final_exp_hint.rs b/guest-libs/pairing/tests/programs/examples/bn_final_exp_hint.rs new file mode 100644 index 0000000000..0a59242b09 --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/bn_final_exp_hint.rs @@ -0,0 +1,24 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; + +use openvm::io::read; +use openvm_ecc_guest::AffinePoint; +use openvm_pairing::{ + bn254::{Bn254, Fp, Fp12, Fp2}, + PairingCheck, +}; + +openvm::entry!(main); + +openvm::init!("openvm_init_bn_final_exp_hint_bn254.rs"); + +pub fn main() { + #[allow(clippy::type_complexity)] + let (p, q, expected): (Vec>, Vec>, (Fp12, Fp12)) = read(); + let actual = Bn254::pairing_check_hint(&p, &q); + assert_eq!(actual, expected); +} diff --git a/guest-libs/pairing/tests/programs/examples/fp12_mul.rs b/guest-libs/pairing/tests/programs/examples/fp12_mul.rs new file mode 100644 index 0000000000..ad58cc7f72 --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/fp12_mul.rs @@ -0,0 +1,82 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(unused_imports)] + +use openvm::io::read_vec; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; + +openvm::entry!(main); + +#[cfg(feature = "bn254")] +mod bn254 { + use openvm_pairing::bn254::{Fp, Fp12}; + + use super::*; + + openvm::init!("openvm_init_fp12_mul_bn254.rs"); + + pub fn test_fp12_mul(io: &[u8]) { + assert_eq!(io.len(), 32 * 36); + + let f0 = &io[0..32 * 12]; + let f1 = &io[32 * 12..32 * 24]; + let r_cmp = &io[32 * 24..32 * 36]; + + let f0_cast = Fp12::from_bytes(f0); + let f1_cast = Fp12::from_bytes(f1); + + let r = f0_cast * f1_cast; + let mut r_bytes = [0u8; 32 * 12]; + r.to_coeffs() + .iter() + .flat_map(|fp2| fp2.clone().to_coeffs()) + .enumerate() + .for_each(|(i, fp)| r_bytes[i * 32..(i + 1) * 32].copy_from_slice(fp.as_le_bytes())); + + assert_eq!(r_bytes, r_cmp); + } +} + +#[cfg(feature = "bls12_381")] +mod bls12_381 { + use openvm_pairing::bls12_381::{Fp, Fp12}; + + use super::*; + + openvm::init!("openvm_init_fp12_mul_bls12_381.rs"); + + pub fn test_fp12_mul(io: &[u8]) { + assert_eq!(io.len(), 48 * 36); + + let f0 = &io[0..48 * 12]; + let f1 = &io[48 * 12..48 * 24]; + let r_cmp = &io[48 * 24..48 * 36]; + + let f0_cast = Fp12::from_bytes(f0); + let f1_cast = Fp12::from_bytes(f1); + + let r = f0_cast * f1_cast; + let mut r_bytes = [0u8; 48 * 12]; + r.to_coeffs() + .iter() + .flat_map(|fp2| fp2.clone().to_coeffs()) + .enumerate() + .for_each(|(i, fp)| r_bytes[i * 48..(i + 1) * 48].copy_from_slice(fp.as_le_bytes())); + + assert_eq!(r_bytes, r_cmp); + } +} + +pub fn main() { + #[allow(unused_variables)] + let io = read_vec(); + + #[cfg(feature = "bn254")] + { + bn254::test_fp12_mul(&io) + } + #[cfg(feature = "bls12_381")] + { + bls12_381::test_fp12_mul(&io) + } +} diff --git a/guest-libs/pairing/tests/programs/examples/pairing_check.rs b/guest-libs/pairing/tests/programs/examples/pairing_check.rs new file mode 100644 index 0000000000..5f77128fd6 --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/pairing_check.rs @@ -0,0 +1,90 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(unused_imports)] + +extern crate alloc; + +use openvm::io::read_vec; +use openvm_ecc_guest::AffinePoint; +use openvm_pairing::PairingCheck; + +openvm::entry!(main); + +#[cfg(feature = "bn254")] +mod bn254 { + use alloc::format; + + use openvm_algebra_guest::{field::FieldExtension, IntMod}; + use openvm_pairing::bn254::{Bn254, Fp, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_check_bn254.rs"); + + pub fn test_pairing_check(io: &[u8]) { + let s0 = &io[0..32 * 2]; + let s1 = &io[32 * 2..32 * 4]; + let q0 = &io[32 * 4..32 * 8]; + let q1 = &io[32 * 8..32 * 12]; + + let s0_cast = + AffinePoint::new(Fp::from_le_bytes(&s0[..32]), Fp::from_le_bytes(&s0[32..64])); + let s1_cast = + AffinePoint::new(Fp::from_le_bytes(&s1[..32]), Fp::from_le_bytes(&s1[32..64])); + let q0_cast = AffinePoint::new(Fp2::from_bytes(&q0[..64]), Fp2::from_bytes(&q0[64..128])); + let q1_cast = AffinePoint::new(Fp2::from_bytes(&q1[..64]), Fp2::from_bytes(&q1[64..128])); + + let f = Bn254::pairing_check( + &[s0_cast.clone(), s1_cast.clone()], + &[q0_cast.clone(), q1_cast.clone()], + ); + assert_eq!(f, Ok(())); + } +} + +#[cfg(feature = "bls12_381")] +mod bls12_381 { + + use alloc::format; + + use openvm_algebra_guest::{field::FieldExtension, IntMod}; + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_check_bls12_381.rs"); + + pub fn test_pairing_check(io: &[u8]) { + let s0 = &io[0..48 * 2]; + let s1 = &io[48 * 2..48 * 4]; + let q0 = &io[48 * 4..48 * 8]; + let q1 = &io[48 * 8..48 * 12]; + + let s0_cast = + AffinePoint::new(Fp::from_le_bytes(&s0[..48]), Fp::from_le_bytes(&s0[48..96])); + let s1_cast = + AffinePoint::new(Fp::from_le_bytes(&s1[..48]), Fp::from_le_bytes(&s1[48..96])); + let q0_cast = AffinePoint::new(Fp2::from_bytes(&q0[..96]), Fp2::from_bytes(&q0[96..192])); + let q1_cast = AffinePoint::new(Fp2::from_bytes(&q1[..96]), Fp2::from_bytes(&q1[96..192])); + + let f = Bls12_381::pairing_check( + &[s0_cast.clone(), s1_cast.clone()], + &[q0_cast.clone(), q1_cast.clone()], + ); + assert_eq!(f, Ok(())); + } +} + +pub fn main() { + #[allow(unused_variables)] + let io = read_vec(); + + #[cfg(feature = "bn254")] + { + bn254::test_pairing_check(&io); + } + #[cfg(feature = "bls12_381")] + { + bls12_381::test_pairing_check(&io); + } +} diff --git a/guest-libs/pairing/tests/programs/examples/pairing_check_fallback.rs b/guest-libs/pairing/tests/programs/examples/pairing_check_fallback.rs new file mode 100644 index 0000000000..7c09388306 --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/pairing_check_fallback.rs @@ -0,0 +1,240 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(unused_imports)] + +extern crate alloc; + +use openvm::io::read_vec; +use openvm_algebra_guest::{ + field::{ComplexConjugate, FieldExtension}, + DivUnsafe, Field, IntMod, +}; +use openvm_ecc_guest::AffinePoint; +use openvm_pairing::PairingCheck; +use openvm_pairing_guest::pairing::{exp_check_fallback, MultiMillerLoop, PairingCheckError}; + +openvm::entry!(main); + +#[cfg(feature = "bn254")] +mod bn254 { + use alloc::format; + + use openvm_pairing::bn254::{Bn254, Fp, Fp12, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_check_fallback_bn254.rs"); + + // Wrapper so that we can override `pairing_check_hint` + struct Bn254Wrapper(Bn254); + + #[allow(non_snake_case)] + impl PairingCheck for Bn254Wrapper { + type Fp = Fp; + type Fp2 = Fp2; + type Fp12 = Fp12; + + fn pairing_check_hint( + _P: &[AffinePoint], + _Q: &[AffinePoint], + ) -> (Self::Fp12, Self::Fp12) { + // return dummy values + (Fp12::ZERO, Fp12::ZERO) + } + + // copied from Bn254::pairing_check + fn pairing_check( + P: &[AffinePoint], + Q: &[AffinePoint], + ) -> Result<(), PairingCheckError> { + Self::try_honest_pairing_check(P, Q).unwrap_or_else(|| { + let f = Bn254::multi_miller_loop(P, Q); + exp_check_fallback(&f, &Bn254::FINAL_EXPONENT) + }) + } + } + + #[allow(non_snake_case)] + impl Bn254Wrapper { + // copied from Bn254::try_honest_pairing_check + fn try_honest_pairing_check( + P: &[AffinePoint<::Fp>], + Q: &[AffinePoint<::Fp2>], + ) -> Option> { + let (c, s) = Self::pairing_check_hint(P, Q); + + // f * s = c^{q - x} + // f * s = c^q * c^-x + // f * c^x * c^-q * s = 1, + // where fc = f * c'^x (embedded Miller loop with c conjugate inverse), + // and the curve seed x = -0xd201000000010000 + // the miller loop computation includes a conjugation at the end because the value of + // the seed is negative, so we need to conjugate the miller loop input c + // as c'. We then substitute y = -x to get c^-y and finally compute c'^-y + // as input to the miller loop: f * c'^-y * c^-q * s = 1 + let c_q = FieldExtension::frobenius_map(&c, 1); + let c_conj = c.conjugate(); + if c_conj == Fp12::ZERO { + return None; + } + let c_conj_inv = Fp12::ONE.div_unsafe(&c_conj); + + // fc = f_{Miller,x,Q}(P) * c^{x} + // where + // fc = conjugate( f_{Miller,-x,Q}(P) * c'^{-x} ), with c' denoting the conjugate of c + let fc = Bn254::multi_miller_loop_embedded_exp(P, Q, Some(c_conj_inv)); + + if fc * s == c_q { + Some(Ok(())) + } else { + None + } + } + } + + pub fn test_pairing_check(io: &[u8]) { + let s0 = &io[0..32 * 2]; + let s1 = &io[32 * 2..32 * 4]; + let q0 = &io[32 * 4..32 * 8]; + let q1 = &io[32 * 8..32 * 12]; + + let s0_cast = + AffinePoint::new(Fp::from_le_bytes(&s0[..32]), Fp::from_le_bytes(&s0[32..64])); + let s1_cast = + AffinePoint::new(Fp::from_le_bytes(&s1[..32]), Fp::from_le_bytes(&s1[32..64])); + let q0_cast = AffinePoint::new(Fp2::from_bytes(&q0[..64]), Fp2::from_bytes(&q0[64..128])); + let q1_cast = AffinePoint::new(Fp2::from_bytes(&q1[..64]), Fp2::from_bytes(&q1[64..128])); + + let f = Bn254Wrapper::pairing_check( + &[s0_cast.clone(), s1_cast.clone()], + &[q0_cast.clone(), q1_cast.clone()], + ); + assert_eq!(f, Ok(())); + + let f = Bn254Wrapper::pairing_check( + &[-s0_cast.clone(), s1_cast.clone()], + &[q0_cast.clone(), q1_cast.clone()], + ); + assert_eq!(f, Err(PairingCheckError)); + } +} + +#[cfg(feature = "bls12_381")] +mod bls12_381 { + + use alloc::format; + + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp12, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_check_fallback_bls12_381.rs"); + + // Wrapper so that we can override `pairing_check_hint` + struct Bls12_381Wrapper(Bls12_381); + + #[allow(non_snake_case)] + impl PairingCheck for Bls12_381Wrapper { + type Fp = Fp; + type Fp2 = Fp2; + type Fp12 = Fp12; + + #[allow(unused_variables)] + fn pairing_check_hint( + _P: &[AffinePoint], + _Q: &[AffinePoint], + ) -> (Self::Fp12, Self::Fp12) { + // return dummy values + (Fp12::ZERO, Fp12::ZERO) + } + + // copied from Bls12_381::pairing_check + fn pairing_check( + P: &[AffinePoint], + Q: &[AffinePoint], + ) -> Result<(), PairingCheckError> { + Self::try_honest_pairing_check(P, Q).unwrap_or_else(|| { + let f = Bls12_381::multi_miller_loop(P, Q); + exp_check_fallback(&f, &Bls12_381::FINAL_EXPONENT) + }) + } + } + + #[allow(non_snake_case)] + impl Bls12_381Wrapper { + // copied from Bls12_381::try_honest_pairing_check + fn try_honest_pairing_check( + P: &[AffinePoint<::Fp>], + Q: &[AffinePoint<::Fp2>], + ) -> Option> { + let (c, s) = Self::pairing_check_hint(P, Q); + + // f * s = c^{q - x} + // f * s = c^q * c^-x + // f * c^x * c^-q * s = 1, + // where fc = f * c'^x (embedded Miller loop with c conjugate inverse), + // and the curve seed x = -0xd201000000010000 + // the miller loop computation includes a conjugation at the end because the value of + // the seed is negative, so we need to conjugate the miller loop input c + // as c'. We then substitute y = -x to get c^-y and finally compute c'^-y + // as input to the miller loop: f * c'^-y * c^-q * s = 1 + let c_q = FieldExtension::frobenius_map(&c, 1); + let c_conj = c.conjugate(); + if c_conj == Fp12::ZERO { + return None; + } + let c_conj_inv = Fp12::ONE.div_unsafe(&c_conj); + + // fc = f_{Miller,x,Q}(P) * c^{x} + // where + // fc = conjugate( f_{Miller,-x,Q}(P) * c'^{-x} ), with c' denoting the conjugate of c + let fc = Bls12_381::multi_miller_loop_embedded_exp(P, Q, Some(c_conj_inv)); + + if fc * s == c_q { + Some(Ok(())) + } else { + None + } + } + } + + pub fn test_pairing_check(io: &[u8]) { + let s0 = &io[0..48 * 2]; + let s1 = &io[48 * 2..48 * 4]; + let q0 = &io[48 * 4..48 * 8]; + let q1 = &io[48 * 8..48 * 12]; + + let s0_cast = + AffinePoint::new(Fp::from_le_bytes(&s0[..48]), Fp::from_le_bytes(&s0[48..96])); + let s1_cast = + AffinePoint::new(Fp::from_le_bytes(&s1[..48]), Fp::from_le_bytes(&s1[48..96])); + let q0_cast = AffinePoint::new(Fp2::from_bytes(&q0[..96]), Fp2::from_bytes(&q0[96..192])); + let q1_cast = AffinePoint::new(Fp2::from_bytes(&q1[..96]), Fp2::from_bytes(&q1[96..192])); + + let f = Bls12_381Wrapper::pairing_check( + &[s0_cast.clone(), s1_cast.clone()], + &[q0_cast.clone(), q1_cast.clone()], + ); + assert_eq!(f, Ok(())); + + let f = Bls12_381Wrapper::pairing_check( + &[-s0_cast.clone(), s1_cast.clone()], + &[q0_cast.clone(), q1_cast.clone()], + ); + assert_eq!(f, Err(PairingCheckError)); + } +} + +pub fn main() { + #[allow(unused_variables)] + let io = read_vec(); + + #[cfg(feature = "bn254")] + { + bn254::test_pairing_check(&io); + } + #[cfg(feature = "bls12_381")] + { + bls12_381::test_pairing_check(&io); + } +} diff --git a/guest-libs/pairing/tests/programs/examples/pairing_line.rs b/guest-libs/pairing/tests/programs/examples/pairing_line.rs new file mode 100644 index 0000000000..4b64f0f081 --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/pairing_line.rs @@ -0,0 +1,147 @@ +#![allow(unused_imports)] +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use openvm::io::read_vec; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; +use openvm_pairing_guest::pairing::{EvaluatedLine, LineMulDType, LineMulMType}; + +openvm::entry!(main); + +#[cfg(feature = "bn254")] +mod bn254 { + use openvm_pairing::bn254::{Bn254, Fp, Fp12, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_line_bn254.rs"); + + pub fn test_mul_013_by_013(io: &[u8]) { + assert_eq!(io.len(), 32 * 18); + let l0 = &io[..32 * 4]; + let l1 = &io[32 * 4..32 * 8]; + let expected = &io[32 * 8..32 * 18]; + + let l0_cast = EvaluatedLine { + b: Fp2::from_bytes(&l0[..64]), + c: Fp2::from_bytes(&l0[64..128]), + }; + let l1_cast = EvaluatedLine { + b: Fp2::from_bytes(&l1[..64]), + c: Fp2::from_bytes(&l1[64..128]), + }; + + let r = Bn254::mul_013_by_013(&l0_cast, &l1_cast); + let mut r_bytes = [0u8; 32 * 10]; + let mut i = 0; + for x in r { + r_bytes[i..i + 32].copy_from_slice(x.c0.as_le_bytes()); + r_bytes[i + 32..i + 64].copy_from_slice(x.c1.as_le_bytes()); + i += 64; + } + assert_eq!(r_bytes, expected); + } + + pub fn test_mul_by_01234(io: &[u8]) { + assert_eq!(io.len(), 32 * 34); + let f = &io[..32 * 12]; + let x = &io[32 * 12..32 * 22]; + let expected = &io[32 * 22..32 * 34]; + + let f_cast = Fp12::from_bytes(f); + let x_cast = [ + Fp2::from_bytes(&x[..64]), + Fp2::from_bytes(&x[64..128]), + Fp2::from_bytes(&x[128..192]), + Fp2::from_bytes(&x[192..256]), + Fp2::from_bytes(&x[256..320]), + ]; + + let r = Bn254::mul_by_01234(&f_cast, &x_cast); + let mut r_bytes = [0u8; 32 * 12]; + let mut i = 0; + for x in r.to_coeffs() { + r_bytes[i..i + 32].copy_from_slice(x.c0.as_le_bytes()); + r_bytes[i + 32..i + 64].copy_from_slice(x.c1.as_le_bytes()); + i += 64; + } + assert_eq!(r_bytes, expected); + } +} + +#[cfg(feature = "bls12_381")] +mod bls12_381 { + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp12, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_line_bls12_381.rs"); + + pub fn test_mul_023_by_023(io: &[u8]) { + assert_eq!(io.len(), 48 * 18); + let l0 = &io[..48 * 4]; + let l1 = &io[48 * 4..48 * 8]; + let expected = &io[48 * 8..48 * 18]; + + let l0_cast = EvaluatedLine { + b: Fp2::from_bytes(&l0[..48 * 2]), + c: Fp2::from_bytes(&l0[48 * 2..48 * 4]), + }; + let l1_cast = EvaluatedLine { + b: Fp2::from_bytes(&l1[..48 * 2]), + c: Fp2::from_bytes(&l1[48 * 2..48 * 4]), + }; + + let r = Bls12_381::mul_023_by_023(&l0_cast, &l1_cast); + let mut r_bytes = [0u8; 48 * 10]; + let mut i = 0; + for x in r { + r_bytes[i..i + 48].copy_from_slice(x.c0.as_le_bytes()); + r_bytes[i + 48..i + 96].copy_from_slice(x.c1.as_le_bytes()); + i += 96; + } + assert_eq!(r_bytes, expected); + } + + pub fn test_mul_by_02345(io: &[u8]) { + assert_eq!(io.len(), 48 * 34); + let f = &io[..48 * 12]; + let x = &io[48 * 12..48 * 22]; + let expected = &io[48 * 22..48 * 34]; + + let f_cast = Fp12::from_bytes(f); + let x_cast = [ + Fp2::from_bytes(&x[..48 * 2]), + Fp2::from_bytes(&x[48 * 2..48 * 4]), + Fp2::from_bytes(&x[48 * 4..48 * 6]), + Fp2::from_bytes(&x[48 * 6..48 * 8]), + Fp2::from_bytes(&x[48 * 8..48 * 10]), + ]; + + let r = Bls12_381::mul_by_02345(&f_cast, &x_cast); + let mut r_bytes = [0u8; 48 * 12]; + let mut i = 0; + for x in r.to_coeffs() { + r_bytes[i..i + 48].copy_from_slice(x.c0.as_le_bytes()); + r_bytes[i + 48..i + 96].copy_from_slice(x.c1.as_le_bytes()); + i += 96; + } + assert_eq!(r_bytes, expected); + } +} + +pub fn main() { + #[allow(unused_variables)] + let io = read_vec(); + + #[cfg(feature = "bn254")] + { + 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::test_mul_023_by_023(&io[..48 * 18]); + bls12_381::test_mul_by_02345(&io[48 * 18..48 * 52]); + } +} diff --git a/guest-libs/pairing/tests/programs/examples/pairing_miller_loop.rs b/guest-libs/pairing/tests/programs/examples/pairing_miller_loop.rs new file mode 100644 index 0000000000..faa2efad4a --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/pairing_miller_loop.rs @@ -0,0 +1,97 @@ +#![allow(unused_imports)] +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use openvm::io::read_vec; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; +use openvm_ecc_guest::AffinePoint; +use openvm_pairing_guest::pairing::MultiMillerLoop; + +openvm::entry!(main); + +#[cfg(feature = "bn254")] +mod bn254 { + use openvm_pairing::bn254::{Bn254, Fp, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_miller_loop_bn254.rs"); + + pub fn test_miller_loop(io: &[u8]) { + let s0 = &io[0..32 * 2]; + let s1 = &io[32 * 2..32 * 4]; + let q0 = &io[32 * 4..32 * 8]; + let q1 = &io[32 * 8..32 * 12]; + let f_cmp = &io[32 * 12..32 * 24]; + + let s0_cast = + AffinePoint::new(Fp::from_le_bytes(&s0[..32]), Fp::from_le_bytes(&s0[32..64])); + let s1_cast = + AffinePoint::new(Fp::from_le_bytes(&s1[..32]), Fp::from_le_bytes(&s1[32..64])); + let q0_cast = AffinePoint::new(Fp2::from_bytes(&q0[..64]), Fp2::from_bytes(&q0[64..128])); + let q1_cast = AffinePoint::new(Fp2::from_bytes(&q1[..64]), Fp2::from_bytes(&q1[64..128])); + + let f = Bn254::multi_miller_loop(&[s0_cast, s1_cast], &[q0_cast, q1_cast]); + let mut f_bytes = [0u8; 32 * 12]; + f.to_coeffs() + .iter() + .flat_map(|fp2| fp2.clone().to_coeffs()) + .enumerate() + .for_each(|(i, fp)| f_bytes[i * 32..(i + 1) * 32].copy_from_slice(fp.as_le_bytes())); + + assert_eq!(f_bytes, f_cmp); + } +} + +#[cfg(feature = "bls12_381")] +mod bls12_381 { + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_miller_loop_bls12_381.rs"); + + pub fn test_miller_loop(io: &[u8]) { + let s0 = &io[0..48 * 2]; + let s1 = &io[48 * 2..48 * 4]; + let q0 = &io[48 * 4..48 * 8]; + let q1 = &io[48 * 8..48 * 12]; + let f_cmp = &io[48 * 12..48 * 24]; + + let s0_cast = + AffinePoint::new(Fp::from_le_bytes(&s0[..48]), Fp::from_le_bytes(&s0[48..96])); + let s1_cast = + AffinePoint::new(Fp::from_le_bytes(&s1[..48]), Fp::from_le_bytes(&s1[48..96])); + let q0_cast = AffinePoint::new(Fp2::from_bytes(&q0[..96]), Fp2::from_bytes(&q0[96..192])); + let q1_cast = AffinePoint::new(Fp2::from_bytes(&q1[..96]), Fp2::from_bytes(&q1[96..192])); + + let f = Bls12_381::multi_miller_loop( + &[s0_cast.clone(), s1_cast.clone()], + &[q0_cast.clone(), q1_cast.clone()], + ); + let mut f_bytes = [0u8; 48 * 12]; + f.to_coeffs() + .iter() + .flat_map(|fp2| fp2.clone().to_coeffs()) + .enumerate() + .for_each(|(i, fp)| f_bytes[i * 48..(i + 1) * 48].copy_from_slice(fp.as_le_bytes())); + + assert_eq!(f_bytes, f_cmp); + } +} + +pub fn main() { + #[allow(unused_variables)] + let io = read_vec(); + + #[cfg(feature = "bn254")] + { + bn254::test_miller_loop(&io); + } + #[cfg(feature = "bls12_381")] + { + bls12_381::test_miller_loop(&io); + } +} diff --git a/guest-libs/pairing/tests/programs/examples/pairing_miller_step.rs b/guest-libs/pairing/tests/programs/examples/pairing_miller_step.rs new file mode 100644 index 0000000000..e726406aff --- /dev/null +++ b/guest-libs/pairing/tests/programs/examples/pairing_miller_step.rs @@ -0,0 +1,166 @@ +#![allow(unused_imports)] +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use openvm::io::read_vec; +use openvm_algebra_guest::IntMod; +use openvm_ecc_guest::AffinePoint; +use openvm_pairing_guest::pairing::MillerStep; + +openvm::entry!(main); + +#[cfg(feature = "bn254")] +mod bn254 { + use openvm_algebra_guest::field::FieldExtension; + use openvm_pairing::bn254::{Bn254, Fp, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_miller_step_bn254.rs"); + + pub fn test_miller_step(io: &[u8]) { + assert_eq!(io.len(), 32 * 12); + let s = &io[..32 * 4]; + let pt = &io[32 * 4..32 * 8]; + let l = &io[32 * 8..32 * 12]; + + let s_cast = AffinePoint::new(Fp2::from_bytes(&s[..64]), Fp2::from_bytes(&s[64..128])); + + let (pt_cmp, l_cmp) = Bn254::miller_double_step(&s_cast); + let mut pt_bytes = [0u8; 32 * 4]; + let mut l_bytes = [0u8; 32 * 4]; + + // TODO: if we ever need to change this, we should switch to using `StdIn::write` to + // serialize for us and use `read()` instead of `read_vec()` + pt_bytes[0..32].copy_from_slice(pt_cmp.x.c0.as_le_bytes()); + pt_bytes[32..2 * 32].copy_from_slice(pt_cmp.x.c1.as_le_bytes()); + pt_bytes[2 * 32..3 * 32].copy_from_slice(pt_cmp.y.c0.as_le_bytes()); + pt_bytes[3 * 32..4 * 32].copy_from_slice(pt_cmp.y.c1.as_le_bytes()); + l_bytes[0..32].copy_from_slice(l_cmp.b.c0.as_le_bytes()); + l_bytes[32..2 * 32].copy_from_slice(l_cmp.b.c1.as_le_bytes()); + l_bytes[2 * 32..3 * 32].copy_from_slice(l_cmp.c.c0.as_le_bytes()); + l_bytes[3 * 32..4 * 32].copy_from_slice(l_cmp.c.c1.as_le_bytes()); + + assert_eq!(pt_bytes, pt); + assert_eq!(l_bytes, l); + } + + pub fn test_miller_double_and_add_step(io: &[u8]) { + assert_eq!(io.len(), 32 * 20); + let s = &io[0..32 * 4]; + let q = &io[32 * 4..32 * 8]; + let pt = &io[32 * 8..32 * 12]; + let l0 = &io[32 * 12..32 * 16]; + let l1 = &io[32 * 16..32 * 20]; + + let s_cast = AffinePoint::new(Fp2::from_bytes(&s[..64]), Fp2::from_bytes(&s[64..128])); + let q_cast = AffinePoint::new(Fp2::from_bytes(&q[..64]), Fp2::from_bytes(&q[64..128])); + let (pt_cmp, l0_cmp, l1_cmp) = Bn254::miller_double_and_add_step(&s_cast, &q_cast); + let mut pt_bytes = [0u8; 32 * 4]; + let mut l0_bytes = [0u8; 32 * 4]; + let mut l1_bytes = [0u8; 32 * 4]; + + // TODO: if we ever need to change this, we should switch to using `StdIn::write` to + // serialize for us and use `read()` instead of `read_vec()` + pt_bytes[0..32].copy_from_slice(pt_cmp.x.c0.as_le_bytes()); + pt_bytes[32..2 * 32].copy_from_slice(pt_cmp.x.c1.as_le_bytes()); + pt_bytes[2 * 32..3 * 32].copy_from_slice(pt_cmp.y.c0.as_le_bytes()); + pt_bytes[3 * 32..4 * 32].copy_from_slice(pt_cmp.y.c1.as_le_bytes()); + l0_bytes[0..32].copy_from_slice(l0_cmp.b.c0.as_le_bytes()); + l0_bytes[32..2 * 32].copy_from_slice(l0_cmp.b.c1.as_le_bytes()); + l0_bytes[2 * 32..3 * 32].copy_from_slice(l0_cmp.c.c0.as_le_bytes()); + l0_bytes[3 * 32..4 * 32].copy_from_slice(l0_cmp.c.c1.as_le_bytes()); + l1_bytes[0..32].copy_from_slice(l1_cmp.b.c0.as_le_bytes()); + l1_bytes[32..2 * 32].copy_from_slice(l1_cmp.b.c1.as_le_bytes()); + l1_bytes[2 * 32..3 * 32].copy_from_slice(l1_cmp.c.c0.as_le_bytes()); + l1_bytes[3 * 32..4 * 32].copy_from_slice(l1_cmp.c.c1.as_le_bytes()); + + assert_eq!(pt_bytes, pt); + assert_eq!(l0_bytes, l0); + assert_eq!(l1_bytes, l1); + } +} + +#[cfg(feature = "bls12_381")] +mod bls12_381 { + use openvm_algebra_guest::field::FieldExtension; + use openvm_pairing::bls12_381::{Bls12_381, Fp, Fp2}; + + use super::*; + + openvm::init!("openvm_init_pairing_miller_step_bls12_381.rs"); + + pub fn test_miller_step(io: &[u8]) { + assert_eq!(io.len(), 48 * 12); + let s = &io[..48 * 4]; + let pt = &io[48 * 4..48 * 8]; + let l = &io[48 * 8..48 * 12]; + + let s_cast = AffinePoint::new(Fp2::from_bytes(&s[..96]), Fp2::from_bytes(&s[96..192])); + + let (pt_cmp, l_cmp) = Bls12_381::miller_double_step(&s_cast); + let mut pt_bytes = [0u8; 48 * 4]; + let mut l_bytes = [0u8; 48 * 4]; + + pt_bytes[0..48].copy_from_slice(pt_cmp.x.c0.as_le_bytes()); + pt_bytes[48..2 * 48].copy_from_slice(pt_cmp.x.c1.as_le_bytes()); + pt_bytes[2 * 48..3 * 48].copy_from_slice(pt_cmp.y.c0.as_le_bytes()); + pt_bytes[3 * 48..4 * 48].copy_from_slice(pt_cmp.y.c1.as_le_bytes()); + l_bytes[0..48].copy_from_slice(l_cmp.b.c0.as_le_bytes()); + l_bytes[48..2 * 48].copy_from_slice(l_cmp.b.c1.as_le_bytes()); + l_bytes[2 * 48..3 * 48].copy_from_slice(l_cmp.c.c0.as_le_bytes()); + l_bytes[3 * 48..4 * 48].copy_from_slice(l_cmp.c.c1.as_le_bytes()); + + assert_eq!(pt_bytes, pt); + assert_eq!(l_bytes, l); + } + + pub fn test_miller_double_and_add_step(io: &[u8]) { + assert_eq!(io.len(), 48 * 20); + let s = &io[0..48 * 4]; + let q = &io[48 * 4..48 * 8]; + let pt = &io[48 * 8..48 * 12]; + let l0 = &io[48 * 12..48 * 16]; + let l1 = &io[48 * 16..48 * 20]; + + let s_cast = AffinePoint::new(Fp2::from_bytes(&s[..96]), Fp2::from_bytes(&s[96..192])); + let q_cast = AffinePoint::new(Fp2::from_bytes(&q[..96]), Fp2::from_bytes(&q[96..192])); + let (pt_cmp, l0_cmp, l1_cmp) = Bls12_381::miller_double_and_add_step(&s_cast, &q_cast); + let mut pt_bytes = [0u8; 48 * 4]; + let mut l0_bytes = [0u8; 48 * 4]; + let mut l1_bytes = [0u8; 48 * 4]; + + pt_bytes[0..48].copy_from_slice(pt_cmp.x.c0.as_le_bytes()); + pt_bytes[48..2 * 48].copy_from_slice(pt_cmp.x.c1.as_le_bytes()); + pt_bytes[2 * 48..3 * 48].copy_from_slice(pt_cmp.y.c0.as_le_bytes()); + pt_bytes[3 * 48..4 * 48].copy_from_slice(pt_cmp.y.c1.as_le_bytes()); + l0_bytes[0..48].copy_from_slice(l0_cmp.b.c0.as_le_bytes()); + l0_bytes[48..2 * 48].copy_from_slice(l0_cmp.b.c1.as_le_bytes()); + l0_bytes[2 * 48..3 * 48].copy_from_slice(l0_cmp.c.c0.as_le_bytes()); + l0_bytes[3 * 48..4 * 48].copy_from_slice(l0_cmp.c.c1.as_le_bytes()); + l1_bytes[0..48].copy_from_slice(l1_cmp.b.c0.as_le_bytes()); + l1_bytes[48..2 * 48].copy_from_slice(l1_cmp.b.c1.as_le_bytes()); + l1_bytes[2 * 48..3 * 48].copy_from_slice(l1_cmp.c.c0.as_le_bytes()); + l1_bytes[3 * 48..4 * 48].copy_from_slice(l1_cmp.c.c1.as_le_bytes()); + + assert_eq!(pt_bytes, pt); + assert_eq!(l0_bytes, l0); + assert_eq!(l1_bytes, l1); + } +} + +pub fn main() { + #[allow(unused_variables)] + let io = read_vec(); + + #[cfg(feature = "bn254")] + { + bn254::test_miller_step(&io[..32 * 12]); + bn254::test_miller_double_and_add_step(&io[32 * 12..]); + } + #[cfg(feature = "bls12_381")] + { + 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/.clippy.toml b/guest-libs/ruint/.clippy.toml new file mode 100644 index 0000000000..b95acb36d6 --- /dev/null +++ b/guest-libs/ruint/.clippy.toml @@ -0,0 +1,3 @@ +# Commented to use repo-level msrv instead +#msrv = "1.65" +doc-valid-idents = ["CPython", "PyPy", ".."] diff --git a/guest-libs/ruint/.editorconfig b/guest-libs/ruint/.editorconfig new file mode 100644 index 0000000000..1b70bd4c3d --- /dev/null +++ b/guest-libs/ruint/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# See + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.{yml,yaml}] +indent_size = 2 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/CHANGELOG.md b/guest-libs/ruint/CHANGELOG.md new file mode 100644 index 0000000000..26dba8f87b --- /dev/null +++ b/guest-libs/ruint/CHANGELOG.md @@ -0,0 +1,357 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + + +## [Unreleased] + +## [1.12.3] - 2024-06-03 + +### Changed + +- Use borrowing/carrying ops in add/sub, remove bound checks in shifts ([#366]) +- Make `mul_mod` non-allocating ([#373]) + +### Fixed + +- Add `alloc` requirement to `num-traits` feature [#363] + +[#363]: https://github.com/recmo/uint/pull/363 +[#366]: https://github.com/recmo/uint/pull/366 +[#373]: https://github.com/recmo/uint/pull/373 + +## [1.12.1] - 2024-03-12 + +### Fixed + +- docs.rs build ([#356]) +- `uint!` in item position ([#360]) + +[#356]: https://github.com/recmo/uint/pull/356 +[#360]: https://github.com/recmo/uint/pull/360 + +## [1.12.0] - 2024-02-27 + +### Added + +- Wrap the `uint!` macro to allow usage without needing `uint` import ([#350]) + +### Fixed + +- Overflow check in `overflowing_shr` implementation ([#347]) + +[#347]: https://github.com/recmo/uint/pull/347 +[#350]: https://github.com/recmo/uint/pull/350 + +## [1.11.1] - 2023-11-18 + +### Fixed + +- Typo in `Shr` implementation ([#343]) + +[#343]: https://github.com/recmo/uint/pull/343 + +### Added + +- Enable `SSZ` ([#344]) + +[#344]: https://github.com/recmo/uint/pull/344 + +## [1.11.0] - 2023-10-31 + +### Added + +- `bytemuck` feature ([#292]) +- `Uint::is_zero() -> bool` ([#296]) +- `num-traits` features ([#298]) +- `U768` alias ([#310]) +- Improved `add` and `sub` performance ([#316]) +- Made `add` and `sub` functions `const` ([#324]) +- Made `{from,to}_{b,l}e_bytes` `const` ([#329]) + +### Fixed + +- Restricted RLP decoding to match the RLP spec and disallow leading zeros ([#335]) +- `leading_ones` failed for non-aligned sizes. + +[#292]: https://github.com/recmo/uint/pull/292 +[#296]: https://github.com/recmo/uint/pull/296 +[#298]: https://github.com/recmo/uint/pull/298 +[#310]: https://github.com/recmo/uint/pull/310 +[#316]: https://github.com/recmo/uint/pull/316 +[#324]: https://github.com/recmo/uint/pull/324 +[#329]: https://github.com/recmo/uint/pull/329 +[#335]: https://github.com/recmo/uint/pull/335 + +## [1.10.1] - 2023-07-30 + +### Fixed + +- Fixed some support features ([#289]) + +[#289]: https://github.com/recmo/uint/pull/289 + +## [1.10.0] - 2023-07-30 + +### Added + +- Support for `no_std` environments ([#274]) +- `alloc` feature ([#277]) + +[#274]: https://github.com/recmo/uint/pull/274 +[#277]: https://github.com/recmo/uint/pull/277 + +## [1.9.0] - 2023-07-25 + +### Added + +- Introduce `ark-ff-04` feature flag for conversion to `ark-ff@0.4` types +- Support for [`alloy-rlp`](https://github.com/alloy-rs/rlp) +- MSRV (Minimum Supported Rust Version) is now set at 1.65.0, from previously undefined +- Implement `TryFrom` for `Uint` +- New method: `byte` + +### Changed + +- Make `serde::Deserialize` impl more permissive +- Use Ethereum `Quantity` encoding for serde serialization when human-readable +- Fix error in `from_base_be` that allowed instantiation of overflowing `Uint` +- Updated `fastrlp` to `0.3`, `pyo3` to `0.19`, and `sqlx-core` to `0.7` +- Improved `fastrlp` perfomance +- Improved `proptest` performance +- Made `support` module and its modules public +- Made more `algorithm` functions public +- Constified `as_le_slice` and `as_le_bytes` + +### Removed + +- Automatic detection of nightly features. Enable them instead with the `nightly` cargo feature +- Dependency on `derive_more` + +### Fixed + +- `from_base_le` implementation by reversing the input iterator + +## [1.8.0] — 2023-04-19 + +### Added + +- Support `bn-rs`, `serde` and `uint!` for `Bits` + +### Fixed + +- Serde human readable now encodes the empty bitstring as `0x0` and rejects zero prefixes. + +## [1.7.0] — 2022-10-31 + +### Added + +- Support `rlp` for `Bits` + +### Fixed + +- Edge case in which an overflow occurs when parsing a `Uint` with `uint!` ([#199]). + +[#199]: https://github.com/recmo/uint/issues/199 + +## [1.6.0] — 2022-10-28 + +### Added + +- `TryFrom for usize` +- Bit type aliases (`B128`, `B160`, `B256`, `B512`, ...) +- `From` and `Into` trait implementations for `primitive-types` bit types +- Support for `bn-rs` +- Derive `Default` for `Bits` + +### Changed + +- (Breaking) Changed the arguments of `pow` and `log` to `Uint`. +- More efficient `wrapping_pow` implementation. + +## [1.5.1] — 2022-10-24 + +### Changed + +- Performance improvements in `wrapping_mul` and `from_bytes_be`. + +## [1.5.0] — 2022-10-24 + +### Added + +- Add `parity-scale-codec` support. +- Added unstable `algorithms::div` module and improved div algorithm. + +## [1.4.1] — 2022-10-15 + +### Changed + +- Made `primitive-types` version flexible. + +## [1.4.0] — 2022-10-02 + +### Added + +- Add `Pyo3` support. +- `from` now supports `Uint` arguments. +- `saturating_from`, `wrapping_from`, `to`, `wrapping_to`, `saturating_to`. +- `wrapping_from_limbs_slice`, `overflowing_from_limbs_slice`, `saturating_from_limbs_slice`. +- Add `zeroize` and `valuable` support. + +### Changed + +- `ToUintError` and `FromUintError` now contain wrapped value and other context. +- `from_uint` and `checked_from_uint` are now deprecated. + +## [1.3.0] — 2022-06-08 + +### Added + +- Added `inv_mod`, `mul_redc`, `gcd`, `gcd_extended`, `lcm`. +- Added `sqlx` support. + +### Changed + +- Renamed `ring_inverse` to `inv_ring`. + +## [1.2.0] — 2022-06-03 + +### Added + +- Added `reduce_mod`, `add_mod`, `mul_mod`, `pow_mod`. +- Added `num-bigint` and `ark-ff` support. +- Made `algorithms` public, but with unstable API for now. + +### Changed + +- Marked `Uint::as_limbs_mut` as unsafe. +- Unified `mul` implementations and move to `algorithms`. + +### Fixed + +- `uint!` macro incorrectly accepting hex digits in decimal. + +## [1.1.0] — 2022-05-31 + +### Added + +- Added `saturating_shl`. +- Added `approx_log`, `approx_log2`, `approx_log10` for `f64` log approximations. +- Added `approx_pow2` to construct from `f64` log2 approximation. +- Added `root` computing integer roots. + +### Changed + +- Made logarithms `usize` to match `BITS` in `pow`, `log` functions. +- Applied `track_caller` to div/rem ops to track div-by-zero easier. + +## [1.0.0] — 2022-05-28 + +### Added + +- Added comparison. +- Added add, sub, neg and sum functions. +- Added mul functions. +- Added division and remainder functions. +- Added pow and log functions. +- Added `next_power_of_two` and `next_multiple_of` functions. +- Added `checked_from_limb_slice` and `from_uint`. + +### Changed + +- `from_limb_slice` now handles arbitrary length slices. + +## [0.3.0] — 2022-05-23 + +### Added + +- All the binary operations (not, and, or, xor, shifts, rotate, etc) +- `Bits`, a newtype wrapped `Uint` restricted to non-numeric operations. +- Postgres `FromSql` support and JSON column support. +- `from_base_le` and `from_base_be` base conversion. +- `from_str_radix` string base conversion up to base64. +- `FromStr` trait with decimal, hex, octal and binary support. + +### Changed + +- `reverse_bits` is now by value instead of `&mut self`. + +## [0.2.1] — 2022-05-18 + +### Added + +- Extensive Postgres ToSql support supporting many column types. +- `TryFrom` for primitive integer types. +- Added `From` conversions to `f32` and `f64`. +- Implement all rust formatting: `Debug`, `Display`, decimal, hex, etc. +- `>>=` operator. +- `to_base_le` and `to_base_le` base conversion spigots +- `reverse_bits`, `most_sigificant_bits` bit methods. +- Optimized `as_le_{slice,bytes)_*` accessors. + +### Changed + +- Rewrote `to_{be,le}_bytes_*` to use optimized methods. This has trickle-down + effects for a lot of conversions and formatting. + +## [0.2.0] — 2022-05-16 + +### Added + +- Changelog +- CI test on stable Rust. +- Common bit-size aliases and nightly-only `Uint` alias. +- Added `to_{be/le}_bytes_vec` and made `try_from_le_byte_iter` public. +- Added `rlp` and `fastrlp` support. +- Added `into_limbs`, `leading_zeros`, `bit_len`, `byte_len`, `checked_log2`. +- Added `primitive-types` support. + +### Changed + +- Changed to `Uint` to get stable compatibility! +- Added generic `BYTES` parameter to `to_{be/le}_bytes`. +- Renamed `try_from_{be/le}_slice`. + +## [0.1.0] — 2022-05-15 + +### Added + +- Const-generic `Uint` structure. +- Basic `overflowing_add` implementation. +- Algorithms for division and gcd (currently unused). +- `uint!` and `const_for!` macros. +- Documentation with examples. +- Support for rand, arbitrary, quickcheck, proptest, serde +- Github actions for linting, testing, code coverage, cargo-audit. +- Pushed to crates.io. + + + +[unreleased]: https://github.com/recmo/uint/compare/v1.12.3...HEAD +[1.12.3]: https://github.com/recmo/uint/releases/tag/v1.12.3 +[1.12.1]: https://github.com/recmo/uint/releases/tag/v1.12.1 +[1.12.0]: https://github.com/recmo/uint/releases/tag/v1.12.0 +[1.11.1]: https://github.com/recmo/uint/releases/tag/v1.11.1 +[1.11.0]: https://github.com/recmo/uint/releases/tag/v1.11.0 +[1.10.1]: https://github.com/recmo/uint/releases/tag/v1.10.1 +[1.10.0]: https://github.com/recmo/uint/releases/tag/v1.10.0 +[1.9.0]: https://github.com/recmo/uint/releases/tag/v1.9.0 +[1.8.0]: https://github.com/recmo/uint/releases/tag/v1.8.0 +[1.7.0]: https://github.com/recmo/uint/releases/tag/v1.7.0 +[1.6.0]: https://github.com/recmo/uint/releases/tag/v1.6.0 +[1.5.1]: https://github.com/recmo/uint/releases/tag/v1.5.1 +[1.5.0]: https://github.com/recmo/uint/releases/tag/v1.5.0 +[1.4.1]: https://github.com/recmo/uint/releases/tag/v1.4.1 +[1.4.0]: https://github.com/recmo/uint/releases/tag/v1.4.0 +[1.3.0]: https://github.com/recmo/uint/releases/tag/v1.3.0 +[1.2.0]: https://github.com/recmo/uint/releases/tag/v1.2.0 +[1.1.0]: https://github.com/recmo/uint/releases/tag/v1.1.0 +[1.0.0]: https://github.com/recmo/uint/releases/tag/v1.0.0 +[0.3.0]: https://github.com/recmo/uint/releases/tag/v0.3.0 +[0.2.1]: https://github.com/recmo/uint/releases/tag/v0.2.1 +[0.2.0]: https://github.com/recmo/uint/releases/tag/v0.2.0 +[0.1.0]: https://github.com/recmo/uint/releases/tag/v0.1.0 diff --git a/guest-libs/ruint/Cargo.toml b/guest-libs/ruint/Cargo.toml new file mode 100644 index 0000000000..9efe860e91 --- /dev/null +++ b/guest-libs/ruint/Cargo.toml @@ -0,0 +1,129 @@ +[package] +name = "openvm-ruint" +description = "OpenVM fork of ruint" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs", "--html-in-header", ".cargo/katex-header.html"] + +[dependencies] +openvm-ruint-macro = { version = "1.2.1", path = "ruint-macro" } +openvm-bigint-guest = { workspace = true, features = ["export-intrinsics"] } + +thiserror = { version = "1.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 = { version = "0.3", optional = true, default-features = false, features = ["alloc"] } +num-bigint = { version = "0.4", optional = true, default-features = false } +num-traits = { version = "0.2.16", optional = true, default-features = false } +parity-scale-codec = { version = "3", optional = true, features = [ + "derive", + "max-encoded-len", +], default-features = false } +primitive-types = { version = "0.12", optional = true, default-features = false } +proptest = { version = "1.2", optional = true, default-features = false } +pyo3 = { version = "0.24", optional = true, default-features = false } +quickcheck = { version = "1", optional = true, default-features = false } +rand = { version = "0.8", 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 } + +# postgres +bytes = { version = "1.4", optional = true } +postgres-types = { version = "0.2", optional = true } + +# sqlx +sqlx-core = { version = "0.7", optional = true } + +[dev-dependencies] +openvm-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" } + +rand = "0.8" + +approx = "0.5" +bincode = "1.3" +hex-literal = "0.4" +postgres = "0.19" +proptest = "1.2" +serde_json = "1.0" + +# 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 = [ + "alloc", + "alloy-rlp?/std", + "ark-ff-03?/std", + "ark-ff-04?/std", + "bytes?/std", + "fastrlp?/std", + "num-bigint?/std", + "num-traits?/std", + "parity-scale-codec?/std", + "primitive-types?/std", + "proptest?/std", + "rand?/std", + "rlp?/std", + "serde?/std", + "valuable?/std", + "zeroize?/std", +] +ssz = ["std", "dep:ethereum_ssz"] +alloc = ["proptest?/alloc", "rand?/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"] +fastrlp = ["dep:fastrlp", "alloc"] +num-bigint = ["dep:num-bigint", "alloc"] +num-traits = ["dep:num-traits", "alloc"] +parity-scale-codec = ["dep:parity-scale-codec", "alloc"] +primitive-types = ["dep:primitive-types"] +proptest = ["dep:proptest", "std"] # TODO: change to "alloc" on the next proptest release (>1.2.0) +pyo3 = ["dep:pyo3", "std"] +quickcheck = ["dep:quickcheck", "std"] +rand = ["dep:rand"] +rlp = ["dep:rlp", "alloc"] +serde = ["dep:serde", "alloc"] # TODO: try to avoid alloc in serde impls +valuable = ["dep:valuable"] +zeroize = ["dep:zeroize"] +bytemuck = ["dep:bytemuck"] + +postgres = ["dep:postgres-types", "dep:bytes", "std", "dep:thiserror"] +sqlx = ["dep:sqlx-core", "std", "dep:thiserror"] diff --git a/guest-libs/ruint/LICENSE b/guest-libs/ruint/LICENSE new file mode 100644 index 0000000000..62ed7829c6 --- /dev/null +++ b/guest-libs/ruint/LICENSE @@ -0,0 +1,9 @@ +# The MIT License (MIT) + +Copyright © 2022 Remco Bloemen + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +**The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.** diff --git a/guest-libs/ruint/README.md b/guest-libs/ruint/README.md new file mode 100644 index 0000000000..9620892316 --- /dev/null +++ b/guest-libs/ruint/README.md @@ -0,0 +1,3 @@ +# OpenVM fork of the `ruint` crate + +See the [upstream docs](https://github.com/recmo/uint/blob/main/README.md) \ No newline at end of file diff --git a/guest-libs/ruint/cspell.json b/guest-libs/ruint/cspell.json new file mode 100644 index 0000000000..a501d95454 --- /dev/null +++ b/guest-libs/ruint/cspell.json @@ -0,0 +1,48 @@ +{ + "version": "0.2", + "ignorePaths": [], + "dictionaryDefinitions": [], + "dictionaries": [], + "words": [ + "aarch", + "adcs", + "archs", + "BYTEA", + "clippy", + "codecov", + "conv", + "cpython", + "cset", + "divq", + "divrem", + "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..caba8a085a --- /dev/null +++ b/guest-libs/ruint/deny.toml @@ -0,0 +1,34 @@ +[advisories] +vulnerability = "deny" +unmaintained = "warn" +unsound = "warn" +yanked = "warn" +notice = "warn" + +[bans] +multiple-versions = "warn" +wildcards = "warn" +highlight = "all" + +[licenses] +unlicensed = "deny" +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" +] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" diff --git a/guest-libs/ruint/ruint-macro/Cargo.toml b/guest-libs/ruint/ruint-macro/Cargo.toml new file mode 100644 index 0000000000..aa0207f57b --- /dev/null +++ b/guest-libs/ruint/ruint-macro/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "openvm-ruint-macro" +description = "OpenVM fork of ruint-macro" +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] +openvm-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..6622988e26 --- /dev/null +++ b/guest-libs/ruint/ruint-macro/README.md @@ -0,0 +1 @@ +See the [upstream docs](https://github.com/recmo/uint/blob/main/ruint-macro/README.md) \ No newline at end of file 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..1905304ea6 --- /dev/null +++ b/guest-libs/ruint/ruint-macro/src/lib.rs @@ -0,0 +1,299 @@ +#![doc = include_str!("../README.md")] +// Silenced these warnings because we use our own clippy rules +//#![warn(clippy::all, clippy::pedantic, clippy::cargo, 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(|| "::openvm_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..4ec163fbeb --- /dev/null +++ b/guest-libs/ruint/ruint-macro/tests/parse_mixed.rs @@ -0,0 +1,31 @@ +use openvm_ruint::aliases::{B256, U256}; +use openvm_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/rustfmt.toml b/guest-libs/ruint/rustfmt.toml new file mode 100644 index 0000000000..c97a630e26 --- /dev/null +++ b/guest-libs/ruint/rustfmt.toml @@ -0,0 +1,18 @@ +# See: https://github.com/rust-lang/rustfmt/blob/master/Configurations.md +edition = "2021" +# group_imports = "One" +imports_granularity = "Crate" +use_field_init_shorthand = true +wrap_comments = true +comment_width = 80 +struct_field_align_threshold = 20 +enum_discrim_align_threshold = 20 +condense_wildcard_suffixes = true +format_strings = true +format_macro_matchers = true +format_macro_bodies = true +normalize_comments = true +format_code_in_doc_comments = true +overflow_delimited_expr = true +normalize_doc_attributes = true +hex_literal_case = "Lower" diff --git a/guest-libs/ruint/src/add.rs b/guest-libs/ruint/src/add.rs new file mode 100644 index 0000000000..0bb78246c7 --- /dev/null +++ b/guest-libs/ruint/src/add.rs @@ -0,0 +1,319 @@ +use crate::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) { + // TODO: Replace with `u64::carrying_add` once stable. + #[inline] + const fn u64_carrying_add(lhs: u64, rhs: u64, carry: bool) -> (u64, bool) { + let (a, b) = lhs.overflowing_add(rhs); + let (c, d) = a.overflowing_add(carry as u64); + (c, b || d) + } + + if BITS == 0 { + return (Self::ZERO, false); + } + let mut carry = false; + let mut i = 0; + while i < LIMBS { + (self.limbs[i], carry) = u64_carrying_add(self.limbs[i], rhs.limbs[i], carry); + i += 1; + } + let overflow = carry || self.limbs[LIMBS - 1] > Self::MASK; + self.limbs[LIMBS - 1] &= Self::MASK; + (self, 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) { + // TODO: Replace with `u64::borrowing_sub` once stable. + #[inline] + const fn u64_borrowing_sub(lhs: u64, rhs: u64, borrow: bool) -> (u64, bool) { + let (a, b) = lhs.overflowing_sub(rhs); + let (c, d) = a.overflowing_sub(borrow as u64); + (c, b || d) + } + + if BITS == 0 { + return (Self::ZERO, false); + } + let mut borrow = false; + let mut i = 0; + while i < LIMBS { + (self.limbs[i], borrow) = u64_borrowing_sub(self.limbs[i], rhs.limbs[i], borrow); + i += 1; + } + let overflow = borrow || self.limbs[LIMBS - 1] > Self::MASK; + self.limbs[LIMBS - 1] &= Self::MASK; + (self, 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::from(1), !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..d9d0d9b855 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/div/knuth.rs @@ -0,0 +1,512 @@ +//! 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. +/// +/// Requires +/// * the highest bit of the divisor to be set, +/// * the `divisor` and `numerator` to be at least two limbs, and +/// * `numerator` is at least as long as `divisor`. +/// +/// # Panics +/// +/// May panic if the above requirements are not met. +#[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. +/// +/// Requires +/// * the highest limb of the divisor to be non-zero, +/// * the `divisor` and `numerator` to be at least two limbs, and +/// * `numerator` is at least as long as `divisor`. +/// +/// # Panics +/// +/// May panic if the above requirements are not met. +#[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..b2138f5a5e --- /dev/null +++ b/guest-libs/ruint/src/algorithms/gcd/matrix.rs @@ -0,0 +1,456 @@ +#![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; + // dbg!(q); + 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; + // dbg!(q); + 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; + // dbg!(q); + 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; + // dbg!(q); + 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; + // TODO: Increase cases when perf is better. + let mut config = Config::default(); + config.cases = min(config.cases, if BITS > 500 { 12 } else { 40 }); + 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..a7d2114d99 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/gcd/mod.rs @@ -0,0 +1,255 @@ +#![allow(clippy::module_name_repetitions)] + +// 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::from(1); + let mut s1 = Uint::ZERO; + let mut t0 = Uint::ZERO; + let mut t1 = Uint::from(1); + 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 == Uint::ZERO { + return None; + } + let mut a = modulus; + let mut b = num; + if b >= a { + b %= a; + } + if b == Uint::ZERO { + return None; + } + + let mut t0 = Uint::ZERO; + let mut t1 = Uint::from(1); + 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::from(1) { + // 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 core::cmp::min; + 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; + // TODO: Increase cases when perf is better. + let mut config = Config::default(); + config.cases = min(config.cases, if BITS > 500 { 9 } else { 30 }); + proptest!(config, |(a: U, b: U)| { + assert_eq!(gcd(a, b), gcd_ref(a, b)); + }); + }); + } + + #[test] + #[allow(clippy::absurd_extreme_comparisons)] // Generated code + fn test_gcd_extended() { + const_for!(BITS in SIZES { + const LIMBS: usize = nlimbs(BITS); + type U = Uint; + // TODO: Increase cases when perf is better. + let mut config = Config::default(); + config.cases = min(config.cases, if BITS > 500 { 3 } else { 10 }); + 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..69e09e2a4b --- /dev/null +++ b/guest-libs/ruint/src/algorithms/mod.rs @@ -0,0 +1,118 @@ +//! ⚠️ 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; +#[cfg(feature = "alloc")] // TODO: Make mul_redc alloc-free +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, addmul_ref, mul_nx1, submul_nx1}, + ops::{adc, sbb}, + shift::{shift_left_small, shift_right_small}, +}; +#[cfg(feature = "alloc")] +pub use mul_redc::mul_redc; + +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()) +} diff --git a/guest-libs/ruint/src/algorithms/mul.rs b/guest-libs/ruint/src/algorithms/mul.rs new file mode 100644 index 0000000000..b554c1b229 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/mul.rs @@ -0,0 +1,354 @@ +#![allow(clippy::module_name_repetitions)] + +use crate::algorithms::{ops::sbb, DoubleWord}; + +#[inline] +#[allow(clippy::cast_possible_truncation)] // Intentional truncation. +#[allow(dead_code)] // Used for testing +pub 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 +} + +/// ⚠️ 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 openvm_ruint::algorithms::addmul; +/// let mut result = [0]; +/// let overflow = addmul(&mut result, &[3], &[4]); +/// assert_eq!(overflow, false); +/// assert_eq!(result, [12]); +/// ``` +#[inline] +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] +pub fn add_nx1(lhs: &mut [u64], mut a: u64) -> u64 { + if a == 0 { + return 0; + } + for lhs in lhs { + let sum = u128::add(*lhs, a); + *lhs = sum.low(); + a = sum.high(); + 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), + _ => { + let _ = addmul(lhs, a, b); + } + } +} + +/// Computes `lhs += a * b` for 1 limb. +#[inline(always)] +fn addmul_1(lhs: &mut [u64], a: &[u64], b: &[u64]) { + assert_eq!(lhs.len(), 1); + assert_eq!(a.len(), 1); + assert_eq!(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]) { + assert_eq!(lhs.len(), 2); + assert_eq!(a.len(), 2); + assert_eq!(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]) { + assert_eq!(lhs.len(), 3); + assert_eq!(a.len(), 3); + assert_eq!(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]) { + assert_eq!(lhs.len(), 4); + assert_eq!(a.len(), 4); + assert_eq!(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] +pub fn mul_nx1(lhs: &mut [u64], a: u64) -> u64 { + let mut carry = 0; + for lhs in &mut *lhs { + let product = u128::muladd(*lhs, a, carry); + *lhs = product.low(); + carry = product.high(); + } + 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] +pub fn addmul_nx1(lhs: &mut [u64], a: &[u64], b: u64) -> u64 { + debug_assert_eq!(lhs.len(), a.len()); + let mut carry = 0; + for (lhs, a) in lhs.iter_mut().zip(a.iter().copied()) { + let product = u128::muladd2(a, b, carry, *lhs); + *lhs = product.low(); + carry = product.high(); + } + 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] +pub fn submul_nx1(lhs: &mut [u64], a: &[u64], b: u64) -> u64 { + debug_assert_eq!(lhs.len(), a.len()); + let mut carry = 0; + let mut borrow = 0; + for (lhs, a) in lhs.iter_mut().zip(a.iter().copied()) { + // Compute product limbs + let limb = { + let product = u128::muladd(a, b, carry); + carry = product.high(); + product.low() + }; + + // Subtract + let (new, b) = sbb(*lhs, limb, borrow); + *lhs = new; + borrow = b; + } + borrow + carry +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::{collection, num::u64, proptest}; + + #[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..0cbbcdc046 --- /dev/null +++ b/guest-libs/ruint/src/algorithms/mul_redc.rs @@ -0,0 +1,70 @@ +use super::addmul; +use core::iter::zip; + +/// See Handbook of Applied Cryptography, Algorithm 14.32, p. 601. +#[allow(clippy::cognitive_complexity)] // REFACTOR: Improve +#[inline] +pub fn mul_redc(a: &[u64], b: &[u64], result: &mut [u64], m: &[u64], inv: u64) { + debug_assert!(!m.is_empty()); + debug_assert_eq!(a.len(), m.len()); + debug_assert_eq!(b.len(), m.len()); + debug_assert_eq!(result.len(), m.len()); + debug_assert_eq!(inv.wrapping_mul(m[0]), u64::MAX); + + // Compute temp full product. + // OPT: Do combined multiplication and reduction. + let mut temp = vec![0; 2 * m.len() + 1]; + addmul(&mut temp, a, b); + + // Reduce temp. + for i in 0..m.len() { + let u = temp[i].wrapping_mul(inv); + + // REFACTOR: Create add_mul1 routine. + let mut carry = 0; + #[allow(clippy::cast_possible_truncation)] // Intentional + for j in 0..m.len() { + carry += u128::from(temp[i + j]) + u128::from(m[j]) * u128::from(u); + temp[i + j] = carry as u64; + carry >>= 64; + } + #[allow(clippy::cast_possible_truncation)] // Intentional + for j in m.len()..(temp.len() - i) { + carry += u128::from(temp[i + j]); + temp[i + j] = carry as u64; + carry >>= 64; + } + debug_assert!(carry == 0); + } + debug_assert!(temp[temp.len() - 1] <= 1); // Basically a carry flag. + + // Copy result. + result.copy_from_slice(&temp[m.len()..2 * m.len()]); + + // Subtract one more m if result >= m + let mut reduce = true; + // REFACTOR: Create cmp routine + if temp[temp.len() - 1] == 0 { + for (r, m) in zip(result.iter().rev(), m.iter().rev()) { + if r < m { + reduce = false; + break; + } + if r > m { + break; + } + } + } + if reduce { + // REFACTOR: Create sub routine + let mut carry = 0; + #[allow(clippy::cast_possible_truncation)] // Intentional + #[allow(clippy::cast_sign_loss)] // Intentional + for (r, m) in zip(result.iter_mut(), m.iter().copied()) { + carry += i128::from(*r) - i128::from(m); + *r = carry as u64; + carry >>= 64; // Sign extending shift + } + debug_assert!(carry == 0 || temp[temp.len() - 1] == 1); + } +} 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..b700652c08 --- /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)] +pub 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..4a981413dc --- /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::from(1); + 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, std::iter::empty()), + Ok(Uint::<0, 0>::ZERO) + ); + assert_eq!( + Uint::<0, 0>::from_base_be(10, std::iter::once(0)), + Ok(Uint::<0, 0>::ZERO) + ); + assert_eq!( + Uint::<0, 0>::from_base_be(10, std::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..8aa73f3eab --- /dev/null +++ b/guest-libs/ruint/src/bits.rs @@ -0,0 +1,1121 @@ +use crate::Uint; +use core::ops::{ + BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, ShlAssign, Shr, + ShrAssign, +}; + +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` exceeds the byte width of the number. + /// + /// # Examples + /// + /// ``` + /// # use openvm_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 openvm_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 + } + } + + /// 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 + } + + /// Returns the number of leading zeros in the binary representation of + /// `self`. + #[inline] + #[must_use] + pub fn leading_zeros(&self) -> usize { + self.as_limbs() + .iter() + .rev() + .position(|&limb| limb != 0) + .map_or(BITS, |n| { + let fixed = Self::MASK.leading_zeros() as usize; + let skipped = n * 64; + let top = self.as_limbs()[LIMBS - n - 1].leading_zeros() as usize; + skipped + top - fixed + }) + } + + /// Returns the number of leading ones in the binary representation of + /// `self`. + #[inline] + #[must_use] + pub fn leading_ones(&self) -> usize { + (self.not()).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 fn count_ones(&self) -> usize { + self.as_limbs() + .iter() + .map(|limb| limb.count_ones() as usize) + .sum() + } + + /// Returns the number of zeros in the binary representation of `self`. + #[must_use] + #[inline] + pub fn count_zeros(&self) -> usize { + BITS - self.count_ones() + } + + /// Length of the number in bits ignoring leading zeros. + #[must_use] + #[inline] + pub fn bit_len(&self) -> usize { + BITS - self.leading_zeros() + } + + /// Length of the number in bytes ignoring leading zeros. + #[must_use] + #[inline] + pub 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(mut self, rhs: usize) -> (Self, bool) { + let (limbs, bits) = (rhs / 64, rhs % 64); + if limbs >= LIMBS { + return (Self::ZERO, self != Self::ZERO); + } + if bits == 0 { + // Check for overflow + let mut overflow = false; + for i in (LIMBS - limbs)..LIMBS { + overflow |= self.limbs[i] != 0; + } + if self.limbs[LIMBS - limbs - 1] > Self::MASK { + overflow = true; + } + + // Shift + for i in (limbs..LIMBS).rev() { + assume!(i >= limbs && i - limbs < LIMBS); + self.limbs[i] = self.limbs[i - limbs]; + } + self.limbs[..limbs].fill(0); + self.limbs[LIMBS - 1] &= Self::MASK; + return (self, overflow); + } + + // Check for overflow + let mut overflow = false; + for i in (LIMBS - limbs)..LIMBS { + overflow |= self.limbs[i] != 0; + } + if self.limbs[LIMBS - limbs - 1] >> (64 - bits) != 0 { + overflow = true; + } + if self.limbs[LIMBS - limbs - 1] << bits > Self::MASK { + overflow = true; + } + + // Shift + for i in (limbs + 1..LIMBS).rev() { + assume!(i - limbs < LIMBS && i - limbs - 1 < LIMBS); + self.limbs[i] = self.limbs[i - limbs] << bits; + self.limbs[i] |= self.limbs[i - limbs - 1] >> (64 - bits); + } + self.limbs[limbs] = self.limbs[0] << bits; + self.limbs[..limbs].fill(0); + self.limbs[LIMBS - 1] &= Self::MASK; + (self, overflow) + } + + /// 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(mut self, rhs: usize) -> (Self, bool) { + let (limbs, bits) = (rhs / 64, rhs % 64); + if limbs >= LIMBS { + return (Self::ZERO, self != Self::ZERO); + } + if bits == 0 { + // Check for overflow + let mut overflow = false; + for i in 0..limbs { + overflow |= self.limbs[i] != 0; + } + + // Shift + for i in 0..(LIMBS - limbs) { + self.limbs[i] = self.limbs[i + limbs]; + } + self.limbs[LIMBS - limbs..].fill(0); + return (self, overflow); + } + + // Check for overflow + let overflow = (self.limbs[LIMBS - limbs - 1] >> (bits - 1)) & 1 != 0; + + // Shift + for i in 0..(LIMBS - limbs - 1) { + assume!(i + limbs < LIMBS && i + limbs + 1 < LIMBS); + self.limbs[i] = self.limbs[i + limbs] >> bits; + self.limbs[i] |= self.limbs[i + limbs + 1] << (64 - bits); + } + self.limbs[LIMBS - limbs - 1] = self.limbs[LIMBS - 1] >> bits; + self.limbs[LIMBS - limbs..].fill(0); + (self, overflow) + } + + /// 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(mut self) -> Self::Output { + if BITS == 0 { + return Self::ZERO; + } + for limb in &mut self.limbs { + *limb = u64::not(*limb); + } + self.limbs[LIMBS - 1] &= Self::MASK; + 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 super::*; + use crate::{aliases::U128, const_for, nlimbs}; + use core::cmp::min; + use proptest::proptest; + + #[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>::from(1).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::from(1).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>::from(1).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)] + 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); + dbg!(value, shifted, 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) + ); + + #[allow(clippy::unusual_byte_groupings)] + // creating a block to silence clippy warning + { + // 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([0x0080_0000_0000_000, 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..0034053c31 --- /dev/null +++ b/guest-libs/ruint/src/bytes.rs @@ -0,0 +1,491 @@ +// 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.reverse_bits(); + } + 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. Leading zeros + /// are ignored. The slice can be any length. + /// + /// # 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. Leading zeros + /// are ignored. The slice can be any length. + /// + /// 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; + limbs[i / 8] += (bytes[c] as u64) << ((i % 8) * 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. Leading zeros + /// are ignored. The slice can be any length. + /// + /// # 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. Leading zeros + /// are ignored. The slice can be any length. + /// + /// 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() / 8 > Self::LIMBS { + 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() { + limbs[i / 8] += (bytes[i] as u64) << ((i % 8) * 8); + i += 1; + } + if Self::LIMBS > 0 && limbs[Self::LIMBS - 1] > Self::MASK { + return None; + } + Some(Self::from_limbs(limbs)) + } +} + +/// 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; + 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)); + + const KL: [u64; 2] = K.limbs; + 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); + } + + #[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::())); + }); + }); + } +} 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..ae8959913e --- /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 openvm_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 openvm_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 openvm_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..ad6b757e90 --- /dev/null +++ b/guest-libs/ruint/src/div.rs @@ -0,0 +1,144 @@ +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 == Self::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 == Self::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 { + assert!(rhs != Self::ZERO, "Division by zero"); + let (q, r) = self.div_rem(rhs); + if r == Self::ZERO { + q + } else { + q + Self::from(1) + } + } + + /// 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) { + assert!(rhs != Self::ZERO, "Division by zero"); + 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::from(1)); + 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..0fa83704c7 --- /dev/null +++ b/guest-libs/ruint/src/from.rs @@ -0,0 +1,755 @@ +// 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 { + /// 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 openvm_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 openvm_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 openvm_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 openvm_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 openvm_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 openvm_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..d17ab2d0cc --- /dev/null +++ b/guest-libs/ruint/src/gcd.rs @@ -0,0 +1,99 @@ +use crate::{algorithms, Uint}; + +impl Uint { + /// Compute the greatest common divisor of two [`Uint`]s. + /// + /// # Examples + /// + /// ``` + /// # use openvm_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 core::cmp::min; + 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; + // TODO: Increase cases when perf is better. + let mut config = Config::default(); + config.cases = min(config.cases, if BITS > 500 { 3 } else { 10 }); + 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..5b2ea66dac --- /dev/null +++ b/guest-libs/ruint/src/lib.rs @@ -0,0 +1,382 @@ +#![doc = include_str!("../README.md")] +#![doc(issue_tracker_base_url = "https://github.com/recmo/uint/issues/")] +// Silenced these warnings because we use our own clippy rules +// #![warn( +// clippy::all, +// clippy::pedantic, +// clippy::nursery, +// clippy::missing_inline_in_public_items, +// missing_docs, +// unreachable_pub +// )] +#![allow( + clippy::doc_markdown, // Unfortunately many false positives on Latex. + clippy::inline_always, + clippy::module_name_repetitions, + clippy::redundant_pub_crate, + clippy::unreadable_literal, + clippy::let_unit_value, + clippy::option_if_let_else, + clippy::cast_sign_loss, + clippy::cast_lossless, +)] +#![cfg_attr(test, allow(clippy::wildcard_imports, clippy::cognitive_complexity))] +#![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")] +#[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; + +#[doc(inline)] +pub use self::{ + base_convert::BaseConvertError, + bytes::nbytes, + from::{FromUintError, ToFieldError, ToUintError, UintTryFrom, UintTryTo}, + string::ParseError, +}; + +// For documentation purposes we expose the macro directly, otherwise it is +// wrapped in ./macros.rs. +#[cfg(doc)] +#[doc(inline)] +pub use openvm_ruint_macro::uint; + +/// 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); + + /// 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 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 = { + let mut limbs = [u64::MAX; LIMBS]; + if BITS > 0 { + limbs[LIMBS - 1] &= Self::MASK; + } + Self::from_limbs(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 to large for the bit-size of the Uint. + #[inline(always)] + #[must_use] + #[track_caller] + pub const fn from_limbs(limbs: [u64; LIMBS]) -> Self { + if BITS > 0 && Self::MASK != u64::MAX { + // 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 } + } + + /// Construct a new integer from little-endian a slice of limbs. + /// + /// # Panics + /// + /// Panics if the value is to 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, + } + } +} + +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 openvm_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..d06d3ae3d9 --- /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 == Self::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 != Self::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 != Self::ZERO); + result -= Self::from(1); + continue; + } + } else { + // Overflow, so definitely larger than `value` + result -= Self::from(1); + } + break; + } + while let Some(trial) = result.checked_add(Self::from(1)) { + 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 openvm_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::from(1)), 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..a3cbb89aaa --- /dev/null +++ b/guest-libs/ruint/src/macros.rs @@ -0,0 +1,137 @@ +/// Wrapper for [`openvm_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::openvm_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) + } + } + }; +} + +macro_rules! assume { + ($e:expr $(,)?) => { + if !$e { + debug_unreachable!(stringify!($e)); + } + }; + + ($e:expr, $($t:tt)+) => { + if !$e { + debug_unreachable!($($t)+); + } + }; +} + +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 + openvm_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, openvm_ruint_macro::uint_with_path!([crate] 0x10U256)); + assert_eq!(value, openvm_ruint_macro::uint_with_path!([aaa] 0x10U256)); + assert_eq!(value, openvm_ruint_macro::uint_with_path!([aaa] 0x10U256)); + assert_eq!(value, openvm_ruint_macro::uint_with_path!([ruint] 0x10U256)); + assert_eq!( + value, + openvm_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..83f18311d5 --- /dev/null +++ b/guest-libs/ruint/src/modular.rs @@ -0,0 +1,305 @@ +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 == Self::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 == Self::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 modulus == Self::ZERO || modulus <= Self::from(1) { + // Also covers Self::BITS == 0 + return Self::ZERO; + } + + // Exponentiation by squaring + let mut result = Self::from(1); + 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. + /// + /// 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 openvm_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. + #[inline] + #[must_use] + #[cfg(feature = "alloc")] // TODO: Make mul_redc alloc-free + pub fn mul_redc(self, other: Self, modulus: Self, inv: u64) -> Self { + if BITS == 0 { + return Self::ZERO; + } + assert_eq!(inv.wrapping_mul(modulus.limbs[0]), u64::MAX); + let mut result = Self::ZERO; + algorithms::mul_redc( + self.as_limbs(), + other.as_limbs(), + &mut result.limbs, + modulus.as_limbs(), + inv, + ); + debug_assert!(result < modulus); + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{aliases::U64, const_for, nlimbs}; + use core::cmp::min; + 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; + // TODO: Increase cases when perf is better. + let mut config = Config::default(); + // BUG: Proptest still runs 5 cases even if we set it to 1. + config.cases = min(config.cases, if BITS > 500 { 1 } else { 3 }); + 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; + // TODO: Increase cases when perf is better. + let mut config = Config::default(); + config.cases = min(config.cases, if BITS > 500 { 6 } else { 20 }); + proptest!(config, |(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); + } + }); + }); + } +} diff --git a/guest-libs/ruint/src/mul.rs b/guest-libs/ruint/src/mul.rs new file mode 100644 index 0000000000..9ded37d8cf --- /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 openvm_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.limbs[LIMBS - 1] &= Self::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.limbs[LIMBS - 1] &= Self::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.limbs[LIMBS - 1] &= Self::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 openvm_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::from(1), 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::from(1), 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..8b486ed1cd --- /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 openvm_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 openvm_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::from(1); + while exp != Self::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::from(1); + while exp != Self::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 openvm_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..40dd339862 --- /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 openvm_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 == Self::ZERO { + return Self::ZERO; + } + + // Handle case where `degree > Self::BITS`. + if degree >= Self::BITS { + return Self::from(1); + } + + // 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::from(1)) + .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::from(1)) + .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..8e5e8889af --- /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 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 openvm_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::from(1) << 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 openvm_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 openvm_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 == Self::ZERO { + return None; + } + let (q, r) = self.div_rem(rhs); + if r == Self::ZERO { + return Some(self); + } + let q = q.checked_add(Self::from(1))?; + 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/bytemuck.rs b/guest-libs/ruint/src/support/bytemuck.rs new file mode 100644 index 0000000000..5b2fb62c65 --- /dev/null +++ b/guest-libs/ruint/src/support/bytemuck.rs @@ -0,0 +1,82 @@ +//! 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 openvm_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(), + std::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/fastrlp.rs b/guest-libs/ruint/src/support/fastrlp.rs new file mode 100644 index 0000000000..c855183397 --- /dev/null +++ b/guest-libs/ruint/src/support/fastrlp.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::{ + 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..2c75fca28d --- /dev/null +++ b/guest-libs/ruint/src/support/mod.rs @@ -0,0 +1,47 @@ +//! 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 bytemuck; +mod fastrlp; +mod num_bigint; +mod num_traits; +pub mod postgres; +mod primitive_types; +mod proptest; +mod pyo3; +mod quickcheck; +mod rand; +mod rlp; +pub mod scale; +mod serde; +pub mod sqlx; +pub mod ssz; +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 + +// More databases: +// * https://crates.io/crates/diesel 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_traits.rs b/guest-libs/ruint/src/support/num_traits.rs new file mode 100644 index 0000000000..e73d5f9b8a --- /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::from(1) + } +} + +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..04d67b42bb --- /dev/null +++ b/guest-libs/ruint/src/support/postgres.rs @@ -0,0 +1,549 @@ +//! 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 postgres_types::{to_sql_checked, FromSql, IsNull, ToSql, Type, WrongType}; +use std::{ + error::Error, + iter, + str::{from_utf8, FromStr}, +}; +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; + } + // dbg!(hex::encode(&serialized)); + + // 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()), + }; + // dbg!(&expr); + let ground_truth = { + let mut client = client.lock().unwrap(); + get_binary(&mut client, &expr) + }; + // dbg!(hex::encode(&ground_truth)); + + // 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..5f798036a5 --- /dev/null +++ b/guest-libs/ruint/src/support/proptest.rs @@ -0,0 +1,54 @@ +//! 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(|mut limbs| { + if LIMBS > 0 { + limbs[LIMBS - 1] &= Self::MASK; + } + Self::from_limbs(limbs) + }) + } +} + +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..d79af0e99a --- /dev/null +++ b/guest-libs/ruint/src/support/rand.rs @@ -0,0 +1,42 @@ +//! 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 crate::Uint; +use rand::{ + distributions::{Distribution, Standard, Uniform}, + Rng, +}; + +impl Distribution> for Standard { + fn sample(&self, rng: &mut R) -> Uint { + let mut limbs = [0; LIMBS]; + if let Some((last, rest)) = limbs.split_last_mut() { + for limb in rest { + *limb = rng.gen(); + } + *last = Uniform::new_inclusive(0, Uint::::MASK).sample(rng); + } + Uint::::from_limbs(limbs) + } +} + +#[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/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..765c437736 --- /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<'a, const BITS: usize, const LIMBS: usize> EncodeLike for CompactRefUint<'a, 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<'a, const BITS: usize, const LIMBS: usize> Encode for CompactRefUint<'a, 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..cd6ba83bc4 --- /dev/null +++ b/guest-libs/ruint/src/support/serde.rs @@ -0,0 +1,266 @@ +//! 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 BITS == 0 { + return s.serialize_str(ZERO_STR); + } + + let le_bytes = self.as_le_bytes(); + let mut bytes = le_bytes.iter().rev().skip_while(|b| **b == 0); + + // We avoid String allocation if there is no non-0 byte + // If there is a first byte, we allocate a string, and write the prefix + // and first byte to it + let mut result = match bytes.next() { + Some(b) => { + let mut result = String::with_capacity(2 + nbytes(BITS) * 2); + write!(result, "0x{b:x}").unwrap(); + result + } + None => return s.serialize_str(ZERO_STR), + }; + bytes + .try_for_each(|byte| write!(result, "{byte:02x}")) + .unwrap(); + + s.serialize_str(&result) + } + + 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 hashes. +/// 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<'de, const BITS: usize, const LIMBS: usize> Visitor<'de> 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<'de, const BITS: usize, const LIMBS: usize> Visitor<'de> 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..c113f87a08 --- /dev/null +++ b/guest-libs/ruint/src/support/sqlx.rs @@ -0,0 +1,54 @@ +//! 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 crate::Uint; +use sqlx_core::{ + database::{Database, HasArguments, HasValueRef}, + decode::Decode, + encode::{Encode, IsNull}, + error::BoxDynError, + types::Type, +}; +use thiserror::Error; + +#[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) -> IsNull { + 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) -> 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..7c09b2b704 --- /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 openvm_ruint::{const_for, nlimbs, Uint}; + use proptest::proptest; + 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/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..4408baed8f --- /dev/null +++ b/guest-libs/ruint/src/support/zkvm.rs @@ -0,0 +1,80 @@ +/// 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}; + +use crate::Uint; + +// clippy thinks this is unused, but its needed to link the extern functions +// below +#[allow(unused_imports)] +use openvm_bigint_guest::externs::*; + +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/guest-libs/ruint/tests/lib.rs b/guest-libs/ruint/tests/lib.rs new file mode 100644 index 0000000000..3db2697775 --- /dev/null +++ b/guest-libs/ruint/tests/lib.rs @@ -0,0 +1,36 @@ +#[cfg(test)] +mod tests { + use eyre::Result; + use openvm_bigint_circuit::Int256Rv32Config; + use openvm_bigint_transpiler::Int256TranspilerExtension; + 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, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + + type F = BabyBear; + + #[test] + fn test_matrix_power() -> Result<()> { + let config = Int256Rv32Config::default(); + let elf = build_example_program_at_path( + get_programs_dir!("tests/programs"), + "matrix_power", + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Int256TranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } +} diff --git a/guest-libs/ruint/tests/programs/Cargo.toml b/guest-libs/ruint/tests/programs/Cargo.toml new file mode 100644 index 0000000000..efb2c57779 --- /dev/null +++ b/guest-libs/ruint/tests/programs/Cargo.toml @@ -0,0 +1,24 @@ +[workspace] +[package] +name = "openvm-ruint-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-ruint = { path = "../../", features = ["num-traits"] } + +serde = { version = "1.0", default-features = false, features = [ + "alloc", + "derive", +] } +num-traits = { version = "0.2.19", default-features = false } + +[features] +default = [] +std = ["serde/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/ruint/tests/programs/examples/matrix_power.rs b/guest-libs/ruint/tests/programs/examples/matrix_power.rs new file mode 100644 index 0000000000..95826d32de --- /dev/null +++ b/guest-libs/ruint/tests/programs/examples/matrix_power.rs @@ -0,0 +1,132 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::needless_range_loop)] +#![allow(clippy::eq_op)] + +openvm::entry!(main); +use core::array; + +use num_traits::cast::FromPrimitive; +use openvm::io::print; +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).unwrap())) +} + +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] = U256::from_u8(1).unwrap(); + } + res +} + +/// 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).unwrap(); + while exp > U256::from_u8(0).unwrap() { + 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 = 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).unwrap(); + let zero = U256::from_u8(0).unwrap(); + + let a: Matrix = get_matrix(1); + 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 { + if c[i][j] != two_to_200 { + print("FAIL: the resulting matrix is incorrect"); + panic!(); + } + } + } + + // Shift right tests + 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).unwrap() != zero { + print("FAIL: 2^200 >> 201 == 0 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 (should have wrapped)"); + panic!(); + } + + if zero - one + one != zero { + print("FAIL: 0 - 1 + 1 == 0 test failed (should have wrapped)"); + panic!(); + } + + if one << U256::from_u32(256).unwrap() != zero { + print("FAIL: 1 << 256 == 0 test failed"); + panic!(); + } + + if two_to_200 != two_to_200 { + print("FAIL: 2^200 clone test failed"); + panic!(); + } + + print("PASS"); +} 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/guest-libs/sha2/tests/lib.rs b/guest-libs/sha2/tests/lib.rs new file mode 100644 index 0000000000..9ebab5ac02 --- /dev/null +++ b/guest-libs/sha2/tests/lib.rs @@ -0,0 +1,33 @@ +#[cfg(test)] +mod tests { + use eyre::Result; + use openvm_circuit::utils::air_test; + use openvm_instructions::exe::VmExe; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_sha256_circuit::Sha256Rv32Config; + 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_sha256() -> Result<()> { + 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() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Sha256TranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } +} diff --git a/guest-libs/sha2/tests/programs/Cargo.toml b/guest-libs/sha2/tests/programs/Cargo.toml new file mode 100644 index 0000000000..df13f8dfc7 --- /dev/null +++ b/guest-libs/sha2/tests/programs/Cargo.toml @@ -0,0 +1,25 @@ +[workspace] +[package] +name = "openvm-sha2-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-platform = { path = "../../../../crates/toolchain/platform" } +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"] + +[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/sha2/tests/programs/examples/sha.rs b/guest-libs/sha2/tests/programs/examples/sha.rs new file mode 100644 index 0000000000..ebfd50cbee --- /dev/null +++ b/guest-libs/sha2/tests/programs/examples/sha.rs @@ -0,0 +1,29 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::hint::black_box; + +use hex::FromHex; +use openvm_sha2::sha256; + +openvm::entry!(main); + +pub fn main() { + let test_vectors = [ + ("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), + ("98c1c0bdb7d5fea9a88859f06c6c439f", "b6b2c9c9b6f30e5c66c977f1bd7ad97071bee739524aecf793384890619f2b05"), + ("5b58f4163e248467cc1cd3eecafe749e8e2baaf82c0f63af06df0526347d7a11327463c115210a46b6740244eddf370be89c", "ac0e25049870b91d78ef6807bb87fce4603c81abd3c097fba2403fd18b6ce0b7"), + ("9ad198539e3160194f38ac076a782bd5210a007560d1fce9ef78f8a4a5e4d78c6b96c250cff3520009036e9c6087d5dab587394edda862862013de49a12072485a6c01165ec0f28ffddf1873fbd53e47fcd02fb6a5ccc9622d5588a92429c663ce298cb71b50022fc2ec4ba9f5bbd250974e1a607b165fee16e8f3f2be20d7348b91a2f518ce928491900d56d9f86970611580350cee08daea7717fe28a73b8dcfdea22a65ed9f5a09198de38e4e4f2cc05b0ba3dd787a5363ab6c9f39dcb66c1a29209b1d6b1152769395df8150b4316658ea6ab19af94903d643fcb0ae4d598035ebe73c8b1b687df1ab16504f633c929569c6d0e5fae6eea43838fbc8ce2c2b43161d0addc8ccf945a9c4e06294e56a67df0000f561f61b630b1983ba403e775aaeefa8d339f669d1e09ead7eae979383eda983321e1743e5404b4b328da656de79ff52d179833a6bd5129f49432d74d001996c37c68d9ab49fcff8061d193576f396c20e1f0d9ee83a51290ba60efa9c3cb2e15b756321a7ca668cdbf63f95ec33b1c450aa100101be059dc00077245b25a6a66698dee81953ed4a606944076e2858b1420de0095a7f60b08194d6d9a997009d345c71f63a7034b976e409af8a9a040ac7113664609a7adedb76b2fadf04b0348392a1650526eb2a4d6ed5e4bbcda8aabc8488b38f4f5d9a398103536bb8250ed82a9b9825f7703c263f9e", "080ad71239852124fc26758982090611b9b19abf22d22db3a57f67a06e984a23") + ]; + for (input, expected_output) in test_vectors.iter() { + let input = Vec::from_hex(input).unwrap(); + let expected_output = Vec::from_hex(expected_output).unwrap(); + let output = sha256(&black_box(input)); + if output != *expected_output { + panic!(); + } + } +}