diff --git a/.github/workflows/bolt-equivalence.yml b/.github/workflows/bolt-equivalence.yml new file mode 100644 index 0000000000..fbc2a5c3e6 --- /dev/null +++ b/.github/workflows/bolt-equivalence.yml @@ -0,0 +1,90 @@ +name: Bolt equivalence + +on: + pull_request: + branches: [main] + push: + branches: [main] + schedule: + - cron: "47 9 * * *" + workflow_dispatch: + inputs: + include_full_sweep: + description: "Run the optional full jolt-equivalence sweep" + required: false + default: "false" + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -D warnings + +jobs: + generated-role-full-field: + name: generated role full-field challenge parity + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rust-src + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Install LLVM + run: brew install llvm + - name: Configure LLVM + run: | + llvm_prefix="$(brew --prefix llvm)" + sdkroot="$(xcrun --show-sdk-path)" + echo "MLIR_SYS_220_PREFIX=${llvm_prefix}" >> "$GITHUB_ENV" + echo "${llvm_prefix}/bin" >> "$GITHUB_PATH" + echo "SDKROOT=${sdkroot}" >> "$GITHUB_ENV" + echo "BINDGEN_EXTRA_CLANG_ARGS=-isysroot${sdkroot}" >> "$GITHUB_ENV" + - name: Run generated role parity + run: cargo nextest run -p jolt-equivalence --test generated_role_crates --cargo-quiet + + real-data-parity-tamper: + name: real-data parity and tamper gates + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rust-src + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Install LLVM + run: brew install llvm + - name: Configure LLVM + run: | + llvm_prefix="$(brew --prefix llvm)" + sdkroot="$(xcrun --show-sdk-path)" + echo "MLIR_SYS_220_PREFIX=${llvm_prefix}" >> "$GITHUB_ENV" + echo "${llvm_prefix}/bin" >> "$GITHUB_PATH" + echo "SDKROOT=${sdkroot}" >> "$GITHUB_ENV" + echo "BINDGEN_EXTRA_CLANG_ARGS=-isysroot${sdkroot}" >> "$GITHUB_ENV" + - name: Run real-data parity and tamper gates + run: cargo nextest run -p jolt-equivalence --test bolt_commitment --cargo-quiet + + full-equivalence-sweep: + name: optional full equivalence/tamper sweep + runs-on: macos-latest + if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.include_full_sweep == 'true') + steps: + - uses: actions/checkout@v6 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rust-src + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Install LLVM + run: brew install llvm + - name: Configure LLVM + run: | + llvm_prefix="$(brew --prefix llvm)" + sdkroot="$(xcrun --show-sdk-path)" + echo "MLIR_SYS_220_PREFIX=${llvm_prefix}" >> "$GITHUB_ENV" + echo "${llvm_prefix}/bin" >> "$GITHUB_PATH" + echo "SDKROOT=${sdkroot}" >> "$GITHUB_ENV" + echo "BINDGEN_EXTRA_CLANG_ARGS=-isysroot${sdkroot}" >> "$GITHUB_ENV" + - name: Run full equivalence/tamper sweep + run: cargo nextest run -p jolt-equivalence --cargo-quiet diff --git a/.github/workflows/bolt-perf-oracle.yml b/.github/workflows/bolt-perf-oracle.yml new file mode 100644 index 0000000000..6def69112c --- /dev/null +++ b/.github/workflows/bolt-perf-oracle.yml @@ -0,0 +1,91 @@ +name: Bolt perf oracle + +on: + pull_request: + branches: [main] + push: + branches: [main] + schedule: + - cron: "23 8 * * *" + workflow_dispatch: + inputs: + include_large: + description: "Run the sha2-chain 2^20 gate" + required: false + default: "true" + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -D warnings + +jobs: + sha2-chain-2-16: + name: sha2-chain 2^16 core-vs-Bolt + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rust-src + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Install LLVM + run: brew install llvm + - name: Configure LLVM + run: | + llvm_prefix="$(brew --prefix llvm)" + sdkroot="$(xcrun --show-sdk-path)" + echo "MLIR_SYS_220_PREFIX=${llvm_prefix}" >> "$GITHUB_ENV" + echo "${llvm_prefix}/bin" >> "$GITHUB_PATH" + echo "SDKROOT=${sdkroot}" >> "$GITHUB_ENV" + echo "BINDGEN_EXTRA_CLANG_ARGS=-isysroot${sdkroot}" >> "$GITHUB_ENV" + - name: Run perf oracle + env: + JOLT_BOLT_PERF_TRACE: "1" + run: > + cargo nextest run -p jolt-equivalence --test bolt_perf --release + --cargo-quiet --run-ignored only --no-capture + bolt_sha2_chain_2_16_core_vs_bolt_perf_oracle + - name: Upload Perfetto trace + if: always() + uses: actions/upload-artifact@v7 + with: + name: bolt-perf-oracle-sha2-chain-2-16 + path: benchmark-runs/perfetto_traces/*.json + if-no-files-found: warn + + sha2-chain-2-20: + name: sha2-chain 2^20 core-vs-Bolt + runs-on: macos-latest + if: github.event_name != 'workflow_dispatch' || inputs.include_large == 'true' + steps: + - uses: actions/checkout@v6 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rust-src + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Install LLVM + run: brew install llvm + - name: Configure LLVM + run: | + llvm_prefix="$(brew --prefix llvm)" + sdkroot="$(xcrun --show-sdk-path)" + echo "MLIR_SYS_220_PREFIX=${llvm_prefix}" >> "$GITHUB_ENV" + echo "${llvm_prefix}/bin" >> "$GITHUB_PATH" + echo "SDKROOT=${sdkroot}" >> "$GITHUB_ENV" + echo "BINDGEN_EXTRA_CLANG_ARGS=-isysroot${sdkroot}" >> "$GITHUB_ENV" + - name: Run perf oracle + env: + JOLT_BOLT_PERF_TRACE: "1" + run: > + cargo nextest run -p jolt-equivalence --test bolt_perf --release + --cargo-quiet --run-ignored only --no-capture + bolt_sha2_chain_2_20_core_vs_bolt_perf_oracle + - name: Upload Perfetto trace + if: always() + uses: actions/upload-artifact@v7 + with: + name: bolt-perf-oracle-sha2-chain-2-20 + path: benchmark-runs/perfetto_traces/*.json + if-no-files-found: warn diff --git a/Cargo.lock b/Cargo.lock index ddc3063d30..1f3bc1d6f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -954,6 +954,26 @@ dependencies = [ "virtue", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.117", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -1075,6 +1095,13 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bolt" +version = "0.1.0" +dependencies = [ + "melior", +] + [[package]] name = "borsh" version = "1.6.0" @@ -1188,6 +1215,15 @@ dependencies = [ "serde", ] +[[package]] +name = "caseless" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" +dependencies = [ + "unicode-normalization", +] + [[package]] name = "cast" version = "0.3.0" @@ -1206,6 +1242,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -1259,6 +1304,17 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.6.1" @@ -1347,6 +1403,23 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "comrak" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07383e7799d964bf7ffa6fc4457d177c54a44614661c7458bb0bd91b108e32" +dependencies = [ + "caseless", + "entities", + "finl_unicode", + "jetscii", + "phf", + "phf_codegen", + "rustc-hash", + "smallvec", + "typed-arena", +] + [[package]] name = "const-hex" version = "1.18.1" @@ -1406,6 +1479,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1746,7 +1828,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case", + "convert_case 0.10.0", "proc-macro2", "quote", "rustc_version 0.4.1", @@ -1928,6 +2010,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + [[package]] name = "enum-ordinalize" version = "4.3.2" @@ -2117,6 +2205,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2644,6 +2738,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jetscii" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" + [[package]] name = "jiff" version = "0.2.23" @@ -2792,6 +2892,42 @@ dependencies = [ "tracing", ] +[[package]] +name = "jolt-equivalence" +version = "0.1.0" +dependencies = [ + "ark-bn254", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "blake2 0.11.0-rc.6", + "bolt", + "common", + "jolt-core", + "jolt-dory", + "jolt-field", + "jolt-inlines-sha2", + "jolt-kernels", + "jolt-lookup-tables", + "jolt-openings", + "jolt-poly", + "jolt-profiling", + "jolt-prover", + "jolt-r1cs", + "jolt-sumcheck", + "jolt-trace", + "jolt-transcript", + "jolt-verifier", + "jolt-witness", + "num-traits", + "postcard", + "rayon", + "serde", + "strum 0.28.0", + "tracer", + "tracing", +] + [[package]] name = "jolt-eval" version = "0.1.0" @@ -2846,6 +2982,48 @@ dependencies = [ "serde", ] +[[package]] +name = "jolt-host" +version = "0.1.0" +dependencies = [ + "common", + "jolt-dory", + "jolt-field", + "jolt-kernels", + "jolt-lookup-tables", + "jolt-openings", + "jolt-prover", + "jolt-r1cs", + "jolt-riscv", + "jolt-trace", + "jolt-transcript", + "jolt-verifier", + "jolt-witness", + "postcard", + "tracer", + "tracing", +] + +[[package]] +name = "jolt-hyperkzg" +version = "0.1.0" +dependencies = [ + "criterion", + "jolt-crypto", + "jolt-field", + "jolt-openings", + "jolt-poly", + "jolt-transcript", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "rayon", + "serde", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "jolt-inlines-bigint" version = "0.1.0" @@ -2947,6 +3125,22 @@ dependencies = [ "tracer", ] +[[package]] +name = "jolt-kernels" +version = "0.1.0" +dependencies = [ + "jolt-field", + "jolt-lookup-tables", + "jolt-poly", + "jolt-r1cs", + "jolt-sumcheck", + "jolt-trace", + "jolt-transcript", + "jolt-witness", + "rayon", + "tracing", +] + [[package]] name = "jolt-lookup-tables" version = "0.1.0" @@ -3034,6 +3228,22 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "jolt-prover" +version = "0.0.0" +dependencies = [ + "jolt-dory", + "jolt-field", + "jolt-kernels", + "jolt-openings", + "jolt-poly", + "jolt-transcript", + "jolt-verifier", + "jolt-witness", + "rayon", + "tracing", +] + [[package]] name = "jolt-r1cs" version = "0.1.0" @@ -3065,8 +3275,10 @@ dependencies = [ "cfg-if", "common", "jolt-core", + "jolt-host", "jolt-platform", "jolt-sdk-macros", + "jolt-trace", "postcard", "riscv 0.16.0", "serde", @@ -3090,8 +3302,6 @@ dependencies = [ "jolt-field", "jolt-poly", "jolt-transcript", - "num-traits", - "rand_core 0.6.4", "serde", "thiserror 2.0.18", "tracing", @@ -3103,7 +3313,10 @@ version = "0.1.0" dependencies = [ "bincode 2.0.1", "common", + "jolt-field", + "jolt-r1cs", "jolt-riscv", + "jolt-witness", "rand 0.8.5", "serde", "tracer", @@ -3126,6 +3339,29 @@ dependencies = [ "sha3 0.11.0", ] +[[package]] +name = "jolt-verifier" +version = "0.0.0" +dependencies = [ + "jolt-dory", + "jolt-field", + "jolt-lookup-tables", + "jolt-openings", + "jolt-poly", + "jolt-sumcheck", + "jolt-transcript", + "serde", + "tracing", +] + +[[package]] +name = "jolt-witness" +version = "0.1.0" +dependencies = [ + "jolt-field", + "jolt-poly", +] + [[package]] name = "js-sys" version = "0.3.91" @@ -3191,6 +3427,16 @@ version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.16" @@ -3290,6 +3536,32 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "melior" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c388c08773539126c32f2a9c65260695ed9e2b9376164ce8b839826e3085d8d" +dependencies = [ + "melior-macro", + "mlir-sys", +] + +[[package]] +name = "melior-macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aac876dfce9f514df46c6d4b7267f1fc513126ddc34dfde11909584fdfc87bd" +dependencies = [ + "comrak", + "convert_case 0.11.0", + "proc-macro2", + "quote", + "regex", + "syn 2.0.117", + "tblgen", + "unindent", +] + [[package]] name = "memchr" version = "2.8.0" @@ -3378,6 +3650,12 @@ name = "mini-template" version = "0.1.0" source = "git+https://github.com/LayerZero-Labs/ZeroOS.git?rev=3b132ce862ba6769a4261d151f69ff32c5f0dc30#3b132ce862ba6769a4261d151f69ff32c5f0dc30" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3388,6 +3666,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mlir-sys" +version = "220.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f8c5781dc23ebe7456f9e9aaecab9eee9e542f7e9051b95b0a31b1d5d4b61f" +dependencies = [ + "bindgen", +] + [[package]] name = "modinv" version = "0.1.0" @@ -3477,6 +3764,16 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -3885,6 +4182,16 @@ dependencies = [ "serde", ] +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + [[package]] name = "phf_generator" version = "0.13.1" @@ -5557,6 +5864,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tblgen" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a6768dc51535aeaecbe812e2a792a77cbef10b1c4529e29bea5864a738dc6a" +dependencies = [ + "bindgen", + "cc", + "paste", + "thiserror 2.0.18", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -5684,6 +6003,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -5997,6 +6331,15 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -6009,6 +6352,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "unsafe-libyaml" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index 42bec6295e..08874dc33b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,8 +32,16 @@ members = [ "crates/jolt-sumcheck", "crates/jolt-openings", "crates/jolt-dory", + "crates/jolt-hyperkzg", "crates/jolt-riscv", "crates/jolt-transcript", + "crates/jolt-witness", + "crates/bolt", + "crates/jolt-kernels", + "crates/jolt-prover", + "crates/jolt-verifier", + "crates/jolt-host", + "crates/jolt-equivalence", "crates/jolt-profiling", "crates/jolt-field", "jolt-core", @@ -376,11 +384,21 @@ common = { path = "./common", default-features = false } tracer = { path = "./tracer", default-features = false } jolt-core = { path = "./jolt-core", default-features = false } jolt-crypto = { path = "./crates/jolt-crypto" } +jolt-dory = { path = "./crates/jolt-dory" } +jolt-hyperkzg = { path = "./crates/jolt-hyperkzg" } jolt-field = { path = "./crates/jolt-field" } jolt-openings = { path = "./crates/jolt-openings" } jolt-poly = { path = "./crates/jolt-poly" } jolt-transcript = { path = "./crates/jolt-transcript" } jolt-sumcheck = { path = "./crates/jolt-sumcheck" } +jolt-r1cs = { path = "./crates/jolt-r1cs" } +jolt-witness = { path = "./crates/jolt-witness" } +bolt = { path = "./crates/bolt" } +jolt-kernels = { path = "./crates/jolt-kernels" } +jolt-prover = { path = "./crates/jolt-prover" } +jolt-verifier = { path = "./crates/jolt-verifier" } +jolt-host = { path = "./crates/jolt-host" } +jolt-equivalence = { path = "./crates/jolt-equivalence" } jolt-riscv = { path = "./crates/jolt-riscv" } jolt-trace = { path = "./crates/jolt-trace" } jolt-lookup-tables = { path = "./crates/jolt-lookup-tables" } diff --git a/Makefile b/Makefile index e3147e9568..e15d10247d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help bootstrap build-emulator \ +.PHONY: help bootstrap bolt-dev-setup build-emulator \ arch-tests-64imac arch-tests-generate arch-tests-run arch-tests-smoke MAKEFILE_DIR := $(abspath $(dir $(firstword $(MAKEFILE_LIST)))) @@ -26,6 +26,9 @@ help: bootstrap: ## Install arch-test prerequisites (riscv64 toolchain, sail_riscv_sim) ./scripts/bootstrap +bolt-dev-setup: ## Install/configure the local Bolt LLVM/MLIR toolchain + ./scripts/setup-bolt-dev.sh + build-emulator: ## Build the jolt-emu binary in debug mode # Debug build is intentional — arch tests are correctness-sensitive and # don't benefit from release optimizations. diff --git a/common/src/attributes.rs b/common/src/attributes.rs index 949e6dde88..3863ac21d8 100644 --- a/common/src/attributes.rs +++ b/common/src/attributes.rs @@ -6,6 +6,12 @@ use crate::constants::{ DEFAULT_MAX_TRUSTED_ADVICE_SIZE, DEFAULT_MAX_UNTRUSTED_ADVICE_SIZE, DEFAULT_STACK_SIZE, }; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Backend { + Legacy, + Modular, +} + pub struct Attributes { pub wasm: bool, pub nightly: bool, @@ -20,6 +26,10 @@ pub struct Attributes { pub max_untrusted_advice_size: u64, pub max_trace_length: u64, pub backtrace: Option, + /// Prover backend selector. `Legacy` (default) drives jolt-core's + /// monolithic `RV64IMACProver`. `Modular` drives the Bolt-based modular + /// stack via `jolt_host::prove_program` + `verify_proof`. + pub backend: Backend, } pub fn parse_attributes(attr: &Punctuated) -> Attributes { @@ -29,6 +39,7 @@ pub fn parse_attributes(attr: &Punctuated) -> Attributes { let mut nightly = false; let mut profile: Option = None; let mut backtrace: Option = None; + let mut backend = Backend::Legacy; for meta in attr { match meta { @@ -53,6 +64,19 @@ pub fn parse_attributes(attr: &Punctuated) -> Attributes { }; profile = Some(value); } + "backend" => { + let value = match lit { + Lit::Str(lit) => lit.value(), + _ => panic!("backend attribute expects a string literal"), + }; + backend = match value.as_str() { + "legacy" => Backend::Legacy, + "modular" => Backend::Modular, + other => panic!( + "invalid backend = \"{other}\"; expected \"legacy\" or \"modular\"" + ), + }; + } _ => { let value: u64 = match lit { Lit::Int(lit) => lit.base10_parse().unwrap(), @@ -122,5 +146,6 @@ pub fn parse_attributes(attr: &Punctuated) -> Attributes { max_untrusted_advice_size, max_trace_length, backtrace, + backend, } } diff --git a/crates/bolt/Cargo.toml b/crates/bolt/Cargo.toml new file mode 100644 index 0000000000..31ca735f59 --- /dev/null +++ b/crates/bolt/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bolt" +version = "0.1.0" +authors = ["Jolt Contributors"] +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Bolt-shaped compiler prototype for the Jolt zkVM" +repository = "https://github.com/a16z/jolt" +keywords = ["SNARK", "compiler", "mlir", "protocol"] +categories = ["cryptography"] + +[lints] +workspace = true + +[dependencies] +melior = "0.27" diff --git a/crates/bolt/GENERIC_PROTOCOL_GOAL.md b/crates/bolt/GENERIC_PROTOCOL_GOAL.md new file mode 100644 index 0000000000..607035f3b3 --- /dev/null +++ b/crates/bolt/GENERIC_PROTOCOL_GOAL.md @@ -0,0 +1,228 @@ +# Bolt Generic Protocol Goal + +Bolt should be a compiler framework for SNARK/PIOP-style protocols, not a +Jolt-shaped compiler with configurable names. Jolt is the first complete +protocol package and the correctness/performance oracle, but generic Bolt +layers must remain reusable for other protocols. + +## Objective + +Refactor the compiler boundaries so generic IRs, passes, validation, and Rust +artifact assembly operate over protocol concepts: + +```text +roles +stages +transcript events +oracles and commitments +claims and relations +sumcheck obligations +opening obligations +proof slots +role-specific execution plans +``` + +Jolt-specific facts should enter only through the Jolt protocol package: + +```text +protocol params +stage ordering +oracle names +relation definitions +proof-slot names +transcript labels +artifact crate names +prover kernel ABI mappings +Jolt-specific evaluation-proof composition +``` + +The result should make adding another protocol a matter of adding a new +`src/protocols//` package plus artifact config and any required prover +kernels, not editing Bolt's generic compiler core. + +## Non-Negotiables + +- Generic Bolt modules must not branch on Jolt stage names, Jolt relation + symbols, or Jolt artifact names. +- Jolt symbols may appear as ordinary MLIR symbol data carried by Jolt-built + modules, but generic passes may only preserve, validate structurally, or emit + that data. +- Generic lowering remains: + +```text +protocol -> concrete -> party -> compute -> cpu -> Rust +``` + +- Rust emission is the final target. Protocol behavior should be represented + in dialect ops or MLIR-derived typed plans before Rust is emitted. +- Verifier emission must remain kernel-free and protocol-auditable. +- Prover kernels are protocol-package implementation details below the + dialect boundary. +- Checked-in generated role crates remain generated artifacts, not + hand-maintained source. + +## Target Source Layout + +Generic compiler modules: + +```text +crates/bolt/src/dialects.rs +crates/bolt/src/ir.rs +crates/bolt/src/mlir.rs +crates/bolt/src/schema/ +crates/bolt/src/pass/ +crates/bolt/src/emit/rust/ +``` + +Jolt protocol package: + +```text +crates/bolt/src/protocols/jolt/ + params.rs + validate.rs + oracles.rs + phases/ + relations/ + emit/ + rust/ + artifacts.rs +``` + +The exact file split can evolve, but ownership should not: generic modules own +compiler mechanics; `protocols/jolt` owns Jolt instantiation facts. + +## Generic IR Criteria + +Generic IR should expose enough structure for passes and emitters to reason +without protocol-specific string matching: + +- `protocol` declares roles, stage boundaries, protocol params, and proof + boundaries. +- `transcript` declares absorb/squeeze events and explicit state threading. +- `piop` declares oracles, claims, sumchecks, relation obligations, opening + claims, opening equalities, and proof slots. +- `pcs` declares commitment, opening, verification, and evaluation-aggregation + obligations over abstract PCS schemes. +- `party` represents role projection without deleting semantic obligations + needed by later validation. +- `compute` represents executable obligations and optional prover kernel hooks. +- `cpu` represents backend-ready execution plans while staying + protocol-agnostic. + +If a generic emitter needs to branch on a string like +`jolt.stage6.booleanity`, the IR has not been lowered into a sufficiently +typed plan. + +## Generic Pass Criteria + +Generic passes may branch on: + +```text +dialect op name +role +phase +declared proof-slot kind +declared relation-plan kind +declared PCS/transcript operation kind +backend target +``` + +Generic passes must not branch on: + +```text +Jolt stage names +Jolt relation symbols +Jolt oracle names +Jolt artifact crate names +Jolt kernel ABI strings +``` + +Jolt-specific lowering is allowed inside `protocols/jolt`, but it should +produce generic dialect ops and typed plans consumed by the shared compiler. + +## Generic Artifact Criteria + +The generic Rust artifact assembler should be driven by `ProtocolArtifactConfig` +and ordered `ProtocolRustArtifact` values: + +- Protocol name, type prefix, transcript label, role crate names, dependencies, + forbidden imports, and type paths are config data. +- Stage modules are emitted from `ProtocolStage` data, not hardcoded enums in + generic artifact code. +- Top-level `prover.rs` and `verifier.rs` are generated from role/stage/proof + plans. +- Protocol-specific proof extensions are represented by explicit extension + config or generic PCS/evaluation IR, not by checks like + `type_prefix == "Jolt"`. +- `jolt-prover` may import verifier-owned proof types; `jolt-verifier` must + never import prover or kernel code. + +## Jolt Quarantine Criteria + +These are the only acceptable homes for Jolt-specific compiler knowledge: + +```text +crates/bolt/src/protocols/jolt/** +crates/bolt/tests/** when the test explicitly targets Jolt +crates/jolt-prover/** +crates/jolt-verifier/** +crates/jolt-kernels/** +crates/jolt-equivalence/** +``` + +Generic Bolt modules should have a hygiene gate rejecting `jolt`, `Jolt`, +`stage6`, `stage7`, `stage8`, and Jolt relation/policy names, with a small +temporary allowlist during migration. + +## Correctness Criteria + +Every genericity cleanup slice must preserve the existing semantic oracles: + +```text +generated role crates still compile +checked-in generated role crates match canonical generation +Bolt prover artifacts are accepted by the generated Bolt verifier +Bolt prover artifacts are accepted by the core oracle for implemented stages +Bolt/core transcript histories match for implemented stages +internal prover/verifier transcript histories match for implemented stages +tampering gates still reject malformed artifacts +generated verifier import boundaries remain intact +verifier CPU IR remains kernel-free +``` + +For pure file moves and namespace refactors, generated output should either be +byte-for-byte unchanged or intentionally regenerated with a clear explanation +of why the generated surface changed. + +## Migration Algorithm + +For each cleanup slice: + +1. Identify the Jolt-specific fact currently living in a generic module. +2. Decide whether it is protocol data, relation semantics, artifact config, or + a prover-kernel implementation detail. +3. Move that fact to `protocols/jolt` or encode it as generic IR/typed plan + data. +4. Keep generic APIs protocol-named (`Protocol*`) and provide Jolt convenience + wrappers only under `protocols::jolt`. +5. Add or tighten a hygiene gate so the leak does not reappear. +6. Regenerate artifacts only through the canonical generator. +7. Run the relevant schema, generation, import, equivalence, and tamper gates. + +Do not hide protocol semantics in opaque Rust helpers to pass the hygiene gate. +If the generic emitter needs new information, add a typed plan field or dialect +operation and validate it. + +## Definition Of Done + +- `crates/bolt/src/lib.rs` exports generic compiler APIs at the root and keeps + Jolt APIs namespaced under `bolt::protocols::jolt`. +- `crates/bolt/src/emit/rust` contains generic Rust backend mechanics only. +- `JoltProtocolStage`, Jolt artifact config, Jolt stage emitters, Jolt relation + mappings, and Jolt eval-proof composition live under `protocols/jolt`. +- Generic artifact assembly can produce role crates for a non-Jolt protocol + fixture using only `ProtocolArtifactConfig` and `ProtocolStage` data. +- Generic passes and validators have automated hygiene tests preventing Jolt + leakage. +- Existing Jolt correctness, transcript, tamper, import, generated-artifact, + and performance gates remain available and green for implemented stages. diff --git a/crates/bolt/GOAL.md b/crates/bolt/GOAL.md new file mode 100644 index 0000000000..4f0aa601ff --- /dev/null +++ b/crates/bolt/GOAL.md @@ -0,0 +1,395 @@ +# Bolt Jolt Verifier Goal + +Bolt's first full-field, non-zk Jolt implementation is semantically complete +enough to move the active goal from stage bring-up to verifier-pipeline +hardening. The next long-haul objective is to make the Bolt-generated Jolt +verifier compact, human-readable, auditable, and security-hardened while +preserving the existing full-`Fr` Jolt semantics. + +## Objective + +Refactor the Bolt-generated Jolt verifier pipeline so the generated verifier is +a small orchestration layer plus declarative verifier plans, backed by reusable +verifier runtime modules. The compiler should continue to own protocol facts +through MLIR and typed plan data; generated Rust should not rediscover Jolt +semantics late through ad hoc string matching or repeated stage-local helper +code. + +Starting baseline: + +```text +generated jolt-verifier: ~21.5k LOC +stage6 + stage7: ~13.2k LOC +verifier.rs: 649 LOC +``` + +Current locked cleanup baseline: + +```text +generated jolt-verifier total: 7,755 LOC +generated verifier surface: 5,966 LOC +shared verifier runtime: 1,789 LOC +stage6 + stage7: 1,669 LOC +verifier.rs: 487 LOC +``` + +Target: + +```text +generated verifier surface: <= 4k-6k LOC +stretch generated surface: <= 2k-3k LOC +verifier.rs orchestration: <= 350-500 LOC +stage6 + stage7 generated surface: <= 2k-3k LOC +shared runtime/helpers: allowed when generic, named, and reviewed +``` + +The goal is to reduce the human-facing generated verifier surface by roughly an +order of magnitude. Shared runtime code may exist, but it must be modular, +boring to audit, and driven by explicit MLIR-derived plan data. + +This verifier cleanup is coupled to the generic protocol cleanup in +`GENERIC_PROTOCOL_GOAL.md`: shrinking the generated verifier should move generic +mechanics into Bolt IR/typed plans and shared runtime, not into Jolt-specific +emitter special cases. + +## Locked Genericity Decisions + +The next cleanup track should make Jolt a quarantined protocol package over +generic Bolt compiler infrastructure: + +- Root `bolt::*` exports should be generic-only. Jolt APIs should be imported + from `bolt::protocols::jolt::*`. +- Jolt-specific emitters are not the long-term target. Quarantine them first so + leakage is explicit, then progressively lift stage emission into a generic + `cpu -> Rust` backend driven by typed MLIR-derived plans. +- Replace the current Jolt evaluation-proof special case with either a generic + protocol extension hook or generic PCS/evaluation IR. Start with the minimal + extension hook if that keeps the cleanup mechanical. +- Add hygiene gates for generic compiler modules, initially targeting + `crates/bolt/src/{schema.rs,pass.rs,emit/rust}`. Any temporary Jolt allowlist + must be explicit and shrink over time. +- Namespace and file-layout refactors should preserve generated + `jolt-prover`/`jolt-verifier` output byte-for-byte unless the change + intentionally updates artifact structure. +- At the end of each goal-mode slice, report which quarantined Jolt emitters are + still genuinely protocol-specific and which are ready to lift into generic + typed-plan emission. + +## Immediate Goal-Mode Slice + +First objective for another agent: + +```text +Quarantine Jolt-specific artifact APIs out of generic Rust artifact assembly +while preserving generated output and all current gates. +``` + +Required steps: + +1. Move `JoltProtocolStage`, `jolt_artifact_config`, `jolt_rust_artifact`, + `assemble_jolt_*`, `write_jolt_generated_crates`, and + `validate_jolt_rust_artifact_imports` out of generic + `crates/bolt/src/emit/rust/artifacts.rs` into a Jolt-owned module such as + `crates/bolt/src/protocols/jolt/artifacts.rs`. +2. Keep `ProtocolArtifactConfig`, `ProtocolStage`, `ProtocolRustArtifact`, + `GeneratedCrate`, `assemble_generated_crates`, `write_generated_crates`, and + `validate_rust_artifact_imports` in the generic artifact layer. +3. Stop re-exporting Jolt APIs from `crates/bolt/src/lib.rs`; update callers in + Bolt tests, `jolt-equivalence`, and perf harnesses to import from + `bolt::protocols::jolt`. +4. Add the first genericity hygiene test that rejects new Jolt protocol strings + in generic compiler modules, using a small documented allowlist only for + migration leftovers. +5. Run focused generation/import gates and confirm checked-in generated role + crates are unchanged unless an intentional artifact-structure change is + documented. + +Acceptance criteria: + +```text +generic artifact assembly has no Jolt stage enum or Jolt artifact config +root bolt exports are generic-only +Jolt artifact helpers are namespaced under protocols::jolt +generated jolt-prover/jolt-verifier are byte-for-byte unchanged, or changes are intentional +genericity hygiene gate exists +existing generated-artifact and verifier-boundary gates pass +``` + +## Non-Negotiables + +- Preserve the current full-field non-zk Jolt protocol path: + `Transcript`. +- `jolt-verifier` must not depend on `jolt-prover`, `jolt-kernels`, + `jolt-core`, `jolt-equivalence`, `jolt-profiling`, or tracer internals. +- Bolt compiler boundaries remain: + `protocol -> concrete -> party -> compute -> cpu -> Rust`. +- Verifier CPU IR must remain kernel-free. Prover kernels are temporary + implementation details below the dialect boundary. +- Jolt semantics should be represented in protocol builders, dialect ops, + validators, lowering passes, or typed verifier plans. The Rust emitter should + not infer protocol meaning from loose strings when a typed enum, attr, op, or + plan field can carry it. +- Generated verifier files should be mostly declarative: + +```rust +pub const STAGE_PLAN: StagePlan = ...; + +pub fn verify_stage(...) -> Result { + runtime::verify_stage(&STAGE_PLAN, ...) +} +``` + +## Target Architecture + +The final verifier shape should read like this: + +```text +crates/jolt-verifier + src/lib.rs + src/verifier.rs + public API + proof shape + stage ordering + error mapping + + src/stages/ + commitment.rs + stage1_outer.rs + stage2.rs + ... + mostly declarative generated plans + + src/runtime/ or shared verifier crate + generic stage verifier + generic field expression evaluator + generic opening-claim machinery + generic sumcheck/eval proof conversion + transcript helpers + typed relation evaluators +``` + +Generated stage files should answer: + +```text +What claims exist? +What expressions are evaluated? +What transcript events happen? +What openings are checked? +What relations are verified? +``` + +Runtime modules should answer: + +```text +How is a field expression plan evaluated? +How is a stage plan verified? +How are opening/eval consistency checks performed? +How are proof records converted into runtime verifier inputs? +``` + +## Main Refactor Tracks + +1. **Verifier runtime extraction** + + Move duplicated stage-local machinery into one runtime: + + ```text + field expression evaluation + opening claim lookup and equality checks + sumcheck driver verification + transcript squeeze/absorb helpers + stage proof conversion + stage plan execution + ``` + +2. **Shared verifier plan types** + + Replace stage-specific copies such as `Stage6FieldExprPlan` and + `Stage7OpeningClaimPlan` with shared plan structs: + + ```text + FieldExprPlan + OpeningClaimPlan + OpeningEqualityPlan + SumcheckClaimPlan + SumcheckDriverPlan + SumcheckEvalPlan + StagePlan + RelationPlan + ``` + +3. **Compact field expression encoding** + + Stage 6 and Stage 7 are bloated by per-expression constants and operand + arrays. Replace those with compact tables or pooled operand slices. + +4. **Typed relation dispatch** + + Replace stringly relation handling with typed plan data where practical: + + ```text + RelationKind::RamReadWrite + RelationKind::InstructionReadRaf + RelationKind::BytecodeReadRaf + RelationKind::Booleanity + RelationKind::HammingBooleanity + RelationKind::RegistersReadWrite + ... + ``` + + Any remaining string dispatch must be explicitly allowlisted and covered by + schema tests. + +5. **Clean top-level verifier API** + + `verifier.rs` should be readable orchestration: proof shape, verifier + inputs, verifier programs, stage ordering, evaluation proof handling, and + clear error mapping. Repeated per-stage proof conversion should disappear. + +## One-Time Hardening Work + +Before large readability refactors, add a durable verifier hardening suite. +The suite should include positive equivalence and negative tamper oracles. + +Verifier tamper cases: + +```text +valid generated proof verifies +core and Bolt verifier accept/reject agree +tampered sumcheck coefficient rejects +tampered sumcheck point rejects +tampered named eval rejects +tampered commitment rejects +missing commitment rejects +missing stage proof rejects +reordered stage proof rejects +stage proof in the wrong slot rejects +wrong transcript state rejects +wrong evaluation proof rejects +missing evaluation setup rejects +missing evaluation proof rejects +extra/missing opening claims reject +opening claims in the wrong order reject +opening equality mismatch rejects +PCS proof mismatch rejects +``` + +MLIR/compiler hardening cases: + +```text +unknown dialects rejected +prover-only ops rejected in verifier pipeline +verifier-only ops rejected in prover pipeline +unthreaded transcript ops rejected +hidden or reordered opening batch claims rejected +unsupported equality modes rejected +duplicate proof slots rejected +invalid point arity rejected +invalid round schedule rejected +invalid relation kind rejected +verifier CPU IR contains no kernel dispatch +generated verifier imports no forbidden crates +``` + +## Concrete Gates + +Readability and LOC gates: + +```text +total generated jolt-verifier LOC trends down +verifier.rs <= 500 LOC, stretch <= 350 +stage6 + stage7 generated LOC <= 3k-5k, stretch <= 2k-3k +no duplicate stage-local generic plan structs +no duplicate stage-local field-expression interpreter +no duplicate stage-local opening equality interpreter +no giant per-expression operand constants after compaction +stage files are mostly declarative plan data +``` + +Security and boundary gates: + +```text +jolt-verifier imports are allowlisted +no jolt-prover dependency from jolt-verifier +no jolt-kernels dependency from jolt-verifier +no jolt-core dependency from jolt-verifier +no prover role ops in verifier MLIR +no kernel attrs in verifier CPU IR +all transcript-producing ops thread transcript state +all opening batches preserve explicit ordered claims +all relation dispatch is typed or allowlisted +``` + +Semantic gates: + +```bash +cargo fmt --check +cargo check -p bolt -p jolt-verifier -p jolt-prover -p jolt-equivalence --quiet +cargo nextest run -p bolt --test verifier_cleanup --no-capture +cargo nextest run -p bolt --test commitment_ir --cargo-quiet +cargo nextest run -p jolt-equivalence --test generated_role_crates --cargo-quiet +cargo nextest run -p jolt-equivalence --test bolt_commitment --no-capture +``` + +Required semantic outcomes: + +```text +core accepts Bolt proof +Bolt verifier accepts Bolt proof +core and Bolt transcript state histories match +core and Bolt observable proof artifacts match +core and Bolt reject equivalent tampered proofs +generated prover/verifier crates stay in sync with artifact rail +``` + +Perf remains a regression guard, not the center of this task. The existing +`sha2-chain` e2e/proving Perfetto traces are useful for confirming cleanup does +not accidentally move prover cost, but the main objective is verifier +readability, simplicity, and security. + +## Iteration Algorithm + +Each cleanup loop should follow the same rule: + +```text +1. Measure current LOC, duplication, imports, and typed-vs-string dispatch. +2. Pick one duplication class or hygiene issue to eliminate. +3. Move generic logic into runtime only if semantics remain explicit in MLIR or + typed plan data. +4. Regenerate checked-in verifier artifacts through the compiler rail. +5. Run hardening, equivalence, import, and schema gates. +6. Keep the change only if readability improves and no oracle weakens. +``` + +Use this scoring function when choosing work: + +```text +score = + LOC reduction ++ fewer duplicate structs/functions ++ fewer string dispatch sites ++ fewer generated helper bodies ++ stronger negative oracles ++ clearer verifier.rs +- semantic opacity introduced into runtime +``` + +## Definition Of Done + +This long-haul cleanup is complete when: + +```text +generated verifier surface is <= 4k-6k LOC +verifier.rs is <= 500 LOC +stage files are mostly declarative plans +generic verifier mechanics live once +Jolt relation semantics are typed and auditable +MLIR verifier pathway has malformed-input rejection tests +tamper suite covers commitments, transcript, stages, openings, evals, and PCS proof +core/Bolt accept/reject equivalence is preserved +generated verifier import boundaries are enforced +``` + +The desired end state is not merely fewer lines. The verifier should be easy to +navigate, easy to audit, and hard for the compiler pipeline to accidentally +weaken. diff --git a/crates/bolt/JOLT_PROTOCOL_IMPLEMENTATION.md b/crates/bolt/JOLT_PROTOCOL_IMPLEMENTATION.md new file mode 100644 index 0000000000..a6034e37f8 --- /dev/null +++ b/crates/bolt/JOLT_PROTOCOL_IMPLEMENTATION.md @@ -0,0 +1,74 @@ +# Jolt Protocol Implementation Notes + +The original stage-by-stage bring-up plan has been completed for the first +full-field, non-zk Jolt-on-Bolt implementation. The active long-haul goal now +lives in `GOAL.md`: make the generated Jolt verifier much smaller, cleaner, +and better hardened. + +This file keeps the durable implementation rules that should continue to guide +that cleanup. + +The companion genericity goal lives in `GENERIC_PROTOCOL_GOAL.md`. It defines +the rule that Jolt is a protocol package over Bolt, not a special case inside +generic IRs, passes, validation, or Rust artifact assembly. + +## Permanent Compiler Rules + +- Protocol facts live in `crates/bolt/src/protocols/jolt` and typed MLIR/plan + structures, not in generated Rust control flow. +- Generic dialects should remain generic. Jolt-only names and parameters may be + ordinary attrs or SSA values carried by the Jolt protocol definition, but + they should not become hidden assumptions in generic lowering code. +- Generic artifact assembly should consume protocol config and ordered stage + artifacts; Jolt artifact names, stage enums, relation mappings, and eval-proof + composition belong under `crates/bolt/src/protocols/jolt`. +- Lowering order remains: + +```text +protocol -> concrete -> party -> compute -> cpu -> Rust +``` + +- Rust emission is the final target. Before emission, behavior should be + represented as dialect ops, validation passes, analyses, rewrites, lowerings, + or typed plan extraction. +- Prover code may use coarse CPU kernels while performance work continues. + Those kernels are below the dialect boundary. +- Verifier code must stay kernel-free and audit-stable. It should use modular + verifier crates and generated plan data, not `jolt-kernels` or `jolt-core`. + +## Verifier Cleanup Algorithm + +For every verifier cleanup iteration: + +1. Measure generated verifier LOC, stage LOC, duplicate plan structs, duplicate + helper functions, forbidden imports, and string-dispatch sites. +2. Pick one duplication class or compiler hygiene issue. +3. Move generic mechanics into shared verifier runtime only when protocol + semantics remain explicit in MLIR-derived typed plan data. +4. Regenerate checked-in `jolt-prover` and `jolt-verifier` artifacts through the + canonical artifact rail. +5. Run schema, import, equivalence, and tamper gates. +6. Keep the change only when generated code is easier to read and no semantic + oracle weakens. + +## Do Not Regress + +- Verifier CPU IR must not contain kernel attrs or prover-only ops. +- Generated verifier Rust must not import `jolt-kernels`, `jolt-core`, + `jolt-prover`, `jolt-equivalence`, `jolt-profiling`, or tracer internals. +- Transcript state must be explicitly threaded through MLIR. +- Opening batches must preserve ordered claim lists. +- Opening equality checks must reject incompatible claim metadata. +- Sumcheck relation dispatch should be typed or explicitly allowlisted. +- Full-field transcript challenges are the intended path: + `Transcript`. + +## Regeneration Rail + +Checked-in generated role crates are not hand-maintained. Regenerate them with: + +```bash +JOLT_UPDATE_GOLDENS=1 cargo nextest run -p bolt generated_jolt_artifacts_have_uniform_crate_layout_and_import_rules --cargo-quiet +``` + +Then run the gates in `TESTING.md`. diff --git a/crates/bolt/README.md b/crates/bolt/README.md new file mode 100644 index 0000000000..19301e32f6 --- /dev/null +++ b/crates/bolt/README.md @@ -0,0 +1,114 @@ +# bolt + +This crate is the Bolt-shaped compiler prototype for the full-field, non-zk +Jolt implementation. `melior::ir::Module` is the IR source of truth; Rust types +provide phase/role guardrails, schema validation, builders, analysis results, +and final Rust emission. + +## Active Goal + +The first Jolt-on-Bolt implementation is semantically complete enough that the +active work is verifier cleanup and hardening, not stage bring-up. See +`GOAL.md` for the long-haul target: + +```text +make the generated jolt-verifier compact, human-readable, auditable, +security-hardened, and driven by explicit MLIR-derived plan data +``` + +`GENERIC_PROTOCOL_GOAL.md` describes the parallel cleanup track that makes Bolt +generic over protocol packages instead of Jolt-shaped. `JOLT_PROTOCOL_IMPLEMENTATION.md` +keeps the durable compiler-boundary rules. `TESTING.md` lists the LOC, +readability, equivalence, import, MLIR, and tamper gates for this cleanup track. + +## Compiler Shape + +Protocol-specific facts live under `src/protocols/`. Generic compiler layers +understand Bolt dialect operations but should not learn Jolt-only protocol +semantics except as ordinary typed attrs, SSA values, or typed plan data carried +by a protocol definition. + +The intended lowering path is: + +```text +protocol -> concrete -> party -> compute -> cpu -> Rust +``` + +The dialect split matters: + +- `protocol`, `piop`, `poly`, `field`, `transcript`, `commit`, and `pcs` model + protocol obligations. +- `party` projects prover/verifier visibility. +- `compute` represents role-specific executable structure while preserving + semantic dataflow. +- `cpu` is the final MLIR target before Rust emission. +- Rust is generated output, not the place where protocol meaning should be + inferred. + +## Verifier Boundary + +The generated verifier must remain audit-stable: + +```text +no jolt-prover dependency +no jolt-kernels dependency +no jolt-core dependency +no jolt-equivalence dependency +no jolt-profiling dependency +no tracer internals +``` + +Verifier CPU IR must stay kernel-free. Prover code may still call coarse +`jolt-kernels` CPU kernels while performance work continues, but those kernels +are below the dialect boundary and must not become verifier infrastructure. + +The cleanup target is for generated verifier modules to become mostly +declarative plan data, with generic mechanics factored into named verifier +runtime modules. + +## Generated Artifacts + +Generated Jolt Rust artifacts are organized as two role crates: + +```text +crates/jolt-prover +crates/jolt-verifier +``` + +The checked-in role crates are generated artifacts, not hand-maintained code. +Regenerate them through the Rust artifact rail with: + +```bash +JOLT_UPDATE_GOLDENS=1 cargo nextest run -p bolt generated_jolt_artifacts_have_uniform_crate_layout_and_import_rules --cargo-quiet +``` + +The generator emits manifests, stage registries, `src/stages/*.rs`, and the +top-level `prover.rs`/`verifier.rs` APIs. `jolt-verifier` owns proof types and +verification. `jolt-prover` may construct verifier-owned proof types, but must +not import verifier stage internals. + +## Local MLIR Toolchain + +The easiest setup path on macOS is: + +```bash +make bolt-dev-setup +source .bolt-dev-env +``` + +The helper installs Homebrew LLVM, Rust components used by CI, and writes the +local environment required by `mlir-sys`. + +On macOS with Homebrew LLVM: + +```bash +brew install llvm +export MLIR_SYS_220_PREFIX=/opt/homebrew/opt/llvm +export PATH="/opt/homebrew/opt/llvm/bin:$PATH" +export SDKROOT="$(xcrun --show-sdk-path)" +export BINDGEN_EXTRA_CLANG_ARGS="-isysroot$(xcrun --show-sdk-path)" +``` + +Do not set `MLIR_SYS_LINK_SHARED=1` with the Homebrew LLVM 22 bottle; it does +not ship `libMLIR-C.dylib`, so `mlir-sys` needs its default static MLIR link +path. diff --git a/crates/bolt/TESTING.md b/crates/bolt/TESTING.md new file mode 100644 index 0000000000..d52f050473 --- /dev/null +++ b/crates/bolt/TESTING.md @@ -0,0 +1,203 @@ +# Jolt-on-Bolt Equivalence Gates + +The first full-field, non-zk Jolt-on-Bolt implementation is in equivalence, +hardening, and perf-gating mode. The active objective is in +`crates/jolt-equivalence/GOAL.md`: keep `jolt-equivalence` as a thin oracle and +gate suite while semantic construction lives in Bolt, generated crates, +`jolt-kernels`, or `jolt-witness`. + +## Fast Local Gates + +Run: + +```bash +cargo fmt --check +cargo check -p bolt -p jolt-verifier -p jolt-prover -p jolt-equivalence --quiet +cargo nextest run -p bolt --test verifier_cleanup --no-capture +cargo nextest run -p bolt --test commitment_ir --cargo-quiet +cargo nextest run -p jolt-equivalence --test generated_role_crates --cargo-quiet +``` + +`commitment_ir` can also materialize ignored MLIR/Rust scratch fixtures for +local inspection: + +```bash +JOLT_UPDATE_GOLDENS=1 cargo nextest run -p bolt --test commitment_ir --cargo-quiet +``` + +These gates cover: + +- MLIR dialect registration and schema validation. +- Concrete transcript threading. +- Prover/verifier party projection. +- `compute` and `cpu` schema validation. +- Prover-only kernel resolution. +- Kernel-free verifier CPU IR. +- Generated Rust compilation. +- Generated role-crate layout and import boundaries. +- Matching generated stage registries. +- Generated verifier LOC, duplication, relation-string, and boundary metrics. + +## Real-Data Equivalence Gate + +Run: + +```bash +cargo nextest run -p jolt-equivalence --test bolt_commitment --no-capture +``` + +This is the main semantic oracle. It should continue to prove: + +- Bolt verifier accepts Bolt proof artifacts on real trace data. +- Core accepts the corresponding Bolt proof path. +- Bolt/core transcript histories match. +- Bolt/core observable proof artifacts match. +- Generated standalone and top-level verifier paths agree. +- Representative tampering is rejected by the generated verifier. + +The `Bolt equivalence` workflow runs the generated role parity and real-data +tamper gates on pull requests. It also has an optional full +`jolt-equivalence` sweep that runs on the nightly schedule, or manually through +`workflow_dispatch` with `include_full_sweep=true`: + +```bash +cargo nextest run -p jolt-equivalence --cargo-quiet +``` + +## Required Hardening Coverage + +The verifier hardening suite should cover these negative cases: + +```text +tampered commitment +missing commitment +tampered sumcheck coefficient +tampered sumcheck point +tampered named eval +missing stage proof +reordered stage proof +stage proof in wrong slot +wrong transcript state +missing opening claim +extra opening claim +opening claims in wrong order +opening equality mismatch +wrong evaluation proof +missing evaluation setup +missing evaluation proof +PCS proof mismatch +``` + +The MLIR/compiler hardening suite should cover: + +```text +unknown dialect rejection +prover-only op rejection in verifier pipeline +verifier-only op rejection in prover pipeline +unthreaded transcript op rejection +hidden/reordered opening batch claim rejection +unsupported equality mode rejection +duplicate proof slot rejection +invalid point arity rejection +invalid round schedule rejection +invalid relation kind rejection +kernel attr rejection in verifier CPU IR +forbidden generated verifier imports +``` + +## LOC And Readability Gates + +Track these metrics before and after each cleanup iteration: + +```text +total generated jolt-verifier LOC +verifier.rs LOC +stage6 + stage7 generated LOC +number of stage-local generic plan structs +number of stage-local helper/interpreter functions +number of field-expression operand constants +number of relation string-dispatch sites +forbidden imports +``` + +Targets: + +```text +generated verifier surface: <= 4k-6k LOC +stretch generated surface: <= 2k-3k LOC +verifier.rs orchestration: <= 350-500 LOC +stage6 + stage7 generated surface: <= 2k-3k LOC +``` + +Do not accept a LOC reduction that hides semantics in opaque runtime code. The +generated surface should shrink because generic mechanics moved into named, +reviewable runtime modules and the remaining generated code became declarative +plan data. + +## Regeneration Gate + +Checked-in generated role crates must stay synchronized with the artifact rail: + +```bash +JOLT_UPDATE_GOLDENS=1 cargo nextest run -p bolt generated_jolt_artifacts_have_uniform_crate_layout_and_import_rules --cargo-quiet +``` + +After regenerating, rerun the fast local gates and the real-data equivalence +gate. + +## Perf Oracle Guard + +New Jolt-on-Bolt changes should preserve a core-vs-Bolt perf oracle that uses +`jolt-profiling` as the shared instrumentation layer. The gate should run the +same program, inputs, trace length, PCS setup size, and transcript mode through: + +```text +core reference path: + setup, prove, verify, proof size, peak RSS + +Bolt generated path: + setup, prove, verify, proof size, peak RSS +``` + +Both paths must emit the same named tracing spans through `jolt-profiling`, at +minimum: + +```text +core.setup +core.prove +core.verify +bolt.setup +bolt.prove +bolt.commitment +bolt.commitment.batch +bolt.commitment.dory_commit +bolt.stage1 ... bolt.stage8 +bolt.evaluate +bolt.evaluate.claims +bolt.evaluate.materialize_joint_polynomial +bolt.evaluate.joint_opening_hint +bolt.evaluate.dory_open +bolt.verify +bolt.verify.evaluation_state +bolt.verify.dory_verify +``` + +The checked-in CI smoke programs are: + +```text +PR gate: bolt_sha2_chain_2_16_core_vs_bolt_perf_oracle +PR gate: bolt_sha2_chain_2_20_core_vs_bolt_perf_oracle +``` + +Both tests live in `jolt-equivalence/tests/bolt_perf.rs` because they reuse the +real semantic oracle fixture and pass paired `PerfMetrics` into +`jolt-profiling`'s `check_core_vs_bolt_gate`. The workflow sets +`JOLT_BOLT_PERF_TRACE=1` so the same run writes Perfetto JSON traces under +`benchmark-runs/perfetto_traces/`. + +To run them locally after `source .bolt-dev-env`: + +```bash +JOLT_BOLT_PERF_TRACE=1 cargo nextest run -p jolt-equivalence --test bolt_perf --release --cargo-quiet --run-ignored only --no-capture bolt_sha2_chain_2_16_core_vs_bolt_perf_oracle +JOLT_BOLT_PERF_TRACE=1 cargo nextest run -p jolt-equivalence --test bolt_perf --release --cargo-quiet --run-ignored only --no-capture bolt_sha2_chain_2_20_core_vs_bolt_perf_oracle +``` diff --git a/crates/bolt/irdl/commit.mlir b/crates/bolt/irdl/commit.mlir new file mode 100644 index 0000000000..b759683bd2 --- /dev/null +++ b/crates/bolt/irdl/commit.mlir @@ -0,0 +1,25 @@ +irdl.dialect @commit { + irdl.type @artifact + irdl.operation @publish_batch { + %artifact_type = irdl.parametric @commit::@artifact<> + %sym = irdl.any + %oracle_family = irdl.any + %label = irdl.any + irdl.attributes {"sym_name" = %sym, "oracle_family" = %oracle_family, "label" = %label} + irdl.results(artifact: %artifact_type) + } + irdl.operation @publish_optional { + %artifact_type = irdl.parametric @commit::@artifact<> + %sym = irdl.any + %oracle = irdl.any + %label = irdl.any + %skip_policy = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "label" = %label, + "skip_policy" = %skip_policy + } + irdl.results(artifact: %artifact_type) + } +} diff --git a/crates/bolt/irdl/compute.mlir b/crates/bolt/irdl/compute.mlir new file mode 100644 index 0000000000..60c01c9eaa --- /dev/null +++ b/crates/bolt/irdl/compute.mlir @@ -0,0 +1,807 @@ +irdl.dialect @compute { + irdl.type @commitment_artifact + irdl.type @transcript_state + irdl.type @oracle_buffer + irdl.type @oracle_family + irdl.type @field_value + irdl.type @point + irdl.type @sumcheck_claim_type + irdl.type @sumcheck_batch_type + irdl.type @sumcheck_result_type + irdl.type @sumcheck_proof_type + irdl.type @opening_claim_type + irdl.type @opening_batch_type + irdl.type @opening_proof_type + + irdl.operation @params { + %sym = irdl.any + %field = irdl.any + %pcs = irdl.any + %transcript = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field, "pcs" = %pcs, "transcript" = %transcript} + } + irdl.operation @function { + %sym = irdl.any + %source = irdl.any + irdl.attributes {"sym_name" = %sym, "source" = %source} + } + irdl.operation @relation { + %sym = irdl.any + %kind = irdl.any + %domain = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + %output_count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "kind" = %kind, + "domain" = %domain, + "num_rounds" = %num_rounds, + "degree" = %degree, + "output_count" = %output_count + } + } + irdl.operation @kernel { + %sym = irdl.any + %relation = irdl.any + %kind = irdl.any + %backend = irdl.any + %abi = irdl.any + irdl.attributes { + "sym_name" = %sym, + "relation" = %relation, + "kind" = %kind, + "backend" = %backend, + "abi" = %abi + } + } + irdl.operation @oracle_dense_trace { + %buffer = irdl.parametric @compute::@oracle_buffer<> + %sym = irdl.any + %oracle = irdl.any + %source = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %padding = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "source" = %source, + "domain" = %domain, + "num_vars" = %num_vars, + "padding" = %padding + } + irdl.results(buffer: %buffer) + } + irdl.operation @oracle_one_hot_chunk { + %buffer = irdl.parametric @compute::@oracle_buffer<> + %sym = irdl.any + %oracle = irdl.any + %source = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %trace_num_vars = irdl.any + %chunk = irdl.any + %num_chunks = irdl.any + %chunk_bits = irdl.any + %padding = irdl.any + %layout = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "source" = %source, + "domain" = %domain, + "num_vars" = %num_vars, + "trace_num_vars" = %trace_num_vars, + "chunk" = %chunk, + "num_chunks" = %num_chunks, + "chunk_bits" = %chunk_bits, + "padding" = %padding, + "layout" = %layout + } + irdl.results(buffer: %buffer) + } + irdl.operation @oracle_optional_advice { + %buffer = irdl.parametric @compute::@oracle_buffer<> + %sym = irdl.any + %oracle = irdl.any + %source = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %skip_policy = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "source" = %source, + "domain" = %domain, + "num_vars" = %num_vars, + "skip_policy" = %skip_policy + } + irdl.results(buffer: %buffer) + } + irdl.operation @oracle_ref { + %buffer = irdl.parametric @compute::@oracle_buffer<> + %sym = irdl.any + %oracle = irdl.any + %domain = irdl.any + %num_vars = irdl.any + irdl.attributes {"sym_name" = %sym, "oracle" = %oracle, "domain" = %domain, "num_vars" = %num_vars} + irdl.results(buffer: %buffer) + } + irdl.operation @oracle_family_init { + %family_type = irdl.parametric @compute::@oracle_family<> + %sym = irdl.any + %family = irdl.any + %count = irdl.any + irdl.attributes {"sym_name" = %sym, "family" = %family, "count" = %count} + irdl.results(family: %family_type) + } + irdl.operation @oracle_family_append { + %family_type = irdl.parametric @compute::@oracle_family<> + %buffer = irdl.parametric @compute::@oracle_buffer<> + %sym = irdl.any + %family = irdl.any + %oracle = irdl.any + %index = irdl.any + irdl.attributes {"sym_name" = %sym, "family" = %family, "oracle" = %oracle, "index" = %index} + irdl.operands(input: %family_type, oracle_buffer: %buffer) + irdl.results(output: %family_type) + } + irdl.operation @pcs_commit_batch { + %artifact_type = irdl.parametric @compute::@commitment_artifact<> + %family_type = irdl.parametric @compute::@oracle_family<> + %sym = irdl.any + %artifact = irdl.any + %pcs = irdl.any + %oracle_family = irdl.any + %ordered_oracles = irdl.any + %label = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "artifact" = %artifact, + "pcs" = %pcs, + "oracle_family" = %oracle_family, + "ordered_oracles" = %ordered_oracles, + "label" = %label, + "domain" = %domain, + "num_vars" = %num_vars, + "count" = %count + } + irdl.operands(oracles: %family_type) + irdl.results(artifact: %artifact_type) + } + irdl.operation @pcs_commit_optional { + %artifact_type = irdl.parametric @compute::@commitment_artifact<> + %buffer = irdl.parametric @compute::@oracle_buffer<> + %sym = irdl.any + %artifact = irdl.any + %pcs = irdl.any + %oracle = irdl.any + %label = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %skip_policy = irdl.any + irdl.attributes { + "sym_name" = %sym, + "artifact" = %artifact, + "pcs" = %pcs, + "oracle" = %oracle, + "label" = %label, + "domain" = %domain, + "num_vars" = %num_vars, + "skip_policy" = %skip_policy + } + irdl.operands(oracle_buffer: %buffer) + irdl.results(artifact: %artifact_type) + } + irdl.operation @pcs_receive_batch { + %artifact_type = irdl.parametric @compute::@commitment_artifact<> + %family_type = irdl.parametric @compute::@oracle_family<> + %sym = irdl.any + %artifact = irdl.any + %pcs = irdl.any + %oracle_family = irdl.any + %ordered_oracles = irdl.any + %label = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "artifact" = %artifact, + "pcs" = %pcs, + "oracle_family" = %oracle_family, + "ordered_oracles" = %ordered_oracles, + "label" = %label, + "domain" = %domain, + "num_vars" = %num_vars, + "count" = %count + } + irdl.operands(oracles: %family_type) + irdl.results(artifact: %artifact_type) + } + irdl.operation @pcs_receive_optional { + %artifact_type = irdl.parametric @compute::@commitment_artifact<> + %buffer = irdl.parametric @compute::@oracle_buffer<> + %sym = irdl.any + %artifact = irdl.any + %pcs = irdl.any + %oracle = irdl.any + %label = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %skip_policy = irdl.any + irdl.attributes { + "sym_name" = %sym, + "artifact" = %artifact, + "pcs" = %pcs, + "oracle" = %oracle, + "label" = %label, + "domain" = %domain, + "num_vars" = %num_vars, + "skip_policy" = %skip_policy + } + irdl.operands(oracle_buffer: %buffer) + irdl.results(artifact: %artifact_type) + } + irdl.operation @transcript_init { + %state = irdl.parametric @compute::@transcript_state<> + %sym = irdl.any + %scheme = irdl.any + irdl.attributes {"sym_name" = %sym, "scheme" = %scheme} + irdl.results(state: %state) + } + irdl.operation @transcript_absorb { + %state = irdl.parametric @compute::@transcript_state<> + %artifact = irdl.parametric @compute::@commitment_artifact<> + %sym = irdl.any + %label = irdl.any + %optional = irdl.any + irdl.attributes { + "sym_name" = %sym, + "label" = %label, + "optional" = %optional + } + irdl.operands(input: %state, artifact: %artifact) + irdl.results(output: %state) + } + irdl.operation @transcript_absorb_bytes { + %state = irdl.parametric @compute::@transcript_state<> + %sym = irdl.any + %label = irdl.any + %payload = irdl.any + irdl.attributes { + "sym_name" = %sym, + "label" = %label, + "payload" = %payload + } + irdl.operands(input: %state) + irdl.results(output: %state) + } + irdl.operation @transcript_squeeze { + %state = irdl.parametric @compute::@transcript_state<> + %challenge = irdl.any + %sym = irdl.any + %label = irdl.any + %kind = irdl.any + %count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "label" = %label, + "kind" = %kind, + "count" = %count + } + irdl.operands(input: %state) + irdl.results(output: %state, challenge: %challenge) + } + irdl.operation @opening_input { + %point = irdl.parametric @compute::@point<> + %eval = irdl.parametric @compute::@field_value<> + %claim = irdl.parametric @compute::@opening_claim_type<> + %sym = irdl.any + %source_stage = irdl.any + %source_claim = irdl.any + %oracle = irdl.any + %domain = irdl.any + %point_arity = irdl.any + %claim_kind = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source_stage" = %source_stage, + "source_claim" = %source_claim, + "oracle" = %oracle, + "domain" = %domain, + "point_arity" = %point_arity, + "claim_kind" = %claim_kind + } + irdl.results(point: %point, eval: %eval, claim: %claim) + } + irdl.operation @point_slice { + %input = irdl.parametric @compute::@point<> + %output = irdl.parametric @compute::@point<> + %sym = irdl.any + %source = irdl.any + %offset = irdl.any + %length = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "offset" = %offset, + "length" = %length + } + irdl.operands(input: %input) + irdl.results(output: %output) + } + irdl.operation @point_zero { + %output = irdl.parametric @compute::@point<> + %sym = irdl.any + %field = irdl.any + %arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "field" = %field, + "arity" = %arity + } + irdl.results(output: %output) + } + irdl.operation @point_concat { + %input = irdl.parametric @compute::@point<> + %output = irdl.parametric @compute::@point<> + %sym = irdl.any + %layout = irdl.any + %arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "layout" = %layout, + "arity" = %arity + } + irdl.operands(inputs: variadic %input) + irdl.results(output: %output) + } + irdl.operation @field_const { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + %field = irdl.any + %value = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field, "value" = %value} + irdl.results(value: %value_type) + } + irdl.operation @field_zero { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + %field = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field} + irdl.results(value: %value_type) + } + irdl.operation @field_one { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + %field = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field} + irdl.results(value: %value_type) + } + irdl.operation @field_add { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %value_type, rhs: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @field_sub { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %value_type, rhs: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @field_neg { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(input: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @field_mul { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %value_type, rhs: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @field_pow { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + %exponent = irdl.any + irdl.attributes {"sym_name" = %sym, "exponent" = %exponent} + irdl.operands(input: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @poly_lagrange_basis_eval { + %value_type = irdl.parametric @compute::@field_value<> + %sym = irdl.any + %domain_start = irdl.any + %domain_size = irdl.any + %index = irdl.any + irdl.attributes { + "sym_name" = %sym, + "domain_start" = %domain_start, + "domain_size" = %domain_size, + "index" = %index + } + irdl.operands(point: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @sumcheck_claim { + %input_claim = irdl.parametric @compute::@field_value<> + %opening_claim = irdl.parametric @compute::@opening_claim_type<> + %claim_type = irdl.parametric @compute::@sumcheck_claim_type<> + %sym = irdl.any + %stage = irdl.any + %domain = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + %claim = irdl.any + %relation = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "domain" = %domain, + "num_rounds" = %num_rounds, + "degree" = %degree, + "claim" = %claim, + "relation" = %relation + } + irdl.operands(input_claim: %input_claim, inputs: variadic %opening_claim) + irdl.results(claim: %claim_type) + } + irdl.operation @sumcheck_kernel_claim { + %input_claim = irdl.parametric @compute::@field_value<> + %opening_claim = irdl.parametric @compute::@opening_claim_type<> + %claim_type = irdl.parametric @compute::@sumcheck_claim_type<> + %sym = irdl.any + %stage = irdl.any + %domain = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + %claim = irdl.any + %kernel = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "domain" = %domain, + "num_rounds" = %num_rounds, + "degree" = %degree, + "claim" = %claim, + "kernel" = %kernel + } + irdl.operands(input_claim: %input_claim, inputs: variadic %opening_claim) + irdl.results(claim: %claim_type) + } + irdl.operation @sumcheck_verify_claim { + %input_claim = irdl.parametric @compute::@field_value<> + %opening_claim = irdl.parametric @compute::@opening_claim_type<> + %claim_type = irdl.parametric @compute::@sumcheck_claim_type<> + %sym = irdl.any + %stage = irdl.any + %domain = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + %claim = irdl.any + %relation = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "domain" = %domain, + "num_rounds" = %num_rounds, + "degree" = %degree, + "claim" = %claim, + "relation" = %relation + } + irdl.operands(input_claim: %input_claim, inputs: variadic %opening_claim) + irdl.results(claim: %claim_type) + } + irdl.operation @sumcheck_batch { + %claim_type = irdl.parametric @compute::@sumcheck_claim_type<> + %batch_type = irdl.parametric @compute::@sumcheck_batch_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %round_schedule = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims, + "claim_label" = %claim_label, + "round_label" = %round_label, + "round_schedule" = %round_schedule + } + irdl.operands(claims: variadic %claim_type) + irdl.results(batch: %batch_type) + } + irdl.operation @sumcheck_driver { + %state = irdl.parametric @compute::@transcript_state<> + %batch_type = irdl.parametric @compute::@sumcheck_batch_type<> + %point = irdl.parametric @compute::@point<> + %result = irdl.parametric @compute::@sumcheck_result_type<> + %proof = irdl.parametric @compute::@sumcheck_proof_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %relation = irdl.any + %policy = irdl.any + %round_schedule = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "relation" = %relation, + "policy" = %policy, + "round_schedule" = %round_schedule, + "claim_label" = %claim_label, + "round_label" = %round_label, + "num_rounds" = %num_rounds, + "degree" = %degree + } + irdl.operands(input: %state, batch: %batch_type) + irdl.results(output: %state, point: %point, result: %result, proof: %proof) + } + irdl.operation @sumcheck_kernel_driver { + %state = irdl.parametric @compute::@transcript_state<> + %batch_type = irdl.parametric @compute::@sumcheck_batch_type<> + %point = irdl.parametric @compute::@point<> + %result = irdl.parametric @compute::@sumcheck_result_type<> + %proof = irdl.parametric @compute::@sumcheck_proof_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %kernel = irdl.any + %policy = irdl.any + %round_schedule = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "kernel" = %kernel, + "policy" = %policy, + "round_schedule" = %round_schedule, + "claim_label" = %claim_label, + "round_label" = %round_label, + "num_rounds" = %num_rounds, + "degree" = %degree + } + irdl.operands(input: %state, batch: %batch_type) + irdl.results(output: %state, point: %point, result: %result, proof: %proof) + } + irdl.operation @sumcheck_verify { + %state = irdl.parametric @compute::@transcript_state<> + %batch_type = irdl.parametric @compute::@sumcheck_batch_type<> + %point = irdl.parametric @compute::@point<> + %result = irdl.parametric @compute::@sumcheck_result_type<> + %proof = irdl.parametric @compute::@sumcheck_proof_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %relation = irdl.any + %policy = irdl.any + %round_schedule = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "relation" = %relation, + "policy" = %policy, + "round_schedule" = %round_schedule, + "claim_label" = %claim_label, + "round_label" = %round_label, + "num_rounds" = %num_rounds, + "degree" = %degree + } + irdl.operands(input: %state, batch: %batch_type) + irdl.results(output: %state, point: %point, result: %result, proof: %proof) + } + irdl.operation @sumcheck_eval { + %result = irdl.parametric @compute::@sumcheck_result_type<> + %eval = irdl.parametric @compute::@field_value<> + %sym = irdl.any + %source = irdl.any + %name = irdl.any + %index = irdl.any + %oracle = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "name" = %name, + "index" = %index, + "oracle" = %oracle + } + irdl.operands(result: %result) + irdl.results(eval: %eval) + } + irdl.operation @sumcheck_instance_result { + %input_point = irdl.parametric @compute::@point<> + %output_point = irdl.parametric @compute::@point<> + %input_result = irdl.parametric @compute::@sumcheck_result_type<> + %output_result = irdl.parametric @compute::@sumcheck_result_type<> + %sym = irdl.any + %source = irdl.any + %claim = irdl.any + %relation = irdl.any + %index = irdl.any + %point_arity = irdl.any + %num_rounds = irdl.any + %round_offset = irdl.any + %point_order = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "claim" = %claim, + "relation" = %relation, + "index" = %index, + "point_arity" = %point_arity, + "num_rounds" = %num_rounds, + "round_offset" = %round_offset, + "point_order" = %point_order, + "degree" = %degree + } + irdl.operands(input_point: %input_point, input_result: %input_result) + irdl.results(instance_point: %output_point, instance_result: %output_result) + } + irdl.operation @opening_claim { + %point = irdl.parametric @compute::@point<> + %eval = irdl.parametric @compute::@field_value<> + %claim = irdl.parametric @compute::@opening_claim_type<> + %sym = irdl.any + %oracle = irdl.any + %domain = irdl.any + %point_arity = irdl.any + %claim_kind = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "domain" = %domain, + "point_arity" = %point_arity, + "claim_kind" = %claim_kind + } + irdl.operands(point: %point, eval: %eval) + irdl.results(claim: %claim) + } + irdl.operation @opening_claim_equal { + %claim = irdl.parametric @compute::@opening_claim_type<> + %sym = irdl.any + %mode = irdl.any + irdl.attributes { + "sym_name" = %sym, + "mode" = %mode + } + irdl.operands(left: %claim, right: %claim) + } + irdl.operation @opening_batch { + %claim = irdl.parametric @compute::@opening_claim_type<> + %batch = irdl.parametric @compute::@opening_batch_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims + } + irdl.operands(claims: variadic %claim) + irdl.results(batch: %batch) + } + irdl.operation @pcs_opening_claim { + %point = irdl.parametric @compute::@point<> + %eval = irdl.parametric @compute::@field_value<> + %claim = irdl.parametric @compute::@opening_claim_type<> + %sym = irdl.any + %oracle = irdl.any + %family = irdl.any + %domain = irdl.any + %point_arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "family" = %family, + "domain" = %domain, + "point_arity" = %point_arity + } + irdl.operands(point: %point, eval: %eval) + irdl.results(claim: %claim) + } + irdl.operation @pcs_opening_batch { + %claim = irdl.parametric @compute::@opening_claim_type<> + %batch = irdl.parametric @compute::@opening_batch_type<> + %sym = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + irdl.attributes { + "sym_name" = %sym, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims + } + irdl.operands(claims: variadic %claim) + irdl.results(batch: %batch) + } + irdl.operation @pcs_batch_open { + %state = irdl.parametric @compute::@transcript_state<> + %batch = irdl.parametric @compute::@opening_batch_type<> + %proof = irdl.parametric @compute::@opening_proof_type<> + %sym = irdl.any + %pcs = irdl.any + %proof_slot = irdl.any + %transcript_label = irdl.any + irdl.attributes { + "sym_name" = %sym, + "pcs" = %pcs, + "proof_slot" = %proof_slot, + "transcript_label" = %transcript_label + } + irdl.operands(input: %state, batch: %batch) + irdl.results(output: %state, proof: %proof) + } + irdl.operation @pcs_batch_verify { + %state = irdl.parametric @compute::@transcript_state<> + %batch = irdl.parametric @compute::@opening_batch_type<> + %proof = irdl.parametric @compute::@opening_proof_type<> + %sym = irdl.any + %pcs = irdl.any + %proof_slot = irdl.any + %transcript_label = irdl.any + irdl.attributes { + "sym_name" = %sym, + "pcs" = %pcs, + "proof_slot" = %proof_slot, + "transcript_label" = %transcript_label + } + irdl.operands(input: %state, batch: %batch) + irdl.results(output: %state, proof: %proof) + } + irdl.operation @generate_oracle { + %sym = irdl.any + %oracle = irdl.any + %source = irdl.any + %generation = irdl.any + irdl.attributes {"sym_name" = %sym, "oracle" = %oracle, "source" = %source, "generation" = %generation} + } + irdl.operation @generate_oracle_family { + %sym = irdl.any + %family = irdl.any + %source = irdl.any + %generation = irdl.any + irdl.attributes {"sym_name" = %sym, "family" = %family, "source" = %source, "generation" = %generation} + } +} diff --git a/crates/bolt/irdl/cpu.mlir b/crates/bolt/irdl/cpu.mlir new file mode 100644 index 0000000000..22fe7ad519 --- /dev/null +++ b/crates/bolt/irdl/cpu.mlir @@ -0,0 +1,723 @@ +irdl.dialect @cpu { + irdl.type @commitment_artifact + irdl.type @transcript_state + irdl.type @oracle_buffer + irdl.type @oracle_family + irdl.type @field_value + irdl.type @point + irdl.type @sumcheck_claim_type + irdl.type @sumcheck_batch_type + irdl.type @sumcheck_result_type + irdl.type @sumcheck_proof_type + irdl.type @opening_claim_type + irdl.type @opening_batch_type + irdl.type @opening_proof_type + + irdl.operation @params { + %sym = irdl.any + %field = irdl.any + %pcs = irdl.any + %transcript = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field, "pcs" = %pcs, "transcript" = %transcript} + } + irdl.operation @function { + %sym = irdl.any + %source = irdl.any + irdl.attributes {"sym_name" = %sym, "source" = %source} + } + irdl.operation @kernel { + %sym = irdl.any + %relation = irdl.any + %kind = irdl.any + %backend = irdl.any + %abi = irdl.any + irdl.attributes { + "sym_name" = %sym, + "relation" = %relation, + "kind" = %kind, + "backend" = %backend, + "abi" = %abi + } + } + irdl.operation @oracle_dense_trace { + %buffer = irdl.parametric @cpu::@oracle_buffer<> + %sym = irdl.any + %oracle = irdl.any + %source = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %padding = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "source" = %source, + "domain" = %domain, + "num_vars" = %num_vars, + "padding" = %padding + } + irdl.results(buffer: %buffer) + } + irdl.operation @oracle_one_hot_chunk { + %buffer = irdl.parametric @cpu::@oracle_buffer<> + %sym = irdl.any + %oracle = irdl.any + %source = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %trace_num_vars = irdl.any + %chunk = irdl.any + %num_chunks = irdl.any + %chunk_bits = irdl.any + %padding = irdl.any + %layout = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "source" = %source, + "domain" = %domain, + "num_vars" = %num_vars, + "trace_num_vars" = %trace_num_vars, + "chunk" = %chunk, + "num_chunks" = %num_chunks, + "chunk_bits" = %chunk_bits, + "padding" = %padding, + "layout" = %layout + } + irdl.results(buffer: %buffer) + } + irdl.operation @oracle_optional_advice { + %buffer = irdl.parametric @cpu::@oracle_buffer<> + %sym = irdl.any + %oracle = irdl.any + %source = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %skip_policy = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "source" = %source, + "domain" = %domain, + "num_vars" = %num_vars, + "skip_policy" = %skip_policy + } + irdl.results(buffer: %buffer) + } + irdl.operation @oracle_ref { + %buffer = irdl.parametric @cpu::@oracle_buffer<> + %sym = irdl.any + %oracle = irdl.any + %domain = irdl.any + %num_vars = irdl.any + irdl.attributes {"sym_name" = %sym, "oracle" = %oracle, "domain" = %domain, "num_vars" = %num_vars} + irdl.results(buffer: %buffer) + } + irdl.operation @oracle_family_init { + %family_type = irdl.parametric @cpu::@oracle_family<> + %sym = irdl.any + %family = irdl.any + %count = irdl.any + irdl.attributes {"sym_name" = %sym, "family" = %family, "count" = %count} + irdl.results(family: %family_type) + } + irdl.operation @oracle_family_append { + %family_type = irdl.parametric @cpu::@oracle_family<> + %buffer = irdl.parametric @cpu::@oracle_buffer<> + %sym = irdl.any + %family = irdl.any + %oracle = irdl.any + %index = irdl.any + irdl.attributes {"sym_name" = %sym, "family" = %family, "oracle" = %oracle, "index" = %index} + irdl.operands(input: %family_type, oracle_buffer: %buffer) + irdl.results(output: %family_type) + } + irdl.operation @pcs_commit_batch { + %artifact_type = irdl.parametric @cpu::@commitment_artifact<> + %family_type = irdl.parametric @cpu::@oracle_family<> + %sym = irdl.any + %artifact = irdl.any + %pcs = irdl.any + %oracle_family = irdl.any + %ordered_oracles = irdl.any + %label = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "artifact" = %artifact, + "pcs" = %pcs, + "oracle_family" = %oracle_family, + "ordered_oracles" = %ordered_oracles, + "label" = %label, + "domain" = %domain, + "num_vars" = %num_vars, + "count" = %count + } + irdl.operands(oracles: %family_type) + irdl.results(artifact: %artifact_type) + } + irdl.operation @pcs_commit_optional { + %artifact_type = irdl.parametric @cpu::@commitment_artifact<> + %buffer = irdl.parametric @cpu::@oracle_buffer<> + %sym = irdl.any + %artifact = irdl.any + %pcs = irdl.any + %oracle = irdl.any + %label = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %skip_policy = irdl.any + irdl.attributes { + "sym_name" = %sym, + "artifact" = %artifact, + "pcs" = %pcs, + "oracle" = %oracle, + "label" = %label, + "domain" = %domain, + "num_vars" = %num_vars, + "skip_policy" = %skip_policy + } + irdl.operands(oracle_buffer: %buffer) + irdl.results(artifact: %artifact_type) + } + irdl.operation @pcs_receive_batch { + %artifact_type = irdl.parametric @cpu::@commitment_artifact<> + %family_type = irdl.parametric @cpu::@oracle_family<> + %sym = irdl.any + %artifact = irdl.any + %pcs = irdl.any + %oracle_family = irdl.any + %ordered_oracles = irdl.any + %label = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "artifact" = %artifact, + "pcs" = %pcs, + "oracle_family" = %oracle_family, + "ordered_oracles" = %ordered_oracles, + "label" = %label, + "domain" = %domain, + "num_vars" = %num_vars, + "count" = %count + } + irdl.operands(oracles: %family_type) + irdl.results(artifact: %artifact_type) + } + irdl.operation @pcs_receive_optional { + %artifact_type = irdl.parametric @cpu::@commitment_artifact<> + %buffer = irdl.parametric @cpu::@oracle_buffer<> + %sym = irdl.any + %artifact = irdl.any + %pcs = irdl.any + %oracle = irdl.any + %label = irdl.any + %domain = irdl.any + %num_vars = irdl.any + %skip_policy = irdl.any + irdl.attributes { + "sym_name" = %sym, + "artifact" = %artifact, + "pcs" = %pcs, + "oracle" = %oracle, + "label" = %label, + "domain" = %domain, + "num_vars" = %num_vars, + "skip_policy" = %skip_policy + } + irdl.operands(oracle_buffer: %buffer) + irdl.results(artifact: %artifact_type) + } + irdl.operation @transcript_init { + %state = irdl.parametric @cpu::@transcript_state<> + %sym = irdl.any + %scheme = irdl.any + irdl.attributes {"sym_name" = %sym, "scheme" = %scheme} + irdl.results(state: %state) + } + irdl.operation @transcript_absorb { + %state = irdl.parametric @cpu::@transcript_state<> + %artifact = irdl.parametric @cpu::@commitment_artifact<> + %sym = irdl.any + %label = irdl.any + %optional = irdl.any + irdl.attributes { + "sym_name" = %sym, + "label" = %label, + "optional" = %optional + } + irdl.operands(input: %state, artifact: %artifact) + irdl.results(output: %state) + } + irdl.operation @transcript_absorb_bytes { + %state = irdl.parametric @cpu::@transcript_state<> + %sym = irdl.any + %label = irdl.any + %payload = irdl.any + irdl.attributes { + "sym_name" = %sym, + "label" = %label, + "payload" = %payload + } + irdl.operands(input: %state) + irdl.results(output: %state) + } + irdl.operation @transcript_squeeze { + %state = irdl.parametric @cpu::@transcript_state<> + %challenge = irdl.any + %sym = irdl.any + %label = irdl.any + %kind = irdl.any + %count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "label" = %label, + "kind" = %kind, + "count" = %count + } + irdl.operands(input: %state) + irdl.results(output: %state, challenge: %challenge) + } + irdl.operation @opening_input { + %point = irdl.parametric @cpu::@point<> + %eval = irdl.parametric @cpu::@field_value<> + %claim = irdl.parametric @cpu::@opening_claim_type<> + %sym = irdl.any + %source_stage = irdl.any + %source_claim = irdl.any + %oracle = irdl.any + %domain = irdl.any + %point_arity = irdl.any + %claim_kind = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source_stage" = %source_stage, + "source_claim" = %source_claim, + "oracle" = %oracle, + "domain" = %domain, + "point_arity" = %point_arity, + "claim_kind" = %claim_kind + } + irdl.results(point: %point, eval: %eval, claim: %claim) + } + irdl.operation @point_slice { + %input = irdl.parametric @cpu::@point<> + %output = irdl.parametric @cpu::@point<> + %sym = irdl.any + %source = irdl.any + %offset = irdl.any + %length = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "offset" = %offset, + "length" = %length + } + irdl.operands(input: %input) + irdl.results(output: %output) + } + irdl.operation @point_zero { + %output = irdl.parametric @cpu::@point<> + %sym = irdl.any + %field = irdl.any + %arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "field" = %field, + "arity" = %arity + } + irdl.results(output: %output) + } + irdl.operation @point_concat { + %input = irdl.parametric @cpu::@point<> + %output = irdl.parametric @cpu::@point<> + %sym = irdl.any + %layout = irdl.any + %arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "layout" = %layout, + "arity" = %arity + } + irdl.operands(inputs: variadic %input) + irdl.results(output: %output) + } + irdl.operation @field_const { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + %field = irdl.any + %value = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field, "value" = %value} + irdl.results(value: %value_type) + } + irdl.operation @field_zero { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + %field = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field} + irdl.results(value: %value_type) + } + irdl.operation @field_one { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + %field = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field} + irdl.results(value: %value_type) + } + irdl.operation @field_add { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %value_type, rhs: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @field_sub { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %value_type, rhs: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @field_neg { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(input: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @field_mul { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %value_type, rhs: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @field_pow { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + %exponent = irdl.any + irdl.attributes {"sym_name" = %sym, "exponent" = %exponent} + irdl.operands(input: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @poly_lagrange_basis_eval { + %value_type = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + %domain_start = irdl.any + %domain_size = irdl.any + %index = irdl.any + irdl.attributes { + "sym_name" = %sym, + "domain_start" = %domain_start, + "domain_size" = %domain_size, + "index" = %index + } + irdl.operands(point: %value_type) + irdl.results(value: %value_type) + } + irdl.operation @sumcheck_claim { + %input_claim = irdl.parametric @cpu::@field_value<> + %opening_claim = irdl.parametric @cpu::@opening_claim_type<> + %claim_type = irdl.parametric @cpu::@sumcheck_claim_type<> + %sym = irdl.any + %stage = irdl.any + %domain = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + %claim = irdl.any + %kernel = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "domain" = %domain, + "num_rounds" = %num_rounds, + "degree" = %degree, + "claim" = %claim, + "kernel" = %kernel + } + irdl.operands(input_claim: %input_claim, inputs: variadic %opening_claim) + irdl.results(claim: %claim_type) + } + irdl.operation @sumcheck_verify_claim { + %input_claim = irdl.parametric @cpu::@field_value<> + %opening_claim = irdl.parametric @cpu::@opening_claim_type<> + %claim_type = irdl.parametric @cpu::@sumcheck_claim_type<> + %sym = irdl.any + %stage = irdl.any + %domain = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + %claim = irdl.any + %relation = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "domain" = %domain, + "num_rounds" = %num_rounds, + "degree" = %degree, + "claim" = %claim, + "relation" = %relation + } + irdl.operands(input_claim: %input_claim, inputs: variadic %opening_claim) + irdl.results(claim: %claim_type) + } + irdl.operation @sumcheck_batch { + %claim_type = irdl.parametric @cpu::@sumcheck_claim_type<> + %batch_type = irdl.parametric @cpu::@sumcheck_batch_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %round_schedule = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims, + "claim_label" = %claim_label, + "round_label" = %round_label, + "round_schedule" = %round_schedule + } + irdl.operands(claims: variadic %claim_type) + irdl.results(batch: %batch_type) + } + irdl.operation @sumcheck_driver { + %state = irdl.parametric @cpu::@transcript_state<> + %batch_type = irdl.parametric @cpu::@sumcheck_batch_type<> + %point = irdl.parametric @cpu::@point<> + %result = irdl.parametric @cpu::@sumcheck_result_type<> + %proof = irdl.parametric @cpu::@sumcheck_proof_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %kernel = irdl.any + %policy = irdl.any + %round_schedule = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "kernel" = %kernel, + "policy" = %policy, + "round_schedule" = %round_schedule, + "claim_label" = %claim_label, + "round_label" = %round_label, + "num_rounds" = %num_rounds, + "degree" = %degree + } + irdl.operands(input: %state, batch: %batch_type) + irdl.results(output: %state, point: %point, result: %result, proof: %proof) + } + irdl.operation @sumcheck_verify { + %state = irdl.parametric @cpu::@transcript_state<> + %batch_type = irdl.parametric @cpu::@sumcheck_batch_type<> + %point = irdl.parametric @cpu::@point<> + %result = irdl.parametric @cpu::@sumcheck_result_type<> + %proof = irdl.parametric @cpu::@sumcheck_proof_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %relation = irdl.any + %policy = irdl.any + %round_schedule = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "relation" = %relation, + "policy" = %policy, + "round_schedule" = %round_schedule, + "claim_label" = %claim_label, + "round_label" = %round_label, + "num_rounds" = %num_rounds, + "degree" = %degree + } + irdl.operands(input: %state, batch: %batch_type) + irdl.results(output: %state, point: %point, result: %result, proof: %proof) + } + irdl.operation @sumcheck_eval { + %result = irdl.parametric @cpu::@sumcheck_result_type<> + %eval = irdl.parametric @cpu::@field_value<> + %sym = irdl.any + %source = irdl.any + %name = irdl.any + %index = irdl.any + %oracle = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "name" = %name, + "index" = %index, + "oracle" = %oracle + } + irdl.operands(result: %result) + irdl.results(eval: %eval) + } + irdl.operation @sumcheck_instance_result { + %input_point = irdl.parametric @cpu::@point<> + %output_point = irdl.parametric @cpu::@point<> + %input_result = irdl.parametric @cpu::@sumcheck_result_type<> + %output_result = irdl.parametric @cpu::@sumcheck_result_type<> + %sym = irdl.any + %source = irdl.any + %claim = irdl.any + %relation = irdl.any + %index = irdl.any + %point_arity = irdl.any + %num_rounds = irdl.any + %round_offset = irdl.any + %point_order = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "claim" = %claim, + "relation" = %relation, + "index" = %index, + "point_arity" = %point_arity, + "num_rounds" = %num_rounds, + "round_offset" = %round_offset, + "point_order" = %point_order, + "degree" = %degree + } + irdl.operands(input_point: %input_point, input_result: %input_result) + irdl.results(instance_point: %output_point, instance_result: %output_result) + } + irdl.operation @opening_claim { + %point = irdl.parametric @cpu::@point<> + %eval = irdl.parametric @cpu::@field_value<> + %claim = irdl.parametric @cpu::@opening_claim_type<> + %sym = irdl.any + %oracle = irdl.any + %domain = irdl.any + %point_arity = irdl.any + %claim_kind = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "domain" = %domain, + "point_arity" = %point_arity, + "claim_kind" = %claim_kind + } + irdl.operands(point: %point, eval: %eval) + irdl.results(claim: %claim) + } + irdl.operation @opening_claim_equal { + %claim = irdl.parametric @cpu::@opening_claim_type<> + %sym = irdl.any + %mode = irdl.any + irdl.attributes { + "sym_name" = %sym, + "mode" = %mode + } + irdl.operands(left: %claim, right: %claim) + } + irdl.operation @opening_batch { + %claim = irdl.parametric @cpu::@opening_claim_type<> + %batch = irdl.parametric @cpu::@opening_batch_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims + } + irdl.operands(claims: variadic %claim) + irdl.results(batch: %batch) + } + irdl.operation @pcs_opening_claim { + %point = irdl.parametric @cpu::@point<> + %eval = irdl.parametric @cpu::@field_value<> + %claim = irdl.parametric @cpu::@opening_claim_type<> + %sym = irdl.any + %oracle = irdl.any + %family = irdl.any + %domain = irdl.any + %point_arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "family" = %family, + "domain" = %domain, + "point_arity" = %point_arity + } + irdl.operands(point: %point, eval: %eval) + irdl.results(claim: %claim) + } + irdl.operation @pcs_opening_batch { + %claim = irdl.parametric @cpu::@opening_claim_type<> + %batch = irdl.parametric @cpu::@opening_batch_type<> + %sym = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + irdl.attributes { + "sym_name" = %sym, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims + } + irdl.operands(claims: variadic %claim) + irdl.results(batch: %batch) + } + irdl.operation @pcs_batch_open { + %state = irdl.parametric @cpu::@transcript_state<> + %batch = irdl.parametric @cpu::@opening_batch_type<> + %proof = irdl.parametric @cpu::@opening_proof_type<> + %sym = irdl.any + %pcs = irdl.any + %proof_slot = irdl.any + %transcript_label = irdl.any + irdl.attributes { + "sym_name" = %sym, + "pcs" = %pcs, + "proof_slot" = %proof_slot, + "transcript_label" = %transcript_label + } + irdl.operands(input: %state, batch: %batch) + irdl.results(output: %state, proof: %proof) + } + irdl.operation @pcs_batch_verify { + %state = irdl.parametric @cpu::@transcript_state<> + %batch = irdl.parametric @cpu::@opening_batch_type<> + %proof = irdl.parametric @cpu::@opening_proof_type<> + %sym = irdl.any + %pcs = irdl.any + %proof_slot = irdl.any + %transcript_label = irdl.any + irdl.attributes { + "sym_name" = %sym, + "pcs" = %pcs, + "proof_slot" = %proof_slot, + "transcript_label" = %transcript_label + } + irdl.operands(input: %state, batch: %batch) + irdl.results(output: %state, proof: %proof) + } +} diff --git a/crates/bolt/irdl/field.mlir b/crates/bolt/irdl/field.mlir new file mode 100644 index 0000000000..2f14d2b061 --- /dev/null +++ b/crates/bolt/irdl/field.mlir @@ -0,0 +1,67 @@ +irdl.dialect @field { + irdl.type @scalar + irdl.operation @define { + %sym = irdl.any + %modulus_bits = irdl.any + %role = irdl.any + irdl.attributes {"sym_name" = %sym, "modulus_bits" = %modulus_bits, "role" = %role} + } + irdl.operation @const { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + %field = irdl.any + %value = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field, "value" = %value} + irdl.results(value: %scalar) + } + irdl.operation @zero { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + %field = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field} + irdl.results(value: %scalar) + } + irdl.operation @one { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + %field = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field} + irdl.results(value: %scalar) + } + irdl.operation @add { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %scalar, rhs: %scalar) + irdl.results(value: %scalar) + } + irdl.operation @sub { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %scalar, rhs: %scalar) + irdl.results(value: %scalar) + } + irdl.operation @neg { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(input: %scalar) + irdl.results(value: %scalar) + } + irdl.operation @mul { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + irdl.attributes {"sym_name" = %sym} + irdl.operands(lhs: %scalar, rhs: %scalar) + irdl.results(value: %scalar) + } + irdl.operation @pow { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + %exponent = irdl.any + irdl.attributes {"sym_name" = %sym, "exponent" = %exponent} + irdl.operands(input: %scalar) + irdl.results(value: %scalar) + } +} diff --git a/crates/bolt/irdl/hash.mlir b/crates/bolt/irdl/hash.mlir new file mode 100644 index 0000000000..66b2f984fa --- /dev/null +++ b/crates/bolt/irdl/hash.mlir @@ -0,0 +1,7 @@ +irdl.dialect @hash { + irdl.operation @function { + %sym = irdl.any + %algorithm = irdl.any + irdl.attributes {"sym_name" = %sym, "algorithm" = %algorithm} + } +} diff --git a/crates/bolt/irdl/party.mlir b/crates/bolt/irdl/party.mlir new file mode 100644 index 0000000000..46dfb26d30 --- /dev/null +++ b/crates/bolt/irdl/party.mlir @@ -0,0 +1,8 @@ +irdl.dialect @party { + irdl.operation @function { + %sym = irdl.any + %source = irdl.any + %role = irdl.any + irdl.attributes {"sym_name" = %sym, "source" = %source, "role" = %role} + } +} diff --git a/crates/bolt/irdl/pcs.mlir b/crates/bolt/irdl/pcs.mlir new file mode 100644 index 0000000000..a5fff00470 --- /dev/null +++ b/crates/bolt/irdl/pcs.mlir @@ -0,0 +1,89 @@ +irdl.dialect @pcs { + irdl.type @scheme_type + irdl.type @opening_claim_type + irdl.type @opening_batch_type + irdl.type @opening_proof_type + irdl.operation @scheme { + %sym = irdl.any + %field = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field} + } + irdl.operation @commit_batch { + %artifact = irdl.parametric @commit::@artifact<> + %sym = irdl.any + %scheme = irdl.any + irdl.attributes {"sym_name" = %sym, "scheme" = %scheme} + irdl.operands(commitment: %artifact) + } + irdl.operation @opening_claim { + %point = irdl.parametric @poly::@point<> + %eval = irdl.parametric @field::@scalar<> + %claim = irdl.parametric @pcs::@opening_claim_type<> + %sym = irdl.any + %oracle = irdl.any + %family = irdl.any + %domain = irdl.any + %point_arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "family" = %family, + "domain" = %domain, + "point_arity" = %point_arity + } + irdl.operands(point: %point, eval: %eval) + irdl.results(claim: %claim) + } + irdl.operation @opening_batch { + %claim = irdl.parametric @pcs::@opening_claim_type<> + %batch = irdl.parametric @pcs::@opening_batch_type<> + %sym = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + irdl.attributes { + "sym_name" = %sym, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims + } + irdl.operands(claims: variadic %claim) + irdl.results(batch: %batch) + } + irdl.operation @batch_open { + %state = irdl.parametric @transcript::@state_type<> + %batch = irdl.parametric @pcs::@opening_batch_type<> + %proof = irdl.parametric @pcs::@opening_proof_type<> + %sym = irdl.any + %pcs = irdl.any + %proof_slot = irdl.any + %transcript_label = irdl.any + irdl.attributes { + "sym_name" = %sym, + "pcs" = %pcs, + "proof_slot" = %proof_slot, + "transcript_label" = %transcript_label + } + irdl.operands(input: %state, batch: %batch) + irdl.results(output: %state, proof: %proof) + } + irdl.operation @batch_verify { + %state = irdl.parametric @transcript::@state_type<> + %batch = irdl.parametric @pcs::@opening_batch_type<> + %proof = irdl.parametric @pcs::@opening_proof_type<> + %sym = irdl.any + %pcs = irdl.any + %proof_slot = irdl.any + %transcript_label = irdl.any + irdl.attributes { + "sym_name" = %sym, + "pcs" = %pcs, + "proof_slot" = %proof_slot, + "transcript_label" = %transcript_label + } + irdl.operands(input: %state, batch: %batch) + irdl.results(output: %state, proof: %proof) + } +} diff --git a/crates/bolt/irdl/piop.mlir b/crates/bolt/irdl/piop.mlir new file mode 100644 index 0000000000..169bc5c9ab --- /dev/null +++ b/crates/bolt/irdl/piop.mlir @@ -0,0 +1,270 @@ +irdl.dialect @piop { + irdl.type @stage_type + irdl.type @sumcheck_claim_type + irdl.type @sumcheck_batch_type + irdl.type @sumcheck_result_type + irdl.type @sumcheck_proof_type + irdl.type @opening_claim_type + irdl.type @opening_batch_type + + irdl.operation @oracle { + %sym = irdl.any + %field = irdl.any + %domain = irdl.any + %commit_domain = irdl.any + %visibility = irdl.any + %layout = irdl.any + irdl.attributes { + "sym_name" = %sym, + "field" = %field, + "domain" = %domain, + "commit_domain" = %commit_domain, + "visibility" = %visibility, + "layout" = %layout + } + } + irdl.operation @oracle_family { + %sym = irdl.any + %ordered_oracles = irdl.any + %visibility = irdl.any + %count = irdl.any + %domain = irdl.any + irdl.attributes { + "sym_name" = %sym, + "ordered_oracles" = %ordered_oracles, + "visibility" = %visibility, + "count" = %count, + "domain" = %domain + } + } + irdl.operation @stage { + %stage_type = irdl.parametric @piop::@stage_type<> + %sym = irdl.any + %name = irdl.any + %order = irdl.any + %roles = irdl.any + irdl.attributes { + "sym_name" = %sym, + "name" = %name, + "order" = %order, + "roles" = %roles + } + irdl.results(stage: %stage_type) + } + irdl.operation @relation { + %sym = irdl.any + %kind = irdl.any + %domain = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + %output_count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "kind" = %kind, + "domain" = %domain, + "num_rounds" = %num_rounds, + "degree" = %degree, + "output_count" = %output_count + } + } + irdl.operation @sumcheck_claim { + %input_claim = irdl.parametric @field::@scalar<> + %opening_claim = irdl.parametric @piop::@opening_claim_type<> + %claim_type = irdl.parametric @piop::@sumcheck_claim_type<> + %sym = irdl.any + %stage = irdl.any + %domain = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + %claim = irdl.any + %relation = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "domain" = %domain, + "num_rounds" = %num_rounds, + "degree" = %degree, + "claim" = %claim, + "relation" = %relation + } + irdl.operands(input_claim: %input_claim, inputs: variadic %opening_claim) + irdl.results(claim: %claim_type) + } + irdl.operation @opening_input { + %point = irdl.parametric @poly::@point<> + %eval = irdl.parametric @field::@scalar<> + %claim = irdl.parametric @piop::@opening_claim_type<> + %sym = irdl.any + %source_stage = irdl.any + %source_claim = irdl.any + %oracle = irdl.any + %domain = irdl.any + %point_arity = irdl.any + %claim_kind = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source_stage" = %source_stage, + "source_claim" = %source_claim, + "oracle" = %oracle, + "domain" = %domain, + "point_arity" = %point_arity, + "claim_kind" = %claim_kind + } + irdl.results(point: %point, eval: %eval, claim: %claim) + } + irdl.operation @sumcheck_batch { + %stage_type = irdl.parametric @piop::@stage_type<> + %claim_type = irdl.parametric @piop::@sumcheck_claim_type<> + %batch_type = irdl.parametric @piop::@sumcheck_batch_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %round_schedule = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims, + "claim_label" = %claim_label, + "round_label" = %round_label, + "round_schedule" = %round_schedule + } + irdl.operands(stage: %stage_type, claims: variadic %claim_type) + irdl.results(batch: %batch_type) + } + irdl.operation @sumcheck { + %state = irdl.parametric @transcript::@state_type<> + %batch_type = irdl.parametric @piop::@sumcheck_batch_type<> + %point = irdl.parametric @poly::@point<> + %result = irdl.parametric @piop::@sumcheck_result_type<> + %proof = irdl.parametric @piop::@sumcheck_proof_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %relation = irdl.any + %policy = irdl.any + %round_schedule = irdl.any + %claim_label = irdl.any + %round_label = irdl.any + %num_rounds = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "relation" = %relation, + "policy" = %policy, + "round_schedule" = %round_schedule, + "claim_label" = %claim_label, + "round_label" = %round_label, + "num_rounds" = %num_rounds, + "degree" = %degree + } + irdl.operands(input: %state, batch: %batch_type) + irdl.results(output: %state, point: %point, result: %result, proof: %proof) + } + irdl.operation @sumcheck_eval { + %result = irdl.parametric @piop::@sumcheck_result_type<> + %eval = irdl.parametric @field::@scalar<> + %sym = irdl.any + %source = irdl.any + %name = irdl.any + %index = irdl.any + %oracle = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "name" = %name, + "index" = %index, + "oracle" = %oracle + } + irdl.operands(result: %result) + irdl.results(eval: %eval) + } + irdl.operation @sumcheck_instance_result { + %input_point = irdl.parametric @poly::@point<> + %output_point = irdl.parametric @poly::@point<> + %input_result = irdl.parametric @piop::@sumcheck_result_type<> + %output_result = irdl.parametric @piop::@sumcheck_result_type<> + %sym = irdl.any + %source = irdl.any + %claim = irdl.any + %relation = irdl.any + %index = irdl.any + %point_arity = irdl.any + %num_rounds = irdl.any + %round_offset = irdl.any + %point_order = irdl.any + %degree = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "claim" = %claim, + "relation" = %relation, + "index" = %index, + "point_arity" = %point_arity, + "num_rounds" = %num_rounds, + "round_offset" = %round_offset, + "point_order" = %point_order, + "degree" = %degree + } + irdl.operands(input_point: %input_point, input_result: %input_result) + irdl.results(instance_point: %output_point, instance_result: %output_result) + } + irdl.operation @opening_claim { + %point = irdl.parametric @poly::@point<> + %eval = irdl.parametric @field::@scalar<> + %claim = irdl.parametric @piop::@opening_claim_type<> + %sym = irdl.any + %oracle = irdl.any + %domain = irdl.any + %point_arity = irdl.any + %claim_kind = irdl.any + irdl.attributes { + "sym_name" = %sym, + "oracle" = %oracle, + "domain" = %domain, + "point_arity" = %point_arity, + "claim_kind" = %claim_kind + } + irdl.operands(point: %point, eval: %eval) + irdl.results(claim: %claim) + } + irdl.operation @opening_claim_equal { + %claim = irdl.parametric @piop::@opening_claim_type<> + %sym = irdl.any + %mode = irdl.any + irdl.attributes { + "sym_name" = %sym, + "mode" = %mode + } + irdl.operands(left: %claim, right: %claim) + } + irdl.operation @opening_batch { + %claim = irdl.parametric @piop::@opening_claim_type<> + %batch = irdl.parametric @piop::@opening_batch_type<> + %sym = irdl.any + %stage = irdl.any + %proof_slot = irdl.any + %policy = irdl.any + %count = irdl.any + %ordered_claims = irdl.any + irdl.attributes { + "sym_name" = %sym, + "stage" = %stage, + "proof_slot" = %proof_slot, + "policy" = %policy, + "count" = %count, + "ordered_claims" = %ordered_claims + } + irdl.operands(claims: variadic %claim) + irdl.results(batch: %batch) + } +} diff --git a/crates/bolt/irdl/poly.mlir b/crates/bolt/irdl/poly.mlir new file mode 100644 index 0000000000..6ad0ea5951 --- /dev/null +++ b/crates/bolt/irdl/poly.mlir @@ -0,0 +1,68 @@ +irdl.dialect @poly { + irdl.type @domain_type + irdl.type @oracle + irdl.type @point + irdl.operation @domain { + %sym = irdl.any + %field = irdl.any + %log_size = irdl.any + irdl.attributes {"sym_name" = %sym, "field" = %field, "log_size" = %log_size} + } + irdl.operation @point_slice { + %input = irdl.parametric @poly::@point<> + %output = irdl.parametric @poly::@point<> + %sym = irdl.any + %source = irdl.any + %offset = irdl.any + %length = irdl.any + irdl.attributes { + "sym_name" = %sym, + "source" = %source, + "offset" = %offset, + "length" = %length + } + irdl.operands(input: %input) + irdl.results(output: %output) + } + irdl.operation @point_zero { + %output = irdl.parametric @poly::@point<> + %sym = irdl.any + %field = irdl.any + %arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "field" = %field, + "arity" = %arity + } + irdl.results(output: %output) + } + irdl.operation @point_concat { + %input = irdl.parametric @poly::@point<> + %output = irdl.parametric @poly::@point<> + %sym = irdl.any + %layout = irdl.any + %arity = irdl.any + irdl.attributes { + "sym_name" = %sym, + "layout" = %layout, + "arity" = %arity + } + irdl.operands(inputs: variadic %input) + irdl.results(output: %output) + } + irdl.operation @lagrange_basis_eval { + %scalar = irdl.parametric @field::@scalar<> + %sym = irdl.any + %domain_start = irdl.any + %domain_size = irdl.any + %index = irdl.any + irdl.attributes { + "sym_name" = %sym, + "domain_start" = %domain_start, + "domain_size" = %domain_size, + "index" = %index + } + irdl.operands(point: %scalar) + irdl.results(value: %scalar) + } +} diff --git a/crates/bolt/irdl/protocol.mlir b/crates/bolt/irdl/protocol.mlir new file mode 100644 index 0000000000..f17629f4b9 --- /dev/null +++ b/crates/bolt/irdl/protocol.mlir @@ -0,0 +1,19 @@ +irdl.dialect @protocol { + irdl.operation @params { + %sym = irdl.any + %field = irdl.any + %pcs = irdl.any + %transcript = irdl.any + irdl.attributes { + "sym_name" = %sym, + "field" = %field, + "pcs" = %pcs, + "transcript" = %transcript + } + } + irdl.operation @boundary { + %sym = irdl.any + %roles = irdl.any + irdl.attributes {"sym_name" = %sym, "roles" = %roles} + } +} diff --git a/crates/bolt/irdl/transcript.mlir b/crates/bolt/irdl/transcript.mlir new file mode 100644 index 0000000000..7e2dd926bf --- /dev/null +++ b/crates/bolt/irdl/transcript.mlir @@ -0,0 +1,62 @@ +irdl.dialect @transcript { + irdl.type @state_type + irdl.operation @scheme { + %sym = irdl.any + %hash = irdl.any + irdl.attributes {"sym_name" = %sym, "hash" = %hash} + } + irdl.operation @state { + %state = irdl.parametric @transcript::@state_type<> + %sym = irdl.any + %scheme = irdl.any + irdl.attributes {"sym_name" = %sym, "scheme" = %scheme} + irdl.results(state: %state) + } + irdl.operation @absorb { + %state = irdl.parametric @transcript::@state_type<> + %artifact = irdl.parametric @commit::@artifact<> + %sym = irdl.any + %label = irdl.any + irdl.attributes {"sym_name" = %sym, "label" = %label} + irdl.operands(input: %state, artifact: %artifact) + irdl.results(output: %state) + } + irdl.operation @absorb_optional { + %state = irdl.parametric @transcript::@state_type<> + %artifact = irdl.parametric @commit::@artifact<> + %sym = irdl.any + %label = irdl.any + irdl.attributes {"sym_name" = %sym, "label" = %label} + irdl.operands(input: %state, artifact: %artifact) + irdl.results(output: %state) + } + irdl.operation @absorb_bytes { + %state = irdl.parametric @transcript::@state_type<> + %sym = irdl.any + %label = irdl.any + %payload = irdl.any + irdl.attributes { + "sym_name" = %sym, + "label" = %label, + "payload" = %payload + } + irdl.operands(input: %state) + irdl.results(output: %state) + } + irdl.operation @squeeze { + %state = irdl.parametric @transcript::@state_type<> + %challenge = irdl.any + %sym = irdl.any + %label = irdl.any + %kind = irdl.any + %count = irdl.any + irdl.attributes { + "sym_name" = %sym, + "label" = %label, + "kind" = %kind, + "count" = %count + } + irdl.operands(input: %state) + irdl.results(output: %state, challenge: %challenge) + } +} diff --git a/crates/bolt/src/dialects.rs b/crates/bolt/src/dialects.rs new file mode 100644 index 0000000000..655ae7c43f --- /dev/null +++ b/crates/bolt/src/dialects.rs @@ -0,0 +1,39 @@ +use melior::ir::Module; +use melior::utility::load_irdl_dialects; +use melior::Context; + +pub const BOLT_IRDL: &str = concat!( + "module {\n", + include_str!("../irdl/field.mlir"), + "\n", + include_str!("../irdl/poly.mlir"), + "\n", + include_str!("../irdl/hash.mlir"), + "\n", + include_str!("../irdl/transcript.mlir"), + "\n", + include_str!("../irdl/commit.mlir"), + "\n", + include_str!("../irdl/pcs.mlir"), + "\n", + include_str!("../irdl/protocol.mlir"), + "\n", + include_str!("../irdl/piop.mlir"), + "\n", + include_str!("../irdl/party.mlir"), + "\n", + include_str!("../irdl/compute.mlir"), + "\n", + include_str!("../irdl/cpu.mlir"), + "\n}\n" +); + +pub fn load_bolt_dialects(context: &Context) -> Result<(), String> { + let module = Module::parse(context, BOLT_IRDL) + .ok_or_else(|| "failed to parse Bolt IRDL dialect definitions".to_owned())?; + if load_irdl_dialects(&module) { + Ok(()) + } else { + Err("failed to load Bolt IRDL dialect definitions".to_owned()) + } +} diff --git a/crates/bolt/src/emit/mod.rs b/crates/bolt/src/emit/mod.rs new file mode 100644 index 0000000000..0ad9e7d3d7 --- /dev/null +++ b/crates/bolt/src/emit/mod.rs @@ -0,0 +1 @@ +pub mod rust; diff --git a/crates/bolt/src/emit/rust/artifacts.rs b/crates/bolt/src/emit/rust/artifacts.rs new file mode 100644 index 0000000000..ec2d2a6c7f --- /dev/null +++ b/crates/bolt/src/emit/rust/artifacts.rs @@ -0,0 +1,1653 @@ +#![expect( + clippy::format_push_string, + reason = "Rust artifact emission assembles generated source text from format templates" +)] + +use std::path::{Component, Path}; + +use crate::ir::Role; + +use super::{EmitError, RustSourceFile}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolArtifactConfig { + pub protocol_name: String, + pub type_prefix: String, + pub transcript_label: String, + pub repository: Option, + pub prover_crate_name: String, + pub verifier_crate_name: String, + pub crates_io_patches: Vec, + pub standalone_dependency_overrides: Vec, + pub common_dependencies: Vec, + pub prover_dependencies: Vec, + pub verifier_dependencies: Vec, + pub instrumentation_prefix: Option, + pub prover_forbidden_imports: Vec, + pub verifier_forbidden_imports: Vec, + pub kernel_crate: Option, + pub field_type: RustTypeRef, + pub default_transcript_type: RustTypeRef, + pub transcript_trait: RustTypeRef, + pub commitment_type: RustTypeRef, + pub prover_setup_type: RustTypeRef, + pub role_api_extension: Option, + pub verifier_runtime_modules: Vec, + pub verifier_named_eval_type: RustTypeRef, + pub verifier_sumcheck_output_type: RustTypeRef, + pub verifier_stage_proof_type: RustTypeRef, +} + +impl ProtocolArtifactConfig { + fn protocol_snake(&self) -> String { + snake_case(&self.protocol_name) + } + + fn crate_name(&self, role: &Role) -> &str { + match role { + Role::Prover => &self.prover_crate_name, + Role::Verifier => &self.verifier_crate_name, + } + } + + fn dependencies(&self, role: &Role) -> Vec { + let mut dependencies = self.common_dependencies.clone(); + match role { + Role::Prover => { + dependencies.extend(self.prover_dependencies.clone()); + if !dependencies.contains(&self.verifier_crate_name) { + dependencies.push(self.verifier_crate_name.clone()); + } + } + Role::Verifier => dependencies.extend(self.verifier_dependencies.clone()), + } + dependencies.sort(); + dependencies.dedup(); + dependencies + } + + fn forbidden_imports(&self, role: &Role) -> &[String] { + match role { + Role::Prover => &self.prover_forbidden_imports, + Role::Verifier => &self.verifier_forbidden_imports, + } + } + + fn verifier_crate_import(&self) -> String { + rust_crate_ident(&self.verifier_crate_name) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolStandaloneDependency { + pub package: String, + pub manifest_entry: String, +} + +impl ProtocolStandaloneDependency { + pub fn new(package: impl Into, manifest_entry: impl Into) -> Self { + Self { + package: package.into(), + manifest_entry: manifest_entry.into(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolArtifactExtension { + pub required_commitment: bool, + pub required_proof_stages: Vec, + pub required_artifact_stages: Vec, + pub prover: ProtocolProverApiExtension, + pub verifier: ProtocolVerifierApiExtension, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ProtocolProverApiExtension { + pub lib_module: String, + pub imports: String, + pub input_fields: String, + pub program_fields: String, + pub default_program_fields: String, + pub error_variants: String, + pub error_items: String, + pub error_conversions: String, + pub after_stage_execution: String, + pub proof_fields: String, + pub helper_items: String, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ProtocolVerifierApiExtension { + pub lib_module: String, + pub imports: String, + pub proof_fields: String, + pub proof_items: String, + pub inputs_derive: Option, + pub input_fields: String, + pub program_fields: String, + pub default_program_fields: String, + pub error_variants: String, + pub error_items: String, + pub error_conversions: String, + pub after_default_verify: String, + pub with_programs_body_intro: String, + pub stage_verification_override: String, + pub after_stage_verification: String, + pub helper_items: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolCrateRef { + pub package: String, + pub import: String, +} + +impl ProtocolCrateRef { + pub fn new(package: impl Into, import: impl Into) -> Self { + Self { + package: package.into(), + import: import.into(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RustTypeRef { + pub path: String, +} + +impl RustTypeRef { + pub fn new(path: impl Into) -> Self { + Self { path: path.into() } + } + + fn ident(&self) -> &str { + self.path.rsplit("::").next().unwrap_or(&self.path) + } + + fn use_line(&self) -> String { + format!("use {};\n", self.path) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ProtocolStageKind { + Commitment, + Proof, + Evaluation, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolStage { + name: String, + module_name: String, + ordinal: usize, + kind: ProtocolStageKind, +} + +impl ProtocolStage { + pub fn new( + name: impl Into, + module_name: impl Into, + ordinal: usize, + kind: ProtocolStageKind, + ) -> Self { + Self { + name: name.into(), + module_name: module_name.into(), + ordinal, + kind, + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn module_name(&self) -> &str { + &self.module_name + } + + pub fn order(&self) -> usize { + self.ordinal + } + + pub fn is_commitment(&self) -> bool { + self.kind == ProtocolStageKind::Commitment + } + + pub fn is_proof(&self) -> bool { + self.kind == ProtocolStageKind::Proof + } + + pub fn is_evaluation(&self) -> bool { + self.kind == ProtocolStageKind::Evaluation + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ArtifactCrateRole { + Prover, + Verifier, +} + +impl ArtifactCrateRole { + pub fn for_role(role: &Role) -> Self { + match role { + Role::Prover => Self::Prover, + Role::Verifier => Self::Verifier, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolRustArtifact { + pub role: Role, + pub stage: ProtocolStage, + pub crate_name: String, + pub path: String, + pub source: RustSourceFile, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GeneratedCrate { + pub crate_name: String, + pub files: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GeneratedFile { + pub path: String, + pub source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolRuntimeModule { + pub module_name: String, + pub file: GeneratedFile, +} + +impl GeneratedCrate { + pub fn write_to(&self, output_root: impl AsRef) -> Result<(), EmitError> { + let crate_root = output_root.as_ref().join(&self.crate_name); + for file in &self.files { + let path = generated_file_path(&crate_root, &file.path)?; + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(|error| { + EmitError::new(format!( + "failed to create generated crate directory `{}`: {error}", + parent.display() + )) + })?; + } + std::fs::write(&path, &file.source).map_err(|error| { + EmitError::new(format!( + "failed to write generated crate file `{}`: {error}", + path.display() + )) + })?; + } + Ok(()) + } +} + +pub fn write_generated_crates( + generated_crates: &[GeneratedCrate], + output_root: impl AsRef, +) -> Result<(), EmitError> { + for generated_crate in generated_crates { + generated_crate.write_to(output_root.as_ref())?; + } + Ok(()) +} + +pub fn protocol_rust_artifact( + config: &ProtocolArtifactConfig, + stage: ProtocolStage, + role: Role, + source: RustSourceFile, +) -> ProtocolRustArtifact { + let crate_name = config.crate_name(&role).to_owned(); + let path = format!("{crate_name}/src/stages/{}.rs", stage.module_name()); + ProtocolRustArtifact { + role, + stage, + crate_name, + path, + source, + } +} + +pub fn validate_rust_artifact_imports( + config: &ProtocolArtifactConfig, + artifact: &ProtocolRustArtifact, +) -> Result<(), EmitError> { + for import in config.forbidden_imports(&artifact.role) { + if artifact.source.source.contains(import) { + return Err(EmitError::new(format!( + "{} artifact `{}` for {} imports forbidden `{import}`", + artifact.crate_name, + artifact.path, + artifact.stage.name() + ))); + } + } + Ok(()) +} + +pub fn assemble_generated_crates( + config: &ProtocolArtifactConfig, + artifacts: Vec, + dependency_root: &str, +) -> Result, EmitError> { + assemble_generated_crates_with_manifest( + config, + artifacts, + ManifestMode::Standalone { dependency_root }, + ) +} + +pub fn assemble_workspace_generated_crates( + config: &ProtocolArtifactConfig, + artifacts: Vec, +) -> Result, EmitError> { + assemble_generated_crates_with_manifest(config, artifacts, ManifestMode::Workspace) +} + +fn assemble_generated_crates_with_manifest( + config: &ProtocolArtifactConfig, + artifacts: Vec, + manifest_mode: ManifestMode<'_>, +) -> Result, EmitError> { + let mut prover = Vec::new(); + let mut verifier = Vec::new(); + for artifact in artifacts { + validate_rust_artifact_imports(config, &artifact)?; + match artifact.role { + Role::Prover => prover.push(artifact), + Role::Verifier => verifier.push(artifact), + } + } + Ok(vec![ + generated_crate(config, Role::Prover, prover, manifest_mode), + generated_crate(config, Role::Verifier, verifier, manifest_mode), + ]) +} + +fn generated_crate( + config: &ProtocolArtifactConfig, + role: Role, + mut artifacts: Vec, + manifest_mode: ManifestMode<'_>, +) -> GeneratedCrate { + artifacts.sort_by_key(|artifact| artifact.stage.order()); + let crate_name = config.crate_name(&role).to_owned(); + let mut stage_module_lines = Vec::new(); + if role == Role::Verifier { + stage_module_lines.extend( + config + .verifier_runtime_modules + .iter() + .map(|module| format!("pub mod {};", module.module_name)), + ); + } + stage_module_lines.extend(artifacts.iter().map(|artifact| { + format!( + "#[rustfmt::skip]\npub mod {};", + artifact.stage.module_name() + ) + })); + let stage_modules = stage_module_lines.join("\n"); + let mut files = vec![ + GeneratedFile { + path: "Cargo.toml".to_owned(), + source: generated_manifest(config, &role, manifest_mode), + }, + GeneratedFile { + path: "src/lib.rs".to_owned(), + source: generated_lib(config, &role, &artifacts), + }, + generated_role_api_file(config, &role, &artifacts), + GeneratedFile { + path: "src/stages/mod.rs".to_owned(), + source: format!("{stage_modules}\n"), + }, + ]; + if role == Role::Verifier { + files.extend( + config + .verifier_runtime_modules + .iter() + .map(|module| module.file.clone()), + ); + } + files.extend(artifacts.into_iter().map(|artifact| GeneratedFile { + path: format!("src/stages/{}.rs", artifact.stage.module_name()), + source: artifact.source.source, + })); + GeneratedCrate { crate_name, files } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ManifestMode<'a> { + Standalone { dependency_root: &'a str }, + Workspace, +} + +fn generated_manifest( + config: &ProtocolArtifactConfig, + role: &Role, + manifest_mode: ManifestMode<'_>, +) -> String { + let crate_name = config.crate_name(role); + let dependencies = config.dependencies(role); + match manifest_mode { + ManifestMode::Standalone { dependency_root } => { + let patch_section = if config.crates_io_patches.is_empty() { + String::new() + } else { + format!( + "\n[patch.crates-io]\n{}\n", + config.crates_io_patches.join("\n") + ) + }; + let dependencies = dependencies + .into_iter() + .map(|name| standalone_dependency_entry(config, dependency_root, &name)) + .collect::>() + .join("\n"); + format!( + "[package]\nname = \"{crate_name}\"\nversion = \"0.0.0\"\nedition = \"2021\"\n{patch_section}\n[dependencies]\n{dependencies}\n" + ) + } + ManifestMode::Workspace => { + let dependencies = dependencies + .into_iter() + .map(|name| format!("{name}.workspace = true")) + .collect::>() + .join("\n"); + let role_name = match role { + Role::Prover => "prover", + Role::Verifier => "verifier", + }; + let repository = config + .repository + .as_ref() + .map(|repository| format!("repository = \"{repository}\"\n")) + .unwrap_or_default(); + format!( + "[package]\nname = \"{crate_name}\"\nversion = \"0.0.0\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Bolt-generated {} {role_name} role crate\"\n{repository}\n[lints]\nworkspace = true\n\n[dependencies]\n{dependencies}\n", + config.protocol_name + ) + } + } +} + +fn standalone_dependency_entry( + config: &ProtocolArtifactConfig, + dependency_root: &str, + package: &str, +) -> String { + config + .standalone_dependency_overrides + .iter() + .find(|dependency| dependency.package == package) + .map_or_else( + || format!("{package} = {{ path = \"{dependency_root}/{package}\" }}"), + |dependency| dependency.manifest_entry.clone(), + ) +} + +fn generated_lib( + config: &ProtocolArtifactConfig, + role: &Role, + artifacts: &[ProtocolRustArtifact], +) -> String { + let protocol_snake = config.protocol_snake(); + let prefix = &config.type_prefix; + let stage_apis = stage_apis(config, artifacts); + let commitment_api = commitment_api(artifacts); + let extension = + active_role_api_extension(config, &stage_apis, commitment_api.as_ref(), artifacts); + let role_module = match (role, extension) { + (Role::Prover, Some(extension)) => extension.prover.lib_module.clone(), + (Role::Prover, None) => format!( + "#[rustfmt::skip]\npub mod prover;\npub mod stages;\n\npub use prover::{{\n default_prover_programs, prove_{protocol_snake}, prove_{protocol_snake}_with_programs,\n Default{prefix}Transcript, {prefix}ProveError, {prefix}ProverArtifacts, {prefix}ProverInputs,\n {prefix}ProverPrograms,\n}};" + ), + (Role::Verifier, Some(extension)) => extension.verifier.lib_module.clone(), + (Role::Verifier, None) => format!( + "pub mod stages;\n#[rustfmt::skip]\npub mod verifier;\n\npub use verifier::{{\n default_verifier_programs, verify_{protocol_snake}, verify_{protocol_snake}_with_programs, {prefix}NamedEval, {prefix}Proof,\n {prefix}StageProof, {prefix}SumcheckOutput, {prefix}VerificationArtifacts, {prefix}VerifierInputs,\n {prefix}VerifierPrograms, {prefix}VerifyError,\n}};" + ), + }; + let stages = artifacts + .iter() + .map(|artifact| { + format!( + " GeneratedStage {{\n name: \"{}\",\n module: \"{}\",\n ordinal: {},\n }},", + artifact.stage.name(), + artifact.stage.module_name(), + artifact.stage.order() + ) + }) + .collect::>() + .join("\n"); + format!( + "{role_module}\n\npub const TRANSCRIPT_LABEL: &[u8] = {};\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct GeneratedStage {{\n pub name: &'static str,\n pub module: &'static str,\n pub ordinal: usize,\n}}\n\npub const GENERATED_STAGES: &[GeneratedStage] = &[\n{stages}\n];\n\npub fn generated_stage_names() -> impl Iterator {{\n GENERATED_STAGES.iter().map(|stage| stage.name)\n}}\n", + byte_string_literal(&config.transcript_label) + ) +} + +fn generated_role_api_file( + config: &ProtocolArtifactConfig, + role: &Role, + artifacts: &[ProtocolRustArtifact], +) -> GeneratedFile { + match role { + Role::Prover => GeneratedFile { + path: "src/prover.rs".to_owned(), + source: generated_prover_api(config, artifacts), + }, + Role::Verifier => GeneratedFile { + path: "src/verifier.rs".to_owned(), + source: generated_verifier_api(config, artifacts), + }, + } +} + +#[derive(Clone, Debug)] +struct StageRustApi { + field_name: String, + module_alias: String, + variant_name: String, + output_type: String, + eval_type: String, + artifacts_type: String, + error_type: String, + verifier_fn: Option, + with_program_verifier_fn: Option, + program_type: Option, + program_const: Option, + prover_fn: Option, + with_program_prover_fn: Option, + kernel_module: Option, + opening_input_type: Option, + ram_data_type: Option, + verifier_data_type: Option, +} + +#[derive(Clone, Debug)] +struct CommitmentRustApi { + field_name: String, + module_alias: String, + variant_name: String, + artifacts_type: String, + error_type: String, + verifier_fn: Option, + with_program_verifier_fn: Option, + program_type: Option, + program_const: Option, + prover_fn: Option, + with_program_prover_fn: Option, + input_provider_trait: Option, +} + +fn generated_verifier_api( + config: &ProtocolArtifactConfig, + artifacts: &[ProtocolRustArtifact], +) -> String { + let stages = stage_apis(config, artifacts); + let modules = role_modules(artifacts); + let commitment = commitment_api(artifacts); + let extension = active_role_api_extension(config, &stages, commitment.as_ref(), artifacts); + let prefix = &config.type_prefix; + let protocol_snake = config.protocol_snake(); + let field_type = config.field_type.ident(); + let transcript_trait = config.transcript_trait.ident(); + let commitment_type = config.commitment_type.ident(); + let runtime_named_eval_type = &config.verifier_named_eval_type.path; + let runtime_sumcheck_output_type = &config.verifier_sumcheck_output_type.path; + let runtime_stage_proof_type = &config.verifier_stage_proof_type.path; + let named_eval_type = format!("{prefix}NamedEval"); + let sumcheck_output_type = format!("{prefix}SumcheckOutput"); + let stage_proof_type = format!("{prefix}StageProof"); + let proof_type = format!("{prefix}Proof"); + let verifier_inputs_type = format!("{prefix}VerifierInputs"); + let verifier_programs_type = format!("{prefix}VerifierPrograms"); + let verification_artifacts_type = format!("{prefix}VerificationArtifacts"); + let verify_error_type = format!("{prefix}VerifyError"); + + let mut source = String::new(); + if let Some(extension) = extension { + source.push_str(&extension.verifier.imports); + } else { + if commitment.is_some() { + source.push_str(&config.commitment_type.use_line()); + } + source.push_str(&config.field_type.use_line()); + source.push_str(&config.transcript_trait.use_line()); + } + source.push('\n'); + if !modules.is_empty() { + source.push_str(&format!( + "use crate::stages::{{{}}};\n\n", + aliased_modules(&modules).join(", ") + )); + } + + source.push_str(&format!( + "pub type {named_eval_type} = {runtime_named_eval_type}<{field_type}>;\n\ + pub type {sumcheck_output_type} = {runtime_sumcheck_output_type}<{field_type}>;\n\ + pub type {stage_proof_type} = {runtime_stage_proof_type}<{field_type}>;\n\n", + )); + source.push_str(&format!( + "#[derive(Clone, Debug)]\npub struct {proof_type} {{\n" + )); + if commitment.is_some() { + source.push_str(&format!( + " pub commitments: Vec>,\n" + )); + } + for stage in &stages { + source.push_str(&format!( + " pub {}: {stage_proof_type},\n", + stage.field_name + )); + } + if let Some(extension) = extension { + source.push_str(&extension.verifier.proof_fields); + } + source.push_str("}\n\n"); + + if let Some(extension) = extension { + source.push_str(&extension.verifier.proof_items); + } + + let verifier_inputs_derive = extension + .and_then(|extension| extension.verifier.inputs_derive.as_deref()) + .unwrap_or("#[derive(Clone, Copy, Debug)]"); + source.push_str(&format!( + "{verifier_inputs_derive}\npub struct {verifier_inputs_type}<'a> {{\n" + )); + for stage in &stages { + if let Some(opening_type) = &stage.opening_input_type { + source.push_str(&format!( + " pub {}_openings: &'a [{}::{}<{field_type}>],\n", + stage.field_name, stage.module_alias, opening_type + )); + } + if let Some(ram_type) = &stage.ram_data_type { + source.push_str(&format!( + " pub {}_ram: Option<&'a {}::{}<'a>>,\n", + stage.field_name, stage.module_alias, ram_type + )); + } + if let Some(data_type) = &stage.verifier_data_type { + source.push_str(&format!( + " pub {}_data: Option<&'a {}::{}>,\n", + stage.field_name, stage.module_alias, data_type + )); + } + } + if let Some(extension) = extension { + source.push_str(&extension.verifier.input_fields); + } + source.push_str("}\n\n"); + + source.push_str(&format!( + "#[derive(Clone, Copy, Debug)]\npub struct {verifier_programs_type} {{\n" + )); + if let Some(commitment) = &commitment { + if let (Some(program_type), Some(_), Some(_)) = ( + &commitment.program_type, + &commitment.program_const, + &commitment.with_program_verifier_fn, + ) { + source.push_str(&format!( + " pub {}: &'static {}::{},\n", + commitment.field_name, commitment.module_alias, program_type + )); + } + } + for stage in &stages { + if let (Some(program_type), Some(_), Some(_)) = ( + &stage.program_type, + &stage.program_const, + &stage.with_program_verifier_fn, + ) { + source.push_str(&format!( + " pub {}: &'static {}::{},\n", + stage.field_name, stage.module_alias, program_type + )); + } + } + if let Some(extension) = extension { + source.push_str(&extension.verifier.program_fields); + } + source.push_str("}\n\n"); + + source.push_str(&format!( + "pub fn default_verifier_programs() -> {verifier_programs_type} {{\n {verifier_programs_type} {{\n" + )); + if let Some(commitment) = &commitment { + if let (Some(_), Some(program_const), Some(_)) = ( + &commitment.program_type, + &commitment.program_const, + &commitment.with_program_verifier_fn, + ) { + source.push_str(&format!( + " {}: &{}::{},\n", + commitment.field_name, commitment.module_alias, program_const + )); + } + } + for stage in &stages { + if let (Some(_), Some(program_const), Some(_)) = ( + &stage.program_type, + &stage.program_const, + &stage.with_program_verifier_fn, + ) { + source.push_str(&format!( + " {}: &{}::{},\n", + stage.field_name, stage.module_alias, program_const + )); + } + } + if let Some(extension) = extension { + source.push_str(&extension.verifier.default_program_fields); + } + source.push_str(" }\n}\n\n"); + + source.push_str(&format!( + "#[derive(Clone, Debug)]\npub struct {verification_artifacts_type} {{\n" + )); + if let Some(commitment) = &commitment { + source.push_str(&format!( + " pub {}: {}::{},\n", + commitment.field_name, commitment.module_alias, commitment.artifacts_type + )); + } + for stage in &stages { + source.push_str(&format!( + " pub {}: {}::{}<{field_type}>,\n", + stage.field_name, stage.module_alias, stage.artifacts_type + )); + } + source.push_str("}\n\n"); + + source.push_str(&format!( + "#[derive(Debug)]\npub enum {verify_error_type} {{\n" + )); + if let Some(commitment) = &commitment { + source.push_str(&format!( + " {}({}::{}),\n", + commitment.variant_name, commitment.module_alias, commitment.error_type + )); + } + for stage in &stages { + source.push_str(&format!( + " {}({}::{}),\n", + stage.variant_name, stage.module_alias, stage.error_type + )); + } + if let Some(extension) = extension { + source.push_str(&extension.verifier.error_variants); + } + source.push_str("}\n\n"); + + if let Some(extension) = extension { + source.push_str(&extension.verifier.error_items); + } + + source.push_str(&format!( + "macro_rules! define_{protocol_snake}_verify_error_from {{\n ($module:ident, $error_ty:ident, $variant:ident) => {{\n impl From<$module::$error_ty> for {verify_error_type} {{\n fn from(error: $module::$error_ty) -> Self {{\n Self::$variant(error)\n }}\n }}\n }};\n}}\n\n" + )); + if let Some(commitment) = &commitment { + source.push_str(&format!( + "define_{protocol_snake}_verify_error_from!({module}, {error}, {variant});\n", + module = commitment.module_alias, + error = commitment.error_type, + variant = commitment.variant_name, + )); + } + for stage in &stages { + source.push_str(&format!( + "define_{protocol_snake}_verify_error_from!({}, {}, {});\n", + stage.module_alias, stage.error_type, stage.variant_name + )); + } + if commitment.is_some() || !stages.is_empty() { + source.push('\n'); + } + if let Some(extension) = extension { + source.push_str(&extension.verifier.error_conversions); + } + + source.push_str(&format!( + "pub fn verify_{protocol_snake}>(proof: &{proof_type}, inputs: {verifier_inputs_type}<'_>, transcript: &mut T) -> Result<{verification_artifacts_type}, {verify_error_type}> {{\n", + )); + source.push_str(&format!( + " verify_{protocol_snake}_with_programs(proof, inputs, default_verifier_programs(), transcript)\n}}\n\n" + )); + if let Some(extension) = extension { + source.push_str(&extension.verifier.after_default_verify); + } + source.push_str(&format!( + "pub fn verify_{protocol_snake}_with_programs>(proof: &{proof_type}, inputs: {verifier_inputs_type}<'_>, programs: {verifier_programs_type}, transcript: &mut T) -> Result<{verification_artifacts_type}, {verify_error_type}> {{\n", + )); + if let Some(extension) = extension { + source.push_str(&extension.verifier.with_programs_body_intro); + } + if let Some(prefix) = &config.instrumentation_prefix { + source.push_str(&format!( + " let _verify_span = tracing::info_span!(\"{prefix}.verify\").entered();\n" + )); + } + if let Some(commitment) = &commitment { + let verifier_fn = commitment + .with_program_verifier_fn + .as_deref() + .or(commitment.verifier_fn.as_deref()) + .unwrap_or("missing_commitment_verifier_function"); + let program_arg = if commitment.with_program_verifier_fn.is_some() + && commitment.program_type.is_some() + && commitment.program_const.is_some() + { + format!("programs.{}, ", commitment.field_name) + } else { + String::new() + }; + source.push_str(&format!( + " let {field} = {module}::{verifier_fn}({program_arg}&proof.commitments, transcript)?;\n", + field = commitment.field_name, + module = commitment.module_alias, + )); + } + if let Some(extension) = extension { + if !extension.verifier.stage_verification_override.is_empty() { + source.push_str(&extension.verifier.stage_verification_override); + } else { + emit_verifier_stage_calls(&mut source, &stages); + } + } else { + emit_verifier_stage_calls(&mut source, &stages); + } + if let Some(extension) = extension { + source.push_str(&extension.verifier.after_stage_verification); + } + source.push_str(&format!("\n Ok({verification_artifacts_type} {{\n")); + if let Some(commitment) = &commitment { + source.push_str(&format!(" {},\n", commitment.field_name)); + } + for stage in &stages { + source.push_str(&format!(" {},\n", stage.field_name)); + } + source.push_str(" })\n}\n\n"); + + if let Some(extension) = extension { + source.push_str(&extension.verifier.helper_items); + } + + source +} + +fn emit_verifier_stage_calls(source: &mut String, stages: &[StageRustApi]) { + for stage in stages { + let verifier_fn = stage + .with_program_verifier_fn + .as_deref() + .or(stage.verifier_fn.as_deref()) + .unwrap_or("missing_verifier_function"); + let mut args = vec![format!("&proof.{}", stage.field_name)]; + if stage.with_program_verifier_fn.is_some() + && stage.program_type.is_some() + && stage.program_const.is_some() + { + args.insert(0, format!("programs.{}", stage.field_name)); + } + if stage.opening_input_type.is_some() { + args.push(format!("inputs.{}_openings", stage.field_name)); + } + if stage.ram_data_type.is_some() { + args.push(format!("inputs.{}_ram", stage.field_name)); + } + if stage.verifier_data_type.is_some() { + args.push(format!("inputs.{}_data", stage.field_name)); + } + args.push("transcript".to_owned()); + source.push_str(&format!( + " let {} = {}::{}({})?;\n", + stage.field_name, + stage.module_alias, + verifier_fn, + args.join(", ") + )); + } +} + +fn generated_prover_api( + config: &ProtocolArtifactConfig, + artifacts: &[ProtocolRustArtifact], +) -> String { + let stages = stage_apis(config, artifacts); + let modules = role_modules(artifacts); + let kernel_modules = unique_kernel_modules(&stages); + let commitment = commitment_api(artifacts); + let has_commitment = commitment.is_some(); + let extension = active_role_api_extension(config, &stages, commitment.as_ref(), artifacts); + let generic_params = prover_generic_params(&stages, has_commitment); + let prefix = &config.type_prefix; + let protocol_snake = config.protocol_snake(); + let field_type = config.field_type.ident(); + let default_transcript_type = config.default_transcript_type.ident(); + let transcript_trait = config.transcript_trait.ident(); + let prover_setup_type = config.prover_setup_type.ident(); + let verifier_import = config.verifier_crate_import(); + let named_eval_type = format!("{prefix}NamedEval"); + let sumcheck_output_type = format!("{prefix}SumcheckOutput"); + let stage_proof_type = format!("{prefix}StageProof"); + let proof_type = format!("{prefix}Proof"); + let prover_inputs_type = format!("{prefix}ProverInputs"); + let prover_programs_type = format!("{prefix}ProverPrograms"); + let prover_artifacts_type = format!("{prefix}ProverArtifacts"); + let prove_error_type = format!("{prefix}ProveError"); + let default_transcript_alias = format!("Default{prefix}Transcript"); + + let mut source = String::new(); + if let Some(extension) = extension { + source.push_str(&extension.prover.imports); + } else { + if has_commitment { + source.push_str(&config.prover_setup_type.use_line()); + } + source.push_str(&config.field_type.use_line()); + if !kernel_modules.is_empty() { + let kernel_crate = config + .kernel_crate + .as_ref() + .map_or("missing_kernel_crate", |kernel_crate| { + kernel_crate.import.as_str() + }); + source.push_str(&format!( + "use {kernel_crate}::{{{}}};\n", + kernel_modules.join(", ") + )); + } + source.push_str(&config.default_transcript_type.use_line()); + source.push_str(&config.transcript_trait.use_line()); + source.push_str(&format!( + "use {verifier_import}::{{{named_eval_type}, {proof_type}, {stage_proof_type}, {sumcheck_output_type}}};\n\n", + )); + } + if !modules.is_empty() { + source.push_str(&format!( + "use crate::stages::{{{}}};\n\n", + aliased_modules(&modules).join(", ") + )); + } + source.push_str(&format!( + "pub type {default_transcript_alias} = {default_transcript_type}<{field_type}>;\n\n" + )); + + source.push_str(&format!( + "pub struct {prover_inputs_type}<'a, {}> {{\n", + generic_params.join(", ") + )); + if has_commitment { + source.push_str(" pub commitment_inputs: &'a mut CommitmentInputs,\n"); + source.push_str(&format!(" pub prover_setup: &'a {prover_setup_type},\n")); + } + for stage in &stages { + source.push_str(&format!( + " pub {}_executor: &'a mut {}Executor,\n", + stage.field_name, stage.variant_name + )); + } + if let Some(extension) = extension { + source.push_str(&extension.prover.input_fields); + } + source.push_str("}\n\n"); + + source.push_str(&format!( + "#[derive(Clone, Copy, Debug)]\npub struct {prover_programs_type} {{\n" + )); + if let Some(commitment) = &commitment { + if let (Some(program_type), Some(_), Some(_)) = ( + &commitment.program_type, + &commitment.program_const, + &commitment.with_program_prover_fn, + ) { + source.push_str(&format!( + " pub {}: &'static {}::{},\n", + commitment.field_name, commitment.module_alias, program_type + )); + } + } + for stage in &stages { + if let (Some(program_type), Some(_), Some(_)) = ( + &stage.program_type, + &stage.program_const, + &stage.with_program_prover_fn, + ) { + let program_module = stage + .kernel_module + .as_deref() + .unwrap_or(stage.module_alias.as_str()); + source.push_str(&format!( + " pub {}: &'static {}::{},\n", + stage.field_name, program_module, program_type + )); + } + } + if let Some(extension) = extension { + source.push_str(&extension.prover.program_fields); + } + source.push_str("}\n\n"); + + source.push_str(&format!( + "pub fn default_prover_programs() -> {prover_programs_type} {{\n {prover_programs_type} {{\n" + )); + if let Some(commitment) = &commitment { + if let (Some(_), Some(program_const), Some(_)) = ( + &commitment.program_type, + &commitment.program_const, + &commitment.with_program_prover_fn, + ) { + source.push_str(&format!( + " {}: &{}::{},\n", + commitment.field_name, commitment.module_alias, program_const + )); + } + } + for stage in &stages { + if let (Some(_), Some(program_const), Some(_)) = ( + &stage.program_type, + &stage.program_const, + &stage.with_program_prover_fn, + ) { + source.push_str(&format!( + " {}: &{}::{},\n", + stage.field_name, stage.module_alias, program_const + )); + } + } + if let Some(extension) = extension { + source.push_str(&extension.prover.default_program_fields); + } + source.push_str(" }\n}\n\n"); + + source.push_str(&format!( + "#[derive(Clone, Debug)]\npub struct {prover_artifacts_type} {{\n" + )); + if let Some(commitment) = &commitment { + source.push_str(&format!( + " pub {}: {}::{},\n", + commitment.field_name, commitment.module_alias, commitment.artifacts_type + )); + } + for stage in &stages { + let kernel_module = stage + .kernel_module + .as_deref() + .unwrap_or(stage.module_alias.as_str()); + source.push_str(&format!( + " pub {}: {}::{}<{field_type}>,\n", + stage.field_name, kernel_module, stage.artifacts_type + )); + } + source.push_str("}\n\n"); + + source.push_str(&format!( + "#[derive(Debug)]\npub enum {prove_error_type} {{\n" + )); + if let Some(commitment) = &commitment { + source.push_str(&format!( + " {}({}::{}),\n", + commitment.variant_name, commitment.module_alias, commitment.error_type + )); + } + for stage in &stages { + let kernel_module = stage + .kernel_module + .as_deref() + .unwrap_or(stage.module_alias.as_str()); + source.push_str(&format!( + " {}({}::{}),\n", + stage.variant_name, kernel_module, stage.error_type + )); + } + if let Some(extension) = extension { + source.push_str(&extension.prover.error_variants); + } + source.push_str("}\n\n"); + + if let Some(extension) = extension { + source.push_str(&extension.prover.error_items); + } + + if let Some(commitment) = &commitment { + source.push_str(&format!( + "impl From<{module}::{error}> for {prove_error_type} {{\n fn from(error: {module}::{error}) -> Self {{\n Self::{variant}(error)\n }}\n}}\n\n", + module = commitment.module_alias, + error = commitment.error_type, + variant = commitment.variant_name, + )); + } + for stage in &stages { + let kernel_module = stage + .kernel_module + .as_deref() + .unwrap_or(stage.module_alias.as_str()); + source.push_str(&format!( + "impl From<{}::{}> for {prove_error_type} {{\n fn from(error: {}::{}) -> Self {{\n Self::{}(error)\n }}\n}}\n\n", + kernel_module, stage.error_type, kernel_module, stage.error_type, stage.variant_name + )); + } + if let Some(extension) = extension { + source.push_str(&extension.prover.error_conversions); + } + + source.push_str(&format!( + "pub fn prove_{protocol_snake}<{}, T>(\n inputs: {prover_inputs_type}<'_, {}>,\n transcript: &mut T,\n) -> Result<({proof_type}, {prover_artifacts_type}), {prove_error_type}>\nwhere\n", + generic_params.join(", "), + generic_params.join(", ") + )); + if let Some(commitment) = &commitment { + let input_provider = commitment + .input_provider_trait + .as_deref() + .unwrap_or("MissingCommitmentInputProvider"); + source.push_str(&format!( + " CommitmentInputs: {}::{input_provider},\n", + commitment.module_alias + )); + } + for stage in &stages { + let kernel_module = stage + .kernel_module + .as_deref() + .unwrap_or(stage.module_alias.as_str()); + let kernel_trait = kernel_executor_type(&stage.error_type); + source.push_str(&format!( + " {}Executor: {}::{}<{field_type}>,\n", + stage.variant_name, kernel_module, kernel_trait + )); + } + source.push_str(&format!( + " T: {transcript_trait},\n" + )); + source.push_str("{\n"); + source.push_str(&format!( + " prove_{protocol_snake}_with_programs(inputs, default_prover_programs(), transcript)\n}}\n\n" + )); + + source.push_str(&format!( + "pub fn prove_{protocol_snake}_with_programs<{}, T>(\n inputs: {prover_inputs_type}<'_, {}>,\n programs: {prover_programs_type},\n transcript: &mut T,\n) -> Result<({proof_type}, {prover_artifacts_type}), {prove_error_type}>\nwhere\n", + generic_params.join(", "), + generic_params.join(", ") + )); + if let Some(commitment) = &commitment { + let input_provider = commitment + .input_provider_trait + .as_deref() + .unwrap_or("MissingCommitmentInputProvider"); + source.push_str(&format!( + " CommitmentInputs: {}::{input_provider},\n", + commitment.module_alias + )); + } + for stage in &stages { + let kernel_module = stage + .kernel_module + .as_deref() + .unwrap_or(stage.module_alias.as_str()); + let kernel_trait = kernel_executor_type(&stage.error_type); + source.push_str(&format!( + " {}Executor: {}::{}<{field_type}>,\n", + stage.variant_name, kernel_module, kernel_trait + )); + } + source.push_str(&format!( + " T: {transcript_trait},\n" + )); + source.push_str("{\n"); + if let Some(prefix) = &config.instrumentation_prefix { + source.push_str(&format!( + " let _prove_span = tracing::info_span!(\"{prefix}.prove\").entered();\n" + )); + } + if let Some(commitment) = &commitment { + let prover_fn = commitment + .with_program_prover_fn + .as_deref() + .or(commitment.prover_fn.as_deref()) + .unwrap_or("missing_commitment_prover_function"); + let program_arg = if commitment.with_program_prover_fn.is_some() + && commitment.program_type.is_some() + && commitment.program_const.is_some() + { + format!("programs.{}, ", commitment.field_name) + } else { + String::new() + }; + if let Some(prefix) = &config.instrumentation_prefix { + source.push_str(&format!( + " let _{field}_span = tracing::info_span!(\"{prefix}.{field}\").entered();\n", + field = commitment.field_name + )); + } + source.push_str(&format!( + " let {field} = {module}::{prover_fn}(\n {program_arg}inputs.commitment_inputs,\n inputs.prover_setup,\n transcript,\n )?;\n", + field = commitment.field_name, + module = commitment.module_alias + )); + if config.instrumentation_prefix.is_some() { + source.push_str(&format!(" drop(_{}_span);\n", commitment.field_name)); + } + } + for stage in &stages { + let prover_fn = stage + .with_program_prover_fn + .as_deref() + .or(stage.prover_fn.as_deref()) + .unwrap_or("missing_prover_function"); + let program_arg = if stage.with_program_prover_fn.is_some() + && stage.program_type.is_some() + && stage.program_const.is_some() + { + format!("programs.{}, ", stage.field_name) + } else { + String::new() + }; + if let Some(prefix) = &config.instrumentation_prefix { + source.push_str(&format!( + " let _{field}_span = tracing::info_span!(\"{prefix}.{span}\").entered();\n", + field = stage.field_name, + span = generated_stage_span_name(&stage.field_name) + )); + } + source.push_str(&format!( + " let {} = {}::{}({program_arg}inputs.{}_executor, transcript)?;\n", + stage.field_name, stage.module_alias, prover_fn, stage.field_name + )); + if config.instrumentation_prefix.is_some() { + source.push_str(&format!(" drop(_{}_span);\n", stage.field_name)); + } + } + if let Some(extension) = extension { + source.push_str(&extension.prover.after_stage_execution); + } + source.push_str(&format!("\n let proof = {proof_type} {{\n")); + if let Some(commitment) = &commitment { + source.push_str(&format!( + " commitments: {}.commitments.clone(),\n", + commitment.field_name + )); + } + for stage in &stages { + source.push_str(&format!( + " {}: {}_proof(&{}),\n", + stage.field_name, stage.field_name, stage.field_name + )); + } + if let Some(extension) = extension { + source.push_str(&extension.prover.proof_fields); + } + source.push_str(&format!( + " }};\n let artifacts = {prover_artifacts_type} {{\n" + )); + if let Some(commitment) = &commitment { + source.push_str(&format!(" {},\n", commitment.field_name)); + } + for stage in &stages { + source.push_str(&format!(" {},\n", stage.field_name)); + } + source.push_str(" };\n Ok((proof, artifacts))\n}\n\n"); + + if let Some(extension) = extension { + source.push_str(&extension.prover.helper_items); + } + + for stage in &stages { + let kernel_module = stage + .kernel_module + .as_deref() + .unwrap_or(stage.module_alias.as_str()); + source.push_str(&format!( + "pub fn {field}_proof(artifacts: &{kernel}::{artifacts_ty}<{field_type}>) -> {stage_proof_type} {{\n {stage_proof_type} {{\n sumchecks: artifacts.sumchecks.iter().map({field}_sumcheck).collect(),\n }}\n}}\n\n", + field = stage.field_name, + kernel = kernel_module, + artifacts_ty = stage.artifacts_type + )); + source.push_str(&format!( + "fn {field}_sumcheck(output: &{kernel}::{output_ty}<{field_type}>) -> {sumcheck_output_type} {{\n {sumcheck_output_type} {{\n driver: output.driver,\n point: output.point.clone(),\n evals: output.evals.iter().map({field}_eval).collect(),\n proof: output.proof.clone(),\n }}\n}}\n\n", + field = stage.field_name, + kernel = kernel_module, + output_ty = stage.output_type + )); + source.push_str(&format!( + "fn {field}_eval(eval: &{kernel}::{eval_ty}<{field_type}>) -> {named_eval_type} {{\n {named_eval_type} {{\n name: eval.name,\n oracle: eval.oracle,\n value: eval.value,\n }}\n}}\n\n", + field = stage.field_name, + kernel = kernel_module, + eval_ty = stage.eval_type + )); + } + source +} + +fn stage_apis( + config: &ProtocolArtifactConfig, + artifacts: &[ProtocolRustArtifact], +) -> Vec { + artifacts + .iter() + .filter(|artifact| artifact.stage.is_proof()) + .map(|artifact| stage_api(config, artifact)) + .collect() +} + +fn stage_api(config: &ProtocolArtifactConfig, artifact: &ProtocolRustArtifact) -> StageRustApi { + let source = artifact.source.source.as_str(); + let artifacts_type = find_type_with_suffix(source, "ExecutionArtifacts").unwrap_or_else(|| { + format!( + "{}ExecutionArtifacts", + upper_camel(artifact.stage.module_name()) + ) + }); + let prefix = artifacts_type + .strip_suffix("ExecutionArtifacts") + .unwrap_or(&artifacts_type); + let opening_input_name = format!("{prefix}OpeningInputValue"); + let ram_data_name = format!("{prefix}RamData"); + let verifier_data_name = format!("{prefix}VerifierData"); + let program_type_suffix = match artifact.role { + Role::Prover => "CpuProgramPlan", + Role::Verifier => "VerifierProgramPlan", + }; + let program_type = find_type_with_suffix(source, program_type_suffix); + let program_const = program_type + .as_deref() + .and_then(|program_type| find_public_const_of_type(source, program_type)); + let error_type = match artifact.role { + Role::Prover => find_type_with_suffix(source, "KernelError") + .unwrap_or_else(|| format!("{prefix}KernelError")), + Role::Verifier => find_public_item(source, "pub enum ", "Error") + .unwrap_or_else(|| format!("Verify{prefix}Error")), + }; + StageRustApi { + field_name: artifact.stage.module_name().to_owned(), + module_alias: module_alias(artifact.stage.module_name()), + variant_name: upper_camel(artifact.stage.module_name()), + output_type: format!("{prefix}SumcheckOutput"), + eval_type: format!("{prefix}NamedEval"), + artifacts_type, + error_type, + verifier_fn: find_public_fn(source, &["verify_"]), + with_program_verifier_fn: find_public_fn_containing(source, &["verify_"], "_with_program"), + program_type, + program_const, + prover_fn: find_public_fn(source, &["prove_", "execute_"]), + with_program_prover_fn: find_public_fn_containing( + source, + &["prove_", "execute_"], + "_with_program", + ), + kernel_module: find_kernel_module(config, source), + opening_input_type: has_public_type_name(source, &opening_input_name) + .then_some(opening_input_name), + ram_data_type: has_public_type_name(source, &ram_data_name).then_some(ram_data_name), + verifier_data_type: has_public_type_name(source, &verifier_data_name) + .then_some(verifier_data_name), + } +} + +fn generated_stage_span_name(field_name: &str) -> &str { + field_name.strip_suffix("_outer").unwrap_or(field_name) +} + +fn commitment_api(artifacts: &[ProtocolRustArtifact]) -> Option { + let artifact = artifacts + .iter() + .find(|artifact| artifact.stage.is_commitment())?; + let source = artifact.source.source.as_str(); + let artifacts_type = find_type_with_suffix(source, "Artifacts") + .unwrap_or_else(|| format!("{}Artifacts", upper_camel(artifact.stage.module_name()))); + let error_type = find_public_item(source, "pub enum ", "Error") + .unwrap_or_else(|| format!("{}Error", upper_camel(artifact.stage.module_name()))); + let input_provider_trait = find_public_item(source, "pub trait ", "InputProvider"); + let program_type_suffix = match artifact.role { + Role::Prover => "ProverProgramPlan", + Role::Verifier => "VerifierProgramPlan", + }; + let program_type = find_type_with_suffix(source, program_type_suffix); + let program_const = program_type + .as_deref() + .and_then(|program_type| find_public_const_of_type(source, program_type)); + Some(CommitmentRustApi { + field_name: artifact.stage.module_name().to_owned(), + module_alias: module_alias(artifact.stage.module_name()), + variant_name: upper_camel(artifact.stage.module_name()), + artifacts_type, + error_type, + verifier_fn: find_public_fn(source, &["verify_"]), + with_program_verifier_fn: find_public_fn_containing(source, &["verify_"], "_with_program"), + program_type, + program_const, + prover_fn: find_public_fn(source, &["prove_"]), + with_program_prover_fn: find_public_fn_containing(source, &["prove_"], "_with_program"), + input_provider_trait, + }) +} + +fn active_role_api_extension<'a>( + config: &'a ProtocolArtifactConfig, + stages: &[StageRustApi], + commitment: Option<&CommitmentRustApi>, + artifacts: &[ProtocolRustArtifact], +) -> Option<&'a ProtocolArtifactExtension> { + let extension = config.role_api_extension.as_ref()?; + if extension.required_commitment && commitment.is_none() { + return None; + } + if !extension + .required_proof_stages + .iter() + .all(|required| stages.iter().any(|stage| &stage.field_name == required)) + { + return None; + } + if !extension.required_artifact_stages.iter().all(|required| { + artifacts + .iter() + .any(|artifact| artifact.stage.module_name() == required) + }) { + return None; + } + Some(extension) +} + +fn role_modules(artifacts: &[ProtocolRustArtifact]) -> Vec { + artifacts + .iter() + .map(|artifact| artifact.stage.module_name().to_owned()) + .collect() +} + +fn aliased_modules(modules: &[String]) -> Vec { + modules + .iter() + .map(|module| format!("{module} as {}", module_alias(module))) + .collect() +} + +fn module_alias(module: &str) -> String { + format!("{module}_stage") +} + +fn unique_kernel_modules(stages: &[StageRustApi]) -> Vec { + let mut modules = Vec::new(); + for stage in stages { + if let Some(kernel_module) = &stage.kernel_module { + if !modules.contains(kernel_module) { + modules.push(kernel_module.clone()); + } + } + } + modules +} + +fn prover_generic_params(stages: &[StageRustApi], has_commitment: bool) -> Vec { + let mut params = if has_commitment { + vec!["CommitmentInputs".to_owned()] + } else { + Vec::new() + }; + params.extend( + stages + .iter() + .map(|stage| format!("{}Executor", stage.variant_name)), + ); + params +} + +fn find_public_item(source: &str, prefix: &str, suffix: &str) -> Option { + source.lines().find_map(|line| { + let trimmed = line.trim_start(); + let rest = trimmed.strip_prefix(prefix)?; + let name = rest + .split(|character: char| { + matches!(character, '<' | '(' | '{') || character.is_whitespace() + }) + .next()?; + name.ends_with(suffix).then(|| name.to_owned()) + }) +} + +fn find_type_with_suffix(source: &str, suffix: &str) -> Option { + source + .split(|character: char| !character.is_ascii_alphanumeric() && character != '_') + .find(|token| token.ends_with(suffix) && token.len() > suffix.len()) + .map(ToOwned::to_owned) +} + +fn find_public_fn(source: &str, prefixes: &[&str]) -> Option { + source.lines().find_map(|line| { + let trimmed = line.trim_start(); + let rest = trimmed.strip_prefix("pub fn ")?; + let name = rest + .split(|character: char| matches!(character, '<' | '(') || character.is_whitespace()) + .next()?; + prefixes + .iter() + .any(|prefix| name.starts_with(prefix)) + .then(|| name.to_owned()) + }) +} + +fn find_public_fn_containing(source: &str, prefixes: &[&str], needle: &str) -> Option { + source.lines().find_map(|line| { + let trimmed = line.trim_start(); + let rest = trimmed.strip_prefix("pub fn ")?; + let name = rest + .split(|character: char| matches!(character, '<' | '(') || character.is_whitespace()) + .next()?; + (name.contains(needle) && prefixes.iter().any(|prefix| name.starts_with(prefix))) + .then(|| name.to_owned()) + }) +} + +fn find_public_const_of_type(source: &str, type_name: &str) -> Option { + source.lines().find_map(|line| { + let trimmed = line.trim_start(); + let rest = trimmed.strip_prefix("pub const ")?; + let name = rest + .split(|character: char| character == ':' || character.is_whitespace()) + .next()?; + rest.contains(&format!(": {type_name}")) + .then(|| name.to_owned()) + }) +} + +fn has_public_type_name(source: &str, type_name: &str) -> bool { + source.contains(&format!("pub struct {type_name}")) + || source.contains(&format!("pub type {type_name}")) + || source.contains(&format!(" as {type_name}")) +} + +fn kernel_executor_type(error_type: &str) -> String { + error_type.strip_suffix("KernelError").map_or_else( + || { + error_type + .replace("Verify", "") + .replace("Error", "KernelExecutor") + }, + |prefix| format!("{prefix}KernelExecutor"), + ) +} + +fn find_kernel_module(config: &ProtocolArtifactConfig, source: &str) -> Option { + let kernel_import = config.kernel_crate.as_ref()?.import.as_str(); + let prefix = format!("use {kernel_import}::"); + source.lines().find_map(|line| { + let rest = line.trim_start().strip_prefix(&prefix)?; + rest.split(|character: char| matches!(character, ':' | '{') || character.is_whitespace()) + .next() + .filter(|name| !name.is_empty()) + .map(ToOwned::to_owned) + }) +} + +fn upper_camel(name: &str) -> String { + let mut output = String::new(); + for segment in name.split('_') { + let mut chars = segment.chars(); + if let Some(first) = chars.next() { + output.extend(first.to_uppercase()); + output.push_str(chars.as_str()); + } + } + output +} + +fn snake_case(name: &str) -> String { + let mut output = String::new(); + for (index, character) in name.chars().enumerate() { + if character.is_ascii_uppercase() { + if index != 0 { + output.push('_'); + } + output.extend(character.to_lowercase()); + } else if character == '-' || character == ' ' { + output.push('_'); + } else { + output.push(character); + } + } + output +} + +fn rust_crate_ident(package_name: &str) -> String { + package_name.replace('-', "_") +} + +fn byte_string_literal(label: &str) -> String { + let escaped = label.escape_default().to_string(); + format!("b\"{escaped}\"") +} + +fn generated_file_path(root: &Path, relative_path: &str) -> Result { + let path = Path::new(relative_path); + if path.is_absolute() + || path.components().any(|component| { + matches!( + component, + Component::ParentDir | Component::RootDir | Component::Prefix(_) + ) + }) + { + return Err(EmitError::new(format!( + "generated crate file path `{relative_path}` must be relative and stay inside the crate" + ))); + } + Ok(root.join(path)) +} diff --git a/crates/bolt/src/emit/rust/mod.rs b/crates/bolt/src/emit/rust/mod.rs new file mode 100644 index 0000000000..aeb2a0bcf2 --- /dev/null +++ b/crates/bolt/src/emit/rust/mod.rs @@ -0,0 +1,19 @@ +mod artifacts; +mod source; + +pub(crate) fn push_format(source: &mut String, args: std::fmt::Arguments<'_>) { + use std::fmt::Write as _; + + if source.write_fmt(args).is_err() { + std::process::abort(); + } +} + +pub use artifacts::{ + assemble_generated_crates, assemble_workspace_generated_crates, protocol_rust_artifact, + validate_rust_artifact_imports, write_generated_crates, ArtifactCrateRole, GeneratedCrate, + GeneratedFile, ProtocolArtifactConfig, ProtocolArtifactExtension, ProtocolCrateRef, + ProtocolProverApiExtension, ProtocolRuntimeModule, ProtocolRustArtifact, ProtocolStage, + ProtocolStageKind, ProtocolStandaloneDependency, ProtocolVerifierApiExtension, RustTypeRef, +}; +pub use source::{EmitError, RustSourceFile}; diff --git a/crates/bolt/src/emit/rust/source.rs b/crates/bolt/src/emit/rust/source.rs new file mode 100644 index 0000000000..8adfb86d44 --- /dev/null +++ b/crates/bolt/src/emit/rust/source.rs @@ -0,0 +1,37 @@ +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use crate::schema::SchemaError; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RustSourceFile { + pub filename: String, + pub source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EmitError { + message: String, +} + +impl EmitError { + pub(crate) fn new(message: impl Into) -> Self { + Self { + message: message.into(), + } + } +} + +impl Display for EmitError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str(&self.message) + } +} + +impl Error for EmitError {} + +impl From for EmitError { + fn from(error: SchemaError) -> Self { + Self::new(error.to_string()) + } +} diff --git a/crates/bolt/src/ir.rs b/crates/bolt/src/ir.rs new file mode 100644 index 0000000000..f4721730ea --- /dev/null +++ b/crates/bolt/src/ir.rs @@ -0,0 +1,160 @@ +use std::fmt::{self, Display, Formatter}; +use std::marker::PhantomData; + +use melior::ir::operation::OperationLike; +use melior::ir::{Attribute, Module}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Protocol; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Concrete; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Party; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Compute; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Cpu; + +pub trait Phase { + const NAME: &'static str; +} + +impl Phase for Protocol { + const NAME: &'static str = "protocol"; +} + +impl Phase for Concrete { + const NAME: &'static str = "concrete"; +} + +impl Phase for Party { + const NAME: &'static str = "party"; +} + +impl Phase for Compute { + const NAME: &'static str = "compute"; +} + +impl Phase for Cpu { + const NAME: &'static str = "cpu"; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Role { + Prover, + Verifier, +} + +impl Role { + pub fn as_str(&self) -> &'static str { + match self { + Self::Prover => "prover", + Self::Verifier => "verifier", + } + } + + fn parse(value: &str) -> Option { + match value { + "prover" => Some(Self::Prover), + "verifier" => Some(Self::Verifier), + _ => None, + } + } +} + +impl Display for Role { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +#[derive(Debug)] +pub struct BoltModule<'c, P: Phase> { + module: Module<'c>, + phase: PhantomData

, +} + +impl<'c, P: Phase> BoltModule<'c, P> { + pub(crate) fn from_mlir(module: Module<'c>) -> Self { + Self { + module, + phase: PhantomData, + } + } + + pub fn as_mlir_module(&self) -> &Module<'c> { + &self.module + } + + pub fn as_mlir_module_mut(&mut self) -> &mut Module<'c> { + &mut self.module + } + + pub fn into_mlir_module(self) -> Module<'c> { + self.module + } + + pub fn name(&self) -> String { + self.string_attr("sym_name") + .unwrap_or_else(|| "anonymous".to_owned()) + } + + pub fn role(&self) -> Option { + self.string_attr("bolt.role") + .and_then(|value| Role::parse(&value)) + } + + pub fn verify(&self) -> bool { + self.module.as_operation().verify() + } + + fn string_attr(&self, name: &str) -> Option { + self.module + .as_operation() + .attribute(name) + .ok() + .and_then(string_attribute_value) + } +} + +pub trait TextMlir { + fn to_text_mlir(&self) -> String; +} + +impl TextMlir for BoltModule<'_, P> { + fn to_text_mlir(&self) -> String { + self.module.as_operation().to_string() + } +} + +pub(crate) fn string_attribute_value(attribute: Attribute<'_>) -> Option { + let value = attribute.to_string(); + value + .strip_prefix('"') + .and_then(|value| value.strip_suffix('"')) + .map(ToOwned::to_owned) +} + +pub(crate) fn symbol_attribute_value(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .strip_prefix('@') + .map(ToOwned::to_owned) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Diagnostic { + pub message: String, +} + +impl Diagnostic { + pub fn new(message: impl Into) -> Self { + Self { + message: message.into(), + } + } +} diff --git a/crates/bolt/src/lib.rs b/crates/bolt/src/lib.rs new file mode 100644 index 0000000000..c802d446ca --- /dev/null +++ b/crates/bolt/src/lib.rs @@ -0,0 +1,28 @@ +pub mod dialects; +pub mod emit; +pub mod ir; +pub mod mlir; +pub mod pass; +pub mod protocols; +pub mod schema; + +pub use emit::rust::{ + assemble_generated_crates, assemble_workspace_generated_crates, protocol_rust_artifact, + validate_rust_artifact_imports, write_generated_crates, ArtifactCrateRole, EmitError, + GeneratedCrate, GeneratedFile, ProtocolArtifactConfig, ProtocolArtifactExtension, + ProtocolCrateRef, ProtocolProverApiExtension, ProtocolRuntimeModule, ProtocolRustArtifact, + ProtocolStage, ProtocolStageKind, ProtocolStandaloneDependency, ProtocolVerifierApiExtension, + RustSourceFile, RustTypeRef, +}; +pub use ir::{ + BoltModule, Compute, Concrete, Cpu, Diagnostic, Party, Phase, Protocol, Role, TextMlir, +}; +pub use mlir::{MeliorContext, MlirError}; +pub use pass::{ + derive_prover_role, derive_verifier_role, lower_piop_and_fiat_shamir, project_party, + project_prover_party, project_verifier_party, verify_concrete_transcript, VerifyError, +}; +pub use schema::{ + verify_compute_schema, verify_concrete_schema, verify_cpu_schema, verify_party_schema, + verify_protocol_schema, SchemaError, +}; diff --git a/crates/bolt/src/mlir.rs b/crates/bolt/src/mlir.rs new file mode 100644 index 0000000000..263bbcbad6 --- /dev/null +++ b/crates/bolt/src/mlir.rs @@ -0,0 +1,292 @@ +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use melior::dialect::DialectRegistry; +use melior::ir::attribute::StringAttribute; +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationBuilder, OperationMutLike}; +use melior::ir::{Attribute, Identifier, Location, Module, OperationRef, Type, Value}; +use melior::utility::register_all_dialects; +use melior::Context; + +use crate::dialects::load_bolt_dialects; +use crate::ir::{BoltModule, Phase, Role, TextMlir}; + +#[derive(Debug)] +pub struct MeliorContext { + context: Context, +} + +impl MeliorContext { + pub fn new() -> Self { + Self::try_new().unwrap_or_else(Self::abort_init_error) + } + + pub fn try_new() -> Result { + let registry = DialectRegistry::new(); + register_all_dialects(®istry); + let context = Context::new_with_registry(®istry, false); + context.load_all_available_dialects(); + load_bolt_dialects(&context) + .map_err(|message| MlirError::DialectRegistration { message })?; + context.set_allow_unregistered_dialects(false); + Ok(Self { context }) + } + + fn abort_init_error(error: MlirError) -> Self { + drop(error); + std::process::abort(); + } + + pub fn context(&self) -> &Context { + &self.context + } + + pub fn new_module<'c, P: Phase>(&'c self, name: &str, role: Option) -> BoltModule<'c, P> { + let mut module = Module::new(Location::unknown(&self.context)); + module + .as_operation_mut() + .set_attribute("sym_name", StringAttribute::new(&self.context, name).into()); + module.as_operation_mut().set_attribute( + "bolt.phase", + StringAttribute::new(&self.context, P::NAME).into(), + ); + if let Some(role) = role { + module.as_operation_mut().set_attribute( + "bolt.role", + StringAttribute::new(&self.context, role.as_str()).into(), + ); + } + BoltModule::from_mlir(module) + } + + pub fn parse_module<'c, P: Phase>( + &'c self, + source: &str, + ) -> Result, MlirError> { + Module::parse(&self.context, source) + .map(BoltModule::from_mlir) + .ok_or_else(|| MlirError::ParseFailed { + source: source.to_owned(), + }) + } + + pub fn append_op<'c, P: Phase>( + &'c self, + module: &BoltModule<'c, P>, + name: &str, + symbol: Option<&str>, + attrs: &[(&str, &str)], + ) -> Result<(), MlirError> { + self.append_op_from_iter(module, name, symbol, attrs.iter().copied()) + } + + pub fn append_op_with_owned_attrs<'c, P: Phase>( + &'c self, + module: &BoltModule<'c, P>, + name: &str, + symbol: Option<&str>, + attrs: &[(String, String)], + ) -> Result<(), MlirError> { + self.append_op_from_iter( + module, + name, + symbol, + attrs + .iter() + .map(|(name, value)| (name.as_str(), value.as_str())), + ) + } + + fn append_op_from_iter<'c, P: Phase, I, K, V>( + &'c self, + module: &BoltModule<'c, P>, + name: &str, + symbol: Option<&str>, + attrs: I, + ) -> Result<(), MlirError> + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + let mut attributes = Vec::new(); + if let Some(symbol) = symbol { + attributes.push(( + Identifier::new(&self.context, "sym_name"), + StringAttribute::new(&self.context, symbol).into(), + )); + } + for (name, source) in attrs { + let name = name.as_ref(); + let source = source.as_ref(); + attributes.push(( + Identifier::new(&self.context, name), + self.parse_attr(name, source)?, + )); + } + + let operation = OperationBuilder::new(name, Location::unknown(&self.context)) + .add_attributes(&attributes) + .build() + .map_err(|source| MlirError::OperationBuild { + op: name.to_owned(), + source, + })?; + let _operation = module.as_mlir_module().body().append_operation(operation); + Ok(()) + } + + pub(crate) fn append_typed_op<'c, 'a, P: Phase>( + &'c self, + module: &'a BoltModule<'c, P>, + name: &str, + symbol: Option<&str>, + attrs: &[(&str, &str)], + operands: &[Value<'c, 'a>], + result_types: &[&str], + ) -> Result, MlirError> { + self.append_typed_op_from_iter( + module, + name, + symbol, + attrs.iter().copied(), + operands, + result_types, + ) + } + + pub(crate) fn append_typed_op_with_owned_attrs<'c, 'a, P: Phase>( + &'c self, + module: &'a BoltModule<'c, P>, + name: &str, + symbol: Option<&str>, + attrs: &[(String, String)], + operands: &[Value<'c, 'a>], + result_types: &[&str], + ) -> Result, MlirError> { + self.append_typed_op_from_iter( + module, + name, + symbol, + attrs + .iter() + .map(|(name, value)| (name.as_str(), value.as_str())), + operands, + result_types, + ) + } + + fn append_typed_op_from_iter<'c, 'a, P: Phase, I, K, V>( + &'c self, + module: &'a BoltModule<'c, P>, + name: &str, + symbol: Option<&str>, + attrs: I, + operands: &[Value<'c, 'a>], + result_types: &[&str], + ) -> Result, MlirError> + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + let mut attributes = Vec::new(); + if let Some(symbol) = symbol { + attributes.push(( + Identifier::new(&self.context, "sym_name"), + StringAttribute::new(&self.context, symbol).into(), + )); + } + for (name, source) in attrs { + let name = name.as_ref(); + let source = source.as_ref(); + attributes.push(( + Identifier::new(&self.context, name), + self.parse_attr(name, source)?, + )); + } + let result_types = result_types + .iter() + .map(|source| { + Type::parse(&self.context, source).ok_or_else(|| MlirError::TypeParse { + source: (*source).to_owned(), + }) + }) + .collect::, _>>()?; + + let operation = OperationBuilder::new(name, Location::unknown(&self.context)) + .add_operands(operands) + .add_results(&result_types) + .add_attributes(&attributes) + .build() + .map_err(|source| MlirError::OperationBuild { + op: name.to_owned(), + source, + })?; + Ok(module.as_mlir_module().body().append_operation(operation)) + } + + fn parse_attr<'c>(&'c self, name: &str, source: &str) -> Result, MlirError> { + Attribute::parse(&self.context, source).ok_or_else(|| MlirError::AttributeParse { + name: name.to_owned(), + source: source.to_owned(), + }) + } +} + +impl Default for MeliorContext { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug)] +pub enum MlirError { + AttributeParse { name: String, source: String }, + TypeParse { source: String }, + OperationBuild { op: String, source: melior::Error }, + ParseFailed { source: String }, + Schema { message: String }, + DialectRegistration { message: String }, + VerificationFailed { source: String }, +} + +impl Display for MlirError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::AttributeParse { name, source } => { + write!( + formatter, + "failed to parse MLIR attribute `{name}` from `{source}`" + ) + } + Self::TypeParse { source } => { + write!(formatter, "failed to parse MLIR type `{source}`") + } + Self::OperationBuild { op, source } => { + write!(formatter, "failed to build MLIR operation `{op}`: {source}") + } + Self::ParseFailed { source } => { + write!(formatter, "failed to parse MLIR module:\n{source}") + } + Self::Schema { message } => formatter.write_str(message), + Self::DialectRegistration { message } => formatter.write_str(message), + Self::VerificationFailed { source } => { + write!(formatter, "MLIR module verification failed:\n{source}") + } + } + } +} + +impl Error for MlirError {} + +pub(crate) fn verify_module(module: &BoltModule<'_, P>) -> Result<(), MlirError> { + if module.verify() { + Ok(()) + } else { + Err(MlirError::VerificationFailed { + source: module.to_text_mlir(), + }) + } +} diff --git a/crates/bolt/src/pass.rs b/crates/bolt/src/pass.rs new file mode 100644 index 0000000000..8b883a1f6b --- /dev/null +++ b/crates/bolt/src/pass.rs @@ -0,0 +1,316 @@ +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::OperationLike; + +use crate::ir::{BoltModule, Concrete, Party, Phase, Protocol, Role}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{verify_concrete_schema, verify_party_schema, verify_protocol_schema}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VerifyError { + message: String, +} + +impl VerifyError { + fn new(message: impl Into) -> Self { + Self { + message: message.into(), + } + } +} + +impl Display for VerifyError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str(&self.message) + } +} + +impl Error for VerifyError {} + +pub fn lower_piop_and_fiat_shamir<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, +) -> Result, MlirError> { + verify_protocol_schema(module)?; + let source = phase_copy_source(module, Concrete::NAME, None, &[]); + let concrete = context.parse_module::(&source)?; + verify_module(&concrete)?; + verify_concrete_schema(&concrete)?; + Ok(concrete) +} + +pub fn derive_prover_role<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Concrete>, +) -> Result, MlirError> { + derive_role(context, module, Role::Prover) +} + +pub fn derive_verifier_role<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Concrete>, +) -> Result, MlirError> { + derive_role(context, module, Role::Verifier) +} + +pub fn project_prover_party<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Concrete>, +) -> Result, MlirError> { + project_party(context, module, Role::Prover) +} + +pub fn project_verifier_party<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Concrete>, +) -> Result, MlirError> { + project_party(context, module, Role::Verifier) +} + +pub fn project_party<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Concrete>, + role: Role, +) -> Result, MlirError> { + verify_concrete_schema(module)?; + require_declared_role(module, &role)?; + let party_function = format!( + " \"party.function\"() {{role = \"{}\", source = @{}, sym_name = \"{}.{}\"}} : () -> ()", + role.as_str(), + module.name(), + module.name(), + role.as_str() + ); + let source = phase_copy_source(module, Party::NAME, Some(&role), &[party_function]); + let party = context.parse_module::(&source)?; + verify_module(&party)?; + verify_party_schema(&party)?; + Ok(party) +} + +pub fn verify_concrete_transcript

(module: &BoltModule<'_, P>) -> Result<(), VerifyError> +where + P: Phase, +{ + let mut current_state = None; + let mut error = None; + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "transcript.state" => { + if current_state.is_some() { + error = Some(VerifyError::new("multiple transcript.state ops")); + break; + } + let Ok(result) = op.result(0) else { + error = Some(VerifyError::new("transcript.state requires one result")); + break; + }; + current_state = Some(result.to_string()); + } + "transcript.absorb" | "transcript.absorb_optional" => { + let Some(expected_input) = current_state.as_deref() else { + error = Some(VerifyError::new( + "transcript absorb requires a prior transcript.state result", + )); + break; + }; + let Ok(input) = op.operand(0) else { + error = Some(VerifyError::new(format!( + "{} requires transcript-state operand 0", + operation_name(op) + ))); + break; + }; + let input = input.to_string(); + if input != expected_input { + error = Some(VerifyError::new(format!( + "{} consumed transcript state {input}, expected {expected_input}", + operation_name(op) + ))); + break; + } + if op.operand(1).is_err() { + error = Some(VerifyError::new(format!( + "{} requires commitment artifact operand 1", + operation_name(op) + ))); + break; + } + let Ok(result) = op.result(0) else { + error = Some(VerifyError::new(format!( + "{} requires one transcript-state result", + operation_name(op) + ))); + break; + }; + current_state = Some(result.to_string()); + } + "transcript.absorb_bytes" => { + let Some(expected_input) = current_state.as_deref() else { + error = Some(VerifyError::new( + "transcript absorb_bytes requires a prior transcript.state result", + )); + break; + }; + let Ok(input) = op.operand(0) else { + error = Some(VerifyError::new( + "transcript.absorb_bytes requires transcript-state operand 0", + )); + break; + }; + let input = input.to_string(); + if input != expected_input { + error = Some(VerifyError::new(format!( + "transcript.absorb_bytes consumed transcript state {input}, expected {expected_input}", + ))); + break; + } + let Ok(result) = op.result(0) else { + error = Some(VerifyError::new( + "transcript.absorb_bytes requires one transcript-state result", + )); + break; + }; + current_state = Some(result.to_string()); + } + "transcript.squeeze" | "piop.sumcheck" | "pcs.batch_open" | "pcs.batch_verify" => { + let Some(expected_input) = current_state.as_deref() else { + error = Some(VerifyError::new(format!( + "{} requires a prior transcript.state result", + operation_name(op) + ))); + break; + }; + let Ok(input) = op.operand(0) else { + error = Some(VerifyError::new(format!( + "{} requires transcript-state operand 0", + operation_name(op) + ))); + break; + }; + let input = input.to_string(); + if input != expected_input { + error = Some(VerifyError::new(format!( + "{} consumed transcript state {input}, expected {expected_input}", + operation_name(op) + ))); + break; + } + let Ok(result) = op.result(0) else { + error = Some(VerifyError::new(format!( + "{} requires transcript-state result 0", + operation_name(op) + ))); + break; + }; + current_state = Some(result.to_string()); + } + _ => {} + } + } + + match error { + Some(error) => Err(error), + None => Ok(()), + } +} + +fn derive_role<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Concrete>, + role: Role, +) -> Result, MlirError> { + let source = phase_copy_source(module, Concrete::NAME, Some(&role), &[]); + context.parse_module::(&source) +} + +fn phase_copy_source( + module: &BoltModule<'_, P>, + target_phase: &str, + role: Option<&Role>, + prefix_ops: &[String], +) -> String { + let mut source = format!( + "module @{} attributes {{bolt.phase = \"{target_phase}\"", + module.name() + ); + if let Some(role) = role { + source.push_str(", bolt.role = \""); + source.push_str(role.as_str()); + source.push('"'); + } + source.push_str("} {\n"); + for op in prefix_ops { + source.push_str(op); + source.push('\n'); + } + append_body_text(&mut source, module); + source.push_str("}\n"); + source +} + +fn require_declared_role(module: &BoltModule<'_, Concrete>, role: &Role) -> Result<(), MlirError> { + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + if operation_name(op) != "protocol.boundary" { + continue; + } + let roles = op + .attribute("roles") + .ok() + .and_then(|attribute| parse_string_array(&attribute.to_string())) + .ok_or_else(|| MlirError::Schema { + message: "protocol.boundary requires string array attr `roles`".to_owned(), + })?; + if roles.iter().any(|declared| declared == role.as_str()) { + return Ok(()); + } + return Err(MlirError::Schema { + message: format!("protocol.boundary does not declare role `{role}`"), + }); + } + + Err(MlirError::Schema { + message: "module missing required op `protocol.boundary`".to_owned(), + }) +} + +fn parse_string_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| { + item.trim() + .strip_prefix('"') + .and_then(|item| item.strip_suffix('"')) + .map(ToOwned::to_owned) + }) + .collect() +} + +fn operation_name<'c: 'a, 'a>(operation: impl OperationLike<'c, 'a>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} + +fn append_body_text(module_source: &mut String, module: &BoltModule<'_, P>) { + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + module_source.push_str(" "); + module_source.push_str(&op.to_string()); + module_source.push('\n'); + } +} diff --git a/crates/bolt/src/protocols/jolt/artifacts.rs b/crates/bolt/src/protocols/jolt/artifacts.rs new file mode 100644 index 0000000000..0a13379d7d --- /dev/null +++ b/crates/bolt/src/protocols/jolt/artifacts.rs @@ -0,0 +1,2349 @@ +use std::path::Path; + +use crate::emit::rust::{ + assemble_generated_crates, assemble_workspace_generated_crates, protocol_rust_artifact, + validate_rust_artifact_imports, write_generated_crates, EmitError, GeneratedCrate, + GeneratedFile, ProtocolArtifactConfig, ProtocolArtifactExtension, ProtocolCrateRef, + ProtocolProverApiExtension, ProtocolRuntimeModule, ProtocolRustArtifact, ProtocolStage, + ProtocolStageKind, ProtocolStandaloneDependency, ProtocolVerifierApiExtension, RustSourceFile, + RustTypeRef, +}; +use crate::ir::Role; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum JoltProtocolStage { + Commitment, + Stage1Outer, + Stage2, + Stage3, + Stage4, + Stage5, + Stage6, + Stage7, + Stage8, +} + +impl JoltProtocolStage { + pub fn name(self) -> &'static str { + match self { + Self::Commitment => "commitment", + Self::Stage1Outer => "stage1_outer", + Self::Stage2 => "stage2", + Self::Stage3 => "stage3", + Self::Stage4 => "stage4", + Self::Stage5 => "stage5", + Self::Stage6 => "stage6", + Self::Stage7 => "stage7", + Self::Stage8 => "stage8", + } + } + + fn expected_filename(self, role: &Role) -> &'static str { + match (self, role) { + (Self::Commitment, Role::Prover) => "prove_commitment_phase.rs", + (Self::Commitment, Role::Verifier) => "verify_commitment_phase.rs", + (Self::Stage1Outer, Role::Prover) => "prove_stage1_outer.rs", + (Self::Stage1Outer, Role::Verifier) => "verify_stage1_outer.rs", + (Self::Stage2, Role::Prover) => "prove_stage2.rs", + (Self::Stage2, Role::Verifier) => "verify_stage2.rs", + (Self::Stage3, Role::Prover) => "prove_stage3.rs", + (Self::Stage3, Role::Verifier) => "verify_stage3.rs", + (Self::Stage4, Role::Prover) => "prove_stage4.rs", + (Self::Stage4, Role::Verifier) => "verify_stage4.rs", + (Self::Stage5, Role::Prover) => "prove_stage5.rs", + (Self::Stage5, Role::Verifier) => "verify_stage5.rs", + (Self::Stage6, Role::Prover) => "prove_stage6.rs", + (Self::Stage6, Role::Verifier) => "verify_stage6.rs", + (Self::Stage7, Role::Prover) => "prove_stage7.rs", + (Self::Stage7, Role::Verifier) => "verify_stage7.rs", + (Self::Stage8, Role::Prover) => "prove_stage8.rs", + (Self::Stage8, Role::Verifier) => "verify_stage8.rs", + } + } +} + +impl From for ProtocolStage { + fn from(stage: JoltProtocolStage) -> Self { + match stage { + JoltProtocolStage::Commitment => { + ProtocolStage::new("commitment", "commitment", 0, ProtocolStageKind::Commitment) + } + JoltProtocolStage::Stage1Outer => { + ProtocolStage::new("stage1_outer", "stage1_outer", 1, ProtocolStageKind::Proof) + } + JoltProtocolStage::Stage2 => { + ProtocolStage::new("stage2", "stage2", 2, ProtocolStageKind::Proof) + } + JoltProtocolStage::Stage3 => { + ProtocolStage::new("stage3", "stage3", 3, ProtocolStageKind::Proof) + } + JoltProtocolStage::Stage4 => { + ProtocolStage::new("stage4", "stage4", 4, ProtocolStageKind::Proof) + } + JoltProtocolStage::Stage5 => { + ProtocolStage::new("stage5", "stage5", 5, ProtocolStageKind::Proof) + } + JoltProtocolStage::Stage6 => { + ProtocolStage::new("stage6", "stage6", 6, ProtocolStageKind::Proof) + } + JoltProtocolStage::Stage7 => { + ProtocolStage::new("stage7", "stage7", 7, ProtocolStageKind::Proof) + } + JoltProtocolStage::Stage8 => { + ProtocolStage::new("stage8", "stage8", 8, ProtocolStageKind::Evaluation) + } + } + } +} + +impl PartialEq for ProtocolStage { + fn eq(&self, other: &JoltProtocolStage) -> bool { + self == &ProtocolStage::from(*other) + } +} + +impl PartialEq for JoltProtocolStage { + fn eq(&self, other: &ProtocolStage) -> bool { + other == self + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum JoltArtifactCrate { + Prover, + Verifier, +} + +impl JoltArtifactCrate { + pub fn for_role(role: &Role) -> Self { + match role { + Role::Prover => Self::Prover, + Role::Verifier => Self::Verifier, + } + } + + pub fn name(self) -> &'static str { + match self { + Self::Prover => "jolt-prover", + Self::Verifier => "jolt-verifier", + } + } +} + +pub type JoltRustArtifact = ProtocolRustArtifact; +pub type JoltGeneratedCrate = GeneratedCrate; +pub type JoltGeneratedFile = GeneratedFile; + +pub fn write_jolt_generated_crates( + generated_crates: &[GeneratedCrate], + output_root: impl AsRef, +) -> Result<(), EmitError> { + write_generated_crates(generated_crates, output_root) +} + +pub fn jolt_rust_artifact( + stage: JoltProtocolStage, + role: Role, + source: RustSourceFile, +) -> Result { + let expected = stage.expected_filename(&role); + if source.filename != expected { + return Err(EmitError::new(format!( + "generated {} artifact for {} expected filename `{expected}`, got `{}`", + role, + stage.name(), + source.filename + ))); + } + + Ok(protocol_rust_artifact( + &jolt_artifact_config(), + ProtocolStage::from(stage), + role, + source, + )) +} + +pub fn validate_jolt_rust_artifact_imports( + artifact: &ProtocolRustArtifact, +) -> Result<(), EmitError> { + validate_rust_artifact_imports(&jolt_artifact_config(), artifact) +} + +pub fn assemble_jolt_generated_crates( + artifacts: Vec, + dependency_root: &str, +) -> Result, EmitError> { + assemble_generated_crates(&jolt_artifact_config(), artifacts, dependency_root) +} + +pub fn assemble_jolt_workspace_generated_crates( + artifacts: Vec, +) -> Result, EmitError> { + assemble_workspace_generated_crates(&jolt_artifact_config(), artifacts) +} + +pub fn jolt_artifact_config() -> ProtocolArtifactConfig { + ProtocolArtifactConfig { + protocol_name: "Jolt".to_owned(), + type_prefix: "Jolt".to_owned(), + transcript_label: "Jolt".to_owned(), + repository: Some("https://github.com/a16z/jolt".to_owned()), + prover_crate_name: "jolt-prover".to_owned(), + verifier_crate_name: "jolt-verifier".to_owned(), + crates_io_patches: vec![ + "ark-bn254 = { git = \"https://github.com/a16z/arkworks-algebra\", branch = \"dev/twist-shout\" }".to_owned(), + "ark-ec = { git = \"https://github.com/a16z/arkworks-algebra\", branch = \"dev/twist-shout\" }".to_owned(), + "ark-ff = { git = \"https://github.com/a16z/arkworks-algebra\", branch = \"dev/twist-shout\" }".to_owned(), + "ark-serialize = { git = \"https://github.com/a16z/arkworks-algebra\", branch = \"dev/twist-shout\" }".to_owned(), + ], + standalone_dependency_overrides: vec![ProtocolStandaloneDependency::new( + "rayon", + "rayon = \"1.12.0\"", + ), ProtocolStandaloneDependency::new( + "serde", + "serde = { version = \"1.0\", default-features = false, features = [\"derive\"] }", + ), ProtocolStandaloneDependency::new( + "tracing", + "tracing = { version = \"0.1.37\", default-features = false, features = [\"attributes\"] }", + )], + common_dependencies: vec![ + "jolt-field".to_owned(), + "jolt-openings".to_owned(), + "jolt-poly".to_owned(), + "jolt-transcript".to_owned(), + "tracing".to_owned(), + ], + prover_dependencies: vec![ + "jolt-dory".to_owned(), + "jolt-kernels".to_owned(), + "jolt-witness".to_owned(), + "rayon".to_owned(), + ], + verifier_dependencies: vec![ + "jolt-dory".to_owned(), + "jolt-lookup-tables".to_owned(), + "jolt-sumcheck".to_owned(), + "serde".to_owned(), + ], + instrumentation_prefix: Some("bolt".to_owned()), + prover_forbidden_imports: PROVER_FORBIDDEN_IMPORTS + .iter() + .map(ToString::to_string) + .collect(), + verifier_forbidden_imports: VERIFIER_FORBIDDEN_IMPORTS + .iter() + .map(ToString::to_string) + .collect(), + kernel_crate: Some(ProtocolCrateRef::new("jolt-kernels", "jolt_kernels")), + field_type: RustTypeRef::new("jolt_field::Fr"), + default_transcript_type: RustTypeRef::new("jolt_transcript::Blake2bTranscript"), + transcript_trait: RustTypeRef::new("jolt_transcript::Transcript"), + commitment_type: RustTypeRef::new("jolt_dory::DoryCommitment"), + prover_setup_type: RustTypeRef::new("jolt_dory::DoryProverSetup"), + role_api_extension: Some(jolt_evaluation_role_api_extension()), + verifier_runtime_modules: vec![ProtocolRuntimeModule { + module_name: "common".to_owned(), + file: GeneratedFile { + path: "src/stages/common.rs".to_owned(), + source: include_str!("verifier_common.rs.template").to_owned(), + }, + }], + verifier_named_eval_type: RustTypeRef::new("crate::stages::common::StageNamedEval"), + verifier_sumcheck_output_type: RustTypeRef::new( + "crate::stages::common::StageSumcheckOutput", + ), + verifier_stage_proof_type: RustTypeRef::new("crate::stages::common::StageProof"), + } +} + +fn jolt_prover_lib_module() -> String { + r"#[rustfmt::skip] +pub mod prover; +pub mod stages; + +pub use prover::{ + default_prover_programs, jolt_proof_through_stage5, jolt_proof_through_stage6, + jolt_proof_through_stage7, prove_jolt, prove_jolt_evaluation_proof, prove_jolt_with_programs, + prove_jolt_with_stage_inputs, prove_jolt_with_witness_inputs, + prove_stage1_outer_inputs_with_program, prove_stage2_inputs_with_program, + prove_stage3_inputs_with_program, prove_stage4_inputs_with_program, + prove_stage5_inputs_with_program, prove_stage6_inputs_with_program, + prove_stage7_inputs_with_program, replay_stage1_outer_proof_with_program, + replay_stage2_proof_with_program, replay_stage3_proof_with_program, + replay_stage4_proof_with_program, replay_stage5_proof_with_program, + replay_stage6_proof_with_program, replay_stage7_proof_with_program, stage1_outer_proof, + stage1_outer_proof_from_kernel_proof, stage1_outer_prover_inputs, + stage2_opening_inputs_from_artifacts, stage2_proof, stage2_prover_inputs, + stage2_verifier_ram_data, stage3_opening_inputs_from_artifacts, stage3_proof, + stage3_prover_inputs, stage4_opening_inputs_from_artifacts, stage4_proof, stage4_prover_inputs, + stage5_kernel_proof, stage5_opening_inputs_from_artifacts, stage5_proof, stage5_prover_inputs, + stage6_bytecode_read_raf_data_from_witness_entries, stage6_execution_artifacts, + stage6_kernel_proof, stage6_opening_inputs_from_artifacts, stage6_proof, stage6_prover_inputs, + stage6_witness_from_opening_inputs, stage7_execution_artifacts, stage7_kernel_proof, + stage7_opening_inputs_from_stage6_artifacts, + stage7_opening_inputs_from_stage6_artifacts_with_program, stage7_proof, stage7_prover_inputs, + verifier_opening_inputs_from_kernel, DefaultJoltTranscript, JoltEvaluationProveError, + JoltKernelOpeningInput, JoltOpeningInputError, JoltProveError, JoltProverArtifacts, + JoltProverInputs, JoltProverPrograms, JoltProverStageInputs, JoltProverWitnessInputs, + JoltStage2RamDataStorage, +}; + +pub use prover::{ + prove_stage1_outer_with_witness_inputs, prove_stage2_with_witness_inputs, + prove_stage3_with_witness_inputs, prove_stage4_with_trace_witness_inputs, + prove_stage4_with_witness_inputs, prove_stage5_with_trace_witness_inputs, + prove_stage5_with_witness_inputs, prove_stage6_with_trace_witness_inputs, + prove_stage6_with_witness_inputs, prove_stage7_with_trace_witness_inputs, + prove_stage7_with_witness_inputs, stage6_verifier_data_from_witness_entries, +};" + .to_owned() +} + +fn jolt_verifier_lib_module() -> String { + r"pub mod stages; +#[rustfmt::skip] +pub mod verifier; + +pub use stages::{ + stage1_outer::{verify_stage1_outer_with_program, Stage1VerifierProgramPlan}, + stage2::{verify_stage2_with_program, Stage2VerifierProgramPlan}, + stage3::{verify_stage3_with_program, Stage3VerifierProgramPlan}, + stage4::{verify_stage4_with_program, Stage4VerifierProgramPlan}, + stage5::{verify_stage5_with_program, Stage5VerifierProgramPlan}, + stage6::{verify_stage6_with_program, Stage6VerifierProgramPlan}, + stage7::{verify_stage7_with_program, Stage7VerifierProgramPlan}, +}; + +pub use verifier::{ + default_verifier_programs, verify_jolt, verify_jolt_evaluation_proof, verify_jolt_prefix, + verify_jolt_prefix_with_programs, verify_jolt_through_stage5, + verify_jolt_through_stage5_with_programs, verify_jolt_through_stage6, + verify_jolt_through_stage6_with_programs, verify_jolt_through_stage7, + verify_jolt_through_stage7_with_programs, verify_jolt_with_programs, JoltEvaluationProof, + JoltEvaluationProofError, JoltNamedEval, JoltProof, JoltStage2RamAccess, JoltStage2RamData, + JoltStage2RamOutputLayout, JoltStageChallengeVector, JoltStageExecutionArtifacts, + JoltStage6BytecodeEntry, JoltStage6BytecodeReadRafData, JoltStage6VerifierData, + JoltStageOpeningInputValue, JoltStageProof, JoltSumcheckOutput, JoltVerificationArtifacts, + JoltVerifierInputs, JoltVerifierPrograms, JoltVerifierTarget, JoltVerifyError, +};" + .to_owned() +} + +fn jolt_evaluation_role_api_extension() -> ProtocolArtifactExtension { + ProtocolArtifactExtension { + required_commitment: true, + required_proof_stages: vec!["stage6".to_owned(), "stage7".to_owned()], + required_artifact_stages: vec!["stage8".to_owned()], + prover: ProtocolProverApiExtension { + lib_module: jolt_prover_lib_module(), + imports: "#![expect(\n clippy::too_many_arguments,\n reason = \"generated prover helpers mirror staged protocol ABIs\"\n)]\n\nuse jolt_dory::{DoryCommitment, DoryHint, DoryProverSetup, DoryScheme};\nuse jolt_field::{Field, Fr};\nuse jolt_kernels::{stage1, stage2, stage3, stage4, stage5, stage6, stage7};\nuse jolt_openings::{AdditivelyHomomorphic, CommitmentScheme};\nuse jolt_poly::{EqPolynomial, Polynomial};\nuse jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript};\nuse jolt_verifier::{JoltEvaluationProof, JoltNamedEval, JoltProof, JoltStage2RamAccess, JoltStage2RamData, JoltStage2RamOutputLayout, JoltStage6BytecodeEntry, JoltStage6BytecodeReadRafData, JoltStage6VerifierData, JoltStageChallengeVector, JoltStageExecutionArtifacts, JoltStageOpeningInputValue, JoltStageProof, JoltSumcheckOutput};\nuse jolt_witness::{stage4_ram_val_init_opening, CycleInput, Stage45SparseTraceWitness, Stage6BytecodeEntry as WitnessStage6BytecodeEntry, Stage6WitnessParams, Stage6WitnessPolynomials, Stage6WitnessSlices};\nuse rayon::prelude::*;\n\n".to_owned(), + input_fields: + " pub stage7_openings: Option<&'a [stage7::Stage7OpeningInputValue]>,\n" + .to_owned(), + program_fields: + " pub stage8: &'static stage8_stage::Stage8EvaluationProgramPlan,\n".to_owned(), + default_program_fields: " stage8: &stage8_stage::STAGE8_PROGRAM,\n".to_owned(), + error_variants: " Evaluation(JoltEvaluationProveError),\n".to_owned(), + error_items: "#[derive(Debug)]\npub enum JoltEvaluationProveError {\n MissingOracle { oracle: &'static str },\n MissingOpeningHint { oracle: &'static str },\n MissingStageEval { stage: &'static str, eval: &'static str },\n MissingStage7RaEval,\n MissingStage7EvaluationPoint,\n InvalidPointLength {\n artifact: &'static str,\n expected: usize,\n actual: usize,\n },\n TargetSizeOverflow { num_vars: usize },\n}\n\n#[derive(Debug)]\npub enum JoltOpeningInputError {\n MissingOpeningClaim { stage: &'static str, source_claim: &'static str },\n MissingStage6OpeningClaim { source_claim: &'static str },\n UnsupportedOpeningInputSource { stage: &'static str, symbol: &'static str, source_stage: &'static str },\n UnsupportedStage7InputSource { symbol: &'static str, source_stage: &'static str },\n InvalidPointLength {\n symbol: &'static str,\n expected: usize,\n actual: usize,\n },\n}\n\n".to_owned(), + error_conversions: "impl From for JoltProveError {\n fn from(error: JoltEvaluationProveError) -> Self {\n Self::Evaluation(error)\n }\n}\n\n".to_owned(), + after_stage_execution: " let evaluation = if let Some(stage7_openings) = inputs.stage7_openings {\n let _stage8_span = tracing::info_span!(\"bolt.stage8\").entered();\n let _evaluate_span = tracing::info_span!(\"bolt.evaluate\").entered();\n Some(prove_jolt_evaluation_proof(\n programs.stage8,\n inputs.commitment_inputs,\n inputs.prover_setup,\n &commitment,\n &stage6,\n &stage7,\n stage7_openings,\n transcript,\n )?)\n } else {\n None\n };\n".to_owned(), + proof_fields: " evaluation,\n".to_owned(), + helper_items: format!( + "{}{}", + jolt_prover_evaluation_helpers("Fr"), + jolt_prover_stage7_opening_input_helpers("Fr") + ), + }, + verifier: ProtocolVerifierApiExtension { + lib_module: jolt_verifier_lib_module(), + imports: "use std::collections::BTreeMap;\n\nuse jolt_dory::{DoryCommitment, DoryProof, DoryScheme, DoryVerifierSetup};\nuse jolt_field::{Field, Fr};\nuse jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, OpeningsError};\nuse jolt_poly::EqPolynomial;\nuse jolt_transcript::{AppendToTranscript, LabelWithCount, Transcript};\n".to_owned(), + proof_fields: " pub evaluation: Option,\n".to_owned(), + proof_items: "pub type JoltStage2RamAccess = crate::stages::stage2::Stage2RamAccess;\npub type JoltStage2RamOutputLayout = crate::stages::stage2::Stage2RamOutputLayout;\npub type JoltStage2RamData<'a> = crate::stages::stage2::Stage2RamData<'a>;\npub type JoltStageChallengeVector = crate::stages::common::StageChallengeVector;\npub type JoltStageExecutionArtifacts = crate::stages::common::StageExecutionArtifacts;\npub type JoltStageOpeningInputValue = crate::stages::common::StageOpeningInputValue;\n\n#[derive(Clone, Debug)]\npub struct JoltEvaluationProof {\n pub joint_opening_proof: DoryProof,\n}\n\n".to_owned(), + inputs_derive: Some("#[derive(Clone, Copy)]".to_owned()), + input_fields: " pub evaluation_setup: Option<&'a DoryVerifierSetup>,\n".to_owned(), + program_fields: + " pub stage8: &'static stage8_stage::Stage8EvaluationProgramPlan,\n".to_owned(), + default_program_fields: " stage8: &stage8_stage::STAGE8_PROGRAM,\n".to_owned(), + error_variants: " Evaluation(JoltEvaluationProofError),\n".to_owned(), + error_items: format!("{}{}", jolt_verifier_target_items(), "#[derive(Debug)]\npub enum JoltEvaluationProofError {\n MissingProof,\n MissingVerifierSetup,\n MissingStageEval { stage: &'static str, eval: &'static str },\n MissingStage7RaEval,\n MissingStage7EvaluationPoint,\n MissingCommitment { oracle: &'static str },\n InvalidPointLength {\n artifact: &'static str,\n expected: usize,\n actual: usize,\n },\n Opening(OpeningsError),\n}\n\n"), + error_conversions: "impl From for JoltVerifyError {\n fn from(error: JoltEvaluationProofError) -> Self {\n Self::Evaluation(error)\n }\n}\n\nimpl From for JoltEvaluationProofError {\n fn from(error: OpeningsError) -> Self {\n Self::Opening(error)\n }\n}\n\n".to_owned(), + after_default_verify: "pub fn verify_jolt_prefix>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { verify_jolt_prefix_with_programs(proof, inputs, default_verifier_programs(), transcript) }\n\npub fn verify_jolt_through_stage5>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { verify_jolt_through_stage5_with_programs(proof, inputs, default_verifier_programs(), transcript) }\n\npub fn verify_jolt_through_stage6>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { verify_jolt_through_stage6_with_programs(proof, inputs, default_verifier_programs(), transcript) }\n\npub fn verify_jolt_through_stage7>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { verify_jolt_through_stage7_with_programs(proof, inputs, default_verifier_programs(), transcript) }\n\n".to_owned(), + with_programs_body_intro: " verify_jolt_with_programs_inner(proof, inputs, programs, transcript, JoltVerifierTarget::Full)\n}\n\npub fn verify_jolt_through_stage5_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { verify_jolt_with_programs_inner(proof, inputs, programs, transcript, JoltVerifierTarget::ThroughStage5) }\n\npub fn verify_jolt_through_stage6_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { verify_jolt_with_programs_inner(proof, inputs, programs, transcript, JoltVerifierTarget::ThroughStage6) }\n\npub fn verify_jolt_through_stage7_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { verify_jolt_with_programs_inner(proof, inputs, programs, transcript, JoltVerifierTarget::ThroughStage7) }\n\npub fn verify_jolt_prefix_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { verify_jolt_through_stage7_with_programs(proof, inputs, programs, transcript) }\n\nfn verify_jolt_with_programs_inner>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T, target: JoltVerifierTarget) -> Result {\n".to_owned(), + stage_verification_override: jolt_verifier_stage_verification(), + after_stage_verification: jolt_verifier_evaluation_check(), + helper_items: format!( + "{}{}", + jolt_verifier_input_helpers("Jolt"), + jolt_verifier_evaluation_helpers("Jolt", "Fr") + ), + }, + } +} + +fn jolt_verifier_target_items() -> String { + "#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum JoltVerifierTarget {\n ThroughStage5,\n ThroughStage6,\n ThroughStage7,\n Full,\n}\n\nimpl JoltVerifierTarget {\n fn verifies_stage6(self) -> bool { matches!(self, Self::ThroughStage6 | Self::ThroughStage7 | Self::Full) }\n fn verifies_stage7(self) -> bool { matches!(self, Self::ThroughStage7 | Self::Full) }\n fn verifies_evaluation(self) -> bool { matches!(self, Self::Full) }\n fn allows_optional_evaluation(self) -> bool { matches!(self, Self::ThroughStage7 | Self::Full) }\n}\n\n".to_owned() +} + +fn jolt_verifier_stage_verification() -> String { + " let stage1_outer = stage1_outer_stage::verify_stage1_outer_with_program(programs.stage1_outer, &proof.stage1_outer, transcript)?;\n let stage2 = stage2_stage::verify_stage2_with_program(programs.stage2, &proof.stage2, inputs.stage2_openings, inputs.stage2_ram, transcript)?;\n let stage3 = stage3_stage::verify_stage3_with_program(programs.stage3, &proof.stage3, inputs.stage3_openings, transcript)?;\n let stage4 = stage4_stage::verify_stage4_with_program(programs.stage4, &proof.stage4, inputs.stage4_openings, transcript)?;\n let stage5 = stage5_stage::verify_stage5_with_program(programs.stage5, &proof.stage5, inputs.stage5_openings, transcript)?;\n let stage6 = if target.verifies_stage6() {\n stage6_stage::verify_stage6_with_program(programs.stage6, &proof.stage6, inputs.stage6_openings, inputs.stage6_data, transcript)?\n } else {\n stage6_stage::Stage6ExecutionArtifacts::default()\n };\n let stage7 = if target.verifies_stage7() {\n stage7_stage::verify_stage7_with_program(programs.stage7, &proof.stage7, inputs.stage7_openings, transcript)?\n } else {\n stage7_stage::Stage7ExecutionArtifacts::default()\n };\n".to_owned() +} + +fn jolt_verifier_evaluation_check() -> String { + " if target.allows_optional_evaluation() {\n match (&proof.evaluation, inputs.evaluation_setup) {\n (Some(evaluation), Some(setup)) => {\n verify_jolt_evaluation_proof(\n programs.stage8,\n evaluation,\n &commitment,\n &proof.stage6,\n &proof.stage7,\n inputs.stage7_openings,\n setup,\n transcript,\n )?;\n }\n (Some(_), None) => return Err(JoltEvaluationProofError::MissingVerifierSetup.into()),\n (None, Some(_)) => return Err(JoltEvaluationProofError::MissingProof.into()),\n (None, None) if target.verifies_evaluation() => return Err(JoltEvaluationProofError::MissingProof.into()),\n (None, None) => {}\n }\n }\n".to_owned() +} + +fn jolt_verifier_input_helpers(prefix: &str) -> String { + format!( + "impl<'a> {prefix}VerifierInputs<'a> {{\n pub fn through_stage5(mut self) -> Self {{ self.stage6_openings = &[]; self.stage7_openings = &[]; self.evaluation_setup = None; self }}\n pub fn through_stage6(mut self) -> Self {{ self.stage7_openings = &[]; self.evaluation_setup = None; self }}\n pub fn through_stage7(mut self) -> Self {{ self.evaluation_setup = None; self }}\n pub fn full(mut self, evaluation_setup: &'a DoryVerifierSetup) -> Self {{ self.evaluation_setup = Some(evaluation_setup); self }}\n}}\n\n" + ) +} + +fn jolt_verifier_evaluation_helpers(prefix: &str, field_type: &str) -> String { + format!( + r#"pub type {prefix}Stage6BytecodeEntry = crate::stages::stage6::Stage6BytecodeEntry; +pub type {prefix}Stage6BytecodeReadRafData = crate::stages::stage6::Stage6BytecodeReadRafData; +pub type {prefix}Stage6VerifierData = crate::stages::stage6::Stage6VerifierData; + +#[expect( + clippy::too_many_arguments, + reason = "generated verifier entry point follows the Jolt proof artifact boundary" +)] +pub fn verify_jolt_evaluation_proof( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + proof: &{prefix}EvaluationProof, + commitments: &commitment_stage::CommitmentArtifacts, + stage6: &{prefix}StageProof, + stage7: &{prefix}StageProof, + stage7_openings: &[stage7_stage::Stage7OpeningInputValue<{field_type}>], + verifier_setup: &DoryVerifierSetup, + transcript: &mut T, +) -> Result<(), {prefix}EvaluationProofError> +where + T: Transcript, +{{ + let _state_span = tracing::info_span!("bolt.verify.evaluation_state").entered(); + let state = + evaluation_proof_state(program, commitments, stage6, stage7, stage7_openings, transcript)?; + drop(_state_span); + let _dory_verify_span = tracing::info_span!("bolt.verify.dory_verify").entered(); + ::verify( + &state.joint_commitment, + &state.opening_point, + state.joint_claim, + &proof.joint_opening_proof, + verifier_setup, + transcript, + )?; + drop(_dory_verify_span); + let _bind_span = tracing::info_span!("bolt.verify.bind_opening_inputs").entered(); + ::bind_opening_inputs( + transcript, + &state.opening_point, + &state.joint_claim, + ); + drop(_bind_span); + Ok(()) +}} + +struct EvaluationProofState {{ + opening_point: Vec<{field_type}>, + joint_claim: {field_type}, + joint_commitment: DoryCommitment, +}} + +struct EvaluationClaim {{ + oracle: &'static str, + value: {field_type}, +}} + +fn evaluation_proof_state( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + commitments: &commitment_stage::CommitmentArtifacts, + stage6: &{prefix}StageProof, + stage7: &{prefix}StageProof, + stage7_openings: &[stage7_stage::Stage7OpeningInputValue<{field_type}>], + transcript: &mut T, +) -> Result +where + T: Transcript, +{{ + let (sumcheck_address_point, stage7_values) = stage7_claim_values(program, stage7)?; + let address_point = reverse_point(&sumcheck_address_point); + let opening_point = stage7_evaluation_opening_point(program, &address_point, stage7_openings)?; + let lagrange_factor = EqPolynomial::<{field_type}>::zero_selector(&address_point); + let claims = evaluation_claims(program, stage6, &stage7_values, lagrange_factor)?; + + append_rlc_claims(transcript, &claims); + let gamma_powers = gamma_powers(transcript, claims.len()); + let joint_claim = claims + .iter() + .zip(&gamma_powers) + .map(|(claim, gamma)| claim.value * *gamma) + .sum(); + let joint_commitment = joint_commitment(commitments, &claims, &gamma_powers)?; + + Ok(EvaluationProofState {{ + opening_point, + joint_claim, + joint_commitment, + }}) +}} + +fn stage_eval( + proof: &{prefix}StageProof, + stage: &'static str, + eval_name: &'static str, +) -> Result<{field_type}, {prefix}EvaluationProofError> {{ + for output in &proof.sumchecks {{ + if let Some(eval) = output.evals.iter().find(|eval| eval.name == eval_name) {{ + return Ok(eval.value); + }} + }} + Err({prefix}EvaluationProofError::MissingStageEval {{ + stage, + eval: eval_name, + }}) +}} + +fn evaluation_claims( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + stage6: &{prefix}StageProof, + stage7_values: &BTreeMap<&'static str, {field_type}>, + lagrange_factor: {field_type}, +) -> Result, {prefix}EvaluationProofError> {{ + let mut claims = Vec::with_capacity(program.opening_claims.len()); + for plan in program.opening_claims {{ + let value = match plan.source_stage {{ + "stage6" => stage_eval(stage6, plan.source_stage, plan.source_claim)? * lagrange_factor, + "stage7" => *stage7_values.get(plan.source_claim).ok_or( + {prefix}EvaluationProofError::MissingStageEval {{ + stage: plan.source_stage, + eval: plan.source_claim, + }}, + )?, + _ => {{ + return Err({prefix}EvaluationProofError::MissingStageEval {{ + stage: plan.source_stage, + eval: plan.source_claim, + }}); + }} + }}; + claims.push(EvaluationClaim {{ + oracle: plan.oracle, + value, + }}); + }} + Ok(claims) +}} + +fn stage7_claim_values( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + proof: &{prefix}StageProof, +) -> Result<(Vec<{field_type}>, BTreeMap<&'static str, {field_type}>), {prefix}EvaluationProofError> {{ + let stage7_plans = program + .opening_claims + .iter() + .filter(|plan| plan.source_stage == "stage7") + .collect::>(); + for output in &proof.sumchecks {{ + let mut values = BTreeMap::new(); + for plan in &stage7_plans {{ + if let Some(eval) = output.evals.iter().find(|eval| eval.name == plan.source_claim) {{ + let _ = values.insert(plan.source_claim, eval.value); + }} + }} + if values.len() == stage7_plans.len() {{ + return Ok((output.point.clone(), values)); + }} + }} + Err({prefix}EvaluationProofError::MissingStage7RaEval) +}} + +fn reverse_point(point: &[{field_type}]) -> Vec<{field_type}> {{ + point.iter().rev().copied().collect() +}} + +fn stage7_evaluation_opening_point( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + address_point: &[{field_type}], + stage7_openings: &[stage7_stage::Stage7OpeningInputValue<{field_type}>], +) -> Result, {prefix}EvaluationProofError> {{ + let cycle_source_symbol = program.evaluation_point_source.source_claim; + let cycle_source = stage7_openings + .iter() + .find(|input| input.symbol == cycle_source_symbol) + .ok_or({prefix}EvaluationProofError::MissingStage7EvaluationPoint)?; + if cycle_source.point.len() < address_point.len() {{ + return Err({prefix}EvaluationProofError::InvalidPointLength {{ + artifact: cycle_source_symbol, + expected: address_point.len(), + actual: cycle_source.point.len(), + }}); + }} + let mut point = Vec::with_capacity(cycle_source.point.len()); + point.extend_from_slice(address_point); + point.extend_from_slice(&cycle_source.point[address_point.len()..]); + Ok(point) +}} + +fn append_rlc_claims(transcript: &mut T, claims: &[EvaluationClaim]) +where + T: Transcript, +{{ + transcript.append(&LabelWithCount(b"rlc_claims", claims.len() as u64)); + for claim in claims {{ + claim.value.append_to_transcript(transcript); + }} +}} + +fn gamma_powers(transcript: &mut T, count: usize) -> Vec<{field_type}> +where + T: Transcript, +{{ + let gamma = transcript.challenge(); + let mut powers = Vec::with_capacity(count); + let mut power = {field_type}::from_u64(1); + for _ in 0..count {{ + powers.push(power); + power *= gamma; + }} + powers +}} + +fn joint_commitment( + commitments: &commitment_stage::CommitmentArtifacts, + claims: &[EvaluationClaim], + gamma_powers: &[{field_type}], +) -> Result {{ + let mut coefficients = BTreeMap::<&'static str, {field_type}>::new(); + for (claim, gamma) in claims.iter().zip(gamma_powers) {{ + let coefficient = coefficients.entry(claim.oracle).or_insert({field_type}::from_u64(0)); + *coefficient += *gamma; + }} + let mut commitment_values = Vec::with_capacity(coefficients.len()); + let mut scalars = Vec::with_capacity(coefficients.len()); + for (oracle, coefficient) in coefficients {{ + commitment_values.push(commitment_for_oracle(commitments, oracle)?); + scalars.push(coefficient); + }} + Ok(::combine( + &commitment_values, + &scalars, + )) +}} + +fn commitment_for_oracle( + commitments: &commitment_stage::CommitmentArtifacts, + oracle: &'static str, +) -> Result {{ + for (record, commitment) in commitments.records.iter().zip(&commitments.commitments) {{ + if record.oracle == oracle {{ + return commitment + .clone() + .ok_or({prefix}EvaluationProofError::MissingCommitment {{ oracle }}); + }} + }} + Err({prefix}EvaluationProofError::MissingCommitment {{ oracle }}) +}} + +"# + ) +} + +fn jolt_prover_stage7_opening_input_helpers(field_type: &str) -> String { + format!( + r#"pub struct JoltProverStageInputs<'a, CommitmentInputs> {{ + pub commitment_inputs: &'a mut CommitmentInputs, + pub prover_setup: &'a DoryProverSetup, + pub stage1_outer: stage1::Stage1ProverInputs<'a, {field_type}>, + pub stage2: stage2::Stage2ProverInputs<'a, {field_type}>, + pub stage3: stage3::Stage3ProverInputs<'a, {field_type}>, + pub stage4: stage4::Stage4ProverInputs<'a, {field_type}>, + pub stage5: stage5::Stage5ProverInputs<'a, {field_type}>, + pub stage6: stage6::Stage6ProverInputs<'a, {field_type}>, + pub stage7: stage7::Stage7ProverInputs<'a, {field_type}>, + pub stage7_openings: Option<&'a [stage7::Stage7OpeningInputValue<{field_type}>]>, +}} + +pub fn prove_jolt_with_stage_inputs( + inputs: JoltProverStageInputs<'_, CommitmentInputs>, + programs: JoltProverPrograms, + transcript: &mut T, +) -> Result<(JoltProof, JoltProverArtifacts), JoltProveError> +where + CommitmentInputs: commitment_stage::CommitmentInputProvider, + T: Transcript, +{{ + let JoltProverStageInputs {{ + commitment_inputs, + prover_setup, + stage1_outer, + stage2, + stage3, + stage4, + stage5, + stage6, + stage7, + stage7_openings, + }} = inputs; + let mut stage1_outer_executor = stage1::Stage1ProverKernelExecutor::new(stage1_outer); + let mut stage2_executor = stage2::Stage2ProverKernelExecutor::new(stage2); + let mut stage3_executor = stage3::Stage3ProverKernelExecutor::new(stage3); + let mut stage4_executor = stage4::Stage4ProverKernelExecutor::new(stage4); + let mut stage5_executor = stage5::Stage5ProverKernelExecutor::new(stage5); + let mut stage6_executor = stage6::Stage6ProverKernelExecutor::new(stage6); + let mut stage7_executor = stage7::Stage7ProverKernelExecutor::new(stage7); + prove_jolt_with_programs( + JoltProverInputs {{ + commitment_inputs, + prover_setup, + stage1_outer_executor: &mut stage1_outer_executor, + stage2_executor: &mut stage2_executor, + stage3_executor: &mut stage3_executor, + stage4_executor: &mut stage4_executor, + stage5_executor: &mut stage5_executor, + stage6_executor: &mut stage6_executor, + stage7_executor: &mut stage7_executor, + stage7_openings, + }}, + programs, + transcript, + ) +}} + +pub struct JoltProverWitnessInputs<'a, CommitmentInputs> {{ + pub commitment_inputs: &'a mut CommitmentInputs, + pub prover_setup: &'a DoryProverSetup, + pub stage1_trace_num_vars: usize, + pub stage1_outer_evaluator: &'a dyn stage1::Stage1OuterRemainingEvaluator<{field_type}>, + pub stage2_openings: &'a [stage2::Stage2OpeningInputValue<{field_type}>], + pub product_virtual_cycles: &'a [stage2::Stage2ProductVirtualCycle], + pub instruction_lookup_cycles: &'a [stage2::Stage2InstructionLookupCycle], + pub ram: &'a stage2::Stage2RamData<'a>, + pub stage3_openings: &'a [stage3::Stage3OpeningInputValue<{field_type}>], + pub stage3_cycles: &'a [stage3::Stage3Cycle], + pub stage4_openings: &'a [stage4::Stage4OpeningInputValue<{field_type}>], + pub register_count: usize, + pub trace_len: usize, + pub ram_k: usize, + pub register_accesses: &'a [stage4::Stage4RegisterAccess], + pub stage5_openings: &'a [stage5::Stage5OpeningInputValue<{field_type}>], + pub lookup_indices: &'a [u128], + pub lookup_table_indices: &'a [Option], + pub is_interleaved_operands: &'a [bool], + pub ra_virtual_log_k_chunk: usize, + pub stage6_openings: &'a [stage6::Stage6OpeningInputValue<{field_type}>], + pub stage6_bytecode_data: stage6::Stage6BytecodeReadRafData<'a, {field_type}>, + pub stage6_witness_params: Stage6WitnessParams, + pub cycle_inputs: &'a [CycleInput], + pub instruction_ra_virtual_d: usize, + pub stage7_openings: &'a [stage7::Stage7OpeningInputValue<{field_type}>], + pub evaluation_openings: Option<&'a [stage7::Stage7OpeningInputValue<{field_type}>]>, +}} + +pub fn prove_jolt_with_witness_inputs( + inputs: JoltProverWitnessInputs<'_, CommitmentInputs>, + programs: JoltProverPrograms, + transcript: &mut T, +) -> Result<(JoltProof, JoltProverArtifacts), JoltProveError> +where + CommitmentInputs: commitment_stage::CommitmentInputProvider, + T: Transcript, +{{ + let _input_span = tracing::info_span!("bolt.prove.inputs").entered(); + let _stage1_input_span = tracing::info_span!("bolt.prove.inputs.stage1").entered(); + let stage1_outer = + stage1_outer_prover_inputs(inputs.stage1_trace_num_vars, inputs.stage1_outer_evaluator); + drop(_stage1_input_span); + let _stage2_input_span = tracing::info_span!("bolt.prove.inputs.stage2").entered(); + let stage2 = stage2_prover_inputs( + inputs.stage2_openings, + inputs.product_virtual_cycles, + inputs.instruction_lookup_cycles, + inputs.ram, + )?; + drop(_stage2_input_span); + let _stage3_input_span = tracing::info_span!("bolt.prove.inputs.stage3").entered(); + let stage3 = stage3_prover_inputs(inputs.stage3_openings, inputs.stage3_cycles); + drop(_stage3_input_span); + let _stage45_witness_span = tracing::info_span!("bolt.prove.inputs.stage45_witness").entered(); + let stage45_witness = stage4::stage4_5_sparse_trace_witness_from_accesses( + inputs.register_accesses, + inputs.ram.accesses, + ); + drop(_stage45_witness_span); + let _stage4_input_span = tracing::info_span!("bolt.prove.inputs.stage4").entered(); + let stage4 = stage4_prover_inputs( + inputs.stage4_openings, + inputs.register_count, + inputs.trace_len, + inputs.ram_k, + inputs.register_accesses, + &stage45_witness, + ); + drop(_stage4_input_span); + let _stage5_input_span = tracing::info_span!("bolt.prove.inputs.stage5").entered(); + let stage5 = stage5_prover_inputs( + inputs.stage5_openings, + inputs.trace_len, + inputs.ram_k, + inputs.register_count, + inputs.lookup_indices, + inputs.lookup_table_indices, + inputs.is_interleaved_operands, + inputs.ra_virtual_log_k_chunk, + &stage45_witness, + ); + drop(_stage5_input_span); + let _stage6_witness_span = tracing::info_span!("bolt.prove.inputs.stage6_witness").entered(); + let stage6_witness = stage6_witness_from_opening_inputs( + inputs.stage6_witness_params, + inputs.cycle_inputs, + inputs.stage6_openings, + ); + let stage6_witness_slices = stage6_witness.slices(); + drop(_stage6_witness_span); + let _stage6_input_span = tracing::info_span!("bolt.prove.inputs.stage6").entered(); + let stage6 = stage6_prover_inputs( + inputs.stage6_openings, + inputs.stage6_bytecode_data, + &stage6_witness, + &stage6_witness_slices, + inputs.instruction_ra_virtual_d, + ); + drop(_stage6_input_span); + let _stage7_input_span = tracing::info_span!("bolt.prove.inputs.stage7").entered(); + let stage7 = stage7_prover_inputs(inputs.stage7_openings, &stage6_witness_slices); + drop(_stage7_input_span); + drop(_input_span); + prove_jolt_with_stage_inputs( + JoltProverStageInputs {{ + commitment_inputs: inputs.commitment_inputs, + prover_setup: inputs.prover_setup, + stage1_outer, + stage2, + stage3, + stage4, + stage5, + stage6, + stage7, + stage7_openings: inputs.evaluation_openings, + }}, + programs, + transcript, + ) +}} + +pub fn stage1_outer_prover_inputs( + trace_num_vars: usize, + evaluator: &dyn stage1::Stage1OuterRemainingEvaluator<{field_type}>, +) -> stage1::Stage1ProverInputs<'_, {field_type}> {{ + stage1::Stage1ProverInputs::empty(trace_num_vars).with_outer_remaining_evaluator(evaluator) +}} + +pub fn prove_stage1_outer_inputs_with_program( + program: &'static stage1::Stage1CpuProgramPlan, + inputs: stage1::Stage1ProverInputs<'_, {field_type}>, + transcript: &mut T, +) -> Result, stage1::Stage1KernelError> +where + T: Transcript, +{{ + let mut executor = stage1::Stage1ProverKernelExecutor::new(inputs); + stage1_outer_stage::prove_stage1_outer_with_program(program, &mut executor, transcript) +}} + +pub fn prove_stage1_outer_with_witness_inputs( + program: &'static stage1::Stage1CpuProgramPlan, + trace_num_vars: usize, + evaluator: &dyn stage1::Stage1OuterRemainingEvaluator<{field_type}>, + transcript: &mut T, +) -> Result, stage1::Stage1KernelError> +where + T: Transcript, +{{ + let inputs = stage1_outer_prover_inputs(trace_num_vars, evaluator); + prove_stage1_outer_inputs_with_program(program, inputs, transcript) +}} + +pub fn replay_stage1_outer_proof_with_program( + program: &'static stage1::Stage1CpuProgramPlan, + proof: &stage1::Stage1Proof<{field_type}>, + transcript: &mut T, +) -> Result, stage1::Stage1KernelError> +where + T: Transcript, +{{ + let mut executor = stage1::Stage1VerifierKernelExecutor::new(proof); + stage1::execute_stage1_program( + program, + stage1::Stage1ExecutionMode::Verifier, + &mut executor, + transcript, + ) +}} + +pub fn stage1_outer_proof_from_kernel_proof( + proof: &stage1::Stage1Proof<{field_type}>, +) -> JoltStageProof {{ + JoltStageProof {{ + sumchecks: proof + .sumchecks + .iter() + .map(stage1_outer_sumcheck) + .collect(), + }} +}} + +pub fn stage2_prover_inputs<'a>( + opening_inputs: &'a [stage2::Stage2OpeningInputValue<{field_type}>], + product_virtual_cycles: &'a [stage2::Stage2ProductVirtualCycle], + instruction_lookup_cycles: &'a [stage2::Stage2InstructionLookupCycle], + ram: &'a stage2::Stage2RamData<'a>, +) -> Result, stage2::Stage2KernelError> {{ + Ok(stage2::Stage2ProverInputs::new(opening_inputs) + .with_product_virtual_witness(product_virtual_cycles)? + .with_instruction_lookup_cycles(instruction_lookup_cycles) + .with_ram_data(ram)) +}} + +pub struct JoltStage2RamDataStorage<'a> {{ + log_k: usize, + start_address: u64, + initial_ram: &'a [u64], + final_ram: &'a [u64], + accesses: Vec, + output_layout: Option, +}} + +impl<'a> JoltStage2RamDataStorage<'a> {{ + pub fn from_kernel(ram: &stage2::Stage2RamData<'a>) -> Self {{ + Self {{ + log_k: ram.log_k, + start_address: ram.start_address, + initial_ram: ram.initial_ram, + final_ram: ram.final_ram, + accesses: ram + .accesses + .iter() + .map(|access| JoltStage2RamAccess {{ + remapped_address: access.remapped_address, + read_value: access.read_value, + write_value: access.write_value, + }}) + .collect(), + output_layout: ram.output_layout.map(|layout| JoltStage2RamOutputLayout {{ + io_start: layout.io_start, + io_end: layout.io_end, + }}), + }} + }} + + pub fn as_input(&self) -> JoltStage2RamData<'_> {{ + JoltStage2RamData {{ + log_k: self.log_k, + start_address: self.start_address, + initial_ram: self.initial_ram, + final_ram: self.final_ram, + accesses: &self.accesses, + output_layout: self.output_layout, + }} + }} +}} + +pub fn stage2_verifier_ram_data<'a>( + ram: &stage2::Stage2RamData<'a>, +) -> JoltStage2RamDataStorage<'a> {{ + JoltStage2RamDataStorage::from_kernel(ram) +}} + +pub trait JoltKernelOpeningInput {{ + fn symbol(&self) -> &'static str; + fn point(&self) -> &[{field_type}]; + fn eval(&self) -> {field_type}; +}} + +macro_rules! impl_jolt_kernel_opening_input {{ + ($opening:ty) => {{ + impl JoltKernelOpeningInput for $opening {{ + fn symbol(&self) -> &'static str {{ + self.symbol + }} + + fn point(&self) -> &[{field_type}] {{ + &self.point + }} + + fn eval(&self) -> {field_type} {{ + self.eval + }} + }} + }}; +}} + +impl_jolt_kernel_opening_input!(stage2::Stage2OpeningInputValue<{field_type}>); +impl_jolt_kernel_opening_input!(stage3::Stage3OpeningInputValue<{field_type}>); +impl_jolt_kernel_opening_input!(stage4::Stage4OpeningInputValue<{field_type}>); + +pub fn verifier_opening_inputs_from_kernel(inputs: &[I]) -> Vec +where + I: JoltKernelOpeningInput, +{{ + inputs + .iter() + .map(|input| JoltStageOpeningInputValue {{ + symbol: input.symbol(), + point: input.point().to_vec(), + eval: input.eval(), + }}) + .collect() +}} + +pub fn prove_stage2_inputs_with_program( + program: &'static stage2::Stage2CpuProgramPlan, + inputs: stage2::Stage2ProverInputs<'_, {field_type}>, + transcript: &mut T, +) -> Result, stage2::Stage2KernelError> +where + T: Transcript, +{{ + let mut executor = stage2::Stage2ProverKernelExecutor::new(inputs); + stage2_stage::execute_stage2_prover_with_program(program, &mut executor, transcript) +}} + +pub fn prove_stage2_with_witness_inputs<'a, T>( + program: &'static stage2::Stage2CpuProgramPlan, + opening_inputs: &'a [stage2::Stage2OpeningInputValue<{field_type}>], + product_virtual_cycles: &'a [stage2::Stage2ProductVirtualCycle], + instruction_lookup_cycles: &'a [stage2::Stage2InstructionLookupCycle], + ram: &'a stage2::Stage2RamData<'a>, + transcript: &mut T, +) -> Result, stage2::Stage2KernelError> +where + T: Transcript, +{{ + let inputs = stage2_prover_inputs( + opening_inputs, + product_virtual_cycles, + instruction_lookup_cycles, + ram, + )?; + prove_stage2_inputs_with_program(program, inputs, transcript) +}} + +pub fn stage2_opening_inputs_from_artifacts( + program: &'static stage2::Stage2CpuProgramPlan, + stage1_artifacts: &stage1::Stage1ExecutionArtifacts<{field_type}>, +) -> Result>, JoltOpeningInputError> {{ + program + .opening_inputs + .iter() + .map(|input| {{ + let (point, eval) = match input.source_stage {{ + "stage1" => stage1_opening_claim(stage1_artifacts, input.source_claim)?, + source_stage => {{ + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource {{ + stage: "stage2", + symbol: input.symbol, + source_stage, + }}); + }} + }}; + validate_point_len(input.symbol, input.point_arity, point.len())?; + Ok(stage2::Stage2OpeningInputValue {{ + symbol: input.symbol, + point, + eval, + }}) + }}) + .collect() +}} + +pub fn replay_stage2_proof_with_program<'a, T>( + program: &'static stage2::Stage2CpuProgramPlan, + proof: &'a stage2::Stage2Proof<{field_type}>, + opening_inputs: &'a [stage2::Stage2OpeningInputValue<{field_type}>], + ram: Option<&'a stage2::Stage2RamData<'a>>, + transcript: &mut T, +) -> Result, stage2::Stage2KernelError> +where + T: Transcript, +{{ + let mut executor = stage2::Stage2VerifierKernelExecutor::new(proof, opening_inputs); + if let Some(ram) = ram {{ + executor = executor.with_ram_data(ram); + }} + stage2::execute_stage2_program( + program, + stage2::Stage2ExecutionMode::Verifier, + &mut executor, + transcript, + ) +}} + +pub fn stage3_prover_inputs<'a>( + opening_inputs: &'a [stage3::Stage3OpeningInputValue<{field_type}>], + cycles: &'a [stage3::Stage3Cycle], +) -> stage3::Stage3ProverInputs<'a, {field_type}> {{ + stage3::Stage3ProverInputs::new(opening_inputs).with_cycles(cycles) +}} + +pub fn prove_stage3_inputs_with_program( + program: &'static stage3::Stage3CpuProgramPlan, + inputs: stage3::Stage3ProverInputs<'_, {field_type}>, + transcript: &mut T, +) -> Result, stage3::Stage3KernelError> +where + T: Transcript, +{{ + let mut executor = stage3::Stage3ProverKernelExecutor::new(inputs); + stage3_stage::execute_stage3_prover_with_program(program, &mut executor, transcript) +}} + +pub fn prove_stage3_with_witness_inputs( + program: &'static stage3::Stage3CpuProgramPlan, + opening_inputs: &[stage3::Stage3OpeningInputValue<{field_type}>], + cycles: &[stage3::Stage3Cycle], + transcript: &mut T, +) -> Result, stage3::Stage3KernelError> +where + T: Transcript, +{{ + let inputs = stage3_prover_inputs(opening_inputs, cycles); + prove_stage3_inputs_with_program(program, inputs, transcript) +}} + +pub fn stage3_opening_inputs_from_artifacts( + program: &'static stage3::Stage3CpuProgramPlan, + stage1_artifacts: &stage1::Stage1ExecutionArtifacts<{field_type}>, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts<{field_type}>, +) -> Result>, JoltOpeningInputError> {{ + program + .opening_inputs + .iter() + .map(|input| {{ + let (point, eval) = match input.source_stage {{ + "stage1" => stage1_opening_claim(stage1_artifacts, input.source_claim)?, + "stage2" => stage2_opening_claim(stage2_artifacts, input.source_claim)?, + source_stage => {{ + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource {{ + stage: "stage3", + symbol: input.symbol, + source_stage, + }}); + }} + }}; + validate_point_len(input.symbol, input.point_arity, point.len())?; + Ok(stage3::Stage3OpeningInputValue {{ + symbol: input.symbol, + point, + eval, + }}) + }}) + .collect() +}} + +pub fn replay_stage3_proof_with_program( + program: &'static stage3::Stage3CpuProgramPlan, + proof: &stage3::Stage3Proof<{field_type}>, + opening_inputs: &[stage3::Stage3OpeningInputValue<{field_type}>], + transcript: &mut T, +) -> Result, stage3::Stage3KernelError> +where + T: Transcript, +{{ + let mut executor = stage3::Stage3VerifierKernelExecutor::new(proof, opening_inputs); + stage3::execute_stage3_program( + program, + stage3::Stage3ExecutionMode::Verifier, + &mut executor, + transcript, + ) +}} + +pub fn stage4_prover_inputs<'a>( + opening_inputs: &'a [stage4::Stage4OpeningInputValue<{field_type}>], + register_count: usize, + trace_len: usize, + ram_k: usize, + register_accesses: &'a [stage4::Stage4RegisterAccess], + witness: &'a Stage45SparseTraceWitness<{field_type}>, +) -> stage4::Stage4ProverInputs<'a, {field_type}> {{ + stage4::Stage4ProverInputs::new(opening_inputs).with_stage45_sparse_trace_witness( + register_count, + trace_len, + ram_k, + register_accesses, + witness, + ) +}} + +pub fn prove_stage4_inputs_with_program( + program: &'static stage4::Stage4CpuProgramPlan, + inputs: stage4::Stage4ProverInputs<'_, {field_type}>, + transcript: &mut T, +) -> Result, stage4::Stage4KernelError> +where + T: Transcript, +{{ + let mut executor = stage4::Stage4ProverKernelExecutor::new(inputs); + stage4_stage::execute_stage4_prover_with_program(program, &mut executor, transcript) +}} + +pub fn prove_stage4_with_witness_inputs( + program: &'static stage4::Stage4CpuProgramPlan, + opening_inputs: &[stage4::Stage4OpeningInputValue<{field_type}>], + register_count: usize, + trace_len: usize, + ram_k: usize, + register_accesses: &[stage4::Stage4RegisterAccess], + witness: &Stage45SparseTraceWitness<{field_type}>, + transcript: &mut T, +) -> Result, stage4::Stage4KernelError> +where + T: Transcript, +{{ + let inputs = stage4_prover_inputs( + opening_inputs, + register_count, + trace_len, + ram_k, + register_accesses, + witness, + ); + prove_stage4_inputs_with_program(program, inputs, transcript) +}} + +pub fn prove_stage4_with_trace_witness_inputs( + program: &'static stage4::Stage4CpuProgramPlan, + opening_inputs: &[stage4::Stage4OpeningInputValue<{field_type}>], + register_count: usize, + trace_len: usize, + ram_k: usize, + register_accesses: &[stage4::Stage4RegisterAccess], + ram_accesses: &[stage2::Stage2RamAccess], + transcript: &mut T, +) -> Result, stage4::Stage4KernelError> +where + T: Transcript, +{{ + let witness = stage4::stage4_5_sparse_trace_witness_from_accesses( + register_accesses, + ram_accesses, + ); + prove_stage4_with_witness_inputs( + program, + opening_inputs, + register_count, + trace_len, + ram_k, + register_accesses, + &witness, + transcript, + ) +}} + +pub fn stage4_opening_inputs_from_artifacts( + program: &'static stage4::Stage4CpuProgramPlan, + initial_ram_state: &[u64], + stage2_artifacts: &stage2::Stage2ExecutionArtifacts<{field_type}>, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts<{field_type}>, +) -> Result>, JoltOpeningInputError> {{ + program + .opening_inputs + .iter() + .map(|input| {{ + let (point, eval) = match input.source_stage {{ + "stage2" => stage2_opening_claim(stage2_artifacts, input.source_claim)?, + "stage3" => stage3_opening_claim(stage3_artifacts, input.source_claim)?, + "stage4_precomputed" => {{ + let (point, _) = stage2_opening_claim( + stage2_artifacts, + "stage2.ram_output.opening.RamValFinal", + )?; + stage4_ram_val_init_opening(initial_ram_state, &point) + }} + source_stage => {{ + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource {{ + stage: "stage4", + symbol: input.symbol, + source_stage, + }}); + }} + }}; + opening_input_value(input.symbol, input.point_arity, point, eval) + }}) + .collect() +}} + +pub fn replay_stage4_proof_with_program( + program: &'static stage4::Stage4CpuProgramPlan, + proof: &stage4::Stage4Proof<{field_type}>, + opening_inputs: &[stage4::Stage4OpeningInputValue<{field_type}>], + transcript: &mut T, +) -> Result, stage4::Stage4KernelError> +where + T: Transcript, +{{ + let mut executor = stage4::Stage4VerifierKernelExecutor::new(proof, opening_inputs); + stage4::execute_stage4_program( + program, + stage4::Stage4ExecutionMode::Verifier, + &mut executor, + transcript, + ) +}} + +pub fn stage5_prover_inputs<'a>( + opening_inputs: &'a [stage5::Stage5OpeningInputValue<{field_type}>], + trace_len: usize, + ram_k: usize, + register_count: usize, + lookup_indices: &'a [u128], + lookup_table_indices: &'a [Option], + is_interleaved_operands: &'a [bool], + ra_virtual_log_k_chunk: usize, + witness: &'a Stage45SparseTraceWitness<{field_type}>, +) -> stage5::Stage5ProverInputs<'a, {field_type}> {{ + stage5::Stage5ProverInputs::new(opening_inputs).with_stage45_sparse_trace_witness( + trace_len, + ram_k, + register_count, + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + ra_virtual_log_k_chunk, + witness, + ) +}} + +pub fn prove_stage5_inputs_with_program( + program: &'static stage5::Stage5CpuProgramPlan, + inputs: stage5::Stage5ProverInputs<'_, {field_type}>, + transcript: &mut T, +) -> Result, stage5::Stage5KernelError> +where + T: Transcript, +{{ + let mut executor = stage5::Stage5ProverKernelExecutor::new(inputs); + stage5_stage::execute_stage5_prover_with_program(program, &mut executor, transcript) +}} + +pub fn prove_stage5_with_witness_inputs( + program: &'static stage5::Stage5CpuProgramPlan, + opening_inputs: &[stage5::Stage5OpeningInputValue<{field_type}>], + trace_len: usize, + ram_k: usize, + register_count: usize, + lookup_indices: &[u128], + lookup_table_indices: &[Option], + is_interleaved_operands: &[bool], + ra_virtual_log_k_chunk: usize, + witness: &Stage45SparseTraceWitness<{field_type}>, + transcript: &mut T, +) -> Result, stage5::Stage5KernelError> +where + T: Transcript, +{{ + let inputs = stage5_prover_inputs( + opening_inputs, + trace_len, + ram_k, + register_count, + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + ra_virtual_log_k_chunk, + witness, + ); + prove_stage5_inputs_with_program(program, inputs, transcript) +}} + +pub fn prove_stage5_with_trace_witness_inputs( + program: &'static stage5::Stage5CpuProgramPlan, + opening_inputs: &[stage5::Stage5OpeningInputValue<{field_type}>], + trace_len: usize, + ram_k: usize, + register_count: usize, + lookup_indices: &[u128], + lookup_table_indices: &[Option], + is_interleaved_operands: &[bool], + ra_virtual_log_k_chunk: usize, + register_accesses: &[stage4::Stage4RegisterAccess], + ram_accesses: &[stage2::Stage2RamAccess], + transcript: &mut T, +) -> Result, stage5::Stage5KernelError> +where + T: Transcript, +{{ + let witness = stage4::stage4_5_sparse_trace_witness_from_accesses( + register_accesses, + ram_accesses, + ); + prove_stage5_with_witness_inputs( + program, + opening_inputs, + trace_len, + ram_k, + register_count, + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + ra_virtual_log_k_chunk, + &witness, + transcript, + ) +}} + +pub fn stage5_opening_inputs_from_artifacts( + program: &'static stage5::Stage5CpuProgramPlan, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts<{field_type}>, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts<{field_type}>, +) -> Result>, JoltOpeningInputError> {{ + program + .opening_inputs + .iter() + .map(|input| {{ + let (point, eval) = match input.source_stage {{ + "stage2" => stage2_opening_claim(stage2_artifacts, input.source_claim)?, + "stage4" => stage4_opening_claim(stage4_artifacts, input.source_claim)?, + source_stage => {{ + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource {{ + stage: "stage5", + symbol: input.symbol, + source_stage, + }}); + }} + }}; + opening_input_value(input.symbol, input.point_arity, point, eval) + }}) + .collect() +}} + +pub fn stage5_kernel_proof( + artifacts: &stage5::Stage5ExecutionArtifacts<{field_type}>, +) -> stage5::Stage5Proof<{field_type}> {{ + stage5::Stage5Proof {{ + sumchecks: artifacts.sumchecks.clone(), + }} +}} + +pub fn jolt_proof_through_stage5( + commitments: &[Option], + stage1_artifacts: &stage1::Stage1ExecutionArtifacts<{field_type}>, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts<{field_type}>, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts<{field_type}>, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts<{field_type}>, + stage5_proof: &JoltStageProof, +) -> JoltProof {{ + JoltProof {{ + commitments: commitments.to_vec(), + stage1_outer: stage1_outer_proof(stage1_artifacts), + stage2: stage2_proof(stage2_artifacts), + stage3: stage3_proof(stage3_artifacts), + stage4: stage4_proof(stage4_artifacts), + stage5: stage5_proof.clone(), + stage6: JoltStageProof::default(), + stage7: JoltStageProof::default(), + evaluation: None, + }} +}} + +pub fn jolt_proof_through_stage6( + commitments: &[Option], + stage1_artifacts: &stage1::Stage1ExecutionArtifacts<{field_type}>, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts<{field_type}>, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts<{field_type}>, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts<{field_type}>, + stage5_proof: &JoltStageProof, + stage6_proof: &JoltStageProof, +) -> JoltProof {{ + let mut proof = jolt_proof_through_stage5( + commitments, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + ); + proof.stage6 = stage6_proof.clone(); + proof +}} + +pub fn jolt_proof_through_stage7( + commitments: &[Option], + stage1_artifacts: &stage1::Stage1ExecutionArtifacts<{field_type}>, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts<{field_type}>, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts<{field_type}>, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts<{field_type}>, + stage5_proof: &JoltStageProof, + stage6_proof: &JoltStageProof, + stage7_proof: &JoltStageProof, +) -> JoltProof {{ + let mut proof = jolt_proof_through_stage6( + commitments, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + stage6_proof, + ); + proof.stage7 = stage7_proof.clone(); + proof +}} + +pub fn replay_stage5_proof_with_program( + program: &'static stage5::Stage5CpuProgramPlan, + proof: &stage5::Stage5Proof<{field_type}>, + opening_inputs: &[stage5::Stage5OpeningInputValue<{field_type}>], + transcript: &mut T, +) -> Result, stage5::Stage5KernelError> +where + T: Transcript, +{{ + let mut executor = stage5::Stage5ProofCarryingKernelExecutor::new(proof, opening_inputs); + stage5_stage::execute_stage5_prover_with_program(program, &mut executor, transcript) +}} + +pub fn stage6_witness_from_opening_inputs( + params: Stage6WitnessParams, + cycle_inputs: &[CycleInput], + opening_inputs: &[stage6::Stage6OpeningInputValue<{field_type}>], +) -> Stage6WitnessPolynomials<{field_type}> {{ + stage6::stage6_witness_from_opening_inputs(params, cycle_inputs, opening_inputs) +}} + +pub fn stage6_bytecode_read_raf_data_from_witness_entries( + entries: &[WitnessStage6BytecodeEntry<{field_type}>], + entry_bytecode_index: usize, + num_lookup_tables: usize, +) -> stage6::Stage6BytecodeReadRafDataStorage<{field_type}> {{ + stage6::Stage6BytecodeReadRafDataStorage::from_witness_entries( + entries, + entry_bytecode_index, + num_lookup_tables, + ) +}} + +pub fn stage6_verifier_data_from_witness_entries( + entries: &[WitnessStage6BytecodeEntry<{field_type}>], + entry_bytecode_index: usize, + num_lookup_tables: usize, +) -> JoltStage6VerifierData {{ + JoltStage6VerifierData {{ + bytecode_read_raf: Some(JoltStage6BytecodeReadRafData {{ + entries: entries + .iter() + .map(|entry| JoltStage6BytecodeEntry {{ + address: entry.address, + imm: entry.imm, + circuit_flags: entry.circuit_flags, + rd: entry.rd, + rs1: entry.rs1, + rs2: entry.rs2, + lookup_table: entry.lookup_table, + is_interleaved: entry.is_interleaved, + is_branch: entry.is_branch, + left_is_rs1: entry.left_is_rs1, + left_is_pc: entry.left_is_pc, + right_is_rs2: entry.right_is_rs2, + right_is_imm: entry.right_is_imm, + is_noop: entry.is_noop, + }}) + .collect(), + entry_bytecode_index, + num_lookup_tables, + }}), + }} +}} + +pub fn stage6_prover_inputs<'a>( + opening_inputs: &'a [stage6::Stage6OpeningInputValue<{field_type}>], + bytecode_data: stage6::Stage6BytecodeReadRafData<'a, {field_type}>, + witness: &'a Stage6WitnessPolynomials<{field_type}>, + slices: &'a Stage6WitnessSlices<'a, {field_type}>, + instruction_ra_virtual_d: usize, +) -> stage6::Stage6ProverInputs<'a, {field_type}> {{ + stage6::Stage6ProverInputs::new(opening_inputs).with_stage6_witness( + bytecode_data, + witness, + slices, + instruction_ra_virtual_d, + ) +}} + +pub fn prove_stage6_inputs_with_program( + program: &'static stage6::Stage6CpuProgramPlan, + inputs: stage6::Stage6ProverInputs<'_, {field_type}>, + transcript: &mut T, +) -> Result, stage6::Stage6KernelError> +where + T: Transcript, +{{ + let mut executor = stage6::Stage6ProverKernelExecutor::new(inputs); + stage6_stage::execute_stage6_prover_with_program(program, &mut executor, transcript) +}} + +pub fn prove_stage6_with_witness_inputs( + program: &'static stage6::Stage6CpuProgramPlan, + opening_inputs: &[stage6::Stage6OpeningInputValue<{field_type}>], + bytecode_data: stage6::Stage6BytecodeReadRafData<'_, {field_type}>, + witness: &Stage6WitnessPolynomials<{field_type}>, + slices: &Stage6WitnessSlices<'_, {field_type}>, + instruction_ra_virtual_d: usize, + transcript: &mut T, +) -> Result, stage6::Stage6KernelError> +where + T: Transcript, +{{ + let inputs = stage6_prover_inputs( + opening_inputs, + bytecode_data, + witness, + slices, + instruction_ra_virtual_d, + ); + prove_stage6_inputs_with_program(program, inputs, transcript) +}} + +pub fn prove_stage6_with_trace_witness_inputs( + program: &'static stage6::Stage6CpuProgramPlan, + opening_inputs: &[stage6::Stage6OpeningInputValue<{field_type}>], + bytecode_data: stage6::Stage6BytecodeReadRafData<'_, {field_type}>, + witness_params: Stage6WitnessParams, + cycle_inputs: &[CycleInput], + instruction_ra_virtual_d: usize, + transcript: &mut T, +) -> Result, stage6::Stage6KernelError> +where + T: Transcript, +{{ + let witness = stage6_witness_from_opening_inputs(witness_params, cycle_inputs, opening_inputs); + let slices = witness.slices(); + prove_stage6_with_witness_inputs( + program, + opening_inputs, + bytecode_data, + &witness, + &slices, + instruction_ra_virtual_d, + transcript, + ) +}} + +pub fn stage6_opening_inputs_from_artifacts( + program: &'static stage6::Stage6CpuProgramPlan, + stage1_artifacts: &stage1::Stage1ExecutionArtifacts<{field_type}>, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts<{field_type}>, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts<{field_type}>, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts<{field_type}>, + stage5_artifacts: &stage5::Stage5ExecutionArtifacts<{field_type}>, +) -> Result>, JoltOpeningInputError> {{ + program + .opening_inputs + .iter() + .map(|input| {{ + let (point, eval) = match input.source_stage {{ + "stage1" => stage1_opening_claim(stage1_artifacts, input.source_claim)?, + "stage2" => stage2_opening_claim(stage2_artifacts, input.source_claim)?, + "stage3" => stage3_opening_claim(stage3_artifacts, input.source_claim)?, + "stage4" => stage4_opening_claim(stage4_artifacts, input.source_claim)?, + "stage5" => stage5_opening_claim(stage5_artifacts, input.source_claim)?, + source_stage => {{ + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource {{ + stage: "stage6", + symbol: input.symbol, + source_stage, + }}); + }} + }}; + opening_input_value(input.symbol, input.point_arity, point, eval) + }}) + .collect() +}} + +pub fn stage6_kernel_proof(proof: &JoltStageProof) -> stage6::Stage6Proof<{field_type}> {{ + stage6::Stage6Proof {{ + sumchecks: proof + .sumchecks + .iter() + .map(stage6_kernel_sumcheck_output) + .collect(), + }} +}} + +fn stage6_kernel_sumcheck_output( + output: &JoltSumcheckOutput, +) -> stage6::Stage6SumcheckOutput<{field_type}> {{ + stage6::Stage6SumcheckOutput {{ + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage6_kernel_eval).collect(), + opening_claims: Vec::new(), + proof: output.proof.clone(), + }} +}} + +fn stage6_kernel_eval(eval: &JoltNamedEval) -> stage6::Stage6NamedEval<{field_type}> {{ + stage6::Stage6NamedEval {{ + name: eval.name, + oracle: eval.oracle, + value: eval.value, + }} +}} + +pub fn stage6_execution_artifacts( + artifacts: &stage6::Stage6ExecutionArtifacts<{field_type}>, +) -> JoltStageExecutionArtifacts {{ + JoltStageExecutionArtifacts {{ + challenge_vectors: artifacts + .challenge_vectors + .iter() + .map(|challenge| JoltStageChallengeVector {{ + symbol: challenge.symbol, + values: challenge.values.clone(), + }}) + .collect(), + sumchecks: stage6_proof(artifacts).sumchecks, + opening_batches: Vec::new(), + }} +}} + +pub fn replay_stage6_proof_with_program<'a, T>( + program: &'static stage6::Stage6CpuProgramPlan, + proof: &'a stage6::Stage6Proof<{field_type}>, + opening_inputs: &'a [stage6::Stage6OpeningInputValue<{field_type}>], + bytecode_data: Option>, + transcript: &mut T, +) -> Result, stage6::Stage6KernelError> +where + T: Transcript, +{{ + let mut executor = stage6::Stage6ProofCarryingKernelExecutor::new(proof, opening_inputs); + if let Some(bytecode_data) = bytecode_data {{ + executor = executor.with_bytecode_read_raf_data(bytecode_data); + }} + stage6_stage::execute_stage6_prover_with_program(program, &mut executor, transcript) +}} + +pub fn stage7_prover_inputs<'a>( + opening_inputs: &'a [stage7::Stage7OpeningInputValue<{field_type}>], + slices: &'a Stage6WitnessSlices<'a, {field_type}>, +) -> stage7::Stage7ProverInputs<'a, {field_type}> {{ + stage7::Stage7ProverInputs::new(opening_inputs).with_stage6_witness_indices(slices) +}} + +pub fn prove_stage7_inputs_with_program( + program: &'static stage7::Stage7CpuProgramPlan, + inputs: stage7::Stage7ProverInputs<'_, {field_type}>, + transcript: &mut T, +) -> Result, stage7::Stage7KernelError> +where + T: Transcript, +{{ + let mut executor = stage7::Stage7ProverKernelExecutor::new(inputs); + stage7_stage::execute_stage7_prover_with_program(program, &mut executor, transcript) +}} + +pub fn prove_stage7_with_witness_inputs( + program: &'static stage7::Stage7CpuProgramPlan, + opening_inputs: &[stage7::Stage7OpeningInputValue<{field_type}>], + slices: &Stage6WitnessSlices<'_, {field_type}>, + transcript: &mut T, +) -> Result, stage7::Stage7KernelError> +where + T: Transcript, +{{ + let inputs = stage7_prover_inputs(opening_inputs, slices); + prove_stage7_inputs_with_program(program, inputs, transcript) +}} + +pub fn prove_stage7_with_trace_witness_inputs( + program: &'static stage7::Stage7CpuProgramPlan, + opening_inputs: &[stage7::Stage7OpeningInputValue<{field_type}>], + witness_params: Stage6WitnessParams, + cycle_inputs: &[CycleInput], + stage6_openings: &[stage6::Stage6OpeningInputValue<{field_type}>], + transcript: &mut T, +) -> Result, stage7::Stage7KernelError> +where + T: Transcript, +{{ + let witness = stage6_witness_from_opening_inputs(witness_params, cycle_inputs, stage6_openings); + let slices = witness.slices(); + prove_stage7_with_witness_inputs(program, opening_inputs, &slices, transcript) +}} + +pub fn stage7_kernel_proof(proof: &JoltStageProof) -> stage7::Stage7Proof<{field_type}> {{ + stage7::Stage7Proof {{ + sumchecks: proof + .sumchecks + .iter() + .map(stage7_kernel_sumcheck_output) + .collect(), + }} +}} + +fn stage7_kernel_sumcheck_output( + output: &JoltSumcheckOutput, +) -> stage7::Stage7SumcheckOutput<{field_type}> {{ + stage7::Stage7SumcheckOutput {{ + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage7_kernel_eval).collect(), + opening_claims: Vec::new(), + proof: output.proof.clone(), + }} +}} + +fn stage7_kernel_eval(eval: &JoltNamedEval) -> stage7::Stage7NamedEval<{field_type}> {{ + stage7::Stage7NamedEval {{ + name: eval.name, + oracle: eval.oracle, + value: eval.value, + }} +}} + +pub fn stage7_execution_artifacts( + artifacts: &stage7::Stage7ExecutionArtifacts<{field_type}>, +) -> JoltStageExecutionArtifacts {{ + JoltStageExecutionArtifacts {{ + challenge_vectors: artifacts + .challenge_vectors + .iter() + .map(|challenge| JoltStageChallengeVector {{ + symbol: challenge.symbol, + values: challenge.values.clone(), + }}) + .collect(), + sumchecks: stage7_proof(artifacts).sumchecks, + opening_batches: Vec::new(), + }} +}} + +pub fn replay_stage7_proof_with_program( + program: &'static stage7::Stage7CpuProgramPlan, + proof: &stage7::Stage7Proof<{field_type}>, + opening_inputs: &[stage7::Stage7OpeningInputValue<{field_type}>], + transcript: &mut T, +) -> Result, stage7::Stage7KernelError> +where + T: Transcript, +{{ + let mut executor = stage7::Stage7ProofCarryingKernelExecutor::new(proof, opening_inputs); + stage7_stage::execute_stage7_prover_with_program(program, &mut executor, transcript) +}} + +pub fn stage7_opening_inputs_from_stage6_artifacts( + artifacts: &stage6::Stage6ExecutionArtifacts<{field_type}>, +) -> Result>, JoltOpeningInputError> {{ + stage7_opening_inputs_from_stage6_artifacts_with_program(&stage7_stage::STAGE7_PROGRAM, artifacts) +}} + +pub fn stage7_opening_inputs_from_stage6_artifacts_with_program( + program: &'static stage7::Stage7CpuProgramPlan, + artifacts: &stage6::Stage6ExecutionArtifacts<{field_type}>, +) -> Result>, JoltOpeningInputError> {{ + program + .opening_inputs + .iter() + .map(|input| {{ + let (point, eval) = stage6_opening_claim(artifacts, input.symbol, input.source_stage, input.source_claim, input.point_arity)?; + Ok(stage7::Stage7OpeningInputValue {{ + symbol: input.symbol, + point, + eval, + }}) + }}) + .collect() +}} + +fn stage6_opening_claim( + artifacts: &stage6::Stage6ExecutionArtifacts<{field_type}>, + symbol: &'static str, + source_stage: &'static str, + source_claim: &'static str, + point_arity: usize, +) -> Result<(Vec<{field_type}>, {field_type}), JoltOpeningInputError> {{ + if source_stage != "stage6" {{ + return Err(JoltOpeningInputError::UnsupportedStage7InputSource {{ + symbol, + source_stage, + }}); + }} + let opening = artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .ok_or(JoltOpeningInputError::MissingStage6OpeningClaim {{ source_claim }})?; + if opening.point.len() != point_arity {{ + return Err(JoltOpeningInputError::InvalidPointLength {{ + symbol, + expected: point_arity, + actual: opening.point.len(), + }}); + }} + Ok((opening.point.clone(), opening.eval)) +}} + +fn opening_input_value( + symbol: &'static str, + point_arity: usize, + point: Vec<{field_type}>, + eval: {field_type}, +) -> Result, JoltOpeningInputError> {{ + validate_point_len(symbol, point_arity, point.len())?; + Ok(stage4::Stage4OpeningInputValue {{ + symbol, + point, + eval, + }}) +}} + +fn validate_point_len( + symbol: &'static str, + expected: usize, + actual: usize, +) -> Result<(), JoltOpeningInputError> {{ + if actual != expected {{ + return Err(JoltOpeningInputError::InvalidPointLength {{ + symbol, + expected, + actual, + }}); + }} + Ok(()) +}} + +fn stage1_opening_claim( + artifacts: &stage1::Stage1ExecutionArtifacts<{field_type}>, + source_claim: &'static str, +) -> Result<(Vec<{field_type}>, {field_type}), JoltOpeningInputError> {{ + let opening = artifacts.opening_value(source_claim).ok_or( + JoltOpeningInputError::MissingOpeningClaim {{ + stage: "stage1", + source_claim, + }}, + )?; + Ok((opening.point.clone(), opening.eval)) +}} + +fn stage2_opening_claim( + artifacts: &stage2::Stage2ExecutionArtifacts<{field_type}>, + source_claim: &'static str, +) -> Result<(Vec<{field_type}>, {field_type}), JoltOpeningInputError> {{ + artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .map(|opening| (opening.point.clone(), opening.eval)) + .ok_or(JoltOpeningInputError::MissingOpeningClaim {{ + stage: "stage2", + source_claim, + }}) +}} + +fn stage3_opening_claim( + artifacts: &stage3::Stage3ExecutionArtifacts<{field_type}>, + source_claim: &'static str, +) -> Result<(Vec<{field_type}>, {field_type}), JoltOpeningInputError> {{ + artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .map(|opening| (opening.point.clone(), opening.eval)) + .ok_or(JoltOpeningInputError::MissingOpeningClaim {{ + stage: "stage3", + source_claim, + }}) +}} + +fn stage4_opening_claim( + artifacts: &stage4::Stage4ExecutionArtifacts<{field_type}>, + source_claim: &'static str, +) -> Result<(Vec<{field_type}>, {field_type}), JoltOpeningInputError> {{ + artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .map(|opening| (opening.point.clone(), opening.eval)) + .ok_or(JoltOpeningInputError::MissingOpeningClaim {{ + stage: "stage4", + source_claim, + }}) +}} + +fn stage5_opening_claim( + artifacts: &stage5::Stage5ExecutionArtifacts<{field_type}>, + source_claim: &'static str, +) -> Result<(Vec<{field_type}>, {field_type}), JoltOpeningInputError> {{ + artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .map(|opening| (opening.point.clone(), opening.eval)) + .ok_or(JoltOpeningInputError::MissingOpeningClaim {{ + stage: "stage5", + source_claim, + }}) +}} + +"# + ) +} + +fn jolt_prover_evaluation_helpers(field_type: &str) -> String { + format!( + r#"pub fn prove_jolt_evaluation_proof( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + commitment_inputs: &mut I, + prover_setup: &DoryProverSetup, + commitments: &commitment_stage::CommitmentArtifacts, + stage6: &stage6::Stage6ExecutionArtifacts<{field_type}>, + stage7: &stage7::Stage7ExecutionArtifacts<{field_type}>, + stage7_openings: &[stage7::Stage7OpeningInputValue<{field_type}>], + transcript: &mut T, +) -> Result +where + I: commitment_stage::CommitmentInputProvider, + T: Transcript, +{{ + let _claims_span = tracing::info_span!("bolt.evaluate.claims").entered(); + let (sumcheck_address_point, stage7_values) = stage7_claim_values(program, stage7)?; + let address_point = reverse_point(&sumcheck_address_point); + let (opening_point, log_t) = + stage7_evaluation_opening_point(program, &address_point, stage7_openings)?; + let lagrange_factor = EqPolynomial::<{field_type}>::zero_selector(&address_point); + let claims = evaluation_claims(program, stage6, &stage7_values, lagrange_factor)?; + drop(_claims_span); + + let _rlc_span = tracing::info_span!("bolt.evaluate.rlc_claims").entered(); + append_rlc_claims(transcript, &claims); + let gamma_powers = gamma_powers(transcript, claims.len()); + let joint_claim = claims + .iter() + .zip(&gamma_powers) + .map(|(claim, gamma)| claim.value * *gamma) + .sum(); + drop(_rlc_span); + let _materialize_span = + tracing::info_span!("bolt.evaluate.materialize_joint_polynomial").entered(); + let joint_evals = materialize_joint_polynomial( + commitment_inputs, + &claims, + &gamma_powers, + log_t, + opening_point.len(), + )?; + drop(_materialize_span); + let joint_poly = Polynomial::new(joint_evals); + let _hint_span = tracing::info_span!("bolt.evaluate.joint_opening_hint").entered(); + let joint_hint = joint_opening_hint(commitments, &claims, &gamma_powers)?; + drop(_hint_span); + let _dory_open_span = tracing::info_span!("bolt.evaluate.dory_open").entered(); + let joint_opening_proof = ::open( + &joint_poly, + &opening_point, + joint_claim, + prover_setup, + Some(joint_hint), + transcript, + ); + drop(_dory_open_span); + let _bind_span = tracing::info_span!("bolt.evaluate.bind_opening_inputs").entered(); + ::bind_opening_inputs( + transcript, + &opening_point, + &joint_claim, + ); + drop(_bind_span); + Ok(JoltEvaluationProof {{ joint_opening_proof }}) +}} + +struct EvaluationClaim {{ + oracle: &'static str, + source_stage: &'static str, + value: {field_type}, +}} + +fn stage6_eval_claim( + artifacts: &stage6::Stage6ExecutionArtifacts<{field_type}>, + eval_name: &'static str, +) -> Result<{field_type}, JoltEvaluationProveError> {{ + for output in &artifacts.sumchecks {{ + if let Some(eval) = output.evals.iter().find(|eval| eval.name == eval_name) {{ + return Ok(eval.value); + }} + }} + Err(JoltEvaluationProveError::MissingStageEval {{ + stage: "stage6", + eval: eval_name, + }}) +}} + +fn evaluation_claims( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + stage6: &stage6::Stage6ExecutionArtifacts<{field_type}>, + stage7_values: &std::collections::BTreeMap<&'static str, {field_type}>, + lagrange_factor: {field_type}, +) -> Result, JoltEvaluationProveError> {{ + let mut claims = Vec::with_capacity(program.opening_claims.len()); + for plan in program.opening_claims {{ + let value = match plan.source_stage {{ + "stage6" => stage6_eval_claim(stage6, plan.source_claim)? * lagrange_factor, + "stage7" => *stage7_values.get(plan.source_claim).ok_or( + JoltEvaluationProveError::MissingStageEval {{ + stage: plan.source_stage, + eval: plan.source_claim, + }}, + )?, + _ => {{ + return Err(JoltEvaluationProveError::MissingStageEval {{ + stage: plan.source_stage, + eval: plan.source_claim, + }}); + }} + }}; + claims.push(EvaluationClaim {{ + oracle: plan.oracle, + source_stage: plan.source_stage, + value, + }}); + }} + Ok(claims) +}} + +fn stage7_claim_values( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + artifacts: &stage7::Stage7ExecutionArtifacts<{field_type}>, +) -> Result<(Vec<{field_type}>, std::collections::BTreeMap<&'static str, {field_type}>), JoltEvaluationProveError> {{ + let stage7_plans = program + .opening_claims + .iter() + .filter(|plan| plan.source_stage == "stage7") + .collect::>(); + for output in &artifacts.sumchecks {{ + let mut values = std::collections::BTreeMap::new(); + for plan in &stage7_plans {{ + if let Some(eval) = output.evals.iter().find(|eval| eval.name == plan.source_claim) {{ + let _ = values.insert(plan.source_claim, eval.value); + }} + }} + if values.len() == stage7_plans.len() {{ + return Ok((output.point.clone(), values)); + }} + }} + Err(JoltEvaluationProveError::MissingStage7RaEval) +}} + +fn reverse_point(point: &[{field_type}]) -> Vec<{field_type}> {{ + point.iter().rev().copied().collect() +}} + +fn stage7_evaluation_opening_point( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + address_point: &[{field_type}], + stage7_openings: &[stage7::Stage7OpeningInputValue<{field_type}>], +) -> Result<(Vec<{field_type}>, usize), JoltEvaluationProveError> {{ + let cycle_source_symbol = program.evaluation_point_source.source_claim; + let cycle_source = stage7_openings + .iter() + .find(|input| input.symbol == cycle_source_symbol) + .ok_or(JoltEvaluationProveError::MissingStage7EvaluationPoint)?; + if cycle_source.point.len() < address_point.len() {{ + return Err(JoltEvaluationProveError::InvalidPointLength {{ + artifact: cycle_source_symbol, + expected: address_point.len(), + actual: cycle_source.point.len(), + }}); + }} + let cycle_len = cycle_source.point.len() - address_point.len(); + let mut point = Vec::with_capacity(cycle_source.point.len()); + point.extend_from_slice(address_point); + point.extend_from_slice(&cycle_source.point[address_point.len()..]); + Ok((point, cycle_len)) +}} + +fn append_rlc_claims(transcript: &mut T, claims: &[EvaluationClaim]) +where + T: Transcript, +{{ + transcript.append(&LabelWithCount(b"rlc_claims", claims.len() as u64)); + for claim in claims {{ + claim.value.append_to_transcript(transcript); + }} +}} + +fn gamma_powers(transcript: &mut T, count: usize) -> Vec<{field_type}> +where + T: Transcript, +{{ + let gamma = transcript.challenge(); + let mut powers = Vec::with_capacity(count); + let mut power = {field_type}::from_u64(1); + for _ in 0..count {{ + powers.push(power); + power *= gamma; + }} + powers +}} + +fn materialize_joint_polynomial( + commitment_inputs: &mut I, + claims: &[EvaluationClaim], + gamma_powers: &[{field_type}], + log_t: usize, + main_num_vars: usize, +) -> Result, JoltEvaluationProveError> +where + I: commitment_stage::CommitmentInputProvider, +{{ + let trace_len = target_len(log_t)?; + let main_len = target_len(main_num_vars)?; + let mut joint = vec![{field_type}::from_u64(0); main_len]; + for (claim, gamma) in claims.iter().zip(gamma_powers) {{ + if claim.source_stage == "stage6" {{ + add_oracle_scaled(commitment_inputs, &mut joint, claim.oracle, log_t, trace_len, *gamma)?; + }} else {{ + add_oracle_scaled( + commitment_inputs, + &mut joint, + claim.oracle, + main_num_vars, + main_len, + *gamma, + )?; + }} + }} + Ok(joint) +}} + +fn add_oracle_scaled( + commitment_inputs: &mut I, + joint: &mut [{field_type}], + oracle: &'static str, + num_vars: usize, + limit: usize, + scalar: {field_type}, +) -> Result<(), JoltEvaluationProveError> +where + I: commitment_stage::CommitmentInputProvider, +{{ + if commitment_inputs.add_scaled_to_joint(oracle, joint, num_vars, limit, scalar) {{ + return Ok(()); + }} + let target_len = target_len(num_vars)?; + let data = commitment_inputs + .materialize_with_num_vars(oracle, num_vars) + .ok_or(JoltEvaluationProveError::MissingOracle {{ oracle }})?; + if data.len() > target_len {{ + return Err(JoltEvaluationProveError::InvalidPointLength {{ + artifact: oracle, + expected: target_len, + actual: data.len(), + }}); + }} + let zero = {field_type}::from_u64(0); + let one = {field_type}::from_u64(1); + let len = limit.min(joint.len()).min(data.len()); + if len >= 1 << 15 {{ + joint[..len] + .par_iter_mut() + .zip(data[..len].par_iter()) + .for_each(|(dst, value)| {{ + if *value == zero {{ + return; + }} + if *value == one {{ + *dst += scalar; + }} else {{ + *dst += *value * scalar; + }} + }}); + }} else {{ + for (dst, value) in joint.iter_mut().take(len).zip(data.iter()) {{ + if *value == zero {{ + continue; + }} + if *value == one {{ + *dst += scalar; + }} else {{ + *dst += *value * scalar; + }} + }} + }} + Ok(()) +}} + +fn joint_opening_hint( + commitments: &commitment_stage::CommitmentArtifacts, + claims: &[EvaluationClaim], + gamma_powers: &[{field_type}], +) -> Result {{ + let mut coefficients = std::collections::BTreeMap::<&'static str, {field_type}>::new(); + for (claim, gamma) in claims.iter().zip(gamma_powers) {{ + let coefficient = coefficients.entry(claim.oracle).or_insert({field_type}::from_u64(0)); + *coefficient += *gamma; + }} + + let mut hints = Vec::with_capacity(coefficients.len()); + let mut scalars = Vec::with_capacity(coefficients.len()); + for (oracle, coefficient) in coefficients {{ + hints.push(opening_hint_for_oracle(commitments, oracle)?); + scalars.push(coefficient); + }} + + Ok(::combine_hints( + hints, &scalars, + )) +}} + +fn opening_hint_for_oracle( + commitments: &commitment_stage::CommitmentArtifacts, + oracle: &'static str, +) -> Result {{ + commitments + .hints + .iter() + .find(|hint| hint.oracle == oracle) + .map(|hint| hint.hint.clone()) + .ok_or(JoltEvaluationProveError::MissingOpeningHint {{ oracle }}) +}} + +fn target_len(num_vars: usize) -> Result {{ + if num_vars >= usize::BITS as usize {{ + return Err(JoltEvaluationProveError::TargetSizeOverflow {{ num_vars }}); + }} + Ok(1usize << num_vars) +}} + +"# + ) +} + +const PROVER_FORBIDDEN_IMPORTS: &[&str] = &[ + "use jolt_core", + "jolt_core::", + "use jolt_verifier::stages", + "jolt_verifier::stages::", + "use jolt_equivalence", + "jolt_equivalence::", + "use jolt_profiling", + "jolt_profiling::", +]; + +const VERIFIER_FORBIDDEN_IMPORTS: &[&str] = &[ + "use jolt_kernels", + "jolt_kernels::", + "use jolt_prover", + "jolt_prover::", + "use jolt_core", + "jolt_core::", + "use jolt_equivalence", + "jolt_equivalence::", + "use jolt_profiling", + "jolt_profiling::", + "tracer::", +]; diff --git a/crates/bolt/src/protocols/jolt/emit/mod.rs b/crates/bolt/src/protocols/jolt/emit/mod.rs new file mode 100644 index 0000000000..0ad9e7d3d7 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/mod.rs @@ -0,0 +1 @@ +pub mod rust; diff --git a/crates/bolt/src/protocols/jolt/emit/rust/commitment.rs b/crates/bolt/src/protocols/jolt/emit/rust/commitment.rs new file mode 100644 index 0000000000..defe5c58c1 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/commitment.rs @@ -0,0 +1,1748 @@ +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{Attribute, OperationRef}; + +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CommitmentCpuProgram { + pub role: Role, + pub params: CommitmentParams, + pub oracle_plans: Vec, + pub batch_plans: Vec, + pub optional_plans: Vec, + pub transcript_steps: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CommitmentParams { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OraclePlan { + pub oracle: String, + pub source: String, + pub domain: String, + pub num_vars: usize, + pub generation: OracleGeneration, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum OracleGeneration { + Reference, + DenseTrace { + padding: String, + }, + OneHotChunk { + trace_num_vars: usize, + chunk: usize, + num_chunks: usize, + chunk_bits: usize, + padding: String, + layout: String, + }, + OptionalAdvice { + skip_policy: OptionalSkipPolicy, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CommitmentBatchPlan { + pub artifact: String, + pub pcs: String, + pub oracle_family: String, + pub label: String, + pub oracles: Vec, + pub count: usize, + pub domain: String, + pub num_vars: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OptionalCommitmentPlan { + pub artifact: String, + pub pcs: String, + pub oracle: String, + pub label: String, + pub domain: String, + pub num_vars: usize, + pub skip_policy: OptionalSkipPolicy, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum OptionalSkipPolicy { + MissingOrZero, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TranscriptStep { + pub label: String, + pub source: String, + pub optional: bool, +} + +pub fn emit_commitment_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = commitment_cpu_program(module)?; + + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source()?, + }) +} + +pub fn commitment_cpu_program( + module: &BoltModule<'_, Cpu>, +) -> Result { + verify_cpu_schema(module)?; + let program = CommitmentCpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +impl CommitmentCpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let mut params = None; + let mut oracle_plans = Vec::new(); + let mut batch_plans = Vec::new(); + let mut optional_plans = Vec::new(); + let mut transcript_steps = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(CommitmentParams { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.oracle_dense_trace" => { + oracle_plans.push(OraclePlan { + oracle: symbol_attr(op, "oracle")?, + source: symbol_attr(op, "source")?, + domain: symbol_attr(op, "domain")?, + num_vars: int_attr(op, "num_vars")?, + generation: OracleGeneration::DenseTrace { + padding: string_attr(op, "padding")?, + }, + }); + } + "cpu.oracle_one_hot_chunk" => { + oracle_plans.push(OraclePlan { + oracle: symbol_attr(op, "oracle")?, + source: symbol_attr(op, "source")?, + domain: symbol_attr(op, "domain")?, + num_vars: int_attr(op, "num_vars")?, + generation: OracleGeneration::OneHotChunk { + trace_num_vars: int_attr(op, "trace_num_vars")?, + chunk: int_attr(op, "chunk")?, + num_chunks: int_attr(op, "num_chunks")?, + chunk_bits: int_attr(op, "chunk_bits")?, + padding: string_attr(op, "padding")?, + layout: string_attr(op, "layout")?, + }, + }); + } + "cpu.oracle_optional_advice" => { + oracle_plans.push(OraclePlan { + oracle: symbol_attr(op, "oracle")?, + source: symbol_attr(op, "source")?, + domain: symbol_attr(op, "domain")?, + num_vars: int_attr(op, "num_vars")?, + generation: OracleGeneration::OptionalAdvice { + skip_policy: skip_policy_attr(op, "skip_policy")?, + }, + }); + } + "cpu.oracle_ref" => { + oracle_plans.push(OraclePlan { + oracle: symbol_attr(op, "oracle")?, + source: String::new(), + domain: symbol_attr(op, "domain")?, + num_vars: int_attr(op, "num_vars")?, + generation: OracleGeneration::Reference, + }); + } + "cpu.pcs_commit_batch" | "cpu.pcs_receive_batch" => { + batch_plans.push(CommitmentBatchPlan { + artifact: symbol_attr(op, "artifact")?, + pcs: symbol_attr(op, "pcs")?, + oracle_family: symbol_attr(op, "oracle_family")?, + label: string_attr(op, "label")?, + oracles: symbol_array_attr(op, "ordered_oracles")?, + count: int_attr(op, "count")?, + domain: symbol_attr(op, "domain")?, + num_vars: int_attr(op, "num_vars")?, + }); + } + "cpu.pcs_commit_optional" | "cpu.pcs_receive_optional" => { + optional_plans.push(OptionalCommitmentPlan { + artifact: symbol_attr(op, "artifact")?, + pcs: symbol_attr(op, "pcs")?, + oracle: symbol_attr(op, "oracle")?, + label: string_attr(op, "label")?, + domain: symbol_attr(op, "domain")?, + num_vars: int_attr(op, "num_vars")?, + skip_policy: skip_policy_attr(op, "skip_policy")?, + }); + } + "cpu.transcript_absorb" => { + transcript_steps.push(TranscriptStep { + label: string_attr(op, "label")?, + source: transcript_artifact_source(op)?, + optional: bool_attr(op, "optional")?, + }); + } + _ => {} + } + } + + Ok(Self { + params: params.ok_or_else(|| EmitError::new("missing cpu.params"))?, + role: module + .role() + .ok_or_else(|| EmitError::new("missing cpu party role"))?, + oracle_plans, + batch_plans, + optional_plans, + transcript_steps, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + require_supported_symbol("field", &self.params.field, "bn254_fr")?; + require_supported_symbol("pcs", &self.params.pcs, "dory")?; + require_supported_symbol("transcript", &self.params.transcript, "blake2b_transcript")?; + for plan in &self.batch_plans { + require_supported_symbol("batch pcs", &plan.pcs, "dory")?; + } + for plan in &self.optional_plans { + require_supported_symbol("optional pcs", &plan.pcs, "dory")?; + } + Ok(()) + } + + fn emit_source(&self) -> Result { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + source.push_str(self.emit_imports()); + source.push_str("\n\n"); + source.push_str(&self.emit_types()?); + source.push('\n'); + source.push_str(&self.emit_constants()); + source.push('\n'); + source.push_str(self.emit_entrypoint()); + Ok(source) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_commitment_phase.rs", + Role::Verifier => "verify_commitment_phase.rs", + } + } + + fn emit_imports(&self) -> &'static str { + match self.role { + Role::Prover => { + "use std::borrow::Cow;\n\ + \n\ + use jolt_dory::{DoryCommitment, DoryHint, DoryProverSetup, DoryScheme};\n\ + use jolt_field::{Field, Fr};\n\ + use jolt_openings::CommitmentScheme as _;\n\ + use jolt_poly::{EqPolynomial, MultilinearPoly};\n\ + use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript};\n\ + use jolt_witness::{dense_i128_column_to_field, one_hot_chunk_address_major, one_hot_chunk_indices, optional_field_oracle, CommitmentTraceSources};\n\ + use rayon::prelude::*;" + } + Role::Verifier => { + "use jolt_dory::DoryCommitment;\n\ + use jolt_field::Fr;\n\ + use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript};" + } + } + } + + fn emit_types(&self) -> Result { + match self.role { + Role::Prover => { + let mut types = Self::emit_prover_types().to_owned(); + types.push('\n'); + types.push_str(&self.emit_oracle_store_types()?); + Ok(types) + } + Role::Verifier => Ok(Self::emit_verifier_types().to_owned()), + } + } + + fn emit_prover_types() -> &'static str { + r"pub type DefaultCommitmentTranscript = Blake2bTranscript; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentParams { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OraclePlan { + pub oracle: &'static str, + pub domain: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentBatchPlan { + pub artifact: &'static str, + pub pcs: &'static str, + pub oracle_family: &'static str, + pub label: &'static str, + pub oracles: &'static [&'static str], + pub count: usize, + pub domain: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OptionalSkipPolicy { + MissingOrZero, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OptionalCommitmentPlan { + pub artifact: &'static str, + pub pcs: &'static str, + pub oracle: &'static str, + pub label: &'static str, + pub domain: &'static str, + pub num_vars: usize, + pub skip_policy: OptionalSkipPolicy, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TranscriptStep { + pub label: &'static str, + pub source: &'static str, + pub optional: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentProverProgramPlan { + pub params: CommitmentParams, + pub oracle_plans: &'static [OraclePlan], + pub batch_plans: &'static [CommitmentBatchPlan], + pub optional_plans: &'static [OptionalCommitmentPlan], + pub transcript_steps: &'static [TranscriptStep], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentRecord { + pub artifact: &'static str, + pub oracle: &'static str, + pub label: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Debug)] +pub struct OracleOpeningHint { + pub oracle: &'static str, + pub hint: DoryHint, +} + +#[derive(Clone, Debug)] +pub struct CommittedOracle { + pub commitment: Option, + pub record: CommitmentRecord, + pub hint: Option, +} + +#[derive(Clone, Debug, Default)] +pub struct CommitmentArtifacts { + pub commitments: Vec>, + pub records: Vec, + pub hints: Vec, +} + +pub trait CommitmentInputProvider { + fn materialize(&mut self, oracle: &'static str) -> Option>; + + fn materialize_with_num_vars( + &mut self, + oracle: &'static str, + _num_vars: usize, + ) -> Option> { + self.materialize(oracle) + } + + fn commit_batch( + &mut self, + _program: &CommitmentProverProgramPlan, + _plan: &CommitmentBatchPlan, + _prover_setup: &DoryProverSetup, + ) -> Option, CommitmentPhaseError>> { + None + } + + fn add_scaled_to_joint( + &mut self, + _oracle: &'static str, + _joint: &mut [Fr], + _num_vars: usize, + _limit: usize, + _scalar: Fr, + ) -> bool { + false + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CommitmentPhaseError { + MissingOracle { oracle: &'static str }, + MissingTranscriptSource { source: &'static str }, + PlanCountMismatch { artifact: &'static str, expected: usize, actual: usize }, + OracleTooLarge { oracle: &'static str, len: usize, target_len: usize }, + TargetSizeOverflow { num_vars: usize }, +}" + } + + fn emit_oracle_store_types(&self) -> Result { + let input_type = r" +pub struct CommitmentOracleInputs<'a> { + pub rd_inc: &'a [i128], + pub ram_inc: &'a [i128], + pub instruction_keys: &'a [Option], + pub ram_addresses: &'a [Option], + pub bytecode_indices: &'a [Option], + pub untrusted_advice: Option<&'a [Fr]>, + pub trusted_advice: Option<&'a [Fr]>, +} + +impl<'a> CommitmentOracleInputs<'a> { + pub fn from_trace_sources( + sources: &'a CommitmentTraceSources, + untrusted_advice: Option<&'a [Fr]>, + trusted_advice: Option<&'a [Fr]>, + ) -> Self { + Self { + rd_inc: &sources.rd_inc, + ram_inc: &sources.ram_inc, + instruction_keys: &sources.instruction_keys, + ram_addresses: &sources.ram_addresses, + bytecode_indices: &sources.bytecode_indices, + untrusted_advice, + trusted_advice, + } + } +} +"; + let sparse_provider = r#" +struct AddressMajorOneHotPolynomial { + trace_len: usize, + chunk_domain: usize, + indices: Vec>, + num_vars: usize, +} + +impl AddressMajorOneHotPolynomial { + fn new( + trace_len: usize, + chunk_domain: usize, + indices: Vec>, + num_vars: usize, + ) -> Result { + let active_len = trace_len + .checked_mul(chunk_domain) + .ok_or(CommitmentPhaseError::TargetSizeOverflow { num_vars })?; + let target_len = target_len(num_vars)?; + if active_len > target_len { + return Err(CommitmentPhaseError::OracleTooLarge { + oracle: "one_hot", + len: active_len, + target_len, + }); + } + Ok(Self { + trace_len, + chunk_domain, + indices, + num_vars, + }) + } + + fn nonzero_flat_indices(&self) -> impl Iterator + '_ { + self.indices + .iter() + .enumerate() + .filter_map(|(cycle, &index)| { + index.map(|index| { + let index = index as usize; + assert!( + index < self.chunk_domain, + "one-hot index {index} exceeds domain {}", + self.chunk_domain + ); + index * self.trace_len + cycle + }) + }) + } +} + +impl MultilinearPoly for AddressMajorOneHotPolynomial { + fn num_vars(&self) -> usize { + self.num_vars + } + + fn evaluate(&self, point: &[Fr]) -> Fr { + assert_eq!(point.len(), self.num_vars); + let eq_evals = EqPolynomial::new(point.to_vec()).evaluations(); + self.nonzero_flat_indices() + .fold(Fr::from_u64(0), |acc, flat| acc + eq_evals[flat]) + } + + fn for_each_row(&self, sigma: usize, f: &mut dyn FnMut(usize, &[Fr])) { + let num_cols = 1usize << sigma; + let num_rows = 1usize << (self.num_vars - sigma); + let mut entries = Vec::with_capacity(self.indices.len()); + for flat in self.nonzero_flat_indices() { + entries.push((flat / num_cols, flat % num_cols)); + } + entries.sort_unstable_by_key(|(row, _)| *row); + + let mut cursor = 0; + let mut row = vec![Fr::from_u64(0); num_cols]; + for row_index in 0..num_rows { + row.fill(Fr::from_u64(0)); + while cursor < entries.len() && entries[cursor].0 == row_index { + row[entries[cursor].1] = Fr::from_u64(1); + cursor += 1; + } + f(row_index, &row); + } + } + + fn fold_rows(&self, left: &[Fr], sigma: usize) -> Vec { + let num_cols = 1usize << sigma; + let num_rows = 1usize << (self.num_vars - sigma); + assert_eq!(left.len(), num_rows); + let mut result = vec![Fr::from_u64(0); num_cols]; + for flat in self.nonzero_flat_indices() { + result[flat % num_cols] += left[flat / num_cols]; + } + result + } + + fn is_sparse(&self) -> bool { + true + } + + fn for_each_nonzero(&self, f: &mut dyn FnMut(usize, Fr)) { + for flat in self.nonzero_flat_indices() { + f(flat, Fr::from_u64(1)); + } + } +} + +pub struct SparseCommitmentInputs<'a> { + pub inputs: CommitmentOracleInputs<'a>, + cache: std::collections::BTreeMap<(&'static str, usize), Option>>, + chunk_counts: OneHotChunkCounts, +} + +impl<'a> SparseCommitmentInputs<'a> { + pub fn new(inputs: CommitmentOracleInputs<'a>) -> Self { + Self { + inputs, + cache: std::collections::BTreeMap::new(), + chunk_counts: OneHotChunkCounts::default(), + } + } + + fn update_chunk_counts(&mut self, program: &CommitmentProverProgramPlan) { + let mut counts = OneHotChunkCounts::default(); + let mut instruction = 0; + let mut ram = 0; + let mut bytecode = 0; + for plan in program.oracle_plans { + if plan.oracle.strip_prefix("InstructionRa_").is_some() { + instruction += 1; + } else if plan.oracle.strip_prefix("RamRa_").is_some() { + ram += 1; + } else if plan.oracle.strip_prefix("BytecodeRa_").is_some() { + bytecode += 1; + } + } + if instruction > 0 { + counts.instruction = instruction; + } + if ram > 0 { + counts.ram = ram; + } + if bytecode > 0 { + counts.bytecode = bytecode; + } + self.chunk_counts = counts; + } + + fn one_hot_spec(&self, oracle: &'static str) -> Option { + let (prefix, num_chunks, values, padding) = + if let Some(suffix) = oracle.strip_prefix("InstructionRa_") { + ( + suffix, + self.chunk_counts.instruction, + OneHotSource::InstructionKeys, + Some(0), + ) + } else if let Some(suffix) = oracle.strip_prefix("RamRa_") { + ( + suffix, + self.chunk_counts.ram, + OneHotSource::RamAddresses, + None, + ) + } else if let Some(suffix) = oracle.strip_prefix("BytecodeRa_") { + ( + suffix, + self.chunk_counts.bytecode, + OneHotSource::BytecodeIndices, + Some(0), + ) + } else { + return None; + }; + let chunk = prefix.parse::().ok()?; + if chunk >= num_chunks { + return None; + } + Some(OneHotSpec { + source: values, + chunk, + num_chunks, + chunk_bits: 4, + padding, + }) + } + + fn source_values(&self, source: OneHotSource) -> &'a [Option] { + match source { + OneHotSource::InstructionKeys => self.inputs.instruction_keys, + OneHotSource::RamAddresses => self.inputs.ram_addresses, + OneHotSource::BytecodeIndices => self.inputs.bytecode_indices, + } + } + + fn one_hot_indices( + &self, + oracle: &'static str, + trace_len: usize, + ) -> Option>> { + let spec = self.one_hot_spec(oracle)?; + let values = self.source_values(spec.source); + Some(one_hot_chunk_indices( + values, + spec.chunk, + spec.num_chunks, + spec.chunk_bits, + trace_len, + spec.padding, + )) + } + + #[expect( + clippy::option_option, + reason = "distinguishes missing oracle from present optional oracle" + )] + fn materialize_oracle( + &self, + oracle: &'static str, + num_vars: usize, + ) -> Option>> { + let materialized = match oracle { + "RdInc" => Some(dense_i128_column_to_field( + self.inputs.rd_inc, + target_len(num_vars).ok()?, + )), + "RamInc" => Some(dense_i128_column_to_field( + self.inputs.ram_inc, + target_len(num_vars).ok()?, + )), + "UntrustedAdvice" => optional_field_oracle( + self.inputs.untrusted_advice, + target_len(num_vars).ok()?, + ), + "TrustedAdvice" => { + optional_field_oracle(self.inputs.trusted_advice, target_len(num_vars).ok()?) + } + _ => { + let spec = self.one_hot_spec(oracle)?; + let trace_len = target_len(num_vars.checked_sub(spec.chunk_bits)?).ok()?; + let values = self.source_values(spec.source); + Some(one_hot_chunk_address_major( + values, + spec.chunk, + spec.num_chunks, + spec.chunk_bits, + trace_len, + spec.padding, + )) + } + }; + Some(materialized) + } + + fn commit_oracle( + &self, + program: &CommitmentProverProgramPlan, + oracle: &'static str, + layout_num_vars: usize, + prover_setup: &DoryProverSetup, + ) -> Result<(DoryCommitment, DoryHint), CommitmentPhaseError> { + let oracle_num_vars = oracle_num_vars(program, oracle, layout_num_vars); + if let Some(spec) = self.one_hot_spec(oracle) { + let trace_len = target_len(oracle_num_vars - spec.chunk_bits)?; + let chunk_domain = target_len(spec.chunk_bits)?; + let indices = self + .one_hot_indices(oracle, trace_len) + .ok_or(CommitmentPhaseError::MissingOracle { oracle })?; + let poly = AddressMajorOneHotPolynomial::new( + trace_len, + chunk_domain, + indices, + layout_num_vars, + )?; + let _dory_commit_span = tracing::info_span!("bolt.commitment.dory_commit").entered(); + Ok(DoryScheme::commit(&poly, prover_setup)) + } else { + let data = self + .materialize_oracle(oracle, oracle_num_vars) + .flatten() + .ok_or(CommitmentPhaseError::MissingOracle { oracle })?; + // Pad to layout_num_vars (not oracle_num_vars) so the row-chunked + // commitment has uniform row-count across all oracles in the batch. + // Required for `joint_opening_hint`'s `combine_hints` to produce a + // valid aggregate commitment. + let data = into_padded_oracle(oracle, layout_num_vars, Cow::Owned(data))?; + commit_with_layout(&data, layout_num_vars, prover_setup) + } + } +} + +impl CommitmentInputProvider for SparseCommitmentInputs<'_> { + fn materialize(&mut self, oracle: &'static str) -> Option> { + let num_vars = match oracle { + "RdInc" | "RamInc" | "UntrustedAdvice" | "TrustedAdvice" => 16, + _ if self.one_hot_spec(oracle).is_some() => 20, + _ => return None, + }; + self.materialize_with_num_vars(oracle, num_vars) + } + + fn materialize_with_num_vars( + &mut self, + oracle: &'static str, + num_vars: usize, + ) -> Option> { + if !self.cache.contains_key(&(oracle, num_vars)) { + let materialized = self.materialize_oracle(oracle, num_vars).flatten(); + let _ = self.cache.insert((oracle, num_vars), materialized); + } + self.cache + .get(&(oracle, num_vars)) + .and_then(|values| values.as_ref()) + .map(|values| Cow::Borrowed(values.as_slice())) + } + + fn commit_batch( + &mut self, + program: &CommitmentProverProgramPlan, + plan: &CommitmentBatchPlan, + prover_setup: &DoryProverSetup, + ) -> Option, CommitmentPhaseError>> { + self.update_chunk_counts(program); + Some( + plan.oracles + .par_iter() + .map(|&oracle| { + let oracle_num_vars = oracle_num_vars(program, oracle, plan.num_vars); + let (commitment, hint) = + self.commit_oracle(program, oracle, plan.num_vars, prover_setup)?; + Ok(CommittedOracle { + commitment: Some(commitment), + record: CommitmentRecord { + artifact: plan.artifact, + oracle, + label: plan.label, + num_vars: oracle_num_vars, + }, + hint: Some(OracleOpeningHint { oracle, hint }), + }) + }) + .collect(), + ) + } + + fn add_scaled_to_joint( + &mut self, + oracle: &'static str, + joint: &mut [Fr], + num_vars: usize, + limit: usize, + scalar: Fr, + ) -> bool { + let dense = match oracle { + "RdInc" => Some(self.inputs.rd_inc), + "RamInc" => Some(self.inputs.ram_inc), + _ => None, + }; + if let Some(values) = dense { + let Ok(target_len) = target_len(num_vars) else { + return false; + }; + let len = limit.min(joint.len()).min(values.len()).min(target_len); + for (dst, &value) in joint.iter_mut().take(len).zip(values.iter()) { + if value != 0 { + *dst += Fr::from_i128(value) * scalar; + } + } + return true; + } + + let Some(spec) = self.one_hot_spec(oracle) else { + return false; + }; + let Some(trace_num_vars) = num_vars.checked_sub(spec.chunk_bits) else { + return false; + }; + let Ok(trace_len) = target_len(trace_num_vars) else { + return false; + }; + let Ok(chunk_domain) = target_len(spec.chunk_bits) else { + return false; + }; + let Some(active_len) = trace_len.checked_mul(chunk_domain) else { + return false; + }; + let max_flat = limit.min(joint.len()).min(active_len); + let Some(indices) = self.one_hot_indices(oracle, trace_len) else { + return false; + }; + for (cycle, index) in indices.into_iter().enumerate() { + let Some(index) = index else { + continue; + }; + let flat = index as usize * trace_len + cycle; + if flat < max_flat { + joint[flat] += scalar; + } + } + true + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum OneHotSource { + InstructionKeys, + RamAddresses, + BytecodeIndices, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct OneHotSpec { + source: OneHotSource, + chunk: usize, + num_chunks: usize, + chunk_bits: usize, + padding: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct OneHotChunkCounts { + instruction: usize, + ram: usize, + bytecode: usize, +} + +impl Default for OneHotChunkCounts { + fn default() -> Self { + Self { + instruction: 32, + ram: 4, + bytecode: 3, + } + } +} +"#; + let mut fields = Vec::new(); + let mut provider_arms = Vec::new(); + let mut initializers = Vec::new(); + for plan in &self.oracle_plans { + match &plan.generation { + OracleGeneration::Reference => {} + OracleGeneration::OptionalAdvice { .. } => { + let field = rust_field_name(&plan.oracle); + fields.push(format!(" pub {field}: Option>,")); + provider_arms.push(format!( + " {} => self.{field}.as_deref().map(Cow::Borrowed),", + rust_str(&plan.oracle) + )); + initializers.push(format!( + " {field}: {},", + Self::oracle_initializer(plan)? + )); + } + _ => { + let field = rust_field_name(&plan.oracle); + fields.push(format!(" pub {field}: Vec,")); + provider_arms.push(format!( + " {} => Some(Cow::Borrowed(&self.{field})),", + rust_str(&plan.oracle) + )); + initializers.push(format!( + " {field}: {},", + Self::oracle_initializer(plan)? + )); + } + } + } + let fields = fields.join("\n"); + let provider_arms = provider_arms.join("\n"); + let initializers = initializers.join("\n"); + + Ok(format!( + "{input_type} +{sparse_provider} +#[derive(Clone, Debug, Default)] +pub struct CommitmentOracles {{ +{fields} +}} + +impl CommitmentInputProvider for CommitmentOracles {{ + fn materialize(&mut self, oracle: &'static str) -> Option> {{ + match oracle {{ +{provider_arms} + _ => None, + }} + }} +}} + +pub fn build_commitment_oracles( + inputs: &CommitmentOracleInputs<'_>, +) -> Result {{ + Ok(CommitmentOracles {{ +{initializers} + }}) +}} +" + )) + } + + fn oracle_initializer(plan: &OraclePlan) -> Result { + match &plan.generation { + OracleGeneration::Reference => Err(EmitError::new(format!( + "reference oracle @{} has no prover initializer", + plan.oracle + ))), + OracleGeneration::DenseTrace { .. } => Ok(format!( + "dense_i128_column_to_field(inputs.{}, target_len({})?)", + rust_input_field(&plan.source)?, + plan.num_vars + )), + OracleGeneration::OneHotChunk { + trace_num_vars, + chunk, + num_chunks, + chunk_bits, + padding, + .. + } => Ok(format!( + "one_hot_chunk_address_major(inputs.{}, {chunk}, {num_chunks}, {chunk_bits}, target_len({trace_num_vars})?, {})", + rust_input_field(&plan.source)?, + rust_padding_value(padding)? + )), + OracleGeneration::OptionalAdvice { .. } => Ok(format!( + "optional_field_oracle(inputs.{}, target_len({})?)", + rust_input_field(&plan.source)?, + plan.num_vars + )), + } + } + + fn emit_verifier_types() -> &'static str { + r"pub type DefaultCommitmentTranscript = Blake2bTranscript; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentParams { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OraclePlan { + pub oracle: &'static str, + pub domain: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentBatchPlan { + pub artifact: &'static str, + pub pcs: &'static str, + pub oracle_family: &'static str, + pub label: &'static str, + pub oracles: &'static [&'static str], + pub count: usize, + pub domain: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OptionalSkipPolicy { + MissingOrZero, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OptionalCommitmentPlan { + pub artifact: &'static str, + pub pcs: &'static str, + pub oracle: &'static str, + pub label: &'static str, + pub domain: &'static str, + pub num_vars: usize, + pub skip_policy: OptionalSkipPolicy, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TranscriptStep { + pub label: &'static str, + pub source: &'static str, + pub optional: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentVerifierProgramPlan { + pub params: CommitmentParams, + pub oracle_plans: &'static [OraclePlan], + pub batch_plans: &'static [CommitmentBatchPlan], + pub optional_plans: &'static [OptionalCommitmentPlan], + pub transcript_steps: &'static [TranscriptStep], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentRecord { + pub artifact: &'static str, + pub oracle: &'static str, + pub label: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Debug, Default)] +pub struct CommitmentArtifacts { + pub commitments: Vec>, + pub records: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CommitmentPhaseError { + MissingProofCommitment { oracle: &'static str }, + MissingProofCommitmentSlot { artifact: &'static str, oracle: &'static str }, + MissingTranscriptSource { source: &'static str }, + PlanCountMismatch { artifact: &'static str, expected: usize, actual: usize }, + ProofCommitmentCountMismatch { expected: usize, actual: usize }, +}" + } + + fn emit_constants(&self) -> String { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const COMMITMENT_PARAMS: CommitmentParams = CommitmentParams {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + + let oracle_plans = self + .oracle_plans + .iter() + .map(|plan| { + format!( + " OraclePlan {{ oracle: {}, domain: {}, num_vars: {} }},", + rust_str(&plan.oracle), + rust_str(&plan.domain), + plan.num_vars + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!("pub const ORACLE_PLANS: &[OraclePlan] = &[\n{oracle_plans}\n];\n"), + ); + + for (index, plan) in self.batch_plans.iter().enumerate() { + let oracles = plan + .oracles + .iter() + .map(|oracle| format!(" {},", rust_str(oracle))) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const COMMITMENT_BATCH_{index}_ORACLES: &[&str] = &[\n{oracles}\n];\n" + ), + ); + } + + let batch_plans = self + .batch_plans + .iter() + .enumerate() + .map(|(index, plan)| { + format!( + " CommitmentBatchPlan {{ artifact: {}, pcs: {}, oracle_family: {}, label: {}, oracles: COMMITMENT_BATCH_{index}_ORACLES, count: {}, domain: {}, num_vars: {} }},", + rust_str(&plan.artifact), + rust_str(&plan.pcs), + rust_str(&plan.oracle_family), + rust_str(&plan.label), + plan.count, + rust_str(&plan.domain), + plan.num_vars + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const COMMITMENT_BATCH_PLANS: &[CommitmentBatchPlan] = &[\n{batch_plans}\n];\n" + ), + ); + + let optional_plans = self + .optional_plans + .iter() + .map(|plan| { + format!( + " OptionalCommitmentPlan {{ artifact: {}, pcs: {}, oracle: {}, label: {}, domain: {}, num_vars: {}, skip_policy: {} }},", + rust_str(&plan.artifact), + rust_str(&plan.pcs), + rust_str(&plan.oracle), + rust_str(&plan.label), + rust_str(&plan.domain), + plan.num_vars, + plan.skip_policy.rust_variant() + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const OPTIONAL_COMMITMENT_PLANS: &[OptionalCommitmentPlan] = &[\n{optional_plans}\n];\n" + ), + ); + + let steps = self + .transcript_steps + .iter() + .map(|step| { + format!( + " TranscriptStep {{ label: {}, source: {}, optional: {} }},", + rust_str(&step.label), + rust_str(&step.source), + step.optional + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!("pub const TRANSCRIPT_PLAN: &[TranscriptStep] = &[\n{steps}\n];"), + ); + source.push('\n'); + let program_type = match self.role { + Role::Prover => "CommitmentProverProgramPlan", + Role::Verifier => "CommitmentVerifierProgramPlan", + }; + push_format( + &mut source, + format_args!( + "pub const COMMITMENT_PROGRAM: {program_type} = {program_type} {{\n\ + \x20 params: COMMITMENT_PARAMS,\n\ + \x20 oracle_plans: ORACLE_PLANS,\n\ + \x20 batch_plans: COMMITMENT_BATCH_PLANS,\n\ + \x20 optional_plans: OPTIONAL_COMMITMENT_PLANS,\n\ + \x20 transcript_steps: TRANSCRIPT_PLAN,\n\ + }};\n" + ), + ); + + source + } + + fn emit_entrypoint(&self) -> &'static str { + match self.role { + Role::Prover => Self::emit_prover_entrypoint(), + Role::Verifier => Self::emit_verifier_entrypoint(), + } + } + + fn emit_prover_entrypoint() -> &'static str { + r#"pub fn prove_commitment_phase( + inputs: &mut I, + prover_setup: &DoryProverSetup, + transcript: &mut T, +) -> Result +where + I: CommitmentInputProvider, + T: Transcript, +{ + prove_commitment_phase_with_program(&COMMITMENT_PROGRAM, inputs, prover_setup, transcript) +} + +pub fn prove_commitment_phase_with_program( + program: &'static CommitmentProverProgramPlan, + inputs: &mut I, + prover_setup: &DoryProverSetup, + transcript: &mut T, +) -> Result +where + I: CommitmentInputProvider, + T: Transcript, +{ + let mut artifacts = CommitmentArtifacts::default(); + for plan in program.batch_plans { + let _batch_span = tracing::info_span!("bolt.commitment.batch").entered(); + commit_batch(program, inputs, prover_setup, &mut artifacts, plan)?; + } + for plan in program.optional_plans { + let _optional_span = tracing::info_span!("bolt.commitment.optional").entered(); + commit_optional(program, inputs, prover_setup, &mut artifacts, plan)?; + } + absorb_transcript(program, &artifacts, transcript)?; + Ok(artifacts) +} + +fn commit_batch( + program: &CommitmentProverProgramPlan, + inputs: &mut I, + prover_setup: &DoryProverSetup, + artifacts: &mut CommitmentArtifacts, + plan: &CommitmentBatchPlan, +) -> Result<(), CommitmentPhaseError> +where + I: CommitmentInputProvider, +{ + if plan.count != plan.oracles.len() { + return Err(CommitmentPhaseError::PlanCountMismatch { + artifact: plan.artifact, + expected: plan.count, + actual: plan.oracles.len(), + }); + } + if let Some(committed) = inputs.commit_batch(program, plan, prover_setup) { + for committed in committed? { + artifacts.records.push(committed.record); + artifacts.commitments.push(committed.commitment); + if let Some(hint) = committed.hint { + artifacts.hints.push(hint); + } + } + return Ok(()); + } + for &oracle in plan.oracles { + let data = inputs + .materialize_with_num_vars(oracle, oracle_num_vars(program, oracle, plan.num_vars)) + .ok_or(CommitmentPhaseError::MissingOracle { oracle })?; + let oracle_num_vars = oracle_num_vars(program, oracle, plan.num_vars); + let data = into_padded_oracle(oracle, plan.num_vars, data)?; + let (commitment, hint) = commit_with_layout(&data, plan.num_vars, prover_setup)?; + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle, + label: plan.label, + num_vars: oracle_num_vars, + }); + artifacts.commitments.push(Some(commitment)); + artifacts.hints.push(OracleOpeningHint { oracle, hint }); + } + Ok(()) +} + +fn commit_optional( + program: &CommitmentProverProgramPlan, + inputs: &mut I, + prover_setup: &DoryProverSetup, + artifacts: &mut CommitmentArtifacts, + plan: &OptionalCommitmentPlan, +) -> Result<(), CommitmentPhaseError> +where + I: CommitmentInputProvider, +{ + let Some(data) = inputs.materialize_with_num_vars(plan.oracle, plan.num_vars) else { + return push_skipped_optional(program, artifacts, plan); + }; + if should_skip_optional(plan.skip_policy, data.as_ref()) { + return push_skipped_optional(program, artifacts, plan); + } + let data = into_padded_oracle(plan.oracle, plan.num_vars, data)?; + let (commitment, hint) = commit_with_layout(&data, plan.num_vars, prover_setup)?; + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle: plan.oracle, + label: plan.label, + num_vars: oracle_num_vars(program, plan.oracle, plan.num_vars), + }); + artifacts.commitments.push(Some(commitment)); + artifacts.hints.push(OracleOpeningHint { + oracle: plan.oracle, + hint, + }); + Ok(()) +} + +fn push_skipped_optional( + program: &CommitmentProverProgramPlan, + artifacts: &mut CommitmentArtifacts, + plan: &OptionalCommitmentPlan, +) -> Result<(), CommitmentPhaseError> { + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle: plan.oracle, + label: plan.label, + num_vars: oracle_num_vars(program, plan.oracle, plan.num_vars), + }); + artifacts.commitments.push(None); + Ok(()) +} + +fn should_skip_optional(policy: OptionalSkipPolicy, data: &[Fr]) -> bool { + match policy { + OptionalSkipPolicy::MissingOrZero => data.iter().all(|value| *value == Fr::from_u64(0)), + } +} + +fn into_padded_oracle( + oracle: &'static str, + num_vars: usize, + data: Cow<'_, [Fr]>, +) -> Result, CommitmentPhaseError> { + let target_len = target_len(num_vars)?; + if data.len() > target_len { + return Err(CommitmentPhaseError::OracleTooLarge { + oracle, + len: data.len(), + target_len, + }); + } + let mut data = data.into_owned(); + data.resize(target_len, Fr::from_u64(0)); + Ok(data) +} + +fn oracle_num_vars( + program: &CommitmentProverProgramPlan, + oracle: &'static str, + fallback: usize, +) -> usize { + program + .oracle_plans + .iter() + .find(|plan| plan.oracle == oracle) + .map_or(fallback, |plan| plan.num_vars) +} + +fn commit_with_layout( + data: &[Fr], + layout_num_vars: usize, + prover_setup: &DoryProverSetup, +) -> Result<(DoryCommitment, DoryHint), CommitmentPhaseError> { + let row_len = target_len(layout_num_vars.div_ceil(2))?; + let _dory_commit_span = tracing::info_span!("bolt.commitment.dory_commit").entered(); + Ok(DoryScheme::commit_evaluations_with_row_len( + data, + row_len, + prover_setup, + )) +} + +fn target_len(num_vars: usize) -> Result { + if num_vars >= usize::BITS as usize { + return Err(CommitmentPhaseError::TargetSizeOverflow { num_vars }); + } + Ok(1usize << num_vars) +} + +fn absorb_transcript( + program: &CommitmentProverProgramPlan, + artifacts: &CommitmentArtifacts, + transcript: &mut T, +) -> Result<(), CommitmentPhaseError> +where + T: Transcript, +{ + for step in program.transcript_steps { + let mut appended = false; + for (record, commitment) in artifacts.records.iter().zip(&artifacts.commitments) { + if record.artifact != step.source { + continue; + } + if let Some(commitment) = commitment { + transcript.append(&LabelWithCount(step.label.as_bytes(), commitment.serialized_len())); + commitment.append_to_transcript(transcript); + appended = true; + } + } + if !step.optional && !appended { + return Err(CommitmentPhaseError::MissingTranscriptSource { + source: step.source, + }); + } + } + Ok(()) +} +"# + } + + fn emit_verifier_entrypoint() -> &'static str { + r"pub fn verify_commitment_phase( + proof_commitments: &[Option], + transcript: &mut T, +) -> Result +where + T: Transcript, +{ + verify_commitment_phase_with_program(&COMMITMENT_PROGRAM, proof_commitments, transcript) +} + +pub fn verify_commitment_phase_with_program( + program: &'static CommitmentVerifierProgramPlan, + proof_commitments: &[Option], + transcript: &mut T, +) -> Result +where + T: Transcript, +{ + let mut artifacts = CommitmentArtifacts::default(); + let mut cursor = 0usize; + for plan in program.batch_plans { + receive_batch(program, proof_commitments, &mut cursor, &mut artifacts, plan)?; + } + for plan in program.optional_plans { + receive_optional(program, proof_commitments, &mut cursor, &mut artifacts, plan)?; + } + if cursor != proof_commitments.len() { + return Err(CommitmentPhaseError::ProofCommitmentCountMismatch { + expected: cursor, + actual: proof_commitments.len(), + }); + } + absorb_transcript(program, &artifacts, transcript)?; + Ok(artifacts) +} + +fn receive_batch( + program: &'static CommitmentVerifierProgramPlan, + proof_commitments: &[Option], + cursor: &mut usize, + artifacts: &mut CommitmentArtifacts, + plan: &CommitmentBatchPlan, +) -> Result<(), CommitmentPhaseError> { + if plan.count != plan.oracles.len() { + return Err(CommitmentPhaseError::PlanCountMismatch { + artifact: plan.artifact, + expected: plan.count, + actual: plan.oracles.len(), + }); + } + for &oracle in plan.oracles { + let commitment = proof_commitments + .get(*cursor) + .ok_or(CommitmentPhaseError::MissingProofCommitmentSlot { + artifact: plan.artifact, + oracle, + })? + .as_ref() + .ok_or(CommitmentPhaseError::MissingProofCommitment { oracle })? + .clone(); + *cursor += 1; + let oracle_num_vars = oracle_num_vars(program, oracle, plan.num_vars); + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle, + label: plan.label, + num_vars: oracle_num_vars, + }); + artifacts.commitments.push(Some(commitment)); + } + Ok(()) +} + +fn receive_optional( + program: &'static CommitmentVerifierProgramPlan, + proof_commitments: &[Option], + cursor: &mut usize, + artifacts: &mut CommitmentArtifacts, + plan: &OptionalCommitmentPlan, +) -> Result<(), CommitmentPhaseError> { + let commitment = proof_commitments + .get(*cursor) + .ok_or(CommitmentPhaseError::MissingProofCommitmentSlot { + artifact: plan.artifact, + oracle: plan.oracle, + })? + .clone(); + *cursor += 1; + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle: plan.oracle, + label: plan.label, + num_vars: oracle_num_vars(program, plan.oracle, plan.num_vars), + }); + artifacts.commitments.push(commitment); + Ok(()) +} + +pub fn commitment_verifier_program() -> &'static CommitmentVerifierProgramPlan { + &COMMITMENT_PROGRAM +} + +fn oracle_num_vars( + program: &'static CommitmentVerifierProgramPlan, + oracle: &'static str, + fallback: usize, +) -> usize { + program + .oracle_plans + .iter() + .find(|plan| plan.oracle == oracle) + .map_or(fallback, |plan| plan.num_vars) +} + +fn absorb_transcript( + program: &'static CommitmentVerifierProgramPlan, + artifacts: &CommitmentArtifacts, + transcript: &mut T, +) -> Result<(), CommitmentPhaseError> +where + T: Transcript, +{ + for step in program.transcript_steps { + let mut appended = false; + for (record, commitment) in artifacts.records.iter().zip(&artifacts.commitments) { + if record.artifact != step.source { + continue; + } + if let Some(commitment) = commitment { + transcript.append(&LabelWithCount(step.label.as_bytes(), commitment.serialized_len())); + commitment.append_to_transcript(transcript); + appended = true; + } + } + if !step.optional && !appended { + return Err(CommitmentPhaseError::MissingTranscriptSource { + source: step.source, + }); + } + } + Ok(()) +} +" + } +} + +impl OptionalSkipPolicy { + fn parse(value: &str) -> Result { + match value { + "missing_or_zero" => Ok(Self::MissingOrZero), + _ => Err(EmitError::new(format!( + "unsupported optional commitment skip policy `{value}`" + ))), + } + } + + fn rust_variant(&self) -> &'static str { + match self { + Self::MissingOrZero => "OptionalSkipPolicy::MissingOrZero", + } + } +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn skip_policy_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result { + OptionalSkipPolicy::parse(&string_attr(operation, attr)?) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn bool_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(|attribute| match attribute.to_string().as_str() { + "true" => Some(true), + "false" => Some(false), + _ => None, + }) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "bool")) +} + +fn transcript_artifact_source(operation: OperationRef<'_, '_>) -> Result { + let artifact = operation + .operand(1) + .map_err(|_| attr_error(operation, "artifact operand", "value"))?; + let owner = OperationResult::try_from(artifact) + .map_err(|_| EmitError::new("cpu.transcript_absorb artifact operand must be op result"))? + .owner(); + symbol_attr(owner, "artifact") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name(operation: OperationRef<'_, '_>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn rust_field_name(value: &str) -> String { + let mut output = String::new(); + let mut previous_was_separator = false; + for (index, character) in value.chars().enumerate() { + if character == '_' { + output.push('_'); + previous_was_separator = true; + continue; + } + if character.is_ascii_uppercase() { + if index != 0 && !previous_was_separator { + output.push('_'); + } + output.push(character.to_ascii_lowercase()); + } else { + output.push(character); + } + previous_was_separator = false; + } + output +} + +fn rust_input_field(source: &str) -> Result<&'static str, EmitError> { + match source { + "trace.rd_inc" => Ok("rd_inc"), + "trace.ram_inc" => Ok("ram_inc"), + "trace.instruction_keys" => Ok("instruction_keys"), + "trace.ram_addresses" => Ok("ram_addresses"), + "trace.bytecode_indices" => Ok("bytecode_indices"), + "advice.untrusted" => Ok("untrusted_advice"), + "advice.trusted" => Ok("trusted_advice"), + _ => Err(EmitError::new(format!( + "unsupported oracle source `{source}`" + ))), + } +} + +fn rust_padding_value(padding: &str) -> Result<&'static str, EmitError> { + match padding { + "zero" => Ok("Some(0)"), + "none" => Ok("None"), + _ => Err(EmitError::new(format!( + "unsupported oracle padding `{padding}`" + ))), + } +} + +fn require_supported_symbol(kind: &str, actual: &str, expected: &str) -> Result<(), EmitError> { + if actual == expected { + Ok(()) + } else { + Err(EmitError::new(format!( + "unsupported {kind} @{actual}; Rust commitment emitter currently supports @{expected}" + ))) + } +} diff --git a/crates/bolt/src/protocols/jolt/emit/rust/mod.rs b/crates/bolt/src/protocols/jolt/emit/rust/mod.rs new file mode 100644 index 0000000000..50f36ba9f7 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/mod.rs @@ -0,0 +1,27 @@ +mod commitment; +mod stage1; +mod stage2; +mod stage3; +mod stage4; +mod stage5; +mod stage6; +mod stage7; +mod stage8; + +pub use commitment::{ + commitment_cpu_program, emit_commitment_rust, CommitmentBatchPlan, CommitmentCpuProgram, + CommitmentParams, OptionalCommitmentPlan, OptionalSkipPolicy, OracleGeneration, OraclePlan, + TranscriptStep, +}; +pub use stage1::{ + emit_stage1_rust, stage1_cpu_program, Stage1CpuProgram, Stage1KernelPlan, + Stage1OpeningBatchPlan, Stage1OpeningClaimPlan, Stage1Params, Stage1SumcheckBatchPlan, + Stage1SumcheckClaimPlan, Stage1SumcheckDriverPlan, Stage1SumcheckEvalPlan, +}; +pub use stage2::{emit_stage2_rust, stage2_cpu_program, Stage2CpuProgram}; +pub use stage3::{emit_stage3_rust, stage3_cpu_program, Stage3CpuProgram}; +pub use stage4::{emit_stage4_rust, stage4_cpu_program, Stage4CpuProgram}; +pub use stage5::{emit_stage5_rust, stage5_cpu_program, Stage5CpuProgram}; +pub use stage6::{emit_stage6_rust, stage6_cpu_program, Stage6CpuProgram}; +pub use stage7::{emit_stage7_rust, stage7_cpu_program, Stage7CpuProgram}; +pub use stage8::{emit_stage8_rust, stage8_cpu_program, Stage8CpuProgram}; diff --git a/crates/bolt/src/protocols/jolt/emit/rust/stage1.rs b/crates/bolt/src/protocols/jolt/emit/rust/stage1.rs new file mode 100644 index 0000000000..0755beb79d --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/stage1.rs @@ -0,0 +1,1653 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{Attribute, OperationRef}; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1CpuProgram { + pub role: Role, + pub params: Stage1Params, + pub transcript_squeezes: Vec, + pub kernels: Vec, + pub claims: Vec, + pub batches: Vec, + pub drivers: Vec, + pub instance_results: Vec, + pub evals: Vec, + pub opening_claims: Vec, + pub opening_batches: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1Params { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1KernelPlan { + pub symbol: String, + pub relation: String, + pub kind: String, + pub backend: String, + pub abi: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1TranscriptSqueezePlan { + pub symbol: String, + pub label: String, + pub kind: String, + pub count: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckClaimPlan { + pub symbol: String, + pub stage: String, + pub domain: String, + pub num_rounds: usize, + pub degree: usize, + pub claim: String, + pub kernel: Option, + pub relation: Option, + pub claim_value: String, + pub input_openings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, + pub claim_label: String, + pub round_label: String, + pub round_schedule: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckDriverPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub kernel: Option, + pub relation: Option, + pub batch: String, + pub policy: String, + pub round_schedule: Vec, + pub claim_label: String, + pub round_label: String, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckInstanceResultPlan { + pub symbol: String, + pub source: String, + pub claim: String, + pub relation: String, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: String, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckEvalPlan { + pub symbol: String, + pub source: String, + pub name: String, + pub index: usize, + pub oracle: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1OpeningClaimPlan { + pub symbol: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, + pub point_source: String, + pub eval_source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1OpeningBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, +} + +pub fn stage1_cpu_program(module: &BoltModule<'_, Cpu>) -> Result { + verify_cpu_schema(module)?; + let program = Stage1CpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +pub fn emit_stage1_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = stage1_cpu_program(module)?; + + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source()?, + }) +} + +impl Stage1CpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let mut params = None; + let mut transcript_squeezes = Vec::new(); + let mut kernels = Vec::new(); + let mut claims = Vec::new(); + let mut batches = Vec::new(); + let mut drivers = Vec::new(); + let mut instance_results = Vec::new(); + let mut evals = Vec::new(); + let mut opening_claims = Vec::new(); + let mut opening_batches = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(Stage1Params { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.kernel" => { + kernels.push(Stage1KernelPlan { + symbol: string_attr(op, "sym_name")?, + relation: symbol_attr(op, "relation")?, + kind: string_attr(op, "kind")?, + backend: string_attr(op, "backend")?, + abi: string_attr(op, "abi")?, + }); + } + "cpu.transcript_squeeze" => { + transcript_squeezes.push(Stage1TranscriptSqueezePlan { + symbol: string_attr(op, "sym_name")?, + label: string_attr(op, "label")?, + kind: string_attr(op, "kind")?, + count: int_attr(op, "count")?, + }); + } + "cpu.sumcheck_claim" => { + claims.push(Stage1SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_verify_claim" => { + claims.push(Stage1SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_batch" => { + batches.push(Stage1SumcheckBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + round_schedule: int_array_attr(op, "round_schedule")?, + }); + } + "cpu.sumcheck_driver" => { + drivers.push(Stage1SumcheckDriverPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_verify" => { + drivers.push(Stage1SumcheckDriverPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_eval" => { + evals.push(Stage1SumcheckEvalPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + name: symbol_attr(op, "name")?, + index: int_attr(op, "index")?, + oracle: symbol_attr(op, "oracle")?, + }); + } + "cpu.sumcheck_instance_result" => { + instance_results.push(Stage1SumcheckInstanceResultPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + claim: symbol_attr(op, "claim")?, + relation: symbol_attr(op, "relation")?, + index: int_attr(op, "index")?, + point_arity: int_attr(op, "point_arity")?, + num_rounds: int_attr(op, "num_rounds")?, + round_offset: int_attr(op, "round_offset")?, + point_order: string_attr(op, "point_order")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.opening_claim" => { + opening_claims.push(Stage1OpeningClaimPlan { + symbol: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + point_source: operand_symbol(op, 0)?, + eval_source: operand_symbol(op, 1)?, + }); + } + "cpu.opening_batch" => { + opening_batches.push(Stage1OpeningBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + }); + } + _ => {} + } + } + + Ok(Self { + params: params.ok_or_else(|| EmitError::new("missing cpu.params"))?, + role: module + .role() + .ok_or_else(|| EmitError::new("missing cpu party role"))?, + transcript_squeezes, + kernels, + claims, + batches, + drivers, + instance_results, + evals, + opening_claims, + opening_batches, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + require_supported_symbol("field", &self.params.field, "bn254_fr")?; + require_supported_symbol("pcs", &self.params.pcs, "dory")?; + require_supported_symbol("transcript", &self.params.transcript, "blake2b_transcript")?; + self.verify_transcript_squeezes()?; + self.verify_claim_batches()?; + match self.role { + Role::Prover => { + self.verify_kernel_definitions()?; + self.verify_prover_driver_bindings()?; + } + Role::Verifier => self.verify_verifier_driver_bindings()?, + } + self.verify_opening_flow() + } + + fn verify_transcript_squeezes(&self) -> Result<(), EmitError> { + for squeeze in &self.transcript_squeezes { + if squeeze.kind != "challenge_vector" { + return Err(EmitError::new(format!( + "stage1 transcript squeeze @{} has unsupported kind `{}`", + squeeze.symbol, squeeze.kind + ))); + } + if squeeze.count == 0 { + return Err(EmitError::new(format!( + "stage1 transcript squeeze @{} has zero count", + squeeze.symbol + ))); + } + } + Ok(()) + } + + fn verify_kernel_definitions(&self) -> Result<(), EmitError> { + for kernel in &self.kernels { + if kernel.backend != "cpu" { + return Err(EmitError::new(format!( + "stage1 kernel @{} targets unsupported backend `{}`", + kernel.symbol, kernel.backend + ))); + } + if kernel.kind != "sumcheck" { + return Err(EmitError::new(format!( + "stage1 kernel @{} has unsupported kind `{}`", + kernel.symbol, kernel.kind + ))); + } + let expected_abi = match kernel.relation.as_str() { + "jolt.stage1.outer.uniskip" => "jolt_stage1_outer_uniskip", + "jolt.stage1.outer.remaining" => "jolt_stage1_outer_remaining", + _ => { + return Err(EmitError::new(format!( + "unsupported stage1 kernel relation @{}", + kernel.relation + ))); + } + }; + if kernel.abi != expected_abi { + return Err(EmitError::new(format!( + "stage1 kernel @{} ABI `{}` does not match relation @{}", + kernel.symbol, kernel.abi, kernel.relation + ))); + } + } + Ok(()) + } + + fn verify_claim_batches(&self) -> Result<(), EmitError> { + let claims = symbols(self.claims.iter().map(|claim| &claim.symbol)); + for batch in &self.batches { + verify_count( + "sumcheck batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "sumcheck batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "sumcheck batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !claims.contains(claim) { + return Err(EmitError::new(format!( + "sumcheck batch @{} references missing claim @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn verify_prover_driver_bindings(&self) -> Result<(), EmitError> { + let kernels = symbols(self.kernels.iter().map(|kernel| &kernel.symbol)); + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + let claims: BTreeMap<_, _> = self + .claims + .iter() + .map(|claim| (claim.symbol.as_str(), claim)) + .collect(); + for claim in &self.claims { + let Some(kernel) = claim.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck claim @{} is missing kernel", + claim.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck claim @{} references missing kernel @{kernel}", + claim.symbol + ))); + } + } + for driver in &self.drivers { + let Some(kernel) = driver.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck driver @{} is missing kernel", + driver.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck driver @{} references missing kernel @{kernel}", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + for claim in &batch.ordered_claims { + let claim = claims.get(claim.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing claim @{claim}", + driver.symbol + )) + })?; + if claim.kernel.as_deref() != Some(kernel) { + return Err(EmitError::new(format!( + "sumcheck driver @{} kernel @{kernel} differs from claim @{} kernel {:?}", + driver.symbol, claim.symbol, claim.kernel + ))); + } + } + } + Ok(()) + } + + fn verify_verifier_driver_bindings(&self) -> Result<(), EmitError> { + if !self.kernels.is_empty() { + return Err(EmitError::new( + "verifier stage1 program must not contain kernels", + )); + } + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + let claims: BTreeMap<_, _> = self + .claims + .iter() + .map(|claim| (claim.symbol.as_str(), claim)) + .collect(); + for claim in &self.claims { + if claim.kernel.is_some() || claim.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck claim @{} must carry relation and no kernel", + claim.symbol + ))); + } + } + for driver in &self.drivers { + let Some(relation) = driver.relation.as_deref() else { + return Err(EmitError::new(format!( + "verifier sumcheck driver @{} is missing relation", + driver.symbol + ))); + }; + if driver.kernel.is_some() { + return Err(EmitError::new(format!( + "verifier sumcheck driver @{} must not carry kernel", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + for claim in &batch.ordered_claims { + let claim = claims.get(claim.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing claim @{claim}", + driver.symbol + )) + })?; + if claim.relation.as_deref() != Some(relation) { + return Err(EmitError::new(format!( + "sumcheck driver @{} relation @{relation} differs from claim @{} relation {:?}", + driver.symbol, claim.symbol, claim.relation + ))); + } + } + } + Ok(()) + } + + fn verify_opening_flow(&self) -> Result<(), EmitError> { + let drivers = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + let instance_results = symbols( + self.instance_results + .iter() + .map(|instance| &instance.symbol), + ); + for instance in &self.instance_results { + if !drivers.contains(&instance.source) { + return Err(EmitError::new(format!( + "sumcheck instance result @{} references missing driver @{}", + instance.symbol, instance.source + ))); + } + } + let mut point_sources = drivers.clone(); + point_sources.extend(instance_results); + let evals = symbols(self.evals.iter().map(|eval| &eval.symbol)); + let openings = symbols(self.opening_claims.iter().map(|claim| &claim.symbol)); + for eval in &self.evals { + if !drivers.contains(&eval.source) { + return Err(EmitError::new(format!( + "sumcheck eval @{} references missing driver @{}", + eval.symbol, eval.source + ))); + } + } + for claim in &self.opening_claims { + if !point_sources.contains(&claim.point_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing point source @{}", + claim.symbol, claim.point_source + ))); + } + if !evals.contains(&claim.eval_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing eval source @{}", + claim.symbol, claim.eval_source + ))); + } + } + for batch in &self.opening_batches { + verify_count( + "opening batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "opening batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "opening batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !openings.contains(claim) { + return Err(EmitError::new(format!( + "opening batch @{} references missing opening @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn emit_source(&self) -> Result { + match self.role { + Role::Prover => self.emit_prover_source(), + Role::Verifier => self.emit_verifier_source(), + } + } + + fn emit_prover_source(&self) -> Result { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + source.push_str(Self::emit_prover_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_prover_types()); + source.push('\n'); + source.push_str(&self.emit_prover_constants()?); + source.push('\n'); + source.push_str(Self::emit_prover_entrypoint()); + Ok(source) + } + + fn emit_verifier_source(&self) -> Result { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + source.push_str(Self::emit_verifier_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_verifier_types()); + source.push('\n'); + source.push_str(&self.emit_verifier_constants()?); + source.push('\n'); + source.push_str(Self::emit_verifier_entrypoint()); + Ok(source) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_stage1_outer.rs", + Role::Verifier => "verify_stage1_outer.rs", + } + } + + fn emit_prover_imports() -> &'static str { + "use jolt_field::Fr;\n\ + use jolt_kernels::stage1::{execute_stage1_program, Stage1CpuProgramPlan, Stage1ExecutionArtifacts, Stage1ExecutionMode, Stage1KernelError, Stage1KernelExecutor, Stage1KernelPlan, Stage1OpeningBatchPlan, Stage1OpeningClaimPlan, Stage1Params, Stage1SumcheckBatchPlan, Stage1SumcheckClaimPlan, Stage1SumcheckDriverPlan, Stage1SumcheckEvalPlan, Stage1SumcheckInstanceResultPlan, Stage1TranscriptSqueezePlan};\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_prover_types() -> &'static str { + "pub type DefaultStage1Transcript = Blake2bTranscript;\n" + } + + fn emit_prover_constants(&self) -> Result { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const STAGE1_PARAMS: Stage1Params = Stage1Params {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + + source.push_str(&self.emit_transcript_squeeze_constants()); + source.push_str(&self.emit_kernel_constants()); + source.push_str(&self.emit_sumcheck_claim_constants()?); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_sumcheck_driver_constants()?); + source.push_str(&self.emit_sumcheck_instance_result_constants()); + source.push_str(&self.emit_sumcheck_eval_constants()); + source.push_str(&self.emit_opening_claim_constants()); + source.push_str(&self.emit_opening_batch_constants()); + source.push_str( + "pub const STAGE1_PROGRAM: Stage1CpuProgramPlan = Stage1CpuProgramPlan {\n\ + \x20 params: STAGE1_PARAMS,\n\ + \x20 transcript_squeezes: STAGE1_TRANSCRIPT_SQUEEZES,\n\ + \x20 kernels: STAGE1_KERNELS,\n\ + \x20 claims: STAGE1_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE1_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE1_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE1_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE1_SUMCHECK_EVALS,\n\ + \x20 opening_claims: STAGE1_OPENING_CLAIMS,\n\ + \x20 opening_batches: STAGE1_OPENING_BATCHES,\n\ + };\n", + ); + Ok(source) + } + + fn emit_sumcheck_instance_result_constants(&self) -> String { + let instances = self + .instance_results + .iter() + .map(|instance| { + format!( + " Stage1SumcheckInstanceResultPlan {{ symbol: {}, source: {}, claim: {}, relation: {}, index: {}, point_arity: {}, num_rounds: {}, round_offset: {}, point_order: {}, degree: {} }},", + rust_str(&instance.symbol), + rust_str(&instance.source), + rust_str(&instance.claim), + rust_str(&instance.relation), + instance.index, + instance.point_arity, + instance.num_rounds, + instance.round_offset, + rust_str(&instance.point_order), + instance.degree + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE1_SUMCHECK_INSTANCE_RESULTS: &[Stage1SumcheckInstanceResultPlan] = &[\n{instances}\n];\n\n" + ) + } + + fn emit_transcript_squeeze_constants(&self) -> String { + let squeezes = self + .transcript_squeezes + .iter() + .map(|squeeze| { + format!( + " Stage1TranscriptSqueezePlan {{ symbol: {}, label: {}, kind: {}, count: {} }},", + rust_str(&squeeze.symbol), + rust_str(&squeeze.label), + rust_str(&squeeze.kind), + squeeze.count, + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE1_TRANSCRIPT_SQUEEZES: &[Stage1TranscriptSqueezePlan] = &[\n{squeezes}\n];\n\n" + ) + } + + fn emit_kernel_constants(&self) -> String { + let kernels = self + .kernels + .iter() + .map(|kernel| { + format!( + " Stage1KernelPlan {{ symbol: {}, relation: {}, kind: {}, backend: {}, abi: {} }},", + rust_str(&kernel.symbol), + rust_str(&kernel.relation), + rust_str(&kernel.kind), + rust_str(&kernel.backend), + rust_str(&kernel.abi) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE1_KERNELS: &[Stage1KernelPlan] = &[\n{kernels}\n];\n\n") + } + + fn emit_sumcheck_claim_constants(&self) -> Result { + let mut source = String::new(); + for (index, claim) in self.claims.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE1_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS"), + &claim.input_openings, + )); + } + let mut claims = Vec::new(); + for (index, claim) in self.claims.iter().enumerate() { + let kernel = claim + .kernel + .as_deref() + .ok_or_else(|| missing_role_binding("prover claim kernel", &claim.symbol))?; + claims.push(format!( + " Stage1SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: Some({}), relation: None, claim_value: {}, input_openings: STAGE1_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_str(kernel), + rust_str(&claim.claim_value) + )); + } + let claims = claims.join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE1_SUMCHECK_CLAIMS: &[Stage1SumcheckClaimPlan] = &[\n{claims}\n];\n" + ), + ); + Ok(source) + } + + fn emit_sumcheck_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE1_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage1SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {}, claim_label: {}, round_label: {}, round_schedule: STAGE1_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")), + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE1_SUMCHECK_BATCHES: &[Stage1SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + return source; + } + + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE1_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE1_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + source.push_str(&emit_usize_array( + &format!("STAGE1_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage1SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE1_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE1_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS, claim_label: {}, round_label: {}, round_schedule: STAGE1_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE1_SUMCHECK_BATCHES: &[Stage1SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_driver_constants(&self) -> Result { + let mut source = String::new(); + for (index, driver) in self.drivers.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE1_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE"), + &driver.round_schedule, + )); + } + let mut drivers = Vec::new(); + for (index, driver) in self.drivers.iter().enumerate() { + let kernel = driver + .kernel + .as_deref() + .ok_or_else(|| missing_role_binding("prover driver kernel", &driver.symbol))?; + drivers.push(format!( + " Stage1SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: Some({}), relation: None, batch: {}, policy: {}, round_schedule: STAGE1_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_str(kernel), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + )); + } + let drivers = drivers.join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE1_SUMCHECK_DRIVERS: &[Stage1SumcheckDriverPlan] = &[\n{drivers}\n];\n" + ), + ); + Ok(source) + } + + fn emit_sumcheck_eval_constants(&self) -> String { + let evals = self + .evals + .iter() + .map(|eval| { + format!( + " Stage1SumcheckEvalPlan {{ symbol: {}, source: {}, name: {}, index: {}, oracle: {} }},", + rust_str(&eval.symbol), + rust_str(&eval.source), + rust_str(&eval.name), + eval.index, + rust_str(&eval.oracle) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE1_SUMCHECK_EVALS: &[Stage1SumcheckEvalPlan] = &[\n{evals}\n];\n\n") + } + + fn emit_opening_claim_constants(&self) -> String { + let claims = self + .opening_claims + .iter() + .map(|claim| { + format!( + " Stage1OpeningClaimPlan {{ symbol: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {}, point_source: {}, eval_source: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.oracle), + rust_str(&claim.domain), + claim.point_arity, + rust_str(&claim.claim_kind), + rust_str(&claim.point_source), + rust_str(&claim.eval_source) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE1_OPENING_CLAIMS: &[Stage1OpeningClaimPlan] = &[\n{claims}\n];\n\n") + } + + fn emit_opening_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let batches = self + .opening_batches + .iter() + .map(|batch| { + format!( + " Stage1OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {} }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE1_OPENING_BATCHES: &[Stage1OpeningBatchPlan] = &[\n{batches}\n];\n" + ); + } + + let mut source = String::new(); + for (index, batch) in self.opening_batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE1_OPENING_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE1_OPENING_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + } + let batches = self + .opening_batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage1OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE1_OPENING_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE1_OPENING_BATCH_{index}_CLAIM_OPERANDS }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE1_OPENING_BATCHES: &[Stage1OpeningBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_verifier_imports() -> &'static str { + "use super::common::append_labeled_scalar;\n\ + use jolt_field::{Field, Fr};\n\ + use jolt_sumcheck::{CompressedLabeledRoundPoly, LabeledRoundPoly, SumcheckClaim, SumcheckError, SumcheckVerifier};\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_verifier_types() -> &'static str { + r"pub type DefaultStage1Transcript = Blake2bTranscript; + +pub type Stage1Params = super::common::StageParams; +pub type Stage1NamedEval = super::common::StageNamedEval; +pub type Stage1SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage1ChallengeVector = super::common::StageChallengeVector; +pub type Stage1ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage1Proof = super::common::StageProof; +pub type Stage1VerifierProgramPlan = super::common::VerifierProgramPlanMinimal; + +pub use super::common::{ + OpeningBatchPlan as Stage1OpeningBatchPlan, OpeningClaimPlan as Stage1OpeningClaimPlan, + SumcheckBatchPlan as Stage1SumcheckBatchPlan, SumcheckEvalPlan as Stage1SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage1SumcheckInstanceResultPlan, + TranscriptSqueezePlan as Stage1TranscriptSqueezePlan, + SumcheckClaimPlan as Stage1SumcheckClaimPlan, + SumcheckDriverPlan as Stage1SumcheckDriverPlan, +}; + +#[derive(Debug)] +pub enum VerifyStage1Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { driver: &'static str, claim: &'static str }, + MissingDependency { driver: &'static str, dependency: &'static str }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} +" + } + + fn emit_verifier_constants(&self) -> Result { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const STAGE1_PARAMS: Stage1Params = Stage1Params {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + + source.push_str(&self.emit_transcript_squeeze_constants()); + source.push_str(&self.emit_verifier_sumcheck_claim_constants()?); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_verifier_sumcheck_driver_constants()?); + source.push_str(&self.emit_sumcheck_instance_result_constants()); + source.push_str(&self.emit_sumcheck_eval_constants()); + source.push_str(&self.emit_opening_claim_constants()); + source.push_str(&self.emit_opening_batch_constants()); + source.push_str( + "pub const STAGE1_PROGRAM: Stage1VerifierProgramPlan = Stage1VerifierProgramPlan {\n\ + \x20 params: STAGE1_PARAMS,\n\ + \x20 transcript_squeezes: STAGE1_TRANSCRIPT_SQUEEZES,\n\ + \x20 claims: STAGE1_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE1_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE1_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE1_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE1_SUMCHECK_EVALS,\n\ + \x20 opening_claims: STAGE1_OPENING_CLAIMS,\n\ + \x20 opening_batches: STAGE1_OPENING_BATCHES,\n\ + };\n", + ); + Ok(source) + } + + fn emit_verifier_sumcheck_claim_constants(&self) -> Result { + let mut claims = Vec::new(); + for claim in &self.claims { + let relation = claim + .relation + .as_deref() + .ok_or_else(|| missing_role_binding("verifier claim relation", &claim.symbol))?; + claims.push(format!( + " Stage1SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: None, relation: Some({}), claim_value: {}, input_openings: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_str(relation), + rust_str(&claim.claim_value), + rust_str(&claim.input_openings.join("|")) + )); + } + let claims = claims.join("\n"); + Ok(format!( + "pub const STAGE1_SUMCHECK_CLAIMS: &[Stage1SumcheckClaimPlan] = &[\n{claims}\n];\n" + )) + } + + fn emit_verifier_sumcheck_driver_constants(&self) -> Result { + let mut source = String::new(); + for (index, driver) in self.drivers.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE1_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE"), + &driver.round_schedule, + )); + } + let mut drivers = Vec::new(); + for (index, driver) in self.drivers.iter().enumerate() { + let relation = driver + .relation + .as_deref() + .ok_or_else(|| missing_role_binding("verifier driver relation", &driver.symbol))?; + drivers.push(format!( + " Stage1SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: None, relation: Some({}), batch: {}, policy: {}, round_schedule: STAGE1_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_str(relation), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + )); + } + let drivers = drivers.join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE1_SUMCHECK_DRIVERS: &[Stage1SumcheckDriverPlan] = &[\n{drivers}\n];\n" + ), + ); + Ok(source) + } + + fn emit_prover_entrypoint() -> &'static str { + r"pub fn prove_stage1_outer( + executor: &mut E, + transcript: &mut T, +) -> Result, Stage1KernelError> +where + E: Stage1KernelExecutor, + T: Transcript, +{ + prove_stage1_outer_with_program(&STAGE1_PROGRAM, executor, transcript) +} + +pub fn prove_stage1_outer_with_program( + program: &'static Stage1CpuProgramPlan, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage1KernelError> +where + E: Stage1KernelExecutor, + T: Transcript, +{ + execute_stage1_program( + program, + Stage1ExecutionMode::Prover, + executor, + transcript, + ) +} +" + } + + fn emit_verifier_entrypoint() -> &'static str { + r#"pub fn verify_stage1_outer( + proof: &Stage1Proof, + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + verify_stage1_outer_with_program(&STAGE1_PROGRAM, proof, transcript) +} + +pub fn verify_stage1_outer_with_program( + program: &'static Stage1VerifierProgramPlan, + proof: &Stage1Proof, + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage1Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut artifacts = Stage1ExecutionArtifacts::default(); + for squeeze in program.transcript_squeezes { + let values = transcript.challenge_vector(squeeze.count); + artifacts.challenge_vectors.push(Stage1ChallengeVector { + symbol: squeeze.symbol, + values, + }); + } + for (index, driver) in program.drivers.iter().enumerate() { + let proof = proof.sumchecks.get(index).ok_or(VerifyStage1Error::MissingProof { + driver: driver.symbol, + })?; + let output = verify_stage1_driver(program, driver, proof, &artifacts.sumchecks, transcript)?; + artifacts.sumchecks.push(output); + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage1_outer_verifier_program() -> &'static Stage1VerifierProgramPlan { + &STAGE1_PROGRAM +} + +fn verify_stage1_driver( + program: &'static Stage1VerifierProgramPlan, + driver: &'static Stage1SumcheckDriverPlan, + proof: &Stage1SumcheckOutput, + completed: &[Stage1SumcheckOutput], + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + if proof.driver != driver.symbol { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "driver symbol mismatch", + }); + } + let relation = driver.relation.unwrap_or(""); + match relation { + "jolt.stage1.outer.uniskip" => verify_outer_uniskip(program, driver, proof, transcript), + "jolt.stage1.outer.remaining" => { + verify_outer_remaining(program, driver, proof, completed, transcript) + } + relation => Err(VerifyStage1Error::UnsupportedRelation { relation }), + } +} + +fn verify_outer_uniskip( + program: &'static Stage1VerifierProgramPlan, + driver: &'static Stage1SumcheckDriverPlan, + proof: &Stage1SumcheckOutput, + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + let claim = SumcheckClaim::new(driver.num_rounds, driver.degree, Fr::from_u64(0)); + let round_proofs = proof + .proof + .round_polynomials + .iter() + .map(|poly| LabeledRoundPoly::new(poly, driver.round_label.as_bytes())) + .collect::>(); + let output = SumcheckVerifier::verify(&claim, &round_proofs, transcript) + .map_err(|error| VerifyStage1Error::Sumcheck { + driver: driver.symbol, + error, + })?; + let eval = output.value; + let point = output.point; + if !proof.point.is_empty() && proof.point != point { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "uniskip point mismatch", + }); + } + validate_eval_shape(program, driver, &proof.evals, Some(eval))?; + append_labeled_scalar(transcript, "opening_claim", &eval); + Ok(Stage1SumcheckOutput { + driver: driver.symbol, + point, + evals: driver_evals(program, driver.symbol, eval), + proof: proof.proof.clone(), + }) +} + +fn verify_outer_remaining( + program: &'static Stage1VerifierProgramPlan, + driver: &'static Stage1SumcheckDriverPlan, + proof: &Stage1SumcheckOutput, + completed: &[Stage1SumcheckOutput], + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + let input_claim = completed + .iter() + .find(|output| output.driver == "stage1.uniskip.sumcheck") + .and_then(|output| output.evals.first()) + .map(|eval| eval.value) + .ok_or(VerifyStage1Error::MissingDependency { + driver: driver.symbol, + dependency: "stage1.uniskip.eval", + })?; + append_labeled_scalar(transcript, driver.claim_label, &input_claim); + let batching_coeff = transcript.challenge(); + let claim = SumcheckClaim::new( + driver.num_rounds, + driver.degree, + input_claim * batching_coeff, + ); + let round_proofs = proof + .proof + .round_polynomials + .iter() + .map(|poly| CompressedLabeledRoundPoly::new(poly, driver.round_label.as_bytes())) + .collect::>(); + let output = SumcheckVerifier::verify(&claim, &round_proofs, transcript) + .map_err(|error| VerifyStage1Error::Sumcheck { + driver: driver.symbol, + error, + })?; + let point = output.point; + if !proof.point.is_empty() && proof.point != point { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "outer remaining point mismatch", + }); + } + validate_eval_shape(program, driver, &proof.evals, None)?; + append_opening_claims(transcript, &proof.evals); + Ok(Stage1SumcheckOutput { + driver: driver.symbol, + point, + evals: proof.evals.clone(), + proof: proof.proof.clone(), + }) +} + +fn driver_evals( + program: &'static Stage1VerifierProgramPlan, + driver: &'static str, + value: Fr, +) -> Vec> { + program + .evals + .iter() + .filter(|eval| eval.source == driver) + .map(|eval| Stage1NamedEval { + name: eval.name, + oracle: eval.oracle, + value, + }) + .collect() +} + +fn validate_eval_shape( + program: &'static Stage1VerifierProgramPlan, + driver: &'static Stage1SumcheckDriverPlan, + actual: &[Stage1NamedEval], + expected_value: Option, +) -> Result<(), VerifyStage1Error> { + let expected = program + .evals + .iter() + .filter(|eval| eval.source == driver.symbol) + .collect::>(); + if actual.len() != expected.len() { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "eval count mismatch", + }); + } + for (actual, expected) in actual.iter().zip(expected) { + if actual.name != expected.name { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "eval name mismatch", + }); + } + if actual.oracle != expected.oracle { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "eval oracle mismatch", + }); + } + if expected_value.is_some_and(|value| actual.value != value) { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "eval value mismatch", + }); + } + } + Ok(()) +} + +fn append_opening_claims(transcript: &mut T, evals: &[Stage1NamedEval]) +where + T: Transcript, +{ + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } +} +"# + } +} + +fn emit_str_array(name: &str, values: &[String]) -> String { + if values.is_empty() { + return format!("pub const {name}: &[&str] = &[];\n\n"); + } + if let [value] = values { + return format!("pub const {name}: &[&str] = &[{}];\n\n", rust_str(value)); + } + let entries = values + .iter() + .map(|value| format!(" {},", rust_str(value))) + .collect::>() + .join("\n"); + format!("pub const {name}: &[&str] = &[\n{entries}\n];\n\n") +} + +fn emit_usize_array(name: &str, values: &[usize]) -> String { + let entries = values + .iter() + .map(|value| format!(" {value},")) + .collect::>() + .join("\n"); + format!("pub const {name}: &[usize] = &[\n{entries}\n];\n\n") +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn verify_count(kind: &str, symbol: &str, expected: usize, actual: usize) -> Result<(), EmitError> { + if expected == actual { + Ok(()) + } else { + Err(EmitError::new(format!( + "{kind} @{symbol} count mismatch: expected {expected}, got {actual}" + ))) + } +} + +fn missing_role_binding(kind: &str, symbol: &str) -> EmitError { + EmitError::new(format!("missing {kind} for `{symbol}`")) +} + +fn symbols<'a>(values: impl Iterator) -> BTreeSet { + values.cloned().collect() +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn int_array_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "integer array"))?; + parse_int_array(&attribute).ok_or_else(|| attr_error(operation, attr, "integer array")) +} + +fn parse_int_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().parse().ok()) + .collect() +} + +fn operand_symbols( + operation: OperationRef<'_, '_>, + start_index: usize, +) -> Result, EmitError> { + (start_index..operation.operand_count()) + .map(|index| operand_symbol(operation, index)) + .collect() +} + +fn operand_symbol(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + EmitError::new(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + EmitError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name(operation: OperationRef<'_, '_>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} + +fn require_supported_symbol(kind: &str, actual: &str, expected: &str) -> Result<(), EmitError> { + if actual == expected { + Ok(()) + } else { + Err(EmitError::new(format!( + "unsupported {kind} @{actual}; Rust stage1 emitter currently supports @{expected}" + ))) + } +} diff --git a/crates/bolt/src/protocols/jolt/emit/rust/stage2.rs b/crates/bolt/src/protocols/jolt/emit/rust/stage2.rs new file mode 100644 index 0000000000..eb8eceadb7 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/stage2.rs @@ -0,0 +1,2695 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{Attribute, OperationRef}; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2CpuProgram { + pub role: Role, + pub params: Stage2Params, + pub steps: Vec, + pub transcript_squeezes: Vec, + pub opening_inputs: Vec, + pub field_constants: Vec, + pub field_exprs: Vec, + pub kernels: Vec, + pub claims: Vec, + pub batches: Vec, + pub drivers: Vec, + pub instance_results: Vec, + pub evals: Vec, + pub point_slices: Vec, + pub point_concats: Vec, + pub opening_claims: Vec, + pub opening_batches: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2Params { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2KernelPlan { + pub symbol: String, + pub relation: String, + pub kind: String, + pub backend: String, + pub abi: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2TranscriptSqueezePlan { + pub symbol: String, + pub label: String, + pub kind: String, + pub count: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2ProgramStepPlan { + pub kind: String, + pub symbol: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2OpeningInputPlan { + pub symbol: String, + pub source_stage: String, + pub source_claim: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2FieldConstantPlan { + pub symbol: String, + pub field: String, + pub value: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2FieldExprPlan { + pub symbol: String, + pub kind: String, + pub formula: String, + pub operand_names: Vec, + pub operands: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckClaimPlan { + pub symbol: String, + pub stage: String, + pub domain: String, + pub num_rounds: usize, + pub degree: usize, + pub claim: String, + pub kernel: Option, + pub relation: Option, + pub claim_value: String, + pub input_openings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, + pub claim_label: String, + pub round_label: String, + pub round_schedule: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckDriverPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub kernel: Option, + pub relation: Option, + pub batch: String, + pub policy: String, + pub round_schedule: Vec, + pub claim_label: String, + pub round_label: String, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckInstanceResultPlan { + pub symbol: String, + pub source: String, + pub claim: String, + pub relation: String, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: String, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckEvalPlan { + pub symbol: String, + pub source: String, + pub name: String, + pub index: usize, + pub oracle: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2PointSlicePlan { + pub symbol: String, + pub source: String, + pub offset: usize, + pub length: usize, + pub input: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2PointConcatPlan { + pub symbol: String, + pub layout: String, + pub arity: usize, + pub inputs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2OpeningClaimPlan { + pub symbol: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, + pub point_source: String, + pub eval_source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2OpeningBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, +} + +pub fn stage2_cpu_program(module: &BoltModule<'_, Cpu>) -> Result { + verify_cpu_schema(module)?; + let program = Stage2CpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +pub fn emit_stage2_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = stage2_cpu_program(module)?; + + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source()?, + }) +} + +impl Stage2CpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let mut params = None; + let mut steps = Vec::new(); + let mut transcript_squeezes = Vec::new(); + let mut opening_inputs = Vec::new(); + let mut field_constants = Vec::new(); + let mut field_exprs = Vec::new(); + let mut kernels = Vec::new(); + let mut claims = Vec::new(); + let mut batches = Vec::new(); + let mut drivers = Vec::new(); + let mut instance_results = Vec::new(); + let mut evals = Vec::new(); + let mut point_slices = Vec::new(); + let mut point_concats = Vec::new(); + let mut opening_claims = Vec::new(); + let mut opening_batches = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(Stage2Params { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.kernel" => { + kernels.push(Stage2KernelPlan { + symbol: string_attr(op, "sym_name")?, + relation: symbol_attr(op, "relation")?, + kind: string_attr(op, "kind")?, + backend: string_attr(op, "backend")?, + abi: string_attr(op, "abi")?, + }); + } + "cpu.transcript_squeeze" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage2ProgramStepPlan { + kind: "transcript_squeeze".to_owned(), + symbol: symbol.clone(), + }); + transcript_squeezes.push(Stage2TranscriptSqueezePlan { + symbol, + label: string_attr(op, "label")?, + kind: string_attr(op, "kind")?, + count: int_attr(op, "count")?, + }); + } + "cpu.opening_input" => { + opening_inputs.push(Stage2OpeningInputPlan { + symbol: string_attr(op, "sym_name")?, + source_stage: symbol_attr(op, "source_stage")?, + source_claim: symbol_attr(op, "source_claim")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + }); + } + "cpu.field_const" => { + field_constants.push(Stage2FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: int_attr(op, "value")?, + }); + } + "cpu.field_zero" => { + field_constants.push(Stage2FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 0, + }); + } + "cpu.field_one" => { + field_constants.push(Stage2FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 1, + }); + } + "cpu.field_add" | "cpu.field_sub" | "cpu.field_mul" | "cpu.field_neg" => { + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage2FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: operation_name(op).replace("cpu.field_", "field."), + operand_names: operands.clone(), + operands, + }); + } + "cpu.field_pow" => { + let exponent = int_attr(op, "exponent")?; + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage2FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: format!("field.pow:{exponent}"), + operand_names: operands.clone(), + operands, + }); + } + "cpu.poly_lagrange_basis_eval" => { + let domain_start = signed_int_attr(op, "domain_start")?; + let domain_size = int_attr(op, "domain_size")?; + let index = int_attr(op, "index")?; + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage2FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: format!( + "poly.lagrange_basis_eval:{domain_start}:{domain_size}:{index}" + ), + operand_names: operands.clone(), + operands, + }); + } + "cpu.sumcheck_claim" => { + claims.push(Stage2SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_verify_claim" => { + claims.push(Stage2SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_batch" => { + batches.push(Stage2SumcheckBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + round_schedule: int_array_attr(op, "round_schedule")?, + }); + } + "cpu.sumcheck_driver" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage2ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage2SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_verify" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage2ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage2SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_instance_result" => { + instance_results.push(Stage2SumcheckInstanceResultPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + claim: symbol_attr(op, "claim")?, + relation: symbol_attr(op, "relation")?, + index: int_attr(op, "index")?, + point_arity: int_attr(op, "point_arity")?, + num_rounds: int_attr(op, "num_rounds")?, + round_offset: int_attr(op, "round_offset")?, + point_order: string_attr(op, "point_order")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_eval" => { + evals.push(Stage2SumcheckEvalPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + name: symbol_attr(op, "name")?, + index: int_attr(op, "index")?, + oracle: symbol_attr(op, "oracle")?, + }); + } + "cpu.point_slice" => { + point_slices.push(Stage2PointSlicePlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + offset: int_attr(op, "offset")?, + length: int_attr(op, "length")?, + input: operand_symbol(op, 0)?, + }); + } + "cpu.point_concat" => { + point_concats.push(Stage2PointConcatPlan { + symbol: string_attr(op, "sym_name")?, + layout: string_attr(op, "layout")?, + arity: int_attr(op, "arity")?, + inputs: operand_symbols(op, 0)?, + }); + } + "cpu.opening_claim" => { + opening_claims.push(Stage2OpeningClaimPlan { + symbol: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + point_source: operand_symbol(op, 0)?, + eval_source: operand_symbol(op, 1)?, + }); + } + "cpu.opening_batch" => { + opening_batches.push(Stage2OpeningBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + }); + } + _ => {} + } + } + + Ok(Self { + params: params.ok_or_else(|| EmitError::new("missing cpu.params"))?, + role: module + .role() + .ok_or_else(|| EmitError::new("missing cpu party role"))?, + steps, + transcript_squeezes, + opening_inputs, + field_constants, + field_exprs, + kernels, + claims, + batches, + drivers, + instance_results, + evals, + point_slices, + point_concats, + opening_claims, + opening_batches, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + require_supported_symbol("field", &self.params.field, "bn254_fr")?; + require_supported_symbol("pcs", &self.params.pcs, "dory")?; + require_supported_symbol("transcript", &self.params.transcript, "blake2b_transcript")?; + self.verify_transcript_squeezes()?; + self.verify_field_flow()?; + self.verify_claim_batches()?; + match self.role { + Role::Prover => { + self.verify_kernel_definitions()?; + self.verify_prover_driver_bindings()?; + } + Role::Verifier => self.verify_verifier_driver_bindings()?, + } + self.verify_opening_flow() + } + + fn verify_transcript_squeezes(&self) -> Result<(), EmitError> { + for squeeze in &self.transcript_squeezes { + if !matches!( + squeeze.kind.as_str(), + "challenge_scalar" | "challenge_vector" + ) { + return Err(EmitError::new(format!( + "stage2 transcript squeeze @{} has unsupported kind `{}`", + squeeze.symbol, squeeze.kind + ))); + } + if squeeze.count == 0 { + return Err(EmitError::new(format!( + "stage2 transcript squeeze @{} has zero count", + squeeze.symbol + ))); + } + } + Ok(()) + } + + fn verify_field_flow(&self) -> Result<(), EmitError> { + for constant in &self.field_constants { + require_supported_symbol("field constant field", &constant.field, "bn254_fr")?; + } + let field_values = self.field_value_symbols(); + for expr in &self.field_exprs { + verify_count( + "field expr operands", + &expr.symbol, + expr.operand_names.len(), + expr.operands.len(), + )?; + for operand in &expr.operands { + if !field_values.contains(operand) { + return Err(EmitError::new(format!( + "field expr @{} references missing field value @{operand}", + expr.symbol + ))); + } + } + } + for claim in &self.claims { + if !field_values.contains(&claim.claim_value) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing claim value @{}", + claim.symbol, claim.claim_value + ))); + } + } + Ok(()) + } + + fn field_value_symbols(&self) -> BTreeSet { + let mut values = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + values.extend(symbols( + self.field_constants.iter().map(|constant| &constant.symbol), + )); + values.extend(symbols( + self.transcript_squeezes + .iter() + .filter(|squeeze| matches!(squeeze.kind.as_str(), "challenge_scalar" | "scalar")) + .map(|squeeze| &squeeze.symbol), + )); + values.extend(symbols(self.field_exprs.iter().map(|expr| &expr.symbol))); + values.extend(symbols(self.evals.iter().map(|eval| &eval.symbol))); + values + } + + fn verify_kernel_definitions(&self) -> Result<(), EmitError> { + for kernel in &self.kernels { + if kernel.backend != "cpu" { + return Err(EmitError::new(format!( + "stage2 kernel @{} targets unsupported backend `{}`", + kernel.symbol, kernel.backend + ))); + } + if kernel.kind != "sumcheck" { + return Err(EmitError::new(format!( + "stage2 kernel @{} has unsupported kind `{}`", + kernel.symbol, kernel.kind + ))); + } + let expected_abi = match kernel.relation.as_str() { + "jolt.stage2.product_virtual.uniskip" => "jolt_stage2_product_virtual_uniskip", + "jolt.stage2.ram.read_write" => "jolt_stage2_ram_read_write", + "jolt.stage2.product_virtual.remainder" => "jolt_stage2_product_virtual_remainder", + "jolt.stage2.instruction_lookup.claim_reduction" => { + "jolt_stage2_instruction_lookup_claim_reduction" + } + "jolt.stage2.ram.raf_evaluation" => "jolt_stage2_ram_raf_evaluation", + "jolt.stage2.ram.output_check" => "jolt_stage2_ram_output_check", + "jolt.stage2.batched" => "jolt_stage2_batched", + _ => { + return Err(EmitError::new(format!( + "unsupported stage2 kernel relation @{}", + kernel.relation + ))); + } + }; + if kernel.abi != expected_abi { + return Err(EmitError::new(format!( + "stage2 kernel @{} ABI `{}` does not match relation @{}", + kernel.symbol, kernel.abi, kernel.relation + ))); + } + } + Ok(()) + } + + fn verify_claim_batches(&self) -> Result<(), EmitError> { + let claims = symbols(self.claims.iter().map(|claim| &claim.symbol)); + for batch in &self.batches { + verify_count( + "sumcheck batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "sumcheck batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "sumcheck batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !claims.contains(claim) { + return Err(EmitError::new(format!( + "sumcheck batch @{} references missing claim @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn verify_prover_driver_bindings(&self) -> Result<(), EmitError> { + let kernels = symbols(self.kernels.iter().map(|kernel| &kernel.symbol)); + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + let Some(kernel) = claim.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck claim @{} is missing kernel", + claim.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck claim @{} references missing kernel @{kernel}", + claim.symbol + ))); + } + } + for driver in &self.drivers { + let Some(kernel) = driver.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck driver @{} is missing kernel", + driver.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck driver @{} references missing kernel @{kernel}", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_verifier_driver_bindings(&self) -> Result<(), EmitError> { + if !self.kernels.is_empty() { + return Err(EmitError::new( + "verifier stage2 program must not contain kernels", + )); + } + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + if claim.kernel.is_some() || claim.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck claim @{} must carry relation and no kernel", + claim.symbol + ))); + } + } + for driver in &self.drivers { + if driver.kernel.is_some() || driver.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck driver @{} must carry relation and no kernel", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_opening_flow(&self) -> Result<(), EmitError> { + let mut point_sources = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + point_sources.extend(symbols( + self.instance_results + .iter() + .map(|instance| &instance.symbol), + )); + point_sources.extend(symbols( + self.opening_inputs.iter().map(|input| &input.symbol), + )); + point_sources.extend(symbols(self.point_slices.iter().map(|slice| &slice.symbol))); + point_sources.extend(symbols( + self.point_concats.iter().map(|concat| &concat.symbol), + )); + for slice in &self.point_slices { + if !point_sources.contains(&slice.input) { + return Err(EmitError::new(format!( + "point slice @{} uses missing point source @{}", + slice.symbol, slice.input + ))); + } + } + for concat in &self.point_concats { + for input in &concat.inputs { + if !point_sources.contains(input) { + return Err(EmitError::new(format!( + "point concat @{} uses missing point source @{input}", + concat.symbol + ))); + } + } + } + let eval_sources = self.field_value_symbols(); + let mut opening_sources = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + opening_sources.extend(symbols( + self.opening_claims.iter().map(|claim| &claim.symbol), + )); + for claim in &self.claims { + for input in &claim.input_openings { + if !opening_sources.contains(input) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing opening @{input}", + claim.symbol + ))); + } + } + } + let drivers = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + for instance in &self.instance_results { + if !drivers.contains(&instance.source) { + return Err(EmitError::new(format!( + "sumcheck instance result @{} references missing driver @{}", + instance.symbol, instance.source + ))); + } + } + for eval in &self.evals { + if !drivers.contains(&eval.source) { + return Err(EmitError::new(format!( + "sumcheck eval @{} references missing driver @{}", + eval.symbol, eval.source + ))); + } + } + for claim in &self.opening_claims { + if !point_sources.contains(&claim.point_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing point source @{}", + claim.symbol, claim.point_source + ))); + } + if !eval_sources.contains(&claim.eval_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing eval source @{}", + claim.symbol, claim.eval_source + ))); + } + } + let openings = symbols(self.opening_claims.iter().map(|claim| &claim.symbol)); + for batch in &self.opening_batches { + verify_count( + "opening batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "opening batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "opening batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !openings.contains(claim) { + return Err(EmitError::new(format!( + "opening batch @{} references missing opening @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn emit_source(&self) -> Result { + match self.role { + Role::Prover => self.emit_prover_source(), + Role::Verifier => self.emit_verifier_source(), + } + } + + fn emit_prover_source(&self) -> Result { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + source.push_str(Self::emit_prover_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_prover_types()); + source.push('\n'); + source.push_str(&self.emit_prover_constants()?); + source.push('\n'); + source.push_str(Self::emit_prover_entrypoint()); + Ok(source) + } + + fn emit_verifier_source(&self) -> Result { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + source.push_str(Self::emit_verifier_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_verifier_types()); + source.push('\n'); + source.push_str(&self.emit_verifier_constants()?); + source.push('\n'); + source.push_str(Self::emit_verifier_entrypoint()); + Ok(source) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_stage2.rs", + Role::Verifier => "verify_stage2.rs", + } + } + + fn emit_prover_imports() -> &'static str { + "use jolt_field::Fr;\n\ + use jolt_kernels::stage2::{execute_stage2_program, Stage2CpuProgramPlan, Stage2ExecutionArtifacts, Stage2ExecutionMode, Stage2FieldConstantPlan, Stage2FieldExprPlan, Stage2KernelError, Stage2KernelExecutor, Stage2KernelPlan, Stage2OpeningBatchPlan, Stage2OpeningClaimPlan, Stage2OpeningInputPlan, Stage2Params, Stage2PointConcatPlan, Stage2PointSlicePlan, Stage2ProgramStepPlan, Stage2SumcheckBatchPlan, Stage2SumcheckClaimPlan, Stage2SumcheckDriverPlan, Stage2SumcheckEvalPlan, Stage2SumcheckInstanceResultPlan, Stage2TranscriptSqueezePlan};\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_prover_types() -> &'static str { + "pub type DefaultStage2Transcript = Blake2bTranscript;\n" + } + + fn emit_verifier_imports() -> &'static str { + "use super::common::{append_labeled_scalar, batch_claims, eval_by_name, find_batch, find_plan, pow_field, require_operand_count, reverse_slice, single_operand};\n\ + use jolt_field::{Field, Fr};\n\ + use jolt_poly::lagrange::{lagrange_evals, lagrange_kernel_eval};\n\ + use jolt_poly::{EqPolynomial, UnivariatePoly};\n\ + use jolt_sumcheck::{CompressedLabeledRoundPoly, SumcheckClaim, SumcheckError, SumcheckVerifier};\n\ + use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript};" + } + + fn emit_verifier_types() -> &'static str { + r"pub type DefaultStage2Transcript = Blake2bTranscript; + +pub type Stage2NamedEval = super::common::StageNamedEval; +pub type Stage2SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage2ChallengeVector = super::common::StageChallengeVector; +pub type Stage2ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage2Proof = super::common::StageProof; +pub type Stage2OpeningInputValue = super::common::StageOpeningInputValue; +pub type Stage2VerifierProgramPlan = super::common::StageVerifierProgramPlanNoEqualities; + +pub use super::common::{ + FieldConstantPlan as Stage2FieldConstantPlan, FieldExprPlan as Stage2FieldExprPlan, + OpeningBatchPlan as Stage2OpeningBatchPlan, OpeningClaimPlan as Stage2OpeningClaimPlan, + OpeningInputPlan as Stage2OpeningInputPlan, PointConcatPlan as Stage2PointConcatPlan, + PointSlicePlan as Stage2PointSlicePlan, ProgramStepPlan as Stage2ProgramStepPlan, + StageParams as Stage2Params, SumcheckBatchPlan as Stage2SumcheckBatchPlan, + SumcheckEvalPlan as Stage2SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage2SumcheckInstanceResultPlan, + TranscriptSqueezePlan as Stage2TranscriptSqueezePlan, + SumcheckClaimPlan as Stage2SumcheckClaimPlan, + SumcheckDriverPlan as Stage2SumcheckDriverPlan, +}; + +#[derive(Clone, Copy, Debug)] +pub struct Stage2RamAccess { + pub remapped_address: Option, + pub read_value: u64, + pub write_value: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage2RamOutputLayout { + pub io_start: usize, + pub io_end: usize, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage2RamData<'a> { + pub log_k: usize, + pub start_address: u64, + pub initial_ram: &'a [u64], + pub final_ram: &'a [u64], + pub accesses: &'a [Stage2RamAccess], + pub output_layout: Option, +} + +#[derive(Clone, Debug, Default)] +struct Stage2ValueStore(super::common::ValueStore); + +#[derive(Debug)] +pub enum VerifyStage2Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + MissingRam { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage2Error); +" + } + + fn emit_prover_constants(&self) -> Result { + let mut source = self.emit_shared_constants(); + source.push_str(&self.emit_kernel_constants()); + source.push_str(&self.emit_prover_sumcheck_claim_constants()?); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_prover_sumcheck_driver_constants()?); + source.push_str(&self.emit_tail_constants()); + source.push_str( + "pub const STAGE2_PROGRAM: Stage2CpuProgramPlan = Stage2CpuProgramPlan {\n\ + \x20 params: STAGE2_PARAMS,\n\ + \x20 steps: STAGE2_PROGRAM_STEPS,\n\ + \x20 transcript_squeezes: STAGE2_TRANSCRIPT_SQUEEZES,\n\ + \x20 opening_inputs: STAGE2_OPENING_INPUTS,\n\ + \x20 field_constants: STAGE2_FIELD_CONSTANTS,\n\ + \x20 field_exprs: STAGE2_FIELD_EXPRS,\n\ + \x20 kernels: STAGE2_KERNELS,\n\ + \x20 claims: STAGE2_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE2_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE2_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE2_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE2_SUMCHECK_EVALS,\n\ + \x20 point_slices: STAGE2_POINT_SLICES,\n\ + \x20 point_concats: STAGE2_POINT_CONCATS,\n\ + \x20 opening_claims: STAGE2_OPENING_CLAIMS,\n\ + \x20 opening_batches: STAGE2_OPENING_BATCHES,\n\ + };\n", + ); + Ok(source) + } + + fn emit_verifier_constants(&self) -> Result { + let mut source = self.emit_shared_constants(); + source.push_str(&self.emit_verifier_sumcheck_claim_constants()?); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_verifier_sumcheck_driver_constants()?); + source.push_str(&self.emit_tail_constants()); + source.push_str( + "pub const STAGE2_PROGRAM: Stage2VerifierProgramPlan = Stage2VerifierProgramPlan {\n\ + \x20 params: STAGE2_PARAMS,\n\ + \x20 steps: STAGE2_PROGRAM_STEPS,\n\ + \x20 transcript_squeezes: STAGE2_TRANSCRIPT_SQUEEZES,\n\ + \x20 opening_inputs: STAGE2_OPENING_INPUTS,\n\ + \x20 field_constants: STAGE2_FIELD_CONSTANTS,\n\ + \x20 field_exprs: STAGE2_FIELD_EXPRS,\n\ + \x20 claims: STAGE2_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE2_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE2_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE2_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE2_SUMCHECK_EVALS,\n\ + \x20 point_slices: STAGE2_POINT_SLICES,\n\ + \x20 point_concats: STAGE2_POINT_CONCATS,\n\ + \x20 opening_claims: STAGE2_OPENING_CLAIMS,\n\ + \x20 opening_batches: STAGE2_OPENING_BATCHES,\n\ + };\n", + ); + Ok(source) + } + + fn emit_shared_constants(&self) -> String { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const STAGE2_PARAMS: Stage2Params = Stage2Params {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + source.push_str(&self.emit_program_step_constants()); + source.push_str(&self.emit_transcript_squeeze_constants()); + source.push_str(&self.emit_opening_input_constants()); + source.push_str(&self.emit_field_constant_constants()); + source.push_str(&self.emit_field_expr_constants()); + source + } + + fn emit_program_step_constants(&self) -> String { + let steps = self + .steps + .iter() + .map(|step| { + format!( + " Stage2ProgramStepPlan {{ kind: {}, symbol: {} }},", + rust_str(&step.kind), + rust_str(&step.symbol), + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE2_PROGRAM_STEPS: &[Stage2ProgramStepPlan] = &[\n{steps}\n];\n\n") + } + + fn emit_transcript_squeeze_constants(&self) -> String { + let squeezes = self + .transcript_squeezes + .iter() + .map(|squeeze| { + format!( + " Stage2TranscriptSqueezePlan {{ symbol: {}, label: {}, kind: {}, count: {} }},", + rust_str(&squeeze.symbol), + rust_str(&squeeze.label), + rust_str(&squeeze.kind), + squeeze.count, + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE2_TRANSCRIPT_SQUEEZES: &[Stage2TranscriptSqueezePlan] = &[\n{squeezes}\n];\n\n" + ) + } + + fn emit_opening_input_constants(&self) -> String { + let inputs = self + .opening_inputs + .iter() + .map(|input| { + format!( + " Stage2OpeningInputPlan {{ symbol: {}, source_stage: {}, source_claim: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {} }},", + rust_str(&input.symbol), + rust_str(&input.source_stage), + rust_str(&input.source_claim), + rust_str(&input.oracle), + rust_str(&input.domain), + input.point_arity, + rust_str(&input.claim_kind) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE2_OPENING_INPUTS: &[Stage2OpeningInputPlan] = &[\n{inputs}\n];\n\n") + } + + fn emit_field_constant_constants(&self) -> String { + let constants = self + .field_constants + .iter() + .map(|constant| { + format!( + " Stage2FieldConstantPlan {{ symbol: {}, field: {}, value: {} }},", + rust_str(&constant.symbol), + rust_str(&constant.field), + constant.value + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE2_FIELD_CONSTANTS: &[Stage2FieldConstantPlan] = &[\n{constants}\n];\n\n" + ) + } + + fn emit_field_expr_constants(&self) -> String { + if self.role == Role::Verifier { + let exprs = self + .field_exprs + .iter() + .map(|expr| { + format!( + " Stage2FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operands: {} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula), + rust_str(&expr.operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE2_FIELD_EXPRS: &[Stage2FieldExprPlan] = &[\n{exprs}\n];\n" + ); + } + + let mut source = String::new(); + let mut arrays = Vec::new(); + let mut array_refs = Vec::new(); + for (index, expr) in self.field_exprs.iter().enumerate() { + let operands = intern_str_array( + &mut source, + &mut arrays, + "STAGE2_FIELD_EXPR_OPERANDS", + &expr.operands, + ); + let operand_names = intern_str_array( + &mut source, + &mut arrays, + "STAGE2_FIELD_EXPR_OPERANDS", + &expr.operand_names, + ); + array_refs.push((index, operand_names, operands)); + } + let exprs = self + .field_exprs + .iter() + .enumerate() + .map(|(index, expr)| { + let (_, operand_names, operands) = &array_refs[index]; + format!( + " Stage2FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operand_names: {operand_names}, operands: {operands} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE2_FIELD_EXPRS: &[Stage2FieldExprPlan] = &[\n{exprs}\n];\n" + ), + ); + source + } + + fn emit_kernel_constants(&self) -> String { + let kernels = self + .kernels + .iter() + .map(|kernel| { + format!( + " Stage2KernelPlan {{ symbol: {}, relation: {}, kind: {}, backend: {}, abi: {} }},", + rust_str(&kernel.symbol), + rust_str(&kernel.relation), + rust_str(&kernel.kind), + rust_str(&kernel.backend), + rust_str(&kernel.abi) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE2_KERNELS: &[Stage2KernelPlan] = &[\n{kernels}\n];\n\n") + } + + fn emit_prover_sumcheck_claim_constants(&self) -> Result { + self.emit_sumcheck_claim_constants(true) + } + + fn emit_verifier_sumcheck_claim_constants(&self) -> Result { + self.emit_sumcheck_claim_constants(false) + } + + fn emit_sumcheck_claim_constants(&self, prover: bool) -> Result { + let mut source = String::new(); + if prover { + for (index, claim) in self.claims.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE2_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS"), + &claim.input_openings, + )); + } + } + let mut claims = Vec::new(); + for (index, claim) in self.claims.iter().enumerate() { + if prover { + let kernel = claim + .kernel + .as_deref() + .ok_or_else(|| missing_role_binding("prover claim kernel", &claim.symbol))?; + claims.push(format!( + " Stage2SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: Some({}), relation: None, claim_value: {}, input_openings: STAGE2_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_str(kernel), + rust_str(&claim.claim_value) + )); + } else { + let relation = claim.relation.as_deref().ok_or_else(|| { + missing_role_binding("verifier claim relation", &claim.symbol) + })?; + claims.push(format!( + " Stage2SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: None, relation: Some({}), claim_value: {}, input_openings: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_str(relation), + rust_str(&claim.claim_value), + rust_str(&claim.input_openings.join("|")) + )); + } + } + let claims = claims.join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE2_SUMCHECK_CLAIMS: &[Stage2SumcheckClaimPlan] = &[\n{claims}\n];\n" + ), + ); + Ok(source) + } + + fn emit_sumcheck_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE2_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage2SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {}, claim_label: {}, round_label: {}, round_schedule: STAGE2_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")), + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE2_SUMCHECK_BATCHES: &[Stage2SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + return source; + } + + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE2_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE2_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + source.push_str(&emit_usize_array( + &format!("STAGE2_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage2SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE2_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE2_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS, claim_label: {}, round_label: {}, round_schedule: STAGE2_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE2_SUMCHECK_BATCHES: &[Stage2SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_prover_sumcheck_driver_constants(&self) -> Result { + self.emit_sumcheck_driver_constants(true) + } + + fn emit_verifier_sumcheck_driver_constants(&self) -> Result { + self.emit_sumcheck_driver_constants(false) + } + + fn emit_sumcheck_driver_constants(&self, prover: bool) -> Result { + let mut source = String::new(); + for (index, driver) in self.drivers.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE2_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE"), + &driver.round_schedule, + )); + } + let mut drivers = Vec::new(); + for (index, driver) in self.drivers.iter().enumerate() { + if prover { + let kernel = driver + .kernel + .as_deref() + .ok_or_else(|| missing_role_binding("prover driver kernel", &driver.symbol))?; + drivers.push(format!( + " Stage2SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: Some({}), relation: None, batch: {}, policy: {}, round_schedule: STAGE2_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_str(kernel), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + )); + } else { + let relation = driver.relation.as_deref().ok_or_else(|| { + missing_role_binding("verifier driver relation", &driver.symbol) + })?; + drivers.push(format!( + " Stage2SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: None, relation: Some({}), batch: {}, policy: {}, round_schedule: STAGE2_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_str(relation), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + )); + } + } + let drivers = drivers.join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE2_SUMCHECK_DRIVERS: &[Stage2SumcheckDriverPlan] = &[\n{drivers}\n];\n" + ), + ); + Ok(source) + } + + fn emit_tail_constants(&self) -> String { + let mut source = String::new(); + source.push_str(&self.emit_sumcheck_instance_result_constants()); + source.push_str(&self.emit_sumcheck_eval_constants()); + source.push_str(&self.emit_point_slice_constants()); + source.push_str(&self.emit_point_concat_constants()); + source.push_str(&self.emit_opening_claim_constants()); + source.push_str(&self.emit_opening_batch_constants()); + source + } + + fn emit_sumcheck_instance_result_constants(&self) -> String { + let instances = self + .instance_results + .iter() + .map(|instance| { + format!( + " Stage2SumcheckInstanceResultPlan {{ symbol: {}, source: {}, claim: {}, relation: {}, index: {}, point_arity: {}, num_rounds: {}, round_offset: {}, point_order: {}, degree: {} }},", + rust_str(&instance.symbol), + rust_str(&instance.source), + rust_str(&instance.claim), + rust_str(&instance.relation), + instance.index, + instance.point_arity, + instance.num_rounds, + instance.round_offset, + rust_str(&instance.point_order), + instance.degree + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE2_SUMCHECK_INSTANCE_RESULTS: &[Stage2SumcheckInstanceResultPlan] = &[\n{instances}\n];\n\n" + ) + } + + fn emit_sumcheck_eval_constants(&self) -> String { + let evals = self + .evals + .iter() + .map(|eval| { + format!( + " Stage2SumcheckEvalPlan {{ symbol: {}, source: {}, name: {}, index: {}, oracle: {} }},", + rust_str(&eval.symbol), + rust_str(&eval.source), + rust_str(&eval.name), + eval.index, + rust_str(&eval.oracle) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE2_SUMCHECK_EVALS: &[Stage2SumcheckEvalPlan] = &[\n{evals}\n];\n\n") + } + + fn emit_point_slice_constants(&self) -> String { + let slices = self + .point_slices + .iter() + .map(|slice| { + format!( + " Stage2PointSlicePlan {{ symbol: {}, source: {}, offset: {}, length: {}, input: {} }},", + rust_str(&slice.symbol), + rust_str(&slice.source), + slice.offset, + slice.length, + rust_str(&slice.input) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE2_POINT_SLICES: &[Stage2PointSlicePlan] = &[\n{slices}\n];\n\n") + } + + fn emit_point_concat_constants(&self) -> String { + if self.role == Role::Verifier { + let concats = self + .point_concats + .iter() + .map(|concat| { + format!( + " Stage2PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: {} }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity, + rust_str(&concat.inputs.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE2_POINT_CONCATS: &[Stage2PointConcatPlan] = &[\n{concats}\n];\n" + ); + } + + let mut source = String::new(); + for (index, concat) in self.point_concats.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE2_POINT_CONCAT_{index}_INPUTS"), + &concat.inputs, + )); + } + let concats = self + .point_concats + .iter() + .enumerate() + .map(|(index, concat)| { + format!( + " Stage2PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: STAGE2_POINT_CONCAT_{index}_INPUTS }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE2_POINT_CONCATS: &[Stage2PointConcatPlan] = &[\n{concats}\n];\n" + ), + ); + source + } + + fn emit_opening_claim_constants(&self) -> String { + let claims = self + .opening_claims + .iter() + .map(|claim| { + format!( + " Stage2OpeningClaimPlan {{ symbol: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {}, point_source: {}, eval_source: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.oracle), + rust_str(&claim.domain), + claim.point_arity, + rust_str(&claim.claim_kind), + rust_str(&claim.point_source), + rust_str(&claim.eval_source) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE2_OPENING_CLAIMS: &[Stage2OpeningClaimPlan] = &[\n{claims}\n];\n\n") + } + + fn emit_opening_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let batches = self + .opening_batches + .iter() + .map(|batch| { + format!( + " Stage2OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {} }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE2_OPENING_BATCHES: &[Stage2OpeningBatchPlan] = &[\n{batches}\n];\n" + ); + } + + let mut source = String::new(); + for (index, batch) in self.opening_batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE2_OPENING_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE2_OPENING_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + } + let batches = self + .opening_batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage2OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE2_OPENING_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE2_OPENING_BATCH_{index}_CLAIM_OPERANDS }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE2_OPENING_BATCHES: &[Stage2OpeningBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_prover_entrypoint() -> &'static str { + "pub fn execute_stage2_prover(\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage2KernelError>\n\ + where\n\ + \x20 E: Stage2KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage2_prover_with_program(&STAGE2_PROGRAM, executor, transcript)\n\ + }\n\ + \n\ + pub fn execute_stage2_prover_with_program(\n\ + \x20 program: &'static Stage2CpuProgramPlan,\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage2KernelError>\n\ + where\n\ + \x20 E: Stage2KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage2_program(program, Stage2ExecutionMode::Prover, executor, transcript)\n\ + }\n" + } + + fn emit_verifier_entrypoint() -> &'static str { + r#"const PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START: i64 = -1; +const PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE: usize = 3; + +pub fn verify_stage2( + proof: &Stage2Proof, + opening_inputs: &[Stage2OpeningInputValue], + ram: Option<&Stage2RamData<'_>>, + transcript: &mut T, +) -> Result, VerifyStage2Error> +where + T: Transcript, +{ + verify_stage2_with_program(&STAGE2_PROGRAM, proof, opening_inputs, ram, transcript) +} + +pub fn verify_stage2_with_program( + program: &'static Stage2VerifierProgramPlan, + proof: &Stage2Proof, + opening_inputs: &[Stage2OpeningInputValue], + ram: Option<&Stage2RamData<'_>>, + transcript: &mut T, +) -> Result, VerifyStage2Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage2Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = Stage2ValueStore::with_opening_inputs(program, opening_inputs)?; + store.seed_constants(program); + let mut artifacts = Stage2ExecutionArtifacts::default(); + if program.steps.is_empty() { + for squeeze in program.transcript_squeezes { + verify_stage2_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + for driver in program.drivers { + verify_stage2_driver(program, driver, proof, ram, &mut store, transcript, &mut artifacts)?; + } + } else { + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage2Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage2_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "sumcheck_driver" => { + let driver = find_plan(program.drivers, step.symbol).ok_or(VerifyStage2Error::MissingProof { + driver: step.symbol, + })?; + verify_stage2_driver(program, driver, proof, ram, &mut store, transcript, &mut artifacts)?; + } + _ => { + return Err(VerifyStage2Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage2 program step", + }); + } + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage2_verifier_program() -> &'static Stage2VerifierProgramPlan { + &STAGE2_PROGRAM +} + +fn verify_stage2_squeeze( + program: &'static Stage2VerifierProgramPlan, + squeeze: &'static Stage2TranscriptSqueezePlan, + store: &mut Stage2ValueStore, + transcript: &mut T, + artifacts: &mut Stage2ExecutionArtifacts, +) -> Result<(), VerifyStage2Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(program, squeeze, &values)?; + artifacts.challenge_vectors.push(Stage2ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn verify_stage2_driver( + program: &'static Stage2VerifierProgramPlan, + driver: &'static Stage2SumcheckDriverPlan, + proof: &Stage2Proof, + ram: Option<&Stage2RamData<'_>>, + store: &mut Stage2ValueStore, + transcript: &mut T, + artifacts: &mut Stage2ExecutionArtifacts, +) -> Result<(), VerifyStage2Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage2Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage2.product_virtual.uniskip" => { + verify_product_virtual_uniskip(program, driver, proof, store, transcript)? + } + "jolt.stage2.batched" => verify_batched_stage2(program, driver, proof, ram, store, transcript)?, + relation => return Err(VerifyStage2Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_product_virtual_uniskip( + program: &'static Stage2VerifierProgramPlan, + driver: &'static Stage2SumcheckDriverPlan, + proof: &Stage2SumcheckOutput, + store: &mut Stage2ValueStore, + transcript: &mut T, +) -> Result, VerifyStage2Error> +where + T: Transcript, +{ + validate_driver_symbol(driver, proof)?; + let [poly] = proof.proof.round_polynomials.as_slice() else { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "unexpected product uniskip round count", + }); + }; + if polynomial_degree(poly) > driver.degree { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "product uniskip polynomial exceeds degree bound", + }); + } + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claim = batch_claims(program.claims, batch)? + .into_iter() + .next() + .ok_or(VerifyStage2Error::MissingClaim { + batch: batch.symbol, + claim: "stage2.product_virtual.uniskip.input", + })?; + let input_claim = store.claim_value(program, claim)?; + if !product_uniskip_sum_matches(poly, input_claim) { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "product uniskip input claim mismatch", + }); + } + append_univariate_poly(transcript, driver.round_label, poly); + let r0 = transcript.challenge(); + if !proof.point.is_empty() && proof.point != [r0] { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "product uniskip point mismatch", + }); + } + let eval = poly.evaluate(r0); + append_labeled_scalar(transcript, "opening_claim", &eval); + let output = Stage2SumcheckOutput { + driver: driver.symbol, + point: vec![r0], + evals: driver_evals(program, driver.symbol, eval), + proof: proof.proof.clone(), + }; + verify_named_evals(driver.symbol, &output.evals, &proof.evals)?; + store.observe_sumcheck_output(program, &output)?; + Ok(output) +} + +fn verify_batched_stage2( + program: &'static Stage2VerifierProgramPlan, + driver: &'static Stage2SumcheckDriverPlan, + proof: &Stage2SumcheckOutput, + ram: Option<&Stage2RamData<'_>>, + store: &mut Stage2ValueStore, + transcript: &mut T, +) -> Result, VerifyStage2Error> +where + T: Transcript, +{ + validate_driver_symbol(driver, proof)?; + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let input_claims = store.batch_claim_values(program, batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let claimed_sum = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), coefficient)| { + claim.mul_pow_2(driver.num_rounds - plan.num_rounds) * *coefficient + }) + .sum::(); + let claim = SumcheckClaim::new(driver.num_rounds, driver.degree, claimed_sum); + let round_proofs = proof + .proof + .round_polynomials + .iter() + .map(|poly| CompressedLabeledRoundPoly::new(poly, driver.round_label.as_bytes())) + .collect::>(); + let output = SumcheckVerifier::verify(&claim, &round_proofs, transcript) + .map_err(|error| VerifyStage2Error::Sumcheck { + driver: driver.symbol, + error, + })?; + if !proof.point.is_empty() && proof.point != output.point { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "batched point mismatch", + }); + } + let expected = + expected_batched_output_claim(program, driver, &*store, &proof.evals, &output.point, &batching_coeffs, ram)?; + if output.value != expected { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "batched output claim mismatch", + }); + } + let verified = Stage2SumcheckOutput { + driver: driver.symbol, + point: output.point, + evals: proof.evals.clone(), + proof: proof.proof.clone(), + }; + store.observe_sumcheck_output(program, &verified)?; + super::common::append_opening_claims( + program.opening_inputs, + program.opening_claims, + program.opening_batches, + &mut store.0, + transcript, + &verified.evals, + |batch, claim| VerifyStage2Error::MissingClaim { batch, claim }, + |symbol| VerifyStage2Error::MissingValue { symbol }, + )?; + Ok(verified) +} + +impl Stage2ValueStore { + fn with_opening_inputs( + program: &'static Stage2VerifierProgramPlan, + inputs: &[Stage2OpeningInputValue], + ) -> Result { + Ok(Self(super::common::ValueStore::with_opening_inputs( + inputs, + program.opening_inputs, + )?)) + } + + fn seed_constants(&mut self, program: &'static Stage2VerifierProgramPlan) { + self.0.seed_constants(program.field_constants); + } + + fn observe_challenge_vector( + &mut self, + program: &'static Stage2VerifierProgramPlan, + plan: &'static Stage2TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), VerifyStage2Error> { + self.0.observe_challenge_vector(plan, values, |input, expected, actual| { + VerifyStage2Error::InvalidInputLength { input, expected, actual } + })?; + self.evaluate_available_points(program)?; + self.evaluate_available_field_exprs(program)?; + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + program: &'static Stage2VerifierProgramPlan, + output: &Stage2SumcheckOutput, + ) -> Result<(), VerifyStage2Error> { + self.0.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + _ => { + return Err(VerifyStage2Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage2Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage2Error::MissingValue { symbol }, + )?; + self.evaluate_available_points(program)?; + self.evaluate_available_field_exprs(program)?; + Ok(()) + } + + fn claim_value( + &mut self, + program: &'static Stage2VerifierProgramPlan, + claim: &Stage2SumcheckClaimPlan, + ) -> Result { + self.evaluate_available_field_exprs(program)?; + self.scalar(claim.claim_value) + } + + fn batch_claim_values( + &mut self, + program: &'static Stage2VerifierProgramPlan, + batch: &Stage2SumcheckBatchPlan, + ) -> Result, VerifyStage2Error> { + super::common::symbol_list(batch.claim_operands) + .map(|symbol| { + let claim = find_plan(program.claims, symbol).ok_or(VerifyStage2Error::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + self.claim_value(program, claim) + }) + .collect() + } + + fn evaluate_available_points( + &mut self, + program: &'static Stage2VerifierProgramPlan, + ) -> Result<(), VerifyStage2Error> { + self.0.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage2Error::InvalidInputLength { + input, + expected, + actual, + }, + ) + } + + fn evaluate_available_field_exprs( + &mut self, + program: &'static Stage2VerifierProgramPlan, + ) -> Result<(), VerifyStage2Error> { + self.0 + .evaluate_available_field_exprs(program.field_exprs, evaluate_stage2_field_expr) + } + + fn scalar(&self, symbol: &'static str) -> Result { + self.0 + .scalar_or(symbol, |symbol| VerifyStage2Error::MissingValue { symbol }) + } + + fn point(&self, symbol: &'static str) -> Result<&[F], VerifyStage2Error> { + self.0 + .point_or(symbol, |symbol| VerifyStage2Error::MissingValue { symbol }) + } + + fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.0.try_point(symbol) + } +} + +fn evaluate_stage2_field_expr( + expr: &Stage2FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => Ok(single_operand(expr.symbol, operands)?), + "jolt_stage2_product_virtual_uniskip_input" => { + require_operand_count(expr.symbol, 4, operands.len())?; + let weights = lagrange_evals( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + operands[0], + ); + Ok(weights[0] * operands[1] + weights[1] * operands[2] + weights[2] * operands[3]) + } + "jolt_stage2_ram_read_write_input" => { + require_operand_count(expr.symbol, 3, operands.len())?; + Ok(operands[1] + operands[0] * operands[2]) + } + "jolt_stage2_instruction_lookup_input" => { + require_operand_count(expr.symbol, 6, operands.len())?; + let gamma = operands[0]; + let gamma2 = gamma.square(); + let gamma3 = gamma2 * gamma; + let gamma4 = gamma2.square(); + Ok(operands[1] + + gamma * operands[2] + + gamma2 * operands[3] + + gamma3 * operands[4] + + gamma4 * operands[5]) + } + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + formula => { + if let Some(exponent) = formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + if let Some(spec) = formula.strip_prefix("poly.lagrange_basis_eval:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let parts = spec.split(':').collect::>(); + if parts.len() != 3 { + return Err(VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }); + } + let domain_start = parts[0].parse::().map_err(|_| { + VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + let domain_size = parts[1].parse::().map_err(|_| { + VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + let index = parts[2].parse::().map_err(|_| { + VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + let weights = lagrange_evals(domain_start, domain_size, operands[0]); + return weights + .get(index) + .copied() + .ok_or(VerifyStage2Error::InvalidInputLength { + input: expr.symbol, + expected: index + 1, + actual: weights.len(), + }); + } + Err(VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }) + } + } +} + +fn expected_batched_output_claim( + program: &'static Stage2VerifierProgramPlan, + driver: &'static Stage2SumcheckDriverPlan, + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage2Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage2Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let value = match instance.relation { + "jolt.stage2.ram.read_write" => expected_ram_read_write(store, evals, local_point)?, + "jolt.stage2.product_virtual.remainder" => { + expected_product_remainder(store, evals, local_point)? + } + "jolt.stage2.instruction_lookup.claim_reduction" => { + expected_instruction_lookup(store, evals, local_point)? + } + "jolt.stage2.ram.raf_evaluation" => expected_ram_raf(evals, local_point, ram)?, + "jolt.stage2.ram.output_check" => expected_ram_output(store, evals, local_point, ram)?, + relation => return Err(VerifyStage2Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_ram_read_write( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[Fr], +) -> Result { + let r_cycle_stage1 = store.point("stage2.input.stage1.RamReadValue")?; + let log_t = r_cycle_stage1.len(); + let r_cycle = reverse_slice(&local_point[..log_t]); + let eq_eval = EqPolynomial::::mle(r_cycle_stage1, &r_cycle); + let gamma = store.scalar("stage2.ram_read_write.gamma")?; + let val = eval_by_name(evals, "stage2.ram_read_write.eval.RamVal")?; + let ra = eval_by_name(evals, "stage2.ram_read_write.eval.RamRa")?; + let inc = eval_by_name(evals, "stage2.ram_read_write.eval.RamInc")?; + Ok(eq_eval * ra * (val + gamma * (val + inc))) +} + +fn expected_product_remainder( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[Fr], +) -> Result { + let tau_low = store.point("stage2.input.stage1.Product")?; + let tau_high = store.scalar("stage2.product_virtual.tau_high")?; + let r0 = *store + .point("stage2.product_virtual.uniskip.sumcheck")? + .first() + .ok_or(VerifyStage2Error::MissingValue { + symbol: "stage2.product_virtual.uniskip.sumcheck", + })?; + let r_tail = reverse_slice(local_point); + let low = EqPolynomial::::mle(tau_low, &r_tail); + let high = lagrange_kernel_eval( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + tau_high, + r0, + ); + let weights = lagrange_evals( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + r0, + ); + let left = weights[0] + * eval_by_name(evals, "stage2.product_virtual.remainder.eval.LeftInstructionInput")? + + weights[1] * eval_by_name(evals, "stage2.product_virtual.remainder.eval.LookupOutput")? + + weights[2] * eval_by_name(evals, "stage2.product_virtual.remainder.eval.OpFlagJump")?; + let right = weights[0] + * eval_by_name(evals, "stage2.product_virtual.remainder.eval.RightInstructionInput")? + + weights[1] + * eval_by_name(evals, "stage2.product_virtual.remainder.eval.InstructionFlagBranch")? + + weights[2] + * (Fr::from_u64(1) + - eval_by_name(evals, "stage2.product_virtual.remainder.eval.NextIsNoop")?); + Ok(high * low * left * right) +} + +fn expected_instruction_lookup( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[Fr], +) -> Result { + let opening_point = reverse_slice(local_point); + let r_spartan = store.point("stage2.input.stage1.LookupOutput")?; + let eq_eval = EqPolynomial::::mle(&opening_point, r_spartan); + let gamma = store.scalar("stage2.instruction_lookup.gamma")?; + let gamma2 = gamma.square(); + let gamma3 = gamma2 * gamma; + let gamma4 = gamma2.square(); + let weighted = eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LookupOutput", + )? + gamma + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand", + )? + + gamma2 + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand", + )? + + gamma3 + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput", + )? + + gamma4 + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput", + )?; + Ok(eq_eval * weighted) +} + +fn expected_ram_raf( + evals: &[Stage2NamedEval], + local_point: &[Fr], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let ram = ram.ok_or(VerifyStage2Error::MissingRam { + relation: "jolt.stage2.ram.raf_evaluation", + })?; + let address = reverse_slice(local_point); + let unmap = unmap_eval(ram.log_k, ram.start_address, &address); + Ok(unmap * eval_by_name(evals, "stage2.ram_raf.eval.RamRa")?) +} + +fn expected_ram_output( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[Fr], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let ram = ram.ok_or(VerifyStage2Error::MissingRam { + relation: "jolt.stage2.ram.output_check", + })?; + let layout = ram.output_layout.ok_or(VerifyStage2Error::MissingRam { + relation: "jolt.stage2.ram.output_check.layout", + })?; + let r_address = store.point("stage2.ram_output.r_address")?; + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle(r_address, &opening_point); + let io_mask = range_mask_eval(layout.io_start, layout.io_end, &opening_point); + let val_io = sparse_final_ram_eval( + ram.final_ram, + layout.io_start, + layout.io_end, + &opening_point, + ); + let val_final = eval_by_name(evals, "stage2.ram_output.eval.RamValFinal")?; + Ok(eq_eval * io_mask * (val_final - val_io)) +} + +fn driver_evals( + program: &'static Stage2VerifierProgramPlan, + driver: &'static str, + value: Fr, +) -> Vec> { + program + .evals + .iter() + .filter(|eval| eval.source == driver) + .map(|eval| Stage2NamedEval { + name: eval.name, + oracle: eval.oracle, + value, + }) + .collect() +} + +fn verify_named_evals( + driver: &'static str, + expected: &[Stage2NamedEval], + actual: &[Stage2NamedEval], +) -> Result<(), VerifyStage2Error> { + if expected.len() != actual.len() { + return Err(VerifyStage2Error::InvalidProof { + driver, + reason: "eval count mismatch", + }); + } + for (expected, actual) in expected.iter().zip(actual) { + if expected.name != actual.name || expected.oracle != actual.oracle || expected.value != actual.value { + return Err(VerifyStage2Error::InvalidProof { + driver, + reason: "eval mismatch", + }); + } + } + Ok(()) +} + +fn validate_driver_symbol( + driver: &'static Stage2SumcheckDriverPlan, + proof: &Stage2SumcheckOutput, +) -> Result<(), VerifyStage2Error> { + if proof.driver == driver.symbol { + Ok(()) + } else { + Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "driver symbol mismatch", + }) + } +} + +fn append_univariate_poly(transcript: &mut T, label: &'static str, poly: &UnivariatePoly) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + label.as_bytes(), + poly.coefficients().len() as u64, + )); + for coefficient in poly.coefficients() { + transcript.append(coefficient); + } +} + +fn product_uniskip_sum_matches(poly: &UnivariatePoly, claim: Fr) -> bool { + (0..PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE) + .map(|index| { + poly.evaluate(Fr::from_i64( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START + index as i64, + )) + }) + .sum::() + == claim +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients() + .iter() + .rposition(|coefficient| *coefficient != Fr::from_u64(0)) + .unwrap_or(0) +} + +fn unmap_eval(log_k: usize, start_address: u64, point: &[Fr]) -> Fr { + point + .iter() + .enumerate() + .fold(Fr::from_u64(start_address), |acc, (index, value)| { + acc + value.mul_pow_2(log_k - 1 - index).mul_u64(8) + }) +} + +fn range_mask_eval(start: usize, end: usize, point: &[Fr]) -> Fr { + eq_prefix_sum(end, point) - eq_prefix_sum(start, point) +} + +fn sparse_final_ram_eval(values: &[u64], start: usize, end: usize, point: &[Fr]) -> Fr { + values[start..end] + .iter() + .enumerate() + .filter(|(_, value)| **value != 0) + .map(|(offset, value)| Fr::from_u64(*value) * eq_eval_at_index(start + offset, point)) + .sum() +} + +fn eq_prefix_sum(end: usize, point: &[Fr]) -> Fr { + let domain_len = 1usize << point.len(); + if end >= domain_len { + return Fr::from_u64(1); + } + let mut sum = Fr::from_u64(0); + let mut prefix = Fr::from_u64(1); + for (bit, r) in point.iter().enumerate() { + let mask = 1usize << (point.len() - 1 - bit); + if end & mask == 0 { + prefix *= Fr::from_u64(1) - *r; + } else { + sum += prefix * (Fr::from_u64(1) - *r); + prefix *= *r; + } + } + sum +} + +fn eq_eval_at_index(index: usize, point: &[Fr]) -> Fr { + point.iter().enumerate().fold(Fr::from_u64(1), |acc, (bit, r)| { + let mask = 1usize << (point.len() - 1 - bit); + if index & mask == 0 { + acc * (Fr::from_u64(1) - *r) + } else { + acc * *r + } + }) +} +"# + } +} + +fn require_supported_symbol(kind: &str, actual: &str, expected: &str) -> Result<(), EmitError> { + if actual == expected { + Ok(()) + } else { + Err(EmitError::new(format!( + "unsupported {kind} @{actual}; expected @{expected}" + ))) + } +} + +fn emit_str_array(name: &str, values: &[String]) -> String { + if values.is_empty() { + return format!("pub const {name}: &[&str] = &[];\n\n"); + } + if let [value] = values { + return format!("pub const {name}: &[&str] = &[{}];\n\n", rust_str(value)); + } + let entries = values + .iter() + .map(|value| format!(" {},", rust_str(value))) + .collect::>() + .join("\n"); + format!("pub const {name}: &[&str] = &[\n{entries}\n];\n\n") +} + +fn emit_usize_array(name: &str, values: &[usize]) -> String { + let entries = values + .iter() + .map(|value| format!(" {value},")) + .collect::>() + .join("\n"); + format!("pub const {name}: &[usize] = &[\n{entries}\n];\n\n") +} + +fn intern_str_array( + source: &mut String, + arrays: &mut Vec<(Vec, String)>, + name_prefix: &str, + values: &[String], +) -> String { + if let Some((_, name)) = arrays + .iter() + .find(|(existing, _)| existing.as_slice() == values) + { + return name.clone(); + } + let name = format!("{name_prefix}_{}", arrays.len()); + source.push_str(&emit_str_array(&name, values)); + arrays.push((values.to_vec(), name.clone())); + name +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn verify_count(kind: &str, symbol: &str, expected: usize, actual: usize) -> Result<(), EmitError> { + if expected == actual { + Ok(()) + } else { + Err(EmitError::new(format!( + "{kind} @{symbol} count mismatch: expected {expected}, got {actual}" + ))) + } +} + +fn missing_role_binding(kind: &str, symbol: &str) -> EmitError { + EmitError::new(format!("missing {kind} for `{symbol}`")) +} + +fn symbols<'a>(values: impl Iterator) -> BTreeSet { + values.cloned().collect() +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn signed_int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_signed_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "signed integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn parse_signed_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn int_array_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "integer array"))?; + parse_int_array(&attribute).ok_or_else(|| attr_error(operation, attr, "integer array")) +} + +fn parse_int_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().parse().ok()) + .collect() +} + +fn operand_symbols( + operation: OperationRef<'_, '_>, + start_index: usize, +) -> Result, EmitError> { + (start_index..operation.operand_count()) + .map(|index| operand_symbol(operation, index)) + .collect() +} + +fn operand_symbol(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + EmitError::new(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + EmitError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name<'c: 'a, 'a>(operation: impl OperationLike<'c, 'a>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} diff --git a/crates/bolt/src/protocols/jolt/emit/rust/stage3.rs b/crates/bolt/src/protocols/jolt/emit/rust/stage3.rs new file mode 100644 index 0000000000..936557f08a --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/stage3.rs @@ -0,0 +1,2245 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{Attribute, OperationRef}; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3CpuProgram { + pub role: Role, + pub params: Stage3Params, + pub steps: Vec, + pub transcript_squeezes: Vec, + pub opening_inputs: Vec, + pub field_constants: Vec, + pub field_exprs: Vec, + pub kernels: Vec, + pub claims: Vec, + pub batches: Vec, + pub drivers: Vec, + pub instance_results: Vec, + pub evals: Vec, + pub point_slices: Vec, + pub point_concats: Vec, + pub opening_claims: Vec, + pub opening_equalities: Vec, + pub opening_batches: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3Params { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3KernelPlan { + pub symbol: String, + pub relation: String, + pub kind: String, + pub backend: String, + pub abi: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3TranscriptSqueezePlan { + pub symbol: String, + pub label: String, + pub kind: String, + pub count: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3ProgramStepPlan { + pub kind: String, + pub symbol: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3OpeningInputPlan { + pub symbol: String, + pub source_stage: String, + pub source_claim: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3FieldConstantPlan { + pub symbol: String, + pub field: String, + pub value: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3FieldExprPlan { + pub symbol: String, + pub kind: String, + pub formula: String, + pub operand_names: Vec, + pub operands: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckClaimPlan { + pub symbol: String, + pub stage: String, + pub domain: String, + pub num_rounds: usize, + pub degree: usize, + pub claim: String, + pub kernel: Option, + pub relation: Option, + pub claim_value: String, + pub input_openings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, + pub claim_label: String, + pub round_label: String, + pub round_schedule: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckDriverPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub kernel: Option, + pub relation: Option, + pub batch: String, + pub policy: String, + pub round_schedule: Vec, + pub claim_label: String, + pub round_label: String, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckInstanceResultPlan { + pub symbol: String, + pub source: String, + pub claim: String, + pub relation: String, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: String, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckEvalPlan { + pub symbol: String, + pub source: String, + pub name: String, + pub index: usize, + pub oracle: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3PointSlicePlan { + pub symbol: String, + pub source: String, + pub offset: usize, + pub length: usize, + pub input: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3PointConcatPlan { + pub symbol: String, + pub layout: String, + pub arity: usize, + pub inputs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3OpeningClaimPlan { + pub symbol: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, + pub point_source: String, + pub eval_source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3OpeningClaimEqualityPlan { + pub symbol: String, + pub mode: String, + pub lhs: String, + pub rhs: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3OpeningBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, +} + +pub fn stage3_cpu_program(module: &BoltModule<'_, Cpu>) -> Result { + verify_cpu_schema(module)?; + let program = Stage3CpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +pub fn emit_stage3_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = stage3_cpu_program(module)?; + + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source()?, + }) +} + +impl Stage3CpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let mut params = None; + let mut steps = Vec::new(); + let mut transcript_squeezes = Vec::new(); + let mut opening_inputs = Vec::new(); + let mut field_constants = Vec::new(); + let mut field_exprs = Vec::new(); + let mut kernels = Vec::new(); + let mut claims = Vec::new(); + let mut batches = Vec::new(); + let mut drivers = Vec::new(); + let mut instance_results = Vec::new(); + let mut evals = Vec::new(); + let mut point_slices = Vec::new(); + let mut point_concats = Vec::new(); + let mut opening_claims = Vec::new(); + let mut opening_equalities = Vec::new(); + let mut opening_batches = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(Stage3Params { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.kernel" => { + kernels.push(Stage3KernelPlan { + symbol: string_attr(op, "sym_name")?, + relation: symbol_attr(op, "relation")?, + kind: string_attr(op, "kind")?, + backend: string_attr(op, "backend")?, + abi: string_attr(op, "abi")?, + }); + } + "cpu.transcript_squeeze" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage3ProgramStepPlan { + kind: "transcript_squeeze".to_owned(), + symbol: symbol.clone(), + }); + transcript_squeezes.push(Stage3TranscriptSqueezePlan { + symbol, + label: string_attr(op, "label")?, + kind: string_attr(op, "kind")?, + count: int_attr(op, "count")?, + }); + } + "cpu.opening_input" => { + opening_inputs.push(Stage3OpeningInputPlan { + symbol: string_attr(op, "sym_name")?, + source_stage: symbol_attr(op, "source_stage")?, + source_claim: symbol_attr(op, "source_claim")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + }); + } + "cpu.field_const" => { + field_constants.push(Stage3FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: int_attr(op, "value")?, + }); + } + "cpu.field_zero" => { + field_constants.push(Stage3FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 0, + }); + } + "cpu.field_one" => { + field_constants.push(Stage3FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 1, + }); + } + "cpu.field_add" | "cpu.field_sub" | "cpu.field_mul" | "cpu.field_neg" => { + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage3FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: operation_name(op).replace("cpu.field_", "field."), + operand_names: operands.clone(), + operands, + }); + } + "cpu.field_pow" => { + let exponent = int_attr(op, "exponent")?; + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage3FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: format!("field.pow:{exponent}"), + operand_names: operands.clone(), + operands, + }); + } + "cpu.poly_lagrange_basis_eval" => { + let domain_start = signed_int_attr(op, "domain_start")?; + let domain_size = int_attr(op, "domain_size")?; + let index = int_attr(op, "index")?; + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage3FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: format!( + "poly.lagrange_basis_eval:{domain_start}:{domain_size}:{index}" + ), + operand_names: operands.clone(), + operands, + }); + } + "cpu.sumcheck_claim" => { + claims.push(Stage3SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_verify_claim" => { + claims.push(Stage3SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_batch" => { + batches.push(Stage3SumcheckBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + round_schedule: int_array_attr(op, "round_schedule")?, + }); + } + "cpu.sumcheck_driver" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage3ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage3SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_verify" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage3ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage3SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_instance_result" => { + instance_results.push(Stage3SumcheckInstanceResultPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + claim: symbol_attr(op, "claim")?, + relation: symbol_attr(op, "relation")?, + index: int_attr(op, "index")?, + point_arity: int_attr(op, "point_arity")?, + num_rounds: int_attr(op, "num_rounds")?, + round_offset: int_attr(op, "round_offset")?, + point_order: string_attr(op, "point_order")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_eval" => { + evals.push(Stage3SumcheckEvalPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + name: symbol_attr(op, "name")?, + index: int_attr(op, "index")?, + oracle: symbol_attr(op, "oracle")?, + }); + } + "cpu.point_slice" => { + point_slices.push(Stage3PointSlicePlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + offset: int_attr(op, "offset")?, + length: int_attr(op, "length")?, + input: operand_symbol(op, 0)?, + }); + } + "cpu.point_concat" => { + point_concats.push(Stage3PointConcatPlan { + symbol: string_attr(op, "sym_name")?, + layout: string_attr(op, "layout")?, + arity: int_attr(op, "arity")?, + inputs: operand_symbols(op, 0)?, + }); + } + "cpu.opening_claim" => { + opening_claims.push(Stage3OpeningClaimPlan { + symbol: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + point_source: operand_symbol(op, 0)?, + eval_source: operand_symbol(op, 1)?, + }); + } + "cpu.opening_claim_equal" => { + opening_equalities.push(Stage3OpeningClaimEqualityPlan { + symbol: string_attr(op, "sym_name")?, + mode: string_attr(op, "mode")?, + lhs: operand_symbol(op, 0)?, + rhs: operand_symbol(op, 1)?, + }); + } + "cpu.opening_batch" => { + opening_batches.push(Stage3OpeningBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + }); + } + _ => {} + } + } + + Ok(Self { + params: params.ok_or_else(|| EmitError::new("missing cpu.params"))?, + role: module + .role() + .ok_or_else(|| EmitError::new("missing cpu party role"))?, + steps, + transcript_squeezes, + opening_inputs, + field_constants, + field_exprs, + kernels, + claims, + batches, + drivers, + instance_results, + evals, + point_slices, + point_concats, + opening_claims, + opening_equalities, + opening_batches, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + require_supported_symbol("field", &self.params.field, "bn254_fr")?; + require_supported_symbol("pcs", &self.params.pcs, "dory")?; + require_supported_symbol("transcript", &self.params.transcript, "blake2b_transcript")?; + self.verify_transcript_squeezes()?; + self.verify_field_flow()?; + self.verify_claim_batches()?; + match self.role { + Role::Prover => { + self.verify_kernel_definitions()?; + self.verify_prover_driver_bindings()?; + } + Role::Verifier => self.verify_verifier_driver_bindings()?, + } + self.verify_opening_flow() + } + + fn verify_transcript_squeezes(&self) -> Result<(), EmitError> { + for squeeze in &self.transcript_squeezes { + if !matches!( + squeeze.kind.as_str(), + "challenge_scalar" | "challenge_vector" + ) { + return Err(EmitError::new(format!( + "stage3 transcript squeeze @{} has unsupported kind `{}`", + squeeze.symbol, squeeze.kind + ))); + } + if squeeze.count == 0 { + return Err(EmitError::new(format!( + "stage3 transcript squeeze @{} has zero count", + squeeze.symbol + ))); + } + } + Ok(()) + } + + fn verify_field_flow(&self) -> Result<(), EmitError> { + for constant in &self.field_constants { + require_supported_symbol("field constant field", &constant.field, "bn254_fr")?; + } + let field_values = self.field_value_symbols(); + for expr in &self.field_exprs { + verify_count( + "field expr operands", + &expr.symbol, + expr.operand_names.len(), + expr.operands.len(), + )?; + for operand in &expr.operands { + if !field_values.contains(operand) { + return Err(EmitError::new(format!( + "field expr @{} references missing field value @{operand}", + expr.symbol + ))); + } + } + } + for claim in &self.claims { + if !field_values.contains(&claim.claim_value) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing claim value @{}", + claim.symbol, claim.claim_value + ))); + } + } + Ok(()) + } + + fn field_value_symbols(&self) -> BTreeSet { + let mut values = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + values.extend(symbols( + self.field_constants.iter().map(|constant| &constant.symbol), + )); + values.extend(symbols( + self.transcript_squeezes + .iter() + .filter(|squeeze| matches!(squeeze.kind.as_str(), "challenge_scalar" | "scalar")) + .map(|squeeze| &squeeze.symbol), + )); + values.extend(symbols(self.field_exprs.iter().map(|expr| &expr.symbol))); + values.extend(symbols(self.evals.iter().map(|eval| &eval.symbol))); + values + } + + fn verify_kernel_definitions(&self) -> Result<(), EmitError> { + for kernel in &self.kernels { + if kernel.backend != "cpu" { + return Err(EmitError::new(format!( + "stage3 kernel @{} targets unsupported backend `{}`", + kernel.symbol, kernel.backend + ))); + } + if kernel.kind != "sumcheck" { + return Err(EmitError::new(format!( + "stage3 kernel @{} has unsupported kind `{}`", + kernel.symbol, kernel.kind + ))); + } + let expected_abi = match kernel.relation.as_str() { + "jolt.stage3.spartan_shift" => "jolt_stage3_spartan_shift", + "jolt.stage3.instruction_input" => "jolt_stage3_instruction_input", + "jolt.stage3.registers_claim_reduction" => "jolt_stage3_registers_claim_reduction", + "jolt.stage3.batched" => "jolt_stage3_batched", + _ => { + return Err(EmitError::new(format!( + "unsupported stage3 kernel relation @{}", + kernel.relation + ))); + } + }; + if kernel.abi != expected_abi { + return Err(EmitError::new(format!( + "stage3 kernel @{} ABI `{}` does not match relation @{}", + kernel.symbol, kernel.abi, kernel.relation + ))); + } + } + Ok(()) + } + + fn verify_claim_batches(&self) -> Result<(), EmitError> { + let claims = symbols(self.claims.iter().map(|claim| &claim.symbol)); + for batch in &self.batches { + verify_count( + "sumcheck batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "sumcheck batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "sumcheck batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !claims.contains(claim) { + return Err(EmitError::new(format!( + "sumcheck batch @{} references missing claim @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn verify_prover_driver_bindings(&self) -> Result<(), EmitError> { + let kernels = symbols(self.kernels.iter().map(|kernel| &kernel.symbol)); + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + let Some(kernel) = claim.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck claim @{} is missing kernel", + claim.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck claim @{} references missing kernel @{kernel}", + claim.symbol + ))); + } + } + for driver in &self.drivers { + let Some(kernel) = driver.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck driver @{} is missing kernel", + driver.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck driver @{} references missing kernel @{kernel}", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_verifier_driver_bindings(&self) -> Result<(), EmitError> { + if !self.kernels.is_empty() { + return Err(EmitError::new( + "verifier stage3 program must not contain kernels", + )); + } + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + if claim.kernel.is_some() || claim.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck claim @{} must carry relation and no kernel", + claim.symbol + ))); + } + } + for driver in &self.drivers { + if driver.kernel.is_some() || driver.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck driver @{} must carry relation and no kernel", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_opening_flow(&self) -> Result<(), EmitError> { + let mut point_sources = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + point_sources.extend(symbols( + self.instance_results + .iter() + .map(|instance| &instance.symbol), + )); + point_sources.extend(symbols( + self.opening_inputs.iter().map(|input| &input.symbol), + )); + point_sources.extend(symbols(self.point_slices.iter().map(|slice| &slice.symbol))); + point_sources.extend(symbols( + self.point_concats.iter().map(|concat| &concat.symbol), + )); + for slice in &self.point_slices { + if !point_sources.contains(&slice.input) { + return Err(EmitError::new(format!( + "point slice @{} uses missing point source @{}", + slice.symbol, slice.input + ))); + } + } + for concat in &self.point_concats { + for input in &concat.inputs { + if !point_sources.contains(input) { + return Err(EmitError::new(format!( + "point concat @{} uses missing point source @{input}", + concat.symbol + ))); + } + } + } + let eval_sources = self.field_value_symbols(); + let mut opening_sources = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + opening_sources.extend(symbols( + self.opening_claims.iter().map(|claim| &claim.symbol), + )); + for equality in &self.opening_equalities { + if !opening_sources.contains(&equality.lhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing lhs opening @{}", + equality.symbol, equality.lhs + ))); + } + if !opening_sources.contains(&equality.rhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing rhs opening @{}", + equality.symbol, equality.rhs + ))); + } + } + for claim in &self.claims { + for input in &claim.input_openings { + if !opening_sources.contains(input) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing opening @{input}", + claim.symbol + ))); + } + } + } + let drivers = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + for instance in &self.instance_results { + if !drivers.contains(&instance.source) { + return Err(EmitError::new(format!( + "sumcheck instance result @{} references missing driver @{}", + instance.symbol, instance.source + ))); + } + } + for eval in &self.evals { + if !drivers.contains(&eval.source) { + return Err(EmitError::new(format!( + "sumcheck eval @{} references missing driver @{}", + eval.symbol, eval.source + ))); + } + } + for claim in &self.opening_claims { + if !point_sources.contains(&claim.point_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing point source @{}", + claim.symbol, claim.point_source + ))); + } + if !eval_sources.contains(&claim.eval_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing eval source @{}", + claim.symbol, claim.eval_source + ))); + } + } + let openings = symbols(self.opening_claims.iter().map(|claim| &claim.symbol)); + for batch in &self.opening_batches { + verify_count( + "opening batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "opening batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "opening batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !openings.contains(claim) { + return Err(EmitError::new(format!( + "opening batch @{} references missing opening @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn emit_source(&self) -> Result { + match self.role { + Role::Prover => self.emit_prover_source(), + Role::Verifier => self.emit_verifier_source(), + } + } + + fn emit_prover_source(&self) -> Result { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + source.push_str(Self::emit_prover_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_prover_types()); + source.push('\n'); + source.push_str(&self.emit_prover_constants()?); + source.push('\n'); + source.push_str(Self::emit_prover_entrypoint()); + Ok(source) + } + + fn emit_verifier_source(&self) -> Result { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + source.push_str(Self::emit_verifier_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_verifier_types()); + source.push('\n'); + source.push_str(&self.emit_verifier_constants()?); + source.push('\n'); + source.push_str(Self::emit_verifier_entrypoint()); + Ok(source) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_stage3.rs", + Role::Verifier => "verify_stage3.rs", + } + } + + fn emit_prover_imports() -> &'static str { + "use jolt_field::Fr;\n\ + use jolt_kernels::stage3::{execute_stage3_program, Stage3CpuProgramPlan, Stage3ExecutionArtifacts, Stage3ExecutionMode, Stage3FieldConstantPlan, Stage3FieldExprPlan, Stage3KernelError, Stage3KernelExecutor, Stage3KernelPlan, Stage3OpeningBatchPlan, Stage3OpeningClaimEqualityPlan, Stage3OpeningClaimPlan, Stage3OpeningInputPlan, Stage3Params, Stage3PointConcatPlan, Stage3PointSlicePlan, Stage3ProgramStepPlan, Stage3SumcheckBatchPlan, Stage3SumcheckClaimPlan, Stage3SumcheckDriverPlan, Stage3SumcheckEvalPlan, Stage3SumcheckInstanceResultPlan, Stage3TranscriptSqueezePlan};\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_prover_types() -> &'static str { + "pub type DefaultStage3Transcript = Blake2bTranscript;\n" + } + + fn emit_verifier_imports() -> &'static str { + "use super::common::{batch_claims, eval_by_name, find_batch, find_plan, reverse_slice};\n\ + use jolt_field::{Field, Fr};\n\ + use jolt_poly::{EqPlusOnePolynomial, EqPolynomial};\n\ + use jolt_sumcheck::SumcheckError;\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_verifier_types() -> &'static str { + r"pub type DefaultStage3Transcript = Blake2bTranscript; + +pub type Stage3NamedEval = super::common::StageNamedEval; +pub type Stage3SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage3ChallengeVector = super::common::StageChallengeVector; +pub type Stage3ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage3Proof = super::common::StageProof; +pub type Stage3OpeningInputValue = super::common::StageOpeningInputValue; +pub type Stage3VerifierProgramPlan = super::common::StageVerifierProgramPlan; + +pub use super::common::{ + FieldConstantPlan as Stage3FieldConstantPlan, FieldExprPlan as Stage3FieldExprPlan, + OpeningBatchPlan as Stage3OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage3OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage3OpeningClaimPlan, OpeningInputPlan as Stage3OpeningInputPlan, + PointConcatPlan as Stage3PointConcatPlan, PointSlicePlan as Stage3PointSlicePlan, + ProgramStepPlan as Stage3ProgramStepPlan, StageParams as Stage3Params, + SumcheckBatchPlan as Stage3SumcheckBatchPlan, SumcheckEvalPlan as Stage3SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage3SumcheckInstanceResultPlan, + TranscriptSqueezePlan as Stage3TranscriptSqueezePlan, + SumcheckClaimPlan as Stage3SumcheckClaimPlan, + SumcheckDriverPlan as Stage3SumcheckDriverPlan, +}; + +#[derive(Debug)] +pub enum VerifyStage3Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage3Error); +" + } + + fn emit_prover_constants(&self) -> Result { + let mut source = self.emit_shared_constants(); + source.push_str(&self.emit_kernel_constants()); + source.push_str(&self.emit_prover_sumcheck_claim_constants()?); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_prover_sumcheck_driver_constants()?); + source.push_str(&self.emit_tail_constants()); + source.push_str( + "pub const STAGE3_PROGRAM: Stage3CpuProgramPlan = Stage3CpuProgramPlan {\n\ + \x20 params: STAGE3_PARAMS,\n\ + \x20 steps: STAGE3_PROGRAM_STEPS,\n\ + \x20 transcript_squeezes: STAGE3_TRANSCRIPT_SQUEEZES,\n\ + \x20 opening_inputs: STAGE3_OPENING_INPUTS,\n\ + \x20 field_constants: STAGE3_FIELD_CONSTANTS,\n\ + \x20 field_exprs: STAGE3_FIELD_EXPRS,\n\ + \x20 kernels: STAGE3_KERNELS,\n\ + \x20 claims: STAGE3_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE3_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE3_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE3_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE3_SUMCHECK_EVALS,\n\ + \x20 point_slices: STAGE3_POINT_SLICES,\n\ + \x20 point_concats: STAGE3_POINT_CONCATS,\n\ + \x20 opening_claims: STAGE3_OPENING_CLAIMS,\n\ + \x20 opening_equalities: STAGE3_OPENING_EQUALITIES,\n\ + \x20 opening_batches: STAGE3_OPENING_BATCHES,\n\ + };\n", + ); + Ok(source) + } + + fn emit_verifier_constants(&self) -> Result { + let mut source = self.emit_shared_constants(); + source.push_str(&self.emit_verifier_sumcheck_claim_constants()?); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_verifier_sumcheck_driver_constants()?); + source.push_str(&self.emit_tail_constants()); + source.push_str( + "pub const STAGE3_PROGRAM: Stage3VerifierProgramPlan = Stage3VerifierProgramPlan {\n\ + \x20 params: STAGE3_PARAMS,\n\ + \x20 steps: STAGE3_PROGRAM_STEPS,\n\ + \x20 transcript_squeezes: STAGE3_TRANSCRIPT_SQUEEZES,\n\ + \x20 opening_inputs: STAGE3_OPENING_INPUTS,\n\ + \x20 field_constants: STAGE3_FIELD_CONSTANTS,\n\ + \x20 field_exprs: STAGE3_FIELD_EXPRS,\n\ + \x20 claims: STAGE3_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE3_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE3_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE3_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE3_SUMCHECK_EVALS,\n\ + \x20 point_slices: STAGE3_POINT_SLICES,\n\ + \x20 point_concats: STAGE3_POINT_CONCATS,\n\ + \x20 opening_claims: STAGE3_OPENING_CLAIMS,\n\ + \x20 opening_equalities: STAGE3_OPENING_EQUALITIES,\n\ + \x20 opening_batches: STAGE3_OPENING_BATCHES,\n\ + };\n", + ); + Ok(source) + } + + fn emit_shared_constants(&self) -> String { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const STAGE3_PARAMS: Stage3Params = Stage3Params {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + source.push_str(&self.emit_program_step_constants()); + source.push_str(&self.emit_transcript_squeeze_constants()); + source.push_str(&self.emit_opening_input_constants()); + source.push_str(&self.emit_field_constant_constants()); + source.push_str(&self.emit_field_expr_constants()); + source + } + + fn emit_program_step_constants(&self) -> String { + let steps = self + .steps + .iter() + .map(|step| { + format!( + " Stage3ProgramStepPlan {{ kind: {}, symbol: {} }},", + rust_str(&step.kind), + rust_str(&step.symbol), + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE3_PROGRAM_STEPS: &[Stage3ProgramStepPlan] = &[\n{steps}\n];\n\n") + } + + fn emit_transcript_squeeze_constants(&self) -> String { + let squeezes = self + .transcript_squeezes + .iter() + .map(|squeeze| { + format!( + " Stage3TranscriptSqueezePlan {{ symbol: {}, label: {}, kind: {}, count: {} }},", + rust_str(&squeeze.symbol), + rust_str(&squeeze.label), + rust_str(&squeeze.kind), + squeeze.count, + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE3_TRANSCRIPT_SQUEEZES: &[Stage3TranscriptSqueezePlan] = &[\n{squeezes}\n];\n\n" + ) + } + + fn emit_opening_input_constants(&self) -> String { + let inputs = self + .opening_inputs + .iter() + .map(|input| { + format!( + " Stage3OpeningInputPlan {{ symbol: {}, source_stage: {}, source_claim: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {} }},", + rust_str(&input.symbol), + rust_str(&input.source_stage), + rust_str(&input.source_claim), + rust_str(&input.oracle), + rust_str(&input.domain), + input.point_arity, + rust_str(&input.claim_kind) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE3_OPENING_INPUTS: &[Stage3OpeningInputPlan] = &[\n{inputs}\n];\n\n") + } + + fn emit_field_constant_constants(&self) -> String { + let constants = self + .field_constants + .iter() + .map(|constant| { + format!( + " Stage3FieldConstantPlan {{ symbol: {}, field: {}, value: {} }},", + rust_str(&constant.symbol), + rust_str(&constant.field), + constant.value + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE3_FIELD_CONSTANTS: &[Stage3FieldConstantPlan] = &[\n{constants}\n];\n\n" + ) + } + + fn emit_field_expr_constants(&self) -> String { + if self.role == Role::Verifier { + let exprs = self + .field_exprs + .iter() + .map(|expr| { + format!( + " Stage3FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operands: {} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula), + rust_str(&expr.operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE3_FIELD_EXPRS: &[Stage3FieldExprPlan] = &[\n{exprs}\n];\n" + ); + } + + let mut source = String::new(); + let mut arrays = Vec::new(); + let mut array_refs = Vec::new(); + for (index, expr) in self.field_exprs.iter().enumerate() { + let operands = intern_str_array( + &mut source, + &mut arrays, + "STAGE3_FIELD_EXPR_OPERANDS", + &expr.operands, + ); + let operand_names = intern_str_array( + &mut source, + &mut arrays, + "STAGE3_FIELD_EXPR_OPERANDS", + &expr.operand_names, + ); + array_refs.push((index, operand_names, operands)); + } + let exprs = self + .field_exprs + .iter() + .enumerate() + .map(|(index, expr)| { + let (_, operand_names, operands) = &array_refs[index]; + format!( + " Stage3FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operand_names: {operand_names}, operands: {operands} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE3_FIELD_EXPRS: &[Stage3FieldExprPlan] = &[\n{exprs}\n];\n" + ), + ); + source + } + + fn emit_kernel_constants(&self) -> String { + let kernels = self + .kernels + .iter() + .map(|kernel| { + format!( + " Stage3KernelPlan {{ symbol: {}, relation: {}, kind: {}, backend: {}, abi: {} }},", + rust_str(&kernel.symbol), + rust_str(&kernel.relation), + rust_str(&kernel.kind), + rust_str(&kernel.backend), + rust_str(&kernel.abi) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE3_KERNELS: &[Stage3KernelPlan] = &[\n{kernels}\n];\n\n") + } + + fn emit_prover_sumcheck_claim_constants(&self) -> Result { + self.emit_sumcheck_claim_constants(true) + } + + fn emit_verifier_sumcheck_claim_constants(&self) -> Result { + self.emit_sumcheck_claim_constants(false) + } + + fn emit_sumcheck_claim_constants(&self, prover: bool) -> Result { + let mut source = String::new(); + if prover { + for (index, claim) in self.claims.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE3_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS"), + &claim.input_openings, + )); + } + } + let mut claims = Vec::new(); + for (index, claim) in self.claims.iter().enumerate() { + if prover { + let kernel = claim + .kernel + .as_deref() + .ok_or_else(|| missing_role_binding("prover claim kernel", &claim.symbol))?; + claims.push(format!( + " Stage3SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: Some({}), relation: None, claim_value: {}, input_openings: STAGE3_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_str(kernel), + rust_str(&claim.claim_value) + )); + } else { + let relation = claim.relation.as_deref().ok_or_else(|| { + missing_role_binding("verifier claim relation", &claim.symbol) + })?; + claims.push(format!( + " Stage3SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: None, relation: Some({}), claim_value: {}, input_openings: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_str(relation), + rust_str(&claim.claim_value), + rust_str(&claim.input_openings.join("|")) + )); + } + } + let claims = claims.join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE3_SUMCHECK_CLAIMS: &[Stage3SumcheckClaimPlan] = &[\n{claims}\n];\n" + ), + ); + Ok(source) + } + + fn emit_sumcheck_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE3_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage3SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {}, claim_label: {}, round_label: {}, round_schedule: STAGE3_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")), + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE3_SUMCHECK_BATCHES: &[Stage3SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + return source; + } + + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE3_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE3_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + source.push_str(&emit_usize_array( + &format!("STAGE3_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage3SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE3_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE3_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS, claim_label: {}, round_label: {}, round_schedule: STAGE3_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE3_SUMCHECK_BATCHES: &[Stage3SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_prover_sumcheck_driver_constants(&self) -> Result { + self.emit_sumcheck_driver_constants(true) + } + + fn emit_verifier_sumcheck_driver_constants(&self) -> Result { + self.emit_sumcheck_driver_constants(false) + } + + fn emit_sumcheck_driver_constants(&self, prover: bool) -> Result { + let mut source = String::new(); + for (index, driver) in self.drivers.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE3_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE"), + &driver.round_schedule, + )); + } + let mut drivers = Vec::new(); + for (index, driver) in self.drivers.iter().enumerate() { + if prover { + let kernel = driver + .kernel + .as_deref() + .ok_or_else(|| missing_role_binding("prover driver kernel", &driver.symbol))?; + drivers.push(format!( + " Stage3SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: Some({}), relation: None, batch: {}, policy: {}, round_schedule: STAGE3_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_str(kernel), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + )); + } else { + let relation = driver.relation.as_deref().ok_or_else(|| { + missing_role_binding("verifier driver relation", &driver.symbol) + })?; + drivers.push(format!( + " Stage3SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: None, relation: Some({}), batch: {}, policy: {}, round_schedule: STAGE3_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_str(relation), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + )); + } + } + let drivers = drivers.join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE3_SUMCHECK_DRIVERS: &[Stage3SumcheckDriverPlan] = &[\n{drivers}\n];\n" + ), + ); + Ok(source) + } + + fn emit_tail_constants(&self) -> String { + let mut source = String::new(); + source.push_str(&self.emit_sumcheck_instance_result_constants()); + source.push_str(&self.emit_sumcheck_eval_constants()); + source.push_str(&self.emit_point_slice_constants()); + source.push_str(&self.emit_point_concat_constants()); + source.push_str(&self.emit_opening_claim_constants()); + source.push_str(&self.emit_opening_claim_equality_constants()); + source.push_str(&self.emit_opening_batch_constants()); + source + } + + fn emit_sumcheck_instance_result_constants(&self) -> String { + let instances = self + .instance_results + .iter() + .map(|instance| { + format!( + " Stage3SumcheckInstanceResultPlan {{ symbol: {}, source: {}, claim: {}, relation: {}, index: {}, point_arity: {}, num_rounds: {}, round_offset: {}, point_order: {}, degree: {} }},", + rust_str(&instance.symbol), + rust_str(&instance.source), + rust_str(&instance.claim), + rust_str(&instance.relation), + instance.index, + instance.point_arity, + instance.num_rounds, + instance.round_offset, + rust_str(&instance.point_order), + instance.degree + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE3_SUMCHECK_INSTANCE_RESULTS: &[Stage3SumcheckInstanceResultPlan] = &[\n{instances}\n];\n\n" + ) + } + + fn emit_sumcheck_eval_constants(&self) -> String { + let evals = self + .evals + .iter() + .map(|eval| { + format!( + " Stage3SumcheckEvalPlan {{ symbol: {}, source: {}, name: {}, index: {}, oracle: {} }},", + rust_str(&eval.symbol), + rust_str(&eval.source), + rust_str(&eval.name), + eval.index, + rust_str(&eval.oracle) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE3_SUMCHECK_EVALS: &[Stage3SumcheckEvalPlan] = &[\n{evals}\n];\n\n") + } + + fn emit_point_slice_constants(&self) -> String { + let slices = self + .point_slices + .iter() + .map(|slice| { + format!( + " Stage3PointSlicePlan {{ symbol: {}, source: {}, offset: {}, length: {}, input: {} }},", + rust_str(&slice.symbol), + rust_str(&slice.source), + slice.offset, + slice.length, + rust_str(&slice.input) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE3_POINT_SLICES: &[Stage3PointSlicePlan] = &[\n{slices}\n];\n\n") + } + + fn emit_point_concat_constants(&self) -> String { + if self.role == Role::Verifier { + let concats = self + .point_concats + .iter() + .map(|concat| { + format!( + " Stage3PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: {} }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity, + rust_str(&concat.inputs.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE3_POINT_CONCATS: &[Stage3PointConcatPlan] = &[\n{concats}\n];\n" + ); + } + + let mut source = String::new(); + for (index, concat) in self.point_concats.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE3_POINT_CONCAT_{index}_INPUTS"), + &concat.inputs, + )); + } + let concats = self + .point_concats + .iter() + .enumerate() + .map(|(index, concat)| { + format!( + " Stage3PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: STAGE3_POINT_CONCAT_{index}_INPUTS }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE3_POINT_CONCATS: &[Stage3PointConcatPlan] = &[\n{concats}\n];\n" + ), + ); + source + } + + fn emit_opening_claim_constants(&self) -> String { + let claims = self + .opening_claims + .iter() + .map(|claim| { + format!( + " Stage3OpeningClaimPlan {{ symbol: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {}, point_source: {}, eval_source: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.oracle), + rust_str(&claim.domain), + claim.point_arity, + rust_str(&claim.claim_kind), + rust_str(&claim.point_source), + rust_str(&claim.eval_source) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE3_OPENING_CLAIMS: &[Stage3OpeningClaimPlan] = &[\n{claims}\n];\n\n") + } + + fn emit_opening_claim_equality_constants(&self) -> String { + let equalities = self + .opening_equalities + .iter() + .map(|equality| { + format!( + " Stage3OpeningClaimEqualityPlan {{ symbol: {}, mode: {}, lhs: {}, rhs: {} }},", + rust_str(&equality.symbol), + rust_str(&equality.mode), + rust_str(&equality.lhs), + rust_str(&equality.rhs) + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE3_OPENING_EQUALITIES: &[Stage3OpeningClaimEqualityPlan] = &[\n{equalities}\n];\n\n" + ) + } + + fn emit_opening_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let batches = self + .opening_batches + .iter() + .map(|batch| { + format!( + " Stage3OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {} }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE3_OPENING_BATCHES: &[Stage3OpeningBatchPlan] = &[\n{batches}\n];\n" + ); + } + + let mut source = String::new(); + for (index, batch) in self.opening_batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE3_OPENING_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE3_OPENING_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + } + let batches = self + .opening_batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage3OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE3_OPENING_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE3_OPENING_BATCH_{index}_CLAIM_OPERANDS }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE3_OPENING_BATCHES: &[Stage3OpeningBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_prover_entrypoint() -> &'static str { + "pub fn execute_stage3_prover(\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage3KernelError>\n\ + where\n\ + \x20 E: Stage3KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage3_prover_with_program(&STAGE3_PROGRAM, executor, transcript)\n\ + }\n\ + \n\ + pub fn execute_stage3_prover_with_program(\n\ + \x20 program: &'static Stage3CpuProgramPlan,\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage3KernelError>\n\ + where\n\ + \x20 E: Stage3KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage3_program(program, Stage3ExecutionMode::Prover, executor, transcript)\n\ + }\n" + } + + fn emit_verifier_entrypoint() -> &'static str { + r#"pub fn verify_stage3( + proof: &Stage3Proof, + opening_inputs: &[Stage3OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage3Error> +where + T: Transcript, +{ + verify_stage3_with_program(&STAGE3_PROGRAM, proof, opening_inputs, transcript) +} + +pub fn verify_stage3_with_program( + program: &'static Stage3VerifierProgramPlan, + proof: &Stage3Proof, + opening_inputs: &[Stage3OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage3Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage3Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + let mut artifacts = Stage3ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage3Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage3_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage3Error::MissingProof { + driver: step.symbol, + })?; + verify_stage3_driver(program, driver, proof, &mut store, transcript, &mut artifacts)?; + } + _ => { + return Err(VerifyStage3Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage3 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage3_verifier_program() -> &'static Stage3VerifierProgramPlan { + &STAGE3_PROGRAM +} + +fn verify_stage3_squeeze( + program: &'static Stage3VerifierProgramPlan, + squeeze: &'static Stage3TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage3ExecutionArtifacts, +) -> Result<(), VerifyStage3Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage3Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage3Error::from)?; + artifacts.challenge_vectors.push(Stage3ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn verify_stage3_driver( + program: &'static Stage3VerifierProgramPlan, + driver: &'static Stage3SumcheckDriverPlan, + proof: &Stage3Proof, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage3ExecutionArtifacts, +) -> Result<(), VerifyStage3Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage3Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage3.batched" => { + verify_batched_stage3(program, driver, proof, store, transcript)? + } + _ => { + return Err(VerifyStage3Error::UnsupportedRelation { + relation, + }); + } + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage3( + program: &'static Stage3VerifierProgramPlan, + driver: &'static Stage3SumcheckDriverPlan, + proof: &Stage3SumcheckOutput, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage3Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim(program, driver, store, evals, point, batching_coeffs) + }, + |store, verified| observe_stage3_sumcheck_output(program, store, verified), + |driver, error| VerifyStage3Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage3_sumcheck_output( + program: &'static Stage3VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage3SumcheckOutput, +) -> Result<(), VerifyStage3Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + _ => { + return Err(VerifyStage3Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage3Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage3Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage3Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage3Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage3Error::InvalidProof { driver, reason }, + |symbol| VerifyStage3Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage3VerifierProgramPlan, + driver: &'static Stage3SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage3NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage3Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage3Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let value = match instance.relation { + "jolt.stage3.spartan_shift" => { + expected_spartan_shift(store, evals, local_point)? + } + "jolt.stage3.instruction_input" => { + expected_instruction_input(store, evals, local_point)? + } + "jolt.stage3.registers_claim_reduction" => { + expected_registers(store, evals, local_point)? + } + _ => { + return Err(VerifyStage3Error::UnsupportedRelation { + relation: instance.relation, + }); + } + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_spartan_shift( + store: &super::common::ValueStore, + evals: &[Stage3NamedEval], + local_point: &[Fr], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_outer = + EqPlusOnePolynomial::::new(super::common::store_point(store, "stage3.input.stage1.NextPC")?.to_vec()) + .evaluate(&opening_point); + let eq_product = EqPlusOnePolynomial::::new( + super::common::store_point(store, "stage3.input.stage2.product_virtual.NextIsNoop")? + .to_vec(), + ) + .evaluate(&opening_point); + let weighted_outer = eval_by_name(evals, "stage3.spartan_shift.eval.UnexpandedPC")? + + super::common::store_scalar(store, "stage3.spartan_shift.gamma")? + * eval_by_name(evals, "stage3.spartan_shift.eval.PC")? + + super::common::store_scalar(store, "stage3.spartan_shift.gamma2")? + * eval_by_name(evals, "stage3.spartan_shift.eval.OpFlagVirtualInstruction")? + + super::common::store_scalar(store, "stage3.spartan_shift.gamma3")? + * eval_by_name(evals, "stage3.spartan_shift.eval.OpFlagIsFirstInSequence")?; + Ok(eq_outer * weighted_outer + + super::common::store_scalar(store, "stage3.spartan_shift.gamma4")? + * eq_product + * (Fr::from_u64(1) + - eval_by_name(evals, "stage3.spartan_shift.eval.InstructionFlagIsNoop")?)) +} + +fn expected_instruction_input( + store: &super::common::ValueStore, + evals: &[Stage3NamedEval], + local_point: &[Fr], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle( + &opening_point, + super::common::store_point(store, "stage3.input.stage2.product_virtual.LeftInstructionInput")?, + ); + let left = eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Rs1Value")? + + eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", + )? * eval_by_name(evals, "stage3.instruction_input.eval.UnexpandedPC")?; + let right = eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Rs2Value")? + + eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Imm")?; + Ok(eq_eval * (right + super::common::store_scalar(store, "stage3.instruction_input.gamma")? * left)) +} + +fn expected_registers( + store: &super::common::ValueStore, + evals: &[Stage3NamedEval], + local_point: &[Fr], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle( + &opening_point, + super::common::store_point(store, "stage3.input.stage1.RdWriteValue")?, + ); + Ok(eq_eval + * (eval_by_name(evals, "stage3.registers_claim_reduction.eval.RdWriteValue")? + + super::common::store_scalar(store, "stage3.registers.gamma")? + * eval_by_name(evals, "stage3.registers_claim_reduction.eval.Rs1Value")? + + super::common::store_scalar(store, "stage3.registers.gamma2")? + * eval_by_name(evals, "stage3.registers_claim_reduction.eval.Rs2Value")?)) +} + +"# + } +} + +fn require_supported_symbol(kind: &str, actual: &str, expected: &str) -> Result<(), EmitError> { + if actual == expected { + Ok(()) + } else { + Err(EmitError::new(format!( + "unsupported {kind} @{actual}; expected @{expected}" + ))) + } +} + +fn emit_str_array(name: &str, values: &[String]) -> String { + if values.is_empty() { + return format!("pub const {name}: &[&str] = &[];\n\n"); + } + if let [value] = values { + return format!("pub const {name}: &[&str] = &[{}];\n\n", rust_str(value)); + } + let entries = values + .iter() + .map(|value| format!(" {},", rust_str(value))) + .collect::>() + .join("\n"); + format!("pub const {name}: &[&str] = &[\n{entries}\n];\n\n") +} + +fn emit_usize_array(name: &str, values: &[usize]) -> String { + let entries = values + .iter() + .map(|value| format!(" {value},")) + .collect::>() + .join("\n"); + format!("pub const {name}: &[usize] = &[\n{entries}\n];\n\n") +} + +fn intern_str_array( + source: &mut String, + arrays: &mut Vec<(Vec, String)>, + name_prefix: &str, + values: &[String], +) -> String { + if let Some((_, name)) = arrays + .iter() + .find(|(existing, _)| existing.as_slice() == values) + { + return name.clone(); + } + let name = format!("{name_prefix}_{}", arrays.len()); + source.push_str(&emit_str_array(&name, values)); + arrays.push((values.to_vec(), name.clone())); + name +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn verify_count(kind: &str, symbol: &str, expected: usize, actual: usize) -> Result<(), EmitError> { + if expected == actual { + Ok(()) + } else { + Err(EmitError::new(format!( + "{kind} @{symbol} count mismatch: expected {expected}, got {actual}" + ))) + } +} + +fn missing_role_binding(kind: &str, symbol: &str) -> EmitError { + EmitError::new(format!("missing {kind} for `{symbol}`")) +} + +fn symbols<'a>(values: impl Iterator) -> BTreeSet { + values.cloned().collect() +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn signed_int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_signed_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "signed integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn parse_signed_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn int_array_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "integer array"))?; + parse_int_array(&attribute).ok_or_else(|| attr_error(operation, attr, "integer array")) +} + +fn parse_int_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().parse().ok()) + .collect() +} + +fn operand_symbols( + operation: OperationRef<'_, '_>, + start_index: usize, +) -> Result, EmitError> { + (start_index..operation.operand_count()) + .map(|index| operand_symbol(operation, index)) + .collect() +} + +fn operand_symbol(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + EmitError::new(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + EmitError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name<'c: 'a, 'a>(operation: impl OperationLike<'c, 'a>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} diff --git a/crates/bolt/src/protocols/jolt/emit/rust/stage4.rs b/crates/bolt/src/protocols/jolt/emit/rust/stage4.rs new file mode 100644 index 0000000000..b0b5c0ca63 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/stage4.rs @@ -0,0 +1,2487 @@ +#![expect( + clippy::needless_raw_string_hashes, + reason = "generated Rust templates are kept as raw string blocks for copyable output" +)] + +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{Attribute, OperationRef}; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4CpuProgram { + pub role: Role, + pub params: Stage4Params, + pub steps: Vec, + pub transcript_squeezes: Vec, + pub transcript_absorb_bytes: Vec, + pub opening_inputs: Vec, + pub field_constants: Vec, + pub field_exprs: Vec, + pub kernels: Vec, + pub claims: Vec, + pub batches: Vec, + pub drivers: Vec, + pub instance_results: Vec, + pub evals: Vec, + pub point_slices: Vec, + pub point_concats: Vec, + pub opening_claims: Vec, + pub opening_equalities: Vec, + pub opening_batches: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4Params { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4KernelPlan { + pub symbol: String, + pub relation: String, + pub kind: String, + pub backend: String, + pub abi: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4TranscriptSqueezePlan { + pub symbol: String, + pub label: String, + pub kind: String, + pub count: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4TranscriptAbsorbBytesPlan { + pub symbol: String, + pub label: String, + pub payload: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4ProgramStepPlan { + pub kind: String, + pub symbol: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4OpeningInputPlan { + pub symbol: String, + pub source_stage: String, + pub source_claim: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4FieldConstantPlan { + pub symbol: String, + pub field: String, + pub value: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4FieldExprPlan { + pub symbol: String, + pub kind: String, + pub formula: String, + pub operand_names: Vec, + pub operands: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckClaimPlan { + pub symbol: String, + pub stage: String, + pub domain: String, + pub num_rounds: usize, + pub degree: usize, + pub claim: String, + pub kernel: Option, + pub relation: Option, + pub claim_value: String, + pub input_openings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, + pub claim_label: String, + pub round_label: String, + pub round_schedule: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckDriverPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub kernel: Option, + pub relation: Option, + pub batch: String, + pub policy: String, + pub round_schedule: Vec, + pub claim_label: String, + pub round_label: String, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckInstanceResultPlan { + pub symbol: String, + pub source: String, + pub claim: String, + pub relation: String, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: String, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckEvalPlan { + pub symbol: String, + pub source: String, + pub name: String, + pub index: usize, + pub oracle: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4PointSlicePlan { + pub symbol: String, + pub source: String, + pub offset: usize, + pub length: usize, + pub input: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4PointConcatPlan { + pub symbol: String, + pub layout: String, + pub arity: usize, + pub inputs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4OpeningClaimPlan { + pub symbol: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, + pub point_source: String, + pub eval_source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4OpeningClaimEqualityPlan { + pub symbol: String, + pub mode: String, + pub lhs: String, + pub rhs: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage4OpeningBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, +} + +pub fn stage4_cpu_program(module: &BoltModule<'_, Cpu>) -> Result { + verify_cpu_schema(module)?; + let program = Stage4CpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +pub fn emit_stage4_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = stage4_cpu_program(module)?; + + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source(), + }) +} + +impl Stage4CpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let mut params = None; + let mut steps = Vec::new(); + let mut transcript_squeezes = Vec::new(); + let mut transcript_absorb_bytes = Vec::new(); + let mut opening_inputs = Vec::new(); + let mut field_constants = Vec::new(); + let mut field_exprs = Vec::new(); + let mut kernels = Vec::new(); + let mut claims = Vec::new(); + let mut batches = Vec::new(); + let mut drivers = Vec::new(); + let mut instance_results = Vec::new(); + let mut evals = Vec::new(); + let mut point_slices = Vec::new(); + let mut point_concats = Vec::new(); + let mut opening_claims = Vec::new(); + let mut opening_equalities = Vec::new(); + let mut opening_batches = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(Stage4Params { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.kernel" => { + kernels.push(Stage4KernelPlan { + symbol: string_attr(op, "sym_name")?, + relation: symbol_attr(op, "relation")?, + kind: string_attr(op, "kind")?, + backend: string_attr(op, "backend")?, + abi: string_attr(op, "abi")?, + }); + } + "cpu.transcript_squeeze" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage4ProgramStepPlan { + kind: "transcript_squeeze".to_owned(), + symbol: symbol.clone(), + }); + transcript_squeezes.push(Stage4TranscriptSqueezePlan { + symbol, + label: string_attr(op, "label")?, + kind: string_attr(op, "kind")?, + count: int_attr(op, "count")?, + }); + } + "cpu.transcript_absorb_bytes" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage4ProgramStepPlan { + kind: "transcript_absorb_bytes".to_owned(), + symbol: symbol.clone(), + }); + transcript_absorb_bytes.push(Stage4TranscriptAbsorbBytesPlan { + symbol, + label: string_attr(op, "label")?, + payload: string_attr(op, "payload")?, + }); + } + "cpu.opening_input" => { + opening_inputs.push(Stage4OpeningInputPlan { + symbol: string_attr(op, "sym_name")?, + source_stage: symbol_attr(op, "source_stage")?, + source_claim: symbol_attr(op, "source_claim")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + }); + } + "cpu.field_const" => { + field_constants.push(Stage4FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: int_attr(op, "value")?, + }); + } + "cpu.field_zero" => { + field_constants.push(Stage4FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 0, + }); + } + "cpu.field_one" => { + field_constants.push(Stage4FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 1, + }); + } + "cpu.field_add" | "cpu.field_sub" | "cpu.field_mul" | "cpu.field_neg" => { + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage4FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: operation_name(op).replace("cpu.field_", "field."), + operand_names: operands.clone(), + operands, + }); + } + "cpu.field_pow" => { + let exponent = int_attr(op, "exponent")?; + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage4FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: format!("field.pow:{exponent}"), + operand_names: operands.clone(), + operands, + }); + } + "cpu.sumcheck_claim" => { + claims.push(Stage4SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_verify_claim" => { + claims.push(Stage4SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_batch" => { + batches.push(Stage4SumcheckBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + round_schedule: int_array_attr(op, "round_schedule")?, + }); + } + "cpu.sumcheck_driver" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage4ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage4SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_verify" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage4ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage4SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_instance_result" => { + instance_results.push(Stage4SumcheckInstanceResultPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + claim: symbol_attr(op, "claim")?, + relation: symbol_attr(op, "relation")?, + index: int_attr(op, "index")?, + point_arity: int_attr(op, "point_arity")?, + num_rounds: int_attr(op, "num_rounds")?, + round_offset: int_attr(op, "round_offset")?, + point_order: string_attr(op, "point_order")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_eval" => { + evals.push(Stage4SumcheckEvalPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + name: symbol_attr(op, "name")?, + index: int_attr(op, "index")?, + oracle: symbol_attr(op, "oracle")?, + }); + } + "cpu.point_slice" => { + point_slices.push(Stage4PointSlicePlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + offset: int_attr(op, "offset")?, + length: int_attr(op, "length")?, + input: operand_symbol(op, 0)?, + }); + } + "cpu.point_concat" => { + point_concats.push(Stage4PointConcatPlan { + symbol: string_attr(op, "sym_name")?, + layout: string_attr(op, "layout")?, + arity: int_attr(op, "arity")?, + inputs: operand_symbols(op, 0)?, + }); + } + "cpu.opening_claim" => { + opening_claims.push(Stage4OpeningClaimPlan { + symbol: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + point_source: operand_symbol(op, 0)?, + eval_source: operand_symbol(op, 1)?, + }); + } + "cpu.opening_claim_equal" => { + opening_equalities.push(Stage4OpeningClaimEqualityPlan { + symbol: string_attr(op, "sym_name")?, + mode: string_attr(op, "mode")?, + lhs: operand_symbol(op, 0)?, + rhs: operand_symbol(op, 1)?, + }); + } + "cpu.opening_batch" => { + opening_batches.push(Stage4OpeningBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + }); + } + _ => {} + } + } + + Ok(Self { + params: params.ok_or_else(|| EmitError::new("missing cpu.params"))?, + role: module + .role() + .ok_or_else(|| EmitError::new("missing cpu party role"))?, + steps, + transcript_squeezes, + transcript_absorb_bytes, + opening_inputs, + field_constants, + field_exprs, + kernels, + claims, + batches, + drivers, + instance_results, + evals, + point_slices, + point_concats, + opening_claims, + opening_equalities, + opening_batches, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + require_supported_symbol("field", &self.params.field, "bn254_fr")?; + require_supported_symbol("pcs", &self.params.pcs, "dory")?; + require_supported_symbol("transcript", &self.params.transcript, "blake2b_transcript")?; + self.verify_transcript_steps()?; + self.verify_field_flow()?; + self.verify_claim_batches()?; + match self.role { + Role::Prover => { + self.verify_kernel_definitions()?; + self.verify_prover_driver_bindings()?; + } + Role::Verifier => self.verify_verifier_driver_bindings()?, + } + self.verify_opening_flow() + } + + fn verify_transcript_steps(&self) -> Result<(), EmitError> { + for squeeze in &self.transcript_squeezes { + if !matches!( + squeeze.kind.as_str(), + "challenge_scalar" | "challenge_vector" + ) { + return Err(EmitError::new(format!( + "stage4 transcript squeeze @{} has unsupported kind `{}`", + squeeze.symbol, squeeze.kind + ))); + } + if squeeze.count == 0 { + return Err(EmitError::new(format!( + "stage4 transcript squeeze @{} has zero count", + squeeze.symbol + ))); + } + } + for absorb in &self.transcript_absorb_bytes { + if absorb.label.is_empty() { + return Err(EmitError::new(format!( + "stage4 transcript byte absorb @{} has empty label", + absorb.symbol + ))); + } + } + Ok(()) + } + + fn verify_field_flow(&self) -> Result<(), EmitError> { + for constant in &self.field_constants { + require_supported_symbol("field constant field", &constant.field, "bn254_fr")?; + } + let field_values = self.field_value_symbols(); + for expr in &self.field_exprs { + verify_count( + "field expr operands", + &expr.symbol, + expr.operand_names.len(), + expr.operands.len(), + )?; + for operand in &expr.operands { + if !field_values.contains(operand) { + return Err(EmitError::new(format!( + "field expr @{} references missing field value @{operand}", + expr.symbol + ))); + } + } + } + for claim in &self.claims { + if !field_values.contains(&claim.claim_value) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing claim value @{}", + claim.symbol, claim.claim_value + ))); + } + } + Ok(()) + } + + fn field_value_symbols(&self) -> BTreeSet { + let mut values = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + values.extend(symbols( + self.field_constants.iter().map(|constant| &constant.symbol), + )); + values.extend(symbols( + self.transcript_squeezes + .iter() + .filter(|squeeze| matches!(squeeze.kind.as_str(), "challenge_scalar" | "scalar")) + .map(|squeeze| &squeeze.symbol), + )); + values.extend(symbols(self.field_exprs.iter().map(|expr| &expr.symbol))); + values.extend(symbols(self.evals.iter().map(|eval| &eval.symbol))); + values + } + + fn verify_kernel_definitions(&self) -> Result<(), EmitError> { + for kernel in &self.kernels { + if kernel.backend != "cpu" { + return Err(EmitError::new(format!( + "stage4 kernel @{} targets unsupported backend `{}`", + kernel.symbol, kernel.backend + ))); + } + if kernel.kind != "sumcheck" { + return Err(EmitError::new(format!( + "stage4 kernel @{} has unsupported kind `{}`", + kernel.symbol, kernel.kind + ))); + } + let expected_abi = match kernel.relation.as_str() { + "jolt.stage4.registers_read_write" => "jolt_stage4_registers_read_write", + "jolt.stage4.ram_val_check" => "jolt_stage4_ram_val_check", + "jolt.stage4.batched" => "jolt_stage4_batched", + _ => { + return Err(EmitError::new(format!( + "unsupported stage4 kernel relation @{}", + kernel.relation + ))); + } + }; + if kernel.abi != expected_abi { + return Err(EmitError::new(format!( + "stage4 kernel @{} ABI `{}` does not match relation @{}", + kernel.symbol, kernel.abi, kernel.relation + ))); + } + } + Ok(()) + } + + fn verify_claim_batches(&self) -> Result<(), EmitError> { + let claims = symbols(self.claims.iter().map(|claim| &claim.symbol)); + for batch in &self.batches { + verify_count( + "sumcheck batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "sumcheck batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "sumcheck batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !claims.contains(claim) { + return Err(EmitError::new(format!( + "sumcheck batch @{} references missing claim @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn verify_prover_driver_bindings(&self) -> Result<(), EmitError> { + let kernels = symbols(self.kernels.iter().map(|kernel| &kernel.symbol)); + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + let Some(kernel) = claim.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck claim @{} is missing kernel", + claim.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck claim @{} references missing kernel @{kernel}", + claim.symbol + ))); + } + } + for driver in &self.drivers { + let Some(kernel) = driver.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck driver @{} is missing kernel", + driver.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck driver @{} references missing kernel @{kernel}", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_verifier_driver_bindings(&self) -> Result<(), EmitError> { + if !self.kernels.is_empty() { + return Err(EmitError::new( + "verifier stage4 program must not contain kernels", + )); + } + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + if claim.kernel.is_some() || claim.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck claim @{} must carry relation and no kernel", + claim.symbol + ))); + } + } + for driver in &self.drivers { + if driver.kernel.is_some() || driver.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck driver @{} must carry relation and no kernel", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_opening_flow(&self) -> Result<(), EmitError> { + let mut point_sources = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + point_sources.extend(symbols( + self.instance_results + .iter() + .map(|instance| &instance.symbol), + )); + point_sources.extend(symbols( + self.opening_inputs.iter().map(|input| &input.symbol), + )); + point_sources.extend(symbols(self.point_slices.iter().map(|slice| &slice.symbol))); + point_sources.extend(symbols( + self.point_concats.iter().map(|concat| &concat.symbol), + )); + for slice in &self.point_slices { + if !point_sources.contains(&slice.input) { + return Err(EmitError::new(format!( + "point slice @{} uses missing point source @{}", + slice.symbol, slice.input + ))); + } + } + for concat in &self.point_concats { + for input in &concat.inputs { + if !point_sources.contains(input) { + return Err(EmitError::new(format!( + "point concat @{} uses missing point source @{input}", + concat.symbol + ))); + } + } + } + let eval_sources = self.field_value_symbols(); + let mut opening_sources = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + opening_sources.extend(symbols( + self.opening_claims.iter().map(|claim| &claim.symbol), + )); + for equality in &self.opening_equalities { + if !opening_sources.contains(&equality.lhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing lhs opening @{}", + equality.symbol, equality.lhs + ))); + } + if !opening_sources.contains(&equality.rhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing rhs opening @{}", + equality.symbol, equality.rhs + ))); + } + } + for claim in &self.claims { + for input in &claim.input_openings { + if !opening_sources.contains(input) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing opening @{input}", + claim.symbol + ))); + } + } + } + let drivers = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + for instance in &self.instance_results { + if !drivers.contains(&instance.source) { + return Err(EmitError::new(format!( + "sumcheck instance result @{} references missing driver @{}", + instance.symbol, instance.source + ))); + } + } + for eval in &self.evals { + if !drivers.contains(&eval.source) { + return Err(EmitError::new(format!( + "sumcheck eval @{} references missing driver @{}", + eval.symbol, eval.source + ))); + } + } + for claim in &self.opening_claims { + if !point_sources.contains(&claim.point_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing point source @{}", + claim.symbol, claim.point_source + ))); + } + if !eval_sources.contains(&claim.eval_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing eval source @{}", + claim.symbol, claim.eval_source + ))); + } + } + let openings = symbols(self.opening_claims.iter().map(|claim| &claim.symbol)); + for batch in &self.opening_batches { + verify_count( + "opening batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "opening batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "opening batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !openings.contains(claim) { + return Err(EmitError::new(format!( + "opening batch @{} references missing opening @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_stage4.rs", + Role::Verifier => "verify_stage4.rs", + } + } + + fn emit_source(&self) -> String { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + match self.role { + Role::Prover => { + source.push_str(Self::emit_prover_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_prover_types()); + } + Role::Verifier => { + source.push_str(Self::emit_verifier_imports()); + source.push_str("\n\n"); + source.push_str(&Self::emit_verifier_types()); + } + } + source.push('\n'); + source.push_str(&self.emit_constants()); + source.push('\n'); + source.push_str(self.emit_entrypoint()); + source + } + + fn emit_prover_imports() -> &'static str { + "use jolt_field::Fr;\n\ + use jolt_kernels::stage4::{execute_stage4_program, Stage4CpuProgramPlan, Stage4ExecutionArtifacts, Stage4ExecutionMode, Stage4FieldConstantPlan, Stage4FieldExprPlan, Stage4KernelError, Stage4KernelExecutor, Stage4KernelPlan, Stage4OpeningBatchPlan, Stage4OpeningClaimEqualityPlan, Stage4OpeningClaimPlan, Stage4OpeningInputPlan, Stage4Params, Stage4PointConcatPlan, Stage4PointSlicePlan, Stage4ProgramStepPlan, Stage4SumcheckBatchPlan, Stage4SumcheckClaimPlan, Stage4SumcheckDriverPlan, Stage4SumcheckEvalPlan, Stage4SumcheckInstanceResultPlan, Stage4TranscriptAbsorbBytesPlan, Stage4TranscriptSqueezePlan};\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_prover_types() -> &'static str { + "pub type DefaultStage4Transcript = Blake2bTranscript;\n" + } + + fn emit_verifier_imports() -> &'static str { + "use super::common::{batch_claims, eval_by_name, find_batch, find_plan, lt_polynomial_eval, reverse_slice};\n\ + use jolt_field::{Field, Fr};\n\ + use jolt_poly::EqPolynomial;\n\ + use jolt_sumcheck::SumcheckError;\n\ + use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript};" + } + + #[expect(dead_code)] + fn emit_types() -> &'static str { + r#"#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4TranscriptAbsorbBytesPlan { + pub symbol: &'static str, + pub label: &'static str, + pub payload: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operand_names: &'static [&'static str], + pub operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4OpeningClaimEqualityPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub lhs: &'static str, + pub rhs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4CpuProgramPlan { + pub role: &'static str, + pub params: Stage4Params, + pub steps: &'static [Stage4ProgramStepPlan], + pub transcript_squeezes: &'static [Stage4TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [Stage4TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [Stage4OpeningInputPlan], + pub field_constants: &'static [Stage4FieldConstantPlan], + pub field_exprs: &'static [Stage4FieldExprPlan], + pub kernels: &'static [Stage4KernelPlan], + pub claims: &'static [Stage4SumcheckClaimPlan], + pub batches: &'static [Stage4SumcheckBatchPlan], + pub drivers: &'static [Stage4SumcheckDriverPlan], + pub instance_results: &'static [Stage4SumcheckInstanceResultPlan], + pub evals: &'static [Stage4SumcheckEvalPlan], + pub point_slices: &'static [Stage4PointSlicePlan], + pub point_concats: &'static [Stage4PointConcatPlan], + pub opening_claims: &'static [Stage4OpeningClaimPlan], + pub opening_equalities: &'static [Stage4OpeningClaimEqualityPlan], + pub opening_batches: &'static [Stage4OpeningBatchPlan], +} +"# + } + + fn emit_verifier_type_aliases() -> &'static str { + r#"pub type Stage4NamedEval = super::common::StageNamedEval; +pub type Stage4SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage4ChallengeVector = super::common::StageChallengeVector; +pub type Stage4ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage4Proof = super::common::StageProof; +pub type Stage4OpeningInputValue = super::common::StageOpeningInputValue; + +pub use super::common::{ + FieldConstantPlan as Stage4FieldConstantPlan, FieldExprPlan as Stage4FieldExprPlan, + KernelPlan as Stage4KernelPlan, OpeningBatchPlan as Stage4OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage4OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage4OpeningClaimPlan, OpeningInputPlan as Stage4OpeningInputPlan, + PointConcatPlan as Stage4PointConcatPlan, PointSlicePlan as Stage4PointSlicePlan, + ProgramStepPlan as Stage4ProgramStepPlan, StageParams as Stage4Params, + StageProgramPlanNoPointZeros as Stage4CpuProgramPlan, + SumcheckBatchPlan as Stage4SumcheckBatchPlan, + SumcheckClaimPlan as Stage4SumcheckClaimPlan, SumcheckDriverPlan as Stage4SumcheckDriverPlan, + SumcheckEvalPlan as Stage4SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage4SumcheckInstanceResultPlan, + TranscriptAbsorbBytesPlan as Stage4TranscriptAbsorbBytesPlan, + TranscriptSqueezePlan as Stage4TranscriptSqueezePlan, +}; +"# + } + + fn emit_verifier_types() -> String { + let mut source = Self::emit_verifier_type_aliases().to_owned(); + source.push_str( + r#" +pub type DefaultStage4Transcript = Blake2bTranscript; +pub type Stage4VerifierProgramPlan = Stage4CpuProgramPlan; + +#[derive(Debug)] +pub enum VerifyStage4Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage4Error); +"#, + ); + source + } + + fn emit_constants(&self) -> String { + let mut source = self.emit_shared_constants(); + source.push_str(&self.emit_kernel_constants()); + source.push_str(&self.emit_sumcheck_claim_constants()); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_sumcheck_driver_constants()); + source.push_str(&self.emit_tail_constants()); + push_format( + &mut source, + format_args!( + "pub const STAGE4_PROGRAM: {} = Stage4CpuProgramPlan {{\n\ + \x20 role: {},\n\ + \x20 params: STAGE4_PARAMS,\n\ + \x20 steps: STAGE4_PROGRAM_STEPS,\n\ + \x20 transcript_squeezes: STAGE4_TRANSCRIPT_SQUEEZES,\n\ + \x20 transcript_absorb_bytes: STAGE4_TRANSCRIPT_ABSORB_BYTES,\n\ + \x20 opening_inputs: STAGE4_OPENING_INPUTS,\n\ + \x20 field_constants: STAGE4_FIELD_CONSTANTS,\n\ + \x20 field_exprs: STAGE4_FIELD_EXPRS,\n\ + \x20 kernels: STAGE4_KERNELS,\n\ + \x20 claims: STAGE4_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE4_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE4_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE4_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE4_SUMCHECK_EVALS,\n\ + \x20 point_slices: STAGE4_POINT_SLICES,\n\ + \x20 point_concats: STAGE4_POINT_CONCATS,\n\ + \x20 opening_claims: STAGE4_OPENING_CLAIMS,\n\ + \x20 opening_equalities: STAGE4_OPENING_EQUALITIES,\n\ + \x20 opening_batches: STAGE4_OPENING_BATCHES,\n\ + }};\n", + self.program_plan_type(), + rust_str(self.role_label()) + ), + ); + source + } + + fn emit_shared_constants(&self) -> String { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const STAGE4_PARAMS: Stage4Params = Stage4Params {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + source.push_str(&self.emit_program_step_constants()); + source.push_str(&self.emit_transcript_squeeze_constants()); + source.push_str(&self.emit_transcript_absorb_bytes_constants()); + source.push_str(&self.emit_opening_input_constants()); + source.push_str(&self.emit_field_constant_constants()); + source.push_str(&self.emit_field_expr_constants()); + source + } + + fn emit_program_step_constants(&self) -> String { + let steps = self + .steps + .iter() + .map(|step| { + format!( + " Stage4ProgramStepPlan {{ kind: {}, symbol: {} }},", + rust_str(&step.kind), + rust_str(&step.symbol), + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE4_PROGRAM_STEPS: &[Stage4ProgramStepPlan] = &[\n{steps}\n];\n\n") + } + + fn emit_transcript_squeeze_constants(&self) -> String { + let squeezes = self + .transcript_squeezes + .iter() + .map(|squeeze| { + format!( + " Stage4TranscriptSqueezePlan {{ symbol: {}, label: {}, kind: {}, count: {} }},", + rust_str(&squeeze.symbol), + rust_str(&squeeze.label), + rust_str(&squeeze.kind), + squeeze.count, + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE4_TRANSCRIPT_SQUEEZES: &[Stage4TranscriptSqueezePlan] = &[\n{squeezes}\n];\n\n" + ) + } + + fn emit_transcript_absorb_bytes_constants(&self) -> String { + let absorbs = self + .transcript_absorb_bytes + .iter() + .map(|absorb| { + format!( + " Stage4TranscriptAbsorbBytesPlan {{ symbol: {}, label: {}, payload: {} }},", + rust_str(&absorb.symbol), + rust_str(&absorb.label), + rust_str(&absorb.payload), + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE4_TRANSCRIPT_ABSORB_BYTES: &[Stage4TranscriptAbsorbBytesPlan] = &[\n{absorbs}\n];\n\n" + ) + } + + fn emit_opening_input_constants(&self) -> String { + let inputs = self + .opening_inputs + .iter() + .map(|input| { + format!( + " Stage4OpeningInputPlan {{ symbol: {}, source_stage: {}, source_claim: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {} }},", + rust_str(&input.symbol), + rust_str(&input.source_stage), + rust_str(&input.source_claim), + rust_str(&input.oracle), + rust_str(&input.domain), + input.point_arity, + rust_str(&input.claim_kind) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE4_OPENING_INPUTS: &[Stage4OpeningInputPlan] = &[\n{inputs}\n];\n\n") + } + + fn emit_field_constant_constants(&self) -> String { + let constants = self + .field_constants + .iter() + .map(|constant| { + format!( + " Stage4FieldConstantPlan {{ symbol: {}, field: {}, value: {} }},", + rust_str(&constant.symbol), + rust_str(&constant.field), + constant.value + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE4_FIELD_CONSTANTS: &[Stage4FieldConstantPlan] = &[\n{constants}\n];\n\n" + ) + } + + fn emit_field_expr_constants(&self) -> String { + if self.role == Role::Verifier { + let exprs = self + .field_exprs + .iter() + .map(|expr| { + format!( + " Stage4FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operands: {} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula), + rust_str(&expr.operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE4_FIELD_EXPRS: &[Stage4FieldExprPlan] = &[\n{exprs}\n];\n" + ); + } + + let mut source = String::new(); + let mut arrays = Vec::new(); + let mut array_refs = Vec::new(); + for (index, expr) in self.field_exprs.iter().enumerate() { + let operands = intern_str_array( + &mut source, + &mut arrays, + "STAGE4_FIELD_EXPR_OPERANDS", + &expr.operands, + ); + let operand_names = intern_str_array( + &mut source, + &mut arrays, + "STAGE4_FIELD_EXPR_OPERANDS", + &expr.operand_names, + ); + array_refs.push((index, operand_names, operands)); + } + let exprs = self + .field_exprs + .iter() + .enumerate() + .map(|(index, expr)| { + let (_, operand_names, operands) = &array_refs[index]; + format!( + " Stage4FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operand_names: {operand_names}, operands: {operands} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE4_FIELD_EXPRS: &[Stage4FieldExprPlan] = &[\n{exprs}\n];\n" + ), + ); + source + } + + fn emit_kernel_constants(&self) -> String { + let kernels = self + .kernels + .iter() + .map(|kernel| { + format!( + " Stage4KernelPlan {{ symbol: {}, relation: {}, kind: {}, backend: {}, abi: {} }},", + rust_str(&kernel.symbol), + rust_str(&kernel.relation), + rust_str(&kernel.kind), + rust_str(&kernel.backend), + rust_str(&kernel.abi) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE4_KERNELS: &[Stage4KernelPlan] = &[\n{kernels}\n];\n\n") + } + + fn emit_sumcheck_claim_constants(&self) -> String { + if self.role == Role::Verifier { + let claims = self + .claims + .iter() + .map(|claim| { + format!( + " Stage4SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: {}, relation: {}, claim_value: {}, input_openings: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_option_str(claim.kernel.as_deref()), + rust_option_str(claim.relation.as_deref()), + rust_str(&claim.claim_value), + rust_str(&claim.input_openings.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE4_SUMCHECK_CLAIMS: &[Stage4SumcheckClaimPlan] = &[\n{claims}\n];\n" + ); + } + + let mut source = String::new(); + for (index, claim) in self.claims.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE4_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS"), + &claim.input_openings, + )); + } + let claims = self + .claims + .iter() + .enumerate() + .map(|(index, claim)| { + format!( + " Stage4SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: {}, relation: {}, claim_value: {}, input_openings: STAGE4_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_option_str(claim.kernel.as_deref()), + rust_option_str(claim.relation.as_deref()), + rust_str(&claim.claim_value) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE4_SUMCHECK_CLAIMS: &[Stage4SumcheckClaimPlan] = &[\n{claims}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE4_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage4SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {}, claim_label: {}, round_label: {}, round_schedule: STAGE4_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")), + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE4_SUMCHECK_BATCHES: &[Stage4SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + return source; + } + + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE4_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE4_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + source.push_str(&emit_usize_array( + &format!("STAGE4_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage4SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE4_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE4_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS, claim_label: {}, round_label: {}, round_schedule: STAGE4_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE4_SUMCHECK_BATCHES: &[Stage4SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_driver_constants(&self) -> String { + let mut source = String::new(); + for (index, driver) in self.drivers.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE4_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE"), + &driver.round_schedule, + )); + } + let drivers = self + .drivers + .iter() + .enumerate() + .map(|(index, driver)| { + format!( + " Stage4SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: {}, relation: {}, batch: {}, policy: {}, round_schedule: STAGE4_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_option_str(driver.kernel.as_deref()), + rust_option_str(driver.relation.as_deref()), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE4_SUMCHECK_DRIVERS: &[Stage4SumcheckDriverPlan] = &[\n{drivers}\n];\n" + ), + ); + source + } + + fn emit_tail_constants(&self) -> String { + let mut source = String::new(); + source.push_str(&self.emit_sumcheck_instance_result_constants()); + source.push_str(&self.emit_sumcheck_eval_constants()); + source.push_str(&self.emit_point_slice_constants()); + source.push_str(&self.emit_point_concat_constants()); + source.push_str(&self.emit_opening_claim_constants()); + source.push_str(&self.emit_opening_claim_equality_constants()); + source.push_str(&self.emit_opening_batch_constants()); + source + } + + fn emit_sumcheck_instance_result_constants(&self) -> String { + let instances = self + .instance_results + .iter() + .map(|instance| { + format!( + " Stage4SumcheckInstanceResultPlan {{ symbol: {}, source: {}, claim: {}, relation: {}, index: {}, point_arity: {}, num_rounds: {}, round_offset: {}, point_order: {}, degree: {} }},", + rust_str(&instance.symbol), + rust_str(&instance.source), + rust_str(&instance.claim), + rust_str(&instance.relation), + instance.index, + instance.point_arity, + instance.num_rounds, + instance.round_offset, + rust_str(&instance.point_order), + instance.degree + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE4_SUMCHECK_INSTANCE_RESULTS: &[Stage4SumcheckInstanceResultPlan] = &[\n{instances}\n];\n\n" + ) + } + + fn emit_sumcheck_eval_constants(&self) -> String { + let evals = self + .evals + .iter() + .map(|eval| { + format!( + " Stage4SumcheckEvalPlan {{ symbol: {}, source: {}, name: {}, index: {}, oracle: {} }},", + rust_str(&eval.symbol), + rust_str(&eval.source), + rust_str(&eval.name), + eval.index, + rust_str(&eval.oracle) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE4_SUMCHECK_EVALS: &[Stage4SumcheckEvalPlan] = &[\n{evals}\n];\n\n") + } + + fn emit_point_slice_constants(&self) -> String { + let slices = self + .point_slices + .iter() + .map(|slice| { + format!( + " Stage4PointSlicePlan {{ symbol: {}, source: {}, offset: {}, length: {}, input: {} }},", + rust_str(&slice.symbol), + rust_str(&slice.source), + slice.offset, + slice.length, + rust_str(&slice.input) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE4_POINT_SLICES: &[Stage4PointSlicePlan] = &[\n{slices}\n];\n\n") + } + + fn emit_point_concat_constants(&self) -> String { + if self.role == Role::Verifier { + let concats = self + .point_concats + .iter() + .map(|concat| { + format!( + " Stage4PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: {} }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity, + rust_str(&concat.inputs.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE4_POINT_CONCATS: &[Stage4PointConcatPlan] = &[\n{concats}\n];\n" + ); + } + + let mut source = String::new(); + for (index, concat) in self.point_concats.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE4_POINT_CONCAT_{index}_INPUTS"), + &concat.inputs, + )); + } + let concats = self + .point_concats + .iter() + .enumerate() + .map(|(index, concat)| { + format!( + " Stage4PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: STAGE4_POINT_CONCAT_{index}_INPUTS }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE4_POINT_CONCATS: &[Stage4PointConcatPlan] = &[\n{concats}\n];\n" + ), + ); + source + } + + fn emit_opening_claim_constants(&self) -> String { + let claims = self + .opening_claims + .iter() + .map(|claim| { + format!( + " Stage4OpeningClaimPlan {{ symbol: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {}, point_source: {}, eval_source: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.oracle), + rust_str(&claim.domain), + claim.point_arity, + rust_str(&claim.claim_kind), + rust_str(&claim.point_source), + rust_str(&claim.eval_source) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE4_OPENING_CLAIMS: &[Stage4OpeningClaimPlan] = &[\n{claims}\n];\n\n") + } + + fn emit_opening_claim_equality_constants(&self) -> String { + let equalities = self + .opening_equalities + .iter() + .map(|equality| { + format!( + " Stage4OpeningClaimEqualityPlan {{ symbol: {}, mode: {}, lhs: {}, rhs: {} }},", + rust_str(&equality.symbol), + rust_str(&equality.mode), + rust_str(&equality.lhs), + rust_str(&equality.rhs) + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE4_OPENING_EQUALITIES: &[Stage4OpeningClaimEqualityPlan] = &[\n{equalities}\n];\n\n" + ) + } + + fn emit_opening_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let batches = self + .opening_batches + .iter() + .map(|batch| { + format!( + " Stage4OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {} }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE4_OPENING_BATCHES: &[Stage4OpeningBatchPlan] = &[\n{batches}\n];\n" + ); + } + + let mut source = String::new(); + for (index, batch) in self.opening_batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE4_OPENING_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE4_OPENING_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + } + let batches = self + .opening_batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage4OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE4_OPENING_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE4_OPENING_BATCH_{index}_CLAIM_OPERANDS }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE4_OPENING_BATCHES: &[Stage4OpeningBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_entrypoint(&self) -> &'static str { + match self.role { + Role::Prover => { + "pub fn execute_stage4_prover(\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage4KernelError>\n\ + where\n\ + \x20 E: Stage4KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage4_prover_with_program(&STAGE4_PROGRAM, executor, transcript)\n\ + }\n\ + \n\ + pub fn execute_stage4_prover_with_program(\n\ + \x20 program: &'static Stage4CpuProgramPlan,\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage4KernelError>\n\ + where\n\ + \x20 E: Stage4KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage4_program(program, Stage4ExecutionMode::Prover, executor, transcript)\n\ + }\n" + } + Role::Verifier => { + r#"pub fn verify_stage4( + proof: &Stage4Proof, + opening_inputs: &[Stage4OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage4Error> +where + T: Transcript, +{ + verify_stage4_with_program(&STAGE4_PROGRAM, proof, opening_inputs, transcript) +} + +pub fn verify_stage4_with_program( + program: &'static Stage4VerifierProgramPlan, + proof: &Stage4Proof, + opening_inputs: &[Stage4OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage4Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage4Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + let mut artifacts = Stage4ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage4Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage4_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "transcript_absorb_bytes" => { + let absorb = find_plan(program.transcript_absorb_bytes, step.symbol).ok_or( + VerifyStage4Error::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage4_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage4Error::MissingProof { + driver: step.symbol, + })?; + verify_stage4_driver(program, driver, proof, &mut store, transcript, &mut artifacts)?; + } + _ => { + return Err(VerifyStage4Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage4 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage4_verifier_program() -> &'static Stage4VerifierProgramPlan { + &STAGE4_PROGRAM +} + +fn verify_stage4_squeeze( + program: &'static Stage4VerifierProgramPlan, + squeeze: &'static Stage4TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage4ExecutionArtifacts, +) -> Result<(), VerifyStage4Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage4Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage4Error::from)?; + artifacts.challenge_vectors.push(Stage4ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn absorb_stage4_bytes(absorb: &'static Stage4TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn verify_stage4_driver( + program: &'static Stage4VerifierProgramPlan, + driver: &'static Stage4SumcheckDriverPlan, + proof: &Stage4Proof, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage4ExecutionArtifacts, +) -> Result<(), VerifyStage4Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage4Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage4.batched" => { + verify_batched_stage4(program, driver, proof, store, transcript)? + } + _ => return Err(VerifyStage4Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage4( + program: &'static Stage4VerifierProgramPlan, + driver: &'static Stage4SumcheckDriverPlan, + proof: &Stage4SumcheckOutput, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage4Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim(program, driver, store, evals, point, batching_coeffs) + }, + |store, verified| observe_stage4_sumcheck_output(program, store, verified), + |driver, error| VerifyStage4Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage4_sumcheck_output( + program: &'static Stage4VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage4SumcheckOutput, +) -> Result<(), VerifyStage4Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "stage4_registers_rw" => { + point = normalize_stage4_registers_rw_point(program, output.driver, &point)?; + } + _ => { + return Err(VerifyStage4Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage4Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage4Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage4Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage4Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage4Error::InvalidProof { driver, reason }, + |symbol| VerifyStage4Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage4VerifierProgramPlan, + driver: &'static Stage4SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage4NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage4Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage4Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(""); + let value = match relation { + "jolt.stage4.registers_read_write" => { + expected_registers_read_write(store, evals, local_point)? + } + "jolt.stage4.ram_val_check" => { + expected_ram_val_check(store, evals, local_point)? + } + _ => return Err(VerifyStage4Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_registers_read_write( + store: &super::common::ValueStore, + evals: &[Stage4NamedEval], + local_point: &[Fr], +) -> Result { + let trace_point = super::common::store_point(store, "stage4.input.stage3.registers.RdWriteValue")?; + let r_cycle = normalize_stage4_registers_rw_cycle_point( + local_point, + trace_point.len(), + "stage4.registers_read_write.instance", + )?; + let eq_eval = EqPolynomial::::mle(&r_cycle, trace_point); + let registers_val = eval_by_name( + evals, + "stage4.registers_read_write.eval.RegistersVal", + )?; + let rs1_ra = eval_by_name(evals, "stage4.registers_read_write.eval.Rs1Ra")?; + let rs2_ra = eval_by_name(evals, "stage4.registers_read_write.eval.Rs2Ra")?; + let rd_wa = eval_by_name(evals, "stage4.registers_read_write.eval.RdWa")?; + let rd_inc = eval_by_name(evals, "stage4.registers_read_write.eval.RdInc")?; + let gamma = super::common::store_scalar(store, "stage4.registers_read_write.gamma")?; + Ok(eq_eval + * (rd_wa * (registers_val + rd_inc) + + gamma * (rs1_ra * registers_val + gamma * rs2_ra * registers_val))) +} + +fn expected_ram_val_check( + store: &super::common::ValueStore, + evals: &[Stage4NamedEval], + local_point: &[Fr], +) -> Result { + let ram_val_point = super::common::store_point(store, "stage4.input.stage2.RamVal")?; + let r_cycle_prime = reverse_slice(local_point); + let r_cycle = suffix_point( + ram_val_point, + r_cycle_prime.len(), + "stage4.input.stage2.RamVal", + )?; + let lt_eval = lt_polynomial_eval(&r_cycle_prime, r_cycle); + let gamma = super::common::store_scalar(store, "stage4.ram_val_check.gamma")?; + let ram_ra = eval_by_name(evals, "stage4.ram_val_check.eval.RamRa")?; + let ram_inc = eval_by_name(evals, "stage4.ram_val_check.eval.RamInc")?; + Ok(ram_inc * ram_ra * (lt_eval + gamma)) +} + +fn suffix_point<'a>( + point: &'a [Fr], + length: usize, + input: &'static str, +) -> Result<&'a [Fr], VerifyStage4Error> { + point + .get(point.len().saturating_sub(length)..) + .filter(|suffix| suffix.len() == length) + .ok_or(VerifyStage4Error::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +fn normalize_stage4_registers_rw_point( + program: &'static Stage4VerifierProgramPlan, + driver: &'static str, + point: &[F], +) -> Result, VerifyStage4Error> { + let driver_plan = find_plan(program.drivers, driver).ok_or(VerifyStage4Error::MissingProof { + driver, + })?; + if driver_plan.round_schedule.len() != 2 { + return Err(VerifyStage4Error::InvalidProof { + driver, + reason: "stage4 registers point normalization requires [cycle, address] schedule", + }); + } + let cycle_rounds = driver_plan.round_schedule[0]; + let address_rounds = driver_plan.round_schedule[1]; + if point.len() != cycle_rounds + address_rounds { + return Err(VerifyStage4Error::InvalidInputLength { + input: "stage4.registers_read_write.instance", + expected: cycle_rounds + address_rounds, + actual: point.len(), + }); + } + let (cycle, address) = point.split_at(cycle_rounds); + Ok(address + .iter() + .rev() + .copied() + .chain(cycle.iter().rev().copied()) + .collect()) +} + +fn normalize_stage4_registers_rw_cycle_point( + point: &[F], + cycle_rounds: usize, + input: &'static str, +) -> Result, VerifyStage4Error> { + let cycle = point + .get(..cycle_rounds) + .filter(|cycle| cycle.len() == cycle_rounds) + .ok_or(VerifyStage4Error::InvalidInputLength { + input, + expected: cycle_rounds, + actual: point.len(), + })?; + Ok(cycle.iter().rev().copied().collect()) +} + +"# + } + } + } + + fn role_label(&self) -> &'static str { + match self.role { + Role::Prover => "prover", + Role::Verifier => "verifier", + } + } + + fn program_plan_type(&self) -> &'static str { + match self.role { + Role::Prover => "Stage4CpuProgramPlan", + Role::Verifier => "Stage4VerifierProgramPlan", + } + } +} + +fn require_supported_symbol(kind: &str, actual: &str, expected: &str) -> Result<(), EmitError> { + if actual == expected { + Ok(()) + } else { + Err(EmitError::new(format!( + "unsupported {kind} @{actual}; expected @{expected}" + ))) + } +} + +fn emit_str_array(name: &str, values: &[String]) -> String { + if values.is_empty() { + return format!("pub const {name}: &[&str] = &[];\n\n"); + } + if let [value] = values { + return format!("pub const {name}: &[&str] = &[{}];\n\n", rust_str(value)); + } + let entries = values + .iter() + .map(|value| format!(" {},", rust_str(value))) + .collect::>() + .join("\n"); + format!("pub const {name}: &[&str] = &[\n{entries}\n];\n\n") +} + +fn emit_usize_array(name: &str, values: &[usize]) -> String { + let entries = values + .iter() + .map(|value| format!(" {value},")) + .collect::>() + .join("\n"); + format!("pub const {name}: &[usize] = &[\n{entries}\n];\n\n") +} + +fn intern_str_array( + source: &mut String, + arrays: &mut Vec<(Vec, String)>, + name_prefix: &str, + values: &[String], +) -> String { + if let Some((_, name)) = arrays + .iter() + .find(|(existing, _)| existing.as_slice() == values) + { + return name.clone(); + } + let name = format!("{name_prefix}_{}", arrays.len()); + source.push_str(&emit_str_array(&name, values)); + arrays.push((values.to_vec(), name.clone())); + name +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn rust_option_str(value: Option<&str>) -> String { + value.map_or_else( + || "None".to_owned(), + |value| format!("Some({})", rust_str(value)), + ) +} + +fn verify_count(kind: &str, symbol: &str, expected: usize, actual: usize) -> Result<(), EmitError> { + if expected == actual { + Ok(()) + } else { + Err(EmitError::new(format!( + "{kind} @{symbol} count mismatch: expected {expected}, got {actual}" + ))) + } +} + +fn symbols<'a>(values: impl Iterator) -> BTreeSet { + values.cloned().collect() +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn int_array_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "integer array"))?; + parse_int_array(&attribute).ok_or_else(|| attr_error(operation, attr, "integer array")) +} + +fn parse_int_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().parse().ok()) + .collect() +} + +fn operand_symbols( + operation: OperationRef<'_, '_>, + start_index: usize, +) -> Result, EmitError> { + (start_index..operation.operand_count()) + .map(|index| operand_symbol(operation, index)) + .collect() +} + +fn operand_symbol(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + EmitError::new(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + EmitError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name<'c: 'a, 'a>(operation: impl OperationLike<'c, 'a>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} diff --git a/crates/bolt/src/protocols/jolt/emit/rust/stage5.rs b/crates/bolt/src/protocols/jolt/emit/rust/stage5.rs new file mode 100644 index 0000000000..30ab3d7edc --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/stage5.rs @@ -0,0 +1,2489 @@ +#![expect( + clippy::needless_raw_string_hashes, + reason = "generated Rust templates are kept as raw string blocks for copyable output" +)] + +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{Attribute, OperationRef}; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5CpuProgram { + pub role: Role, + pub params: Stage5Params, + pub steps: Vec, + pub transcript_squeezes: Vec, + pub transcript_absorb_bytes: Vec, + pub opening_inputs: Vec, + pub field_constants: Vec, + pub field_exprs: Vec, + pub kernels: Vec, + pub claims: Vec, + pub batches: Vec, + pub drivers: Vec, + pub instance_results: Vec, + pub evals: Vec, + pub point_slices: Vec, + pub point_concats: Vec, + pub opening_claims: Vec, + pub opening_equalities: Vec, + pub opening_batches: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5Params { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5KernelPlan { + pub symbol: String, + pub relation: String, + pub kind: String, + pub backend: String, + pub abi: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5TranscriptSqueezePlan { + pub symbol: String, + pub label: String, + pub kind: String, + pub count: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5TranscriptAbsorbBytesPlan { + pub symbol: String, + pub label: String, + pub payload: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5ProgramStepPlan { + pub kind: String, + pub symbol: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5OpeningInputPlan { + pub symbol: String, + pub source_stage: String, + pub source_claim: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5FieldConstantPlan { + pub symbol: String, + pub field: String, + pub value: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5FieldExprPlan { + pub symbol: String, + pub kind: String, + pub formula: String, + pub operand_names: Vec, + pub operands: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckClaimPlan { + pub symbol: String, + pub stage: String, + pub domain: String, + pub num_rounds: usize, + pub degree: usize, + pub claim: String, + pub kernel: Option, + pub relation: Option, + pub claim_value: String, + pub input_openings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, + pub claim_label: String, + pub round_label: String, + pub round_schedule: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckDriverPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub kernel: Option, + pub relation: Option, + pub batch: String, + pub policy: String, + pub round_schedule: Vec, + pub claim_label: String, + pub round_label: String, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckInstanceResultPlan { + pub symbol: String, + pub source: String, + pub claim: String, + pub relation: String, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: String, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckEvalPlan { + pub symbol: String, + pub source: String, + pub name: String, + pub index: usize, + pub oracle: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5PointSlicePlan { + pub symbol: String, + pub source: String, + pub offset: usize, + pub length: usize, + pub input: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5PointConcatPlan { + pub symbol: String, + pub layout: String, + pub arity: usize, + pub inputs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5OpeningClaimPlan { + pub symbol: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, + pub point_source: String, + pub eval_source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5OpeningClaimEqualityPlan { + pub symbol: String, + pub mode: String, + pub lhs: String, + pub rhs: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5OpeningBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, +} + +pub fn stage5_cpu_program(module: &BoltModule<'_, Cpu>) -> Result { + verify_cpu_schema(module)?; + let program = Stage5CpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +pub fn emit_stage5_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = stage5_cpu_program(module)?; + + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source(), + }) +} + +impl Stage5CpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let mut params = None; + let mut steps = Vec::new(); + let mut transcript_squeezes = Vec::new(); + let mut transcript_absorb_bytes = Vec::new(); + let mut opening_inputs = Vec::new(); + let mut field_constants = Vec::new(); + let mut field_exprs = Vec::new(); + let mut kernels = Vec::new(); + let mut claims = Vec::new(); + let mut batches = Vec::new(); + let mut drivers = Vec::new(); + let mut instance_results = Vec::new(); + let mut evals = Vec::new(); + let mut point_slices = Vec::new(); + let mut point_concats = Vec::new(); + let mut opening_claims = Vec::new(); + let mut opening_equalities = Vec::new(); + let mut opening_batches = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(Stage5Params { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.kernel" => { + kernels.push(Stage5KernelPlan { + symbol: string_attr(op, "sym_name")?, + relation: symbol_attr(op, "relation")?, + kind: string_attr(op, "kind")?, + backend: string_attr(op, "backend")?, + abi: string_attr(op, "abi")?, + }); + } + "cpu.transcript_squeeze" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage5ProgramStepPlan { + kind: "transcript_squeeze".to_owned(), + symbol: symbol.clone(), + }); + transcript_squeezes.push(Stage5TranscriptSqueezePlan { + symbol, + label: string_attr(op, "label")?, + kind: string_attr(op, "kind")?, + count: int_attr(op, "count")?, + }); + } + "cpu.transcript_absorb_bytes" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage5ProgramStepPlan { + kind: "transcript_absorb_bytes".to_owned(), + symbol: symbol.clone(), + }); + transcript_absorb_bytes.push(Stage5TranscriptAbsorbBytesPlan { + symbol, + label: string_attr(op, "label")?, + payload: string_attr(op, "payload")?, + }); + } + "cpu.opening_input" => { + opening_inputs.push(Stage5OpeningInputPlan { + symbol: string_attr(op, "sym_name")?, + source_stage: symbol_attr(op, "source_stage")?, + source_claim: symbol_attr(op, "source_claim")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + }); + } + "cpu.field_const" => { + field_constants.push(Stage5FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: int_attr(op, "value")?, + }); + } + "cpu.field_zero" => { + field_constants.push(Stage5FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 0, + }); + } + "cpu.field_one" => { + field_constants.push(Stage5FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 1, + }); + } + "cpu.field_add" | "cpu.field_sub" | "cpu.field_mul" | "cpu.field_neg" => { + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage5FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: operation_name(op).replace("cpu.field_", "field."), + operand_names: operands.clone(), + operands, + }); + } + "cpu.field_pow" => { + let exponent = int_attr(op, "exponent")?; + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage5FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: format!("field.pow:{exponent}"), + operand_names: operands.clone(), + operands, + }); + } + "cpu.sumcheck_claim" => { + claims.push(Stage5SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_verify_claim" => { + claims.push(Stage5SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_batch" => { + batches.push(Stage5SumcheckBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + round_schedule: int_array_attr(op, "round_schedule")?, + }); + } + "cpu.sumcheck_driver" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage5ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage5SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_verify" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage5ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage5SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_instance_result" => { + instance_results.push(Stage5SumcheckInstanceResultPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + claim: symbol_attr(op, "claim")?, + relation: symbol_attr(op, "relation")?, + index: int_attr(op, "index")?, + point_arity: int_attr(op, "point_arity")?, + num_rounds: int_attr(op, "num_rounds")?, + round_offset: int_attr(op, "round_offset")?, + point_order: string_attr(op, "point_order")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_eval" => { + evals.push(Stage5SumcheckEvalPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + name: symbol_attr(op, "name")?, + index: int_attr(op, "index")?, + oracle: symbol_attr(op, "oracle")?, + }); + } + "cpu.point_slice" => { + point_slices.push(Stage5PointSlicePlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + offset: int_attr(op, "offset")?, + length: int_attr(op, "length")?, + input: operand_symbol(op, 0)?, + }); + } + "cpu.point_concat" => { + point_concats.push(Stage5PointConcatPlan { + symbol: string_attr(op, "sym_name")?, + layout: string_attr(op, "layout")?, + arity: int_attr(op, "arity")?, + inputs: operand_symbols(op, 0)?, + }); + } + "cpu.opening_claim" => { + opening_claims.push(Stage5OpeningClaimPlan { + symbol: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + point_source: operand_symbol(op, 0)?, + eval_source: operand_symbol(op, 1)?, + }); + } + "cpu.opening_claim_equal" => { + opening_equalities.push(Stage5OpeningClaimEqualityPlan { + symbol: string_attr(op, "sym_name")?, + mode: string_attr(op, "mode")?, + lhs: operand_symbol(op, 0)?, + rhs: operand_symbol(op, 1)?, + }); + } + "cpu.opening_batch" => { + opening_batches.push(Stage5OpeningBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + }); + } + _ => {} + } + } + + Ok(Self { + params: params.ok_or_else(|| EmitError::new("missing cpu.params"))?, + role: module + .role() + .ok_or_else(|| EmitError::new("missing cpu party role"))?, + steps, + transcript_squeezes, + transcript_absorb_bytes, + opening_inputs, + field_constants, + field_exprs, + kernels, + claims, + batches, + drivers, + instance_results, + evals, + point_slices, + point_concats, + opening_claims, + opening_equalities, + opening_batches, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + require_supported_symbol("field", &self.params.field, "bn254_fr")?; + require_supported_symbol("pcs", &self.params.pcs, "dory")?; + require_supported_symbol("transcript", &self.params.transcript, "blake2b_transcript")?; + self.verify_transcript_steps()?; + self.verify_field_flow()?; + self.verify_claim_batches()?; + match self.role { + Role::Prover => { + self.verify_kernel_definitions()?; + self.verify_prover_driver_bindings()?; + } + Role::Verifier => self.verify_verifier_driver_bindings()?, + } + self.verify_opening_flow() + } + + fn verify_transcript_steps(&self) -> Result<(), EmitError> { + for squeeze in &self.transcript_squeezes { + if !matches!( + squeeze.kind.as_str(), + "challenge_scalar" | "challenge_vector" + ) { + return Err(EmitError::new(format!( + "stage5 transcript squeeze @{} has unsupported kind `{}`", + squeeze.symbol, squeeze.kind + ))); + } + if squeeze.count == 0 { + return Err(EmitError::new(format!( + "stage5 transcript squeeze @{} has zero count", + squeeze.symbol + ))); + } + } + for absorb in &self.transcript_absorb_bytes { + if absorb.label.is_empty() { + return Err(EmitError::new(format!( + "stage5 transcript byte absorb @{} has empty label", + absorb.symbol + ))); + } + } + Ok(()) + } + + fn verify_field_flow(&self) -> Result<(), EmitError> { + for constant in &self.field_constants { + require_supported_symbol("field constant field", &constant.field, "bn254_fr")?; + } + let field_values = self.field_value_symbols(); + for expr in &self.field_exprs { + verify_count( + "field expr operands", + &expr.symbol, + expr.operand_names.len(), + expr.operands.len(), + )?; + for operand in &expr.operands { + if !field_values.contains(operand) { + return Err(EmitError::new(format!( + "field expr @{} references missing field value @{operand}", + expr.symbol + ))); + } + } + } + for claim in &self.claims { + if !field_values.contains(&claim.claim_value) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing claim value @{}", + claim.symbol, claim.claim_value + ))); + } + } + Ok(()) + } + + fn field_value_symbols(&self) -> BTreeSet { + let mut values = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + values.extend(symbols( + self.field_constants.iter().map(|constant| &constant.symbol), + )); + values.extend(symbols( + self.transcript_squeezes + .iter() + .filter(|squeeze| matches!(squeeze.kind.as_str(), "challenge_scalar" | "scalar")) + .map(|squeeze| &squeeze.symbol), + )); + values.extend(symbols(self.field_exprs.iter().map(|expr| &expr.symbol))); + values.extend(symbols(self.evals.iter().map(|eval| &eval.symbol))); + values + } + + fn verify_kernel_definitions(&self) -> Result<(), EmitError> { + for kernel in &self.kernels { + if kernel.backend != "cpu" { + return Err(EmitError::new(format!( + "stage5 kernel @{} targets unsupported backend `{}`", + kernel.symbol, kernel.backend + ))); + } + if kernel.kind != "sumcheck" { + return Err(EmitError::new(format!( + "stage5 kernel @{} has unsupported kind `{}`", + kernel.symbol, kernel.kind + ))); + } + let expected_abi = match kernel.relation.as_str() { + "jolt.stage5.instruction_read_raf" => "jolt_stage5_instruction_read_raf", + "jolt.stage5.ram_ra_claim_reduction" => "jolt_stage5_ram_ra_claim_reduction", + "jolt.stage5.registers_val_evaluation" => "jolt_stage5_registers_val_evaluation", + "jolt.stage5.batched" => "jolt_stage5_batched", + _ => { + return Err(EmitError::new(format!( + "unsupported stage5 kernel relation @{}", + kernel.relation + ))); + } + }; + if kernel.abi != expected_abi { + return Err(EmitError::new(format!( + "stage5 kernel @{} ABI `{}` does not match relation @{}", + kernel.symbol, kernel.abi, kernel.relation + ))); + } + } + Ok(()) + } + + fn verify_claim_batches(&self) -> Result<(), EmitError> { + let claims = symbols(self.claims.iter().map(|claim| &claim.symbol)); + for batch in &self.batches { + verify_count( + "sumcheck batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "sumcheck batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "sumcheck batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !claims.contains(claim) { + return Err(EmitError::new(format!( + "sumcheck batch @{} references missing claim @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn verify_prover_driver_bindings(&self) -> Result<(), EmitError> { + let kernels = symbols(self.kernels.iter().map(|kernel| &kernel.symbol)); + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + let Some(kernel) = claim.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck claim @{} is missing kernel", + claim.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck claim @{} references missing kernel @{kernel}", + claim.symbol + ))); + } + } + for driver in &self.drivers { + let Some(kernel) = driver.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck driver @{} is missing kernel", + driver.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck driver @{} references missing kernel @{kernel}", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_verifier_driver_bindings(&self) -> Result<(), EmitError> { + if !self.kernels.is_empty() { + return Err(EmitError::new( + "verifier stage5 program must not contain kernels", + )); + } + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + if claim.kernel.is_some() || claim.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck claim @{} must carry relation and no kernel", + claim.symbol + ))); + } + } + for driver in &self.drivers { + if driver.kernel.is_some() || driver.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck driver @{} must carry relation and no kernel", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_opening_flow(&self) -> Result<(), EmitError> { + let mut point_sources = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + point_sources.extend(symbols( + self.instance_results + .iter() + .map(|instance| &instance.symbol), + )); + point_sources.extend(symbols( + self.opening_inputs.iter().map(|input| &input.symbol), + )); + point_sources.extend(symbols(self.point_slices.iter().map(|slice| &slice.symbol))); + point_sources.extend(symbols( + self.point_concats.iter().map(|concat| &concat.symbol), + )); + for slice in &self.point_slices { + if !point_sources.contains(&slice.input) { + return Err(EmitError::new(format!( + "point slice @{} uses missing point source @{}", + slice.symbol, slice.input + ))); + } + } + for concat in &self.point_concats { + for input in &concat.inputs { + if !point_sources.contains(input) { + return Err(EmitError::new(format!( + "point concat @{} uses missing point source @{input}", + concat.symbol + ))); + } + } + } + let eval_sources = self.field_value_symbols(); + let mut opening_sources = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + opening_sources.extend(symbols( + self.opening_claims.iter().map(|claim| &claim.symbol), + )); + for equality in &self.opening_equalities { + if !opening_sources.contains(&equality.lhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing lhs opening @{}", + equality.symbol, equality.lhs + ))); + } + if !opening_sources.contains(&equality.rhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing rhs opening @{}", + equality.symbol, equality.rhs + ))); + } + } + for claim in &self.claims { + for input in &claim.input_openings { + if !opening_sources.contains(input) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing opening @{input}", + claim.symbol + ))); + } + } + } + let drivers = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + for instance in &self.instance_results { + if !drivers.contains(&instance.source) { + return Err(EmitError::new(format!( + "sumcheck instance result @{} references missing driver @{}", + instance.symbol, instance.source + ))); + } + } + for eval in &self.evals { + if !drivers.contains(&eval.source) { + return Err(EmitError::new(format!( + "sumcheck eval @{} references missing driver @{}", + eval.symbol, eval.source + ))); + } + } + for claim in &self.opening_claims { + if !point_sources.contains(&claim.point_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing point source @{}", + claim.symbol, claim.point_source + ))); + } + if !eval_sources.contains(&claim.eval_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing eval source @{}", + claim.symbol, claim.eval_source + ))); + } + } + let openings = symbols(self.opening_claims.iter().map(|claim| &claim.symbol)); + for batch in &self.opening_batches { + verify_count( + "opening batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "opening batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "opening batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !openings.contains(claim) { + return Err(EmitError::new(format!( + "opening batch @{} references missing opening @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_stage5.rs", + Role::Verifier => "verify_stage5.rs", + } + } + + fn emit_source(&self) -> String { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + match self.role { + Role::Prover => { + source.push_str(Self::emit_prover_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_prover_types()); + } + Role::Verifier => { + source.push_str(Self::emit_verifier_imports()); + source.push_str("\n\n"); + source.push_str(&Self::emit_verifier_types()); + } + } + source.push('\n'); + source.push_str(&self.emit_constants()); + source.push('\n'); + source.push_str(self.emit_entrypoint()); + source + } + + fn emit_prover_imports() -> &'static str { + "use jolt_field::Fr;\n\ + use jolt_kernels::stage5::{execute_stage5_program, Stage5CpuProgramPlan, Stage5ExecutionArtifacts, Stage5ExecutionMode, Stage5FieldConstantPlan, Stage5FieldExprPlan, Stage5KernelError, Stage5KernelExecutor, Stage5KernelPlan, Stage5OpeningBatchPlan, Stage5OpeningClaimEqualityPlan, Stage5OpeningClaimPlan, Stage5OpeningInputPlan, Stage5Params, Stage5PointConcatPlan, Stage5PointSlicePlan, Stage5ProgramStepPlan, Stage5SumcheckBatchPlan, Stage5SumcheckClaimPlan, Stage5SumcheckDriverPlan, Stage5SumcheckEvalPlan, Stage5SumcheckInstanceResultPlan, Stage5TranscriptAbsorbBytesPlan, Stage5TranscriptSqueezePlan};\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_prover_types() -> &'static str { + "pub type DefaultStage5Transcript = Blake2bTranscript;\n" + } + + fn emit_verifier_imports() -> &'static str { + "use super::common::{batch_claims, eval_by_name, find_batch, find_plan, identity_polynomial_eval, indexed_evals_by_prefix, indexed_evals_by_prefix_any, lt_polynomial_eval, normalize_instruction_read_raf_point, operand_polynomial_eval, reverse_slice, suffix_point};\n\ + use jolt_field::{Field, Fr};\n\ + use jolt_lookup_tables::LookupTableKind;\n\ + use jolt_poly::EqPolynomial;\n\ + use jolt_sumcheck::SumcheckError;\n\ + use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript};" + } + + #[expect(dead_code)] + fn emit_types() -> &'static str { + r#"#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5TranscriptAbsorbBytesPlan { + pub symbol: &'static str, + pub label: &'static str, + pub payload: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operand_names: &'static [&'static str], + pub operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5OpeningClaimEqualityPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub lhs: &'static str, + pub rhs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage5CpuProgramPlan { + pub role: &'static str, + pub params: Stage5Params, + pub steps: &'static [Stage5ProgramStepPlan], + pub transcript_squeezes: &'static [Stage5TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [Stage5TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [Stage5OpeningInputPlan], + pub field_constants: &'static [Stage5FieldConstantPlan], + pub field_exprs: &'static [Stage5FieldExprPlan], + pub kernels: &'static [Stage5KernelPlan], + pub claims: &'static [Stage5SumcheckClaimPlan], + pub batches: &'static [Stage5SumcheckBatchPlan], + pub drivers: &'static [Stage5SumcheckDriverPlan], + pub instance_results: &'static [Stage5SumcheckInstanceResultPlan], + pub evals: &'static [Stage5SumcheckEvalPlan], + pub point_slices: &'static [Stage5PointSlicePlan], + pub point_concats: &'static [Stage5PointConcatPlan], + pub opening_claims: &'static [Stage5OpeningClaimPlan], + pub opening_equalities: &'static [Stage5OpeningClaimEqualityPlan], + pub opening_batches: &'static [Stage5OpeningBatchPlan], +} +"# + } + + fn emit_verifier_type_aliases() -> &'static str { + r#"pub type Stage5NamedEval = super::common::StageNamedEval; +pub type Stage5SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage5ChallengeVector = super::common::StageChallengeVector; +pub type Stage5ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage5Proof = super::common::StageProof; +pub type Stage5OpeningInputValue = super::common::StageOpeningInputValue; + +pub use super::common::{ + FieldConstantPlan as Stage5FieldConstantPlan, FieldExprPlan as Stage5FieldExprPlan, + KernelPlan as Stage5KernelPlan, OpeningBatchPlan as Stage5OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage5OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage5OpeningClaimPlan, OpeningInputPlan as Stage5OpeningInputPlan, + PointConcatPlan as Stage5PointConcatPlan, PointSlicePlan as Stage5PointSlicePlan, + ProgramStepPlan as Stage5ProgramStepPlan, StageParams as Stage5Params, + StageProgramPlanNoPointZeros as Stage5CpuProgramPlan, + SumcheckBatchPlan as Stage5SumcheckBatchPlan, + SumcheckClaimPlan as Stage5SumcheckClaimPlan, SumcheckDriverPlan as Stage5SumcheckDriverPlan, + SumcheckEvalPlan as Stage5SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage5SumcheckInstanceResultPlan, + TranscriptAbsorbBytesPlan as Stage5TranscriptAbsorbBytesPlan, + TranscriptSqueezePlan as Stage5TranscriptSqueezePlan, +}; +"# + } + + fn emit_verifier_types() -> String { + let mut source = Self::emit_verifier_type_aliases().to_owned(); + source.push_str( + r#" +pub type DefaultStage5Transcript = Blake2bTranscript; +pub type Stage5VerifierProgramPlan = Stage5CpuProgramPlan; + +#[derive(Debug)] +pub enum VerifyStage5Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage5Error); +"#, + ); + source + } + + fn emit_constants(&self) -> String { + let mut source = self.emit_shared_constants(); + source.push_str(&self.emit_kernel_constants()); + source.push_str(&self.emit_sumcheck_claim_constants()); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_sumcheck_driver_constants()); + source.push_str(&self.emit_tail_constants()); + push_format( + &mut source, + format_args!( + "pub const STAGE5_PROGRAM: {} = Stage5CpuProgramPlan {{\n\ + \x20 role: {},\n\ + \x20 params: STAGE5_PARAMS,\n\ + \x20 steps: STAGE5_PROGRAM_STEPS,\n\ + \x20 transcript_squeezes: STAGE5_TRANSCRIPT_SQUEEZES,\n\ + \x20 transcript_absorb_bytes: STAGE5_TRANSCRIPT_ABSORB_BYTES,\n\ + \x20 opening_inputs: STAGE5_OPENING_INPUTS,\n\ + \x20 field_constants: STAGE5_FIELD_CONSTANTS,\n\ + \x20 field_exprs: STAGE5_FIELD_EXPRS,\n\ + \x20 kernels: STAGE5_KERNELS,\n\ + \x20 claims: STAGE5_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE5_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE5_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE5_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE5_SUMCHECK_EVALS,\n\ + \x20 point_slices: STAGE5_POINT_SLICES,\n\ + \x20 point_concats: STAGE5_POINT_CONCATS,\n\ + \x20 opening_claims: STAGE5_OPENING_CLAIMS,\n\ + \x20 opening_equalities: STAGE5_OPENING_EQUALITIES,\n\ + \x20 opening_batches: STAGE5_OPENING_BATCHES,\n\ + }};\n", + self.program_plan_type(), + rust_str(self.role_label()) + ), + ); + source + } + + fn emit_shared_constants(&self) -> String { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const STAGE5_PARAMS: Stage5Params = Stage5Params {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + source.push_str(&self.emit_program_step_constants()); + source.push_str(&self.emit_transcript_squeeze_constants()); + source.push_str(&self.emit_transcript_absorb_bytes_constants()); + source.push_str(&self.emit_opening_input_constants()); + source.push_str(&self.emit_field_constant_constants()); + source.push_str(&self.emit_field_expr_constants()); + source + } + + fn emit_program_step_constants(&self) -> String { + let steps = self + .steps + .iter() + .map(|step| { + format!( + " Stage5ProgramStepPlan {{ kind: {}, symbol: {} }},", + rust_str(&step.kind), + rust_str(&step.symbol), + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE5_PROGRAM_STEPS: &[Stage5ProgramStepPlan] = &[\n{steps}\n];\n\n") + } + + fn emit_transcript_squeeze_constants(&self) -> String { + let squeezes = self + .transcript_squeezes + .iter() + .map(|squeeze| { + format!( + " Stage5TranscriptSqueezePlan {{ symbol: {}, label: {}, kind: {}, count: {} }},", + rust_str(&squeeze.symbol), + rust_str(&squeeze.label), + rust_str(&squeeze.kind), + squeeze.count, + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE5_TRANSCRIPT_SQUEEZES: &[Stage5TranscriptSqueezePlan] = &[\n{squeezes}\n];\n\n" + ) + } + + fn emit_transcript_absorb_bytes_constants(&self) -> String { + let absorbs = self + .transcript_absorb_bytes + .iter() + .map(|absorb| { + format!( + " Stage5TranscriptAbsorbBytesPlan {{ symbol: {}, label: {}, payload: {} }},", + rust_str(&absorb.symbol), + rust_str(&absorb.label), + rust_str(&absorb.payload), + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE5_TRANSCRIPT_ABSORB_BYTES: &[Stage5TranscriptAbsorbBytesPlan] = &[\n{absorbs}\n];\n\n" + ) + } + + fn emit_opening_input_constants(&self) -> String { + let inputs = self + .opening_inputs + .iter() + .map(|input| { + format!( + " Stage5OpeningInputPlan {{ symbol: {}, source_stage: {}, source_claim: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {} }},", + rust_str(&input.symbol), + rust_str(&input.source_stage), + rust_str(&input.source_claim), + rust_str(&input.oracle), + rust_str(&input.domain), + input.point_arity, + rust_str(&input.claim_kind) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE5_OPENING_INPUTS: &[Stage5OpeningInputPlan] = &[\n{inputs}\n];\n\n") + } + + fn emit_field_constant_constants(&self) -> String { + let constants = self + .field_constants + .iter() + .map(|constant| { + format!( + " Stage5FieldConstantPlan {{ symbol: {}, field: {}, value: {} }},", + rust_str(&constant.symbol), + rust_str(&constant.field), + constant.value + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE5_FIELD_CONSTANTS: &[Stage5FieldConstantPlan] = &[\n{constants}\n];\n\n" + ) + } + + fn emit_field_expr_constants(&self) -> String { + if self.role == Role::Verifier { + let exprs = self + .field_exprs + .iter() + .map(|expr| { + format!( + " Stage5FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operands: {} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula), + rust_str(&expr.operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE5_FIELD_EXPRS: &[Stage5FieldExprPlan] = &[\n{exprs}\n];\n" + ); + } + + let mut source = String::new(); + let mut arrays = Vec::new(); + let mut array_refs = Vec::new(); + for (index, expr) in self.field_exprs.iter().enumerate() { + let operands = intern_str_array( + &mut source, + &mut arrays, + "STAGE5_FIELD_EXPR_OPERANDS", + &expr.operands, + ); + let operand_names = intern_str_array( + &mut source, + &mut arrays, + "STAGE5_FIELD_EXPR_OPERANDS", + &expr.operand_names, + ); + array_refs.push((index, operand_names, operands)); + } + let exprs = self + .field_exprs + .iter() + .enumerate() + .map(|(index, expr)| { + let (_, operand_names, operands) = &array_refs[index]; + format!( + " Stage5FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operand_names: {operand_names}, operands: {operands} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE5_FIELD_EXPRS: &[Stage5FieldExprPlan] = &[\n{exprs}\n];\n" + ), + ); + source + } + + fn emit_kernel_constants(&self) -> String { + let kernels = self + .kernels + .iter() + .map(|kernel| { + format!( + " Stage5KernelPlan {{ symbol: {}, relation: {}, kind: {}, backend: {}, abi: {} }},", + rust_str(&kernel.symbol), + rust_str(&kernel.relation), + rust_str(&kernel.kind), + rust_str(&kernel.backend), + rust_str(&kernel.abi) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE5_KERNELS: &[Stage5KernelPlan] = &[\n{kernels}\n];\n\n") + } + + fn emit_sumcheck_claim_constants(&self) -> String { + if self.role == Role::Verifier { + let claims = self + .claims + .iter() + .map(|claim| { + format!( + " Stage5SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: {}, relation: {}, claim_value: {}, input_openings: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_option_str(claim.kernel.as_deref()), + rust_option_str(claim.relation.as_deref()), + rust_str(&claim.claim_value), + rust_str(&claim.input_openings.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE5_SUMCHECK_CLAIMS: &[Stage5SumcheckClaimPlan] = &[\n{claims}\n];\n" + ); + } + + let mut source = String::new(); + for (index, claim) in self.claims.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE5_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS"), + &claim.input_openings, + )); + } + let claims = self + .claims + .iter() + .enumerate() + .map(|(index, claim)| { + format!( + " Stage5SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: {}, relation: {}, claim_value: {}, input_openings: STAGE5_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_option_str(claim.kernel.as_deref()), + rust_option_str(claim.relation.as_deref()), + rust_str(&claim.claim_value) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE5_SUMCHECK_CLAIMS: &[Stage5SumcheckClaimPlan] = &[\n{claims}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE5_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage5SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {}, claim_label: {}, round_label: {}, round_schedule: STAGE5_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")), + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE5_SUMCHECK_BATCHES: &[Stage5SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + return source; + } + + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE5_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE5_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + source.push_str(&emit_usize_array( + &format!("STAGE5_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage5SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE5_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE5_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS, claim_label: {}, round_label: {}, round_schedule: STAGE5_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE5_SUMCHECK_BATCHES: &[Stage5SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_driver_constants(&self) -> String { + let mut source = String::new(); + for (index, driver) in self.drivers.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE5_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE"), + &driver.round_schedule, + )); + } + let drivers = self + .drivers + .iter() + .enumerate() + .map(|(index, driver)| { + format!( + " Stage5SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: {}, relation: {}, batch: {}, policy: {}, round_schedule: STAGE5_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_option_str(driver.kernel.as_deref()), + rust_option_str(driver.relation.as_deref()), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE5_SUMCHECK_DRIVERS: &[Stage5SumcheckDriverPlan] = &[\n{drivers}\n];\n" + ), + ); + source + } + + fn emit_tail_constants(&self) -> String { + let mut source = String::new(); + source.push_str(&self.emit_sumcheck_instance_result_constants()); + source.push_str(&self.emit_sumcheck_eval_constants()); + source.push_str(&self.emit_point_slice_constants()); + source.push_str(&self.emit_point_concat_constants()); + source.push_str(&self.emit_opening_claim_constants()); + source.push_str(&self.emit_opening_claim_equality_constants()); + source.push_str(&self.emit_opening_batch_constants()); + source + } + + fn emit_sumcheck_instance_result_constants(&self) -> String { + let instances = self + .instance_results + .iter() + .map(|instance| { + format!( + " Stage5SumcheckInstanceResultPlan {{ symbol: {}, source: {}, claim: {}, relation: {}, index: {}, point_arity: {}, num_rounds: {}, round_offset: {}, point_order: {}, degree: {} }},", + rust_str(&instance.symbol), + rust_str(&instance.source), + rust_str(&instance.claim), + rust_str(&instance.relation), + instance.index, + instance.point_arity, + instance.num_rounds, + instance.round_offset, + rust_str(&instance.point_order), + instance.degree + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE5_SUMCHECK_INSTANCE_RESULTS: &[Stage5SumcheckInstanceResultPlan] = &[\n{instances}\n];\n\n" + ) + } + + fn emit_sumcheck_eval_constants(&self) -> String { + let evals = self + .evals + .iter() + .map(|eval| { + format!( + " Stage5SumcheckEvalPlan {{ symbol: {}, source: {}, name: {}, index: {}, oracle: {} }},", + rust_str(&eval.symbol), + rust_str(&eval.source), + rust_str(&eval.name), + eval.index, + rust_str(&eval.oracle) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE5_SUMCHECK_EVALS: &[Stage5SumcheckEvalPlan] = &[\n{evals}\n];\n\n") + } + + fn emit_point_slice_constants(&self) -> String { + let slices = self + .point_slices + .iter() + .map(|slice| { + format!( + " Stage5PointSlicePlan {{ symbol: {}, source: {}, offset: {}, length: {}, input: {} }},", + rust_str(&slice.symbol), + rust_str(&slice.source), + slice.offset, + slice.length, + rust_str(&slice.input) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE5_POINT_SLICES: &[Stage5PointSlicePlan] = &[\n{slices}\n];\n\n") + } + + fn emit_point_concat_constants(&self) -> String { + if self.role == Role::Verifier { + let concats = self + .point_concats + .iter() + .map(|concat| { + format!( + " Stage5PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: {} }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity, + rust_str(&concat.inputs.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE5_POINT_CONCATS: &[Stage5PointConcatPlan] = &[\n{concats}\n];\n" + ); + } + + let mut source = String::new(); + for (index, concat) in self.point_concats.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE5_POINT_CONCAT_{index}_INPUTS"), + &concat.inputs, + )); + } + let concats = self + .point_concats + .iter() + .enumerate() + .map(|(index, concat)| { + format!( + " Stage5PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: STAGE5_POINT_CONCAT_{index}_INPUTS }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE5_POINT_CONCATS: &[Stage5PointConcatPlan] = &[\n{concats}\n];\n" + ), + ); + source + } + + fn emit_opening_claim_constants(&self) -> String { + let claims = self + .opening_claims + .iter() + .map(|claim| { + format!( + " Stage5OpeningClaimPlan {{ symbol: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {}, point_source: {}, eval_source: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.oracle), + rust_str(&claim.domain), + claim.point_arity, + rust_str(&claim.claim_kind), + rust_str(&claim.point_source), + rust_str(&claim.eval_source) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE5_OPENING_CLAIMS: &[Stage5OpeningClaimPlan] = &[\n{claims}\n];\n\n") + } + + fn emit_opening_claim_equality_constants(&self) -> String { + let equalities = self + .opening_equalities + .iter() + .map(|equality| { + format!( + " Stage5OpeningClaimEqualityPlan {{ symbol: {}, mode: {}, lhs: {}, rhs: {} }},", + rust_str(&equality.symbol), + rust_str(&equality.mode), + rust_str(&equality.lhs), + rust_str(&equality.rhs) + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE5_OPENING_EQUALITIES: &[Stage5OpeningClaimEqualityPlan] = &[\n{equalities}\n];\n\n" + ) + } + + fn emit_opening_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let batches = self + .opening_batches + .iter() + .map(|batch| { + format!( + " Stage5OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {} }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE5_OPENING_BATCHES: &[Stage5OpeningBatchPlan] = &[\n{batches}\n];\n" + ); + } + + let mut source = String::new(); + for (index, batch) in self.opening_batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE5_OPENING_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE5_OPENING_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + } + let batches = self + .opening_batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage5OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE5_OPENING_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE5_OPENING_BATCH_{index}_CLAIM_OPERANDS }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE5_OPENING_BATCHES: &[Stage5OpeningBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_entrypoint(&self) -> &'static str { + match self.role { + Role::Prover => { + "pub fn execute_stage5_prover(\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage5KernelError>\n\ + where\n\ + \x20 E: Stage5KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage5_prover_with_program(&STAGE5_PROGRAM, executor, transcript)\n\ + }\n\ + \n\ + pub fn execute_stage5_prover_with_program(\n\ + \x20 program: &'static Stage5CpuProgramPlan,\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage5KernelError>\n\ + where\n\ + \x20 E: Stage5KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage5_program(program, Stage5ExecutionMode::Prover, executor, transcript)\n\ + }\n" + } + Role::Verifier => { + r#"pub fn verify_stage5( + proof: &Stage5Proof, + opening_inputs: &[Stage5OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage5Error> +where + T: Transcript, +{ + verify_stage5_with_program(&STAGE5_PROGRAM, proof, opening_inputs, transcript) +} + +pub fn verify_stage5_with_program( + program: &'static Stage5VerifierProgramPlan, + proof: &Stage5Proof, + opening_inputs: &[Stage5OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage5Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage5Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + let mut artifacts = Stage5ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage5Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage5_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "transcript_absorb_bytes" => { + let absorb = find_plan(program.transcript_absorb_bytes, step.symbol).ok_or( + VerifyStage5Error::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage5_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage5Error::MissingProof { + driver: step.symbol, + })?; + verify_stage5_driver(program, driver, proof, &mut store, transcript, &mut artifacts)?; + } + _ => { + return Err(VerifyStage5Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage5 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage5_verifier_program() -> &'static Stage5VerifierProgramPlan { + &STAGE5_PROGRAM +} + +fn verify_stage5_squeeze( + program: &'static Stage5VerifierProgramPlan, + squeeze: &'static Stage5TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage5ExecutionArtifacts, +) -> Result<(), VerifyStage5Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage5Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage5Error::from)?; + artifacts.challenge_vectors.push(Stage5ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn absorb_stage5_bytes(absorb: &'static Stage5TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn verify_stage5_driver( + program: &'static Stage5VerifierProgramPlan, + driver: &'static Stage5SumcheckDriverPlan, + proof: &Stage5Proof, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage5ExecutionArtifacts, +) -> Result<(), VerifyStage5Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage5Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage5.batched" => { + verify_batched_stage5(program, driver, proof, store, transcript)? + } + _ => return Err(VerifyStage5Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage5( + program: &'static Stage5VerifierProgramPlan, + driver: &'static Stage5SumcheckDriverPlan, + proof: &Stage5SumcheckOutput, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage5Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim(program, driver, store, evals, point, batching_coeffs) + }, + |store, verified| observe_stage5_sumcheck_output(program, store, verified), + |driver, error| VerifyStage5Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage5_sumcheck_output( + program: &'static Stage5VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage5SumcheckOutput, +) -> Result<(), VerifyStage5Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "instruction_read_raf" => { + point = normalize_instruction_read_raf_point(&point, "stage5.instruction_read_raf.point")?; + } + _ => { + return Err(VerifyStage5Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage5Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage5Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage5Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage5Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage5Error::InvalidProof { driver, reason }, + |symbol| VerifyStage5Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage5VerifierProgramPlan, + driver: &'static Stage5SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage5NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage5Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage5Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(""); + let value = match relation { + "jolt.stage5.instruction_read_raf" => { + expected_instruction_read_raf(store, evals, local_point)? + } + "jolt.stage5.ram_ra_claim_reduction" => { + expected_ram_ra_claim_reduction(store, evals, local_point)? + } + "jolt.stage5.registers_val_evaluation" => { + expected_registers_val_evaluation(store, evals, local_point)? + } + _ => return Err(VerifyStage5Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_instruction_read_raf( + store: &super::common::ValueStore, + evals: &[Stage5NamedEval], + local_point: &[Fr], +) -> Result { + const LOG_K: usize = 128; + const XLEN: usize = 64; + + if local_point.len() < LOG_K { + return Err(VerifyStage5Error::InvalidInputLength { + input: "stage5.instruction_read_raf.point", + expected: LOG_K, + actual: local_point.len(), + }); + } + + let (r_address_prime, r_cycle) = local_point.split_at(LOG_K); + let r_cycle_prime = reverse_slice(r_cycle); + let r_reduction = super::common::store_point(store, "stage5.input.stage2.instruction.LookupOutput")?; + let eq_eval_r_reduction = EqPolynomial::::mle(r_reduction, &r_cycle_prime); + + let left_operand_eval = operand_polynomial_eval(r_address_prime, true); + let right_operand_eval = operand_polynomial_eval(r_address_prime, false); + let identity_poly_eval = identity_polynomial_eval(r_address_prime); + + let table_values = LookupTableKind::::all() + .iter() + .map(|table| table.evaluate_mle::(r_address_prime)) + .collect::>(); + let table_flag_claims = indexed_evals_by_prefix( + evals, + "stage5.instruction_read_raf.eval.LookupTableFlag_", + table_values.len(), + )?; + let val_claim = table_values + .into_iter() + .zip(table_flag_claims) + .map(|(table_value, flag_claim)| table_value * flag_claim) + .sum::(); + + let ra_claim = indexed_evals_by_prefix_any( + evals, + "stage5.instruction_read_raf.eval.InstructionRa_", + )? + .into_iter() + .product::(); + let raf_flag_claim = eval_by_name( + evals, + "stage5.instruction_read_raf.eval.InstructionRafFlag", + )?; + let gamma = super::common::store_scalar(store, "stage5.instruction_read_raf.gamma")?; + + let raf_claim = (Fr::from_u64(1) - raf_flag_claim) + * (left_operand_eval + gamma * right_operand_eval) + + raf_flag_claim * gamma * identity_poly_eval; + Ok(eq_eval_r_reduction * ra_claim * (val_claim + gamma * raf_claim)) +} + +fn expected_ram_ra_claim_reduction( + store: &super::common::ValueStore, + evals: &[Stage5NamedEval], + local_point: &[Fr], +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle_raf = suffix_point( + super::common::store_point(store, "stage5.input.stage2.ram_raf.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage2.ram_raf.RamRa", + )?; + let r_cycle_rw = suffix_point( + super::common::store_point(store, "stage5.input.stage2.ram_read_write.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage2.ram_read_write.RamRa", + )?; + let r_cycle_val = suffix_point( + super::common::store_point(store, "stage5.input.stage4.ram_val_check.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage4.ram_val_check.RamRa", + )?; + let gamma = super::common::store_scalar(store, "stage5.ram_ra_claim_reduction.gamma")?; + let eq_combined = EqPolynomial::::mle(r_cycle_raf, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(r_cycle_rw, &r_cycle_reduced) + + gamma.square() * EqPolynomial::::mle(r_cycle_val, &r_cycle_reduced); + let ram_ra = eval_by_name(evals, "stage5.ram_ra_claim_reduction.eval.RamRa")?; + Ok(eq_combined * ram_ra) +} + +fn expected_registers_val_evaluation( + store: &super::common::ValueStore, + evals: &[Stage5NamedEval], + local_point: &[Fr], +) -> Result { + let registers_val_point = super::common::store_point(store, "stage5.input.stage4.registers.RegistersVal")?; + let r_cycle = suffix_point( + registers_val_point, + local_point.len(), + "stage5.input.stage4.registers.RegistersVal", + )?; + let r_reduced = reverse_slice(local_point); + let lt_eval = lt_polynomial_eval(&r_reduced, r_cycle); + let rd_inc = eval_by_name(evals, "stage5.registers_val_evaluation.eval.RdInc")?; + let rd_wa = eval_by_name(evals, "stage5.registers_val_evaluation.eval.RdWa")?; + Ok(rd_inc * rd_wa * lt_eval) +} + +"# + } + } + } + + fn role_label(&self) -> &'static str { + match self.role { + Role::Prover => "prover", + Role::Verifier => "verifier", + } + } + + fn program_plan_type(&self) -> &'static str { + match self.role { + Role::Prover => "Stage5CpuProgramPlan", + Role::Verifier => "Stage5VerifierProgramPlan", + } + } +} + +fn require_supported_symbol(kind: &str, actual: &str, expected: &str) -> Result<(), EmitError> { + if actual == expected { + Ok(()) + } else { + Err(EmitError::new(format!( + "unsupported {kind} @{actual}; expected @{expected}" + ))) + } +} + +fn emit_str_array(name: &str, values: &[String]) -> String { + if values.is_empty() { + return format!("pub const {name}: &[&str] = &[];\n\n"); + } + if let [value] = values { + return format!("pub const {name}: &[&str] = &[{}];\n\n", rust_str(value)); + } + let entries = values + .iter() + .map(|value| format!(" {},", rust_str(value))) + .collect::>() + .join("\n"); + format!("pub const {name}: &[&str] = &[\n{entries}\n];\n\n") +} + +fn emit_usize_array(name: &str, values: &[usize]) -> String { + let entries = values + .iter() + .map(|value| format!(" {value},")) + .collect::>() + .join("\n"); + format!("pub const {name}: &[usize] = &[\n{entries}\n];\n\n") +} + +fn intern_str_array( + source: &mut String, + arrays: &mut Vec<(Vec, String)>, + name_prefix: &str, + values: &[String], +) -> String { + if let Some((_, name)) = arrays + .iter() + .find(|(existing, _)| existing.as_slice() == values) + { + return name.clone(); + } + let name = format!("{name_prefix}_{}", arrays.len()); + source.push_str(&emit_str_array(&name, values)); + arrays.push((values.to_vec(), name.clone())); + name +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn rust_option_str(value: Option<&str>) -> String { + value.map_or_else( + || "None".to_owned(), + |value| format!("Some({})", rust_str(value)), + ) +} + +fn verify_count(kind: &str, symbol: &str, expected: usize, actual: usize) -> Result<(), EmitError> { + if expected == actual { + Ok(()) + } else { + Err(EmitError::new(format!( + "{kind} @{symbol} count mismatch: expected {expected}, got {actual}" + ))) + } +} + +fn symbols<'a>(values: impl Iterator) -> BTreeSet { + values.cloned().collect() +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn int_array_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "integer array"))?; + parse_int_array(&attribute).ok_or_else(|| attr_error(operation, attr, "integer array")) +} + +fn parse_int_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().parse().ok()) + .collect() +} + +fn operand_symbols( + operation: OperationRef<'_, '_>, + start_index: usize, +) -> Result, EmitError> { + (start_index..operation.operand_count()) + .map(|index| operand_symbol(operation, index)) + .collect() +} + +fn operand_symbol(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + EmitError::new(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + EmitError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name<'c: 'a, 'a>(operation: impl OperationLike<'c, 'a>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} diff --git a/crates/bolt/src/protocols/jolt/emit/rust/stage6.rs b/crates/bolt/src/protocols/jolt/emit/rust/stage6.rs new file mode 100644 index 0000000000..800eed9731 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/stage6.rs @@ -0,0 +1,2655 @@ +#![expect( + clippy::needless_raw_string_hashes, + reason = "generated Rust templates are kept as raw string blocks for copyable output" +)] + +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{Attribute, OperationRef}; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6CpuProgram { + pub role: Role, + pub params: Stage6Params, + pub steps: Vec, + pub transcript_squeezes: Vec, + pub transcript_absorb_bytes: Vec, + pub opening_inputs: Vec, + pub field_constants: Vec, + pub field_exprs: Vec, + pub kernels: Vec, + pub claims: Vec, + pub batches: Vec, + pub drivers: Vec, + pub instance_results: Vec, + pub evals: Vec, + pub point_zeros: Vec, + pub point_slices: Vec, + pub point_concats: Vec, + pub opening_claims: Vec, + pub opening_equalities: Vec, + pub opening_batches: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6Params { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6KernelPlan { + pub symbol: String, + pub relation: String, + pub kind: String, + pub backend: String, + pub abi: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6TranscriptSqueezePlan { + pub symbol: String, + pub label: String, + pub kind: String, + pub count: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6TranscriptAbsorbBytesPlan { + pub symbol: String, + pub label: String, + pub payload: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6ProgramStepPlan { + pub kind: String, + pub symbol: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6OpeningInputPlan { + pub symbol: String, + pub source_stage: String, + pub source_claim: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6FieldConstantPlan { + pub symbol: String, + pub field: String, + pub value: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6FieldExprPlan { + pub symbol: String, + pub kind: String, + pub formula: String, + pub operand_names: Vec, + pub operands: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckClaimPlan { + pub symbol: String, + pub stage: String, + pub domain: String, + pub num_rounds: usize, + pub degree: usize, + pub claim: String, + pub kernel: Option, + pub relation: Option, + pub claim_value: String, + pub input_openings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, + pub claim_label: String, + pub round_label: String, + pub round_schedule: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckDriverPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub kernel: Option, + pub relation: Option, + pub batch: String, + pub policy: String, + pub round_schedule: Vec, + pub claim_label: String, + pub round_label: String, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckInstanceResultPlan { + pub symbol: String, + pub source: String, + pub claim: String, + pub relation: String, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: String, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckEvalPlan { + pub symbol: String, + pub source: String, + pub name: String, + pub index: usize, + pub oracle: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6PointZeroPlan { + pub symbol: String, + pub field: String, + pub arity: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6PointSlicePlan { + pub symbol: String, + pub source: String, + pub offset: usize, + pub length: usize, + pub input: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6PointConcatPlan { + pub symbol: String, + pub layout: String, + pub arity: usize, + pub inputs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6OpeningClaimPlan { + pub symbol: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, + pub point_source: String, + pub eval_source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6OpeningClaimEqualityPlan { + pub symbol: String, + pub mode: String, + pub lhs: String, + pub rhs: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage6OpeningBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, +} + +pub fn stage6_cpu_program(module: &BoltModule<'_, Cpu>) -> Result { + verify_cpu_schema(module)?; + let program = Stage6CpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +pub fn emit_stage6_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = stage6_cpu_program(module)?; + + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source(), + }) +} + +impl Stage6CpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let mut params = None; + let mut steps = Vec::new(); + let mut transcript_squeezes = Vec::new(); + let mut transcript_absorb_bytes = Vec::new(); + let mut opening_inputs = Vec::new(); + let mut field_constants = Vec::new(); + let mut field_exprs = Vec::new(); + let mut kernels = Vec::new(); + let mut claims = Vec::new(); + let mut batches = Vec::new(); + let mut drivers = Vec::new(); + let mut instance_results = Vec::new(); + let mut evals = Vec::new(); + let mut point_zeros = Vec::new(); + let mut point_slices = Vec::new(); + let mut point_concats = Vec::new(); + let mut opening_claims = Vec::new(); + let mut opening_equalities = Vec::new(); + let mut opening_batches = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(Stage6Params { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.kernel" => { + kernels.push(Stage6KernelPlan { + symbol: string_attr(op, "sym_name")?, + relation: symbol_attr(op, "relation")?, + kind: string_attr(op, "kind")?, + backend: string_attr(op, "backend")?, + abi: string_attr(op, "abi")?, + }); + } + "cpu.transcript_squeeze" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage6ProgramStepPlan { + kind: "transcript_squeeze".to_owned(), + symbol: symbol.clone(), + }); + transcript_squeezes.push(Stage6TranscriptSqueezePlan { + symbol, + label: string_attr(op, "label")?, + kind: string_attr(op, "kind")?, + count: int_attr(op, "count")?, + }); + } + "cpu.transcript_absorb_bytes" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage6ProgramStepPlan { + kind: "transcript_absorb_bytes".to_owned(), + symbol: symbol.clone(), + }); + transcript_absorb_bytes.push(Stage6TranscriptAbsorbBytesPlan { + symbol, + label: string_attr(op, "label")?, + payload: string_attr(op, "payload")?, + }); + } + "cpu.opening_input" => { + opening_inputs.push(Stage6OpeningInputPlan { + symbol: string_attr(op, "sym_name")?, + source_stage: symbol_attr(op, "source_stage")?, + source_claim: symbol_attr(op, "source_claim")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + }); + } + "cpu.field_const" => { + field_constants.push(Stage6FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: int_attr(op, "value")?, + }); + } + "cpu.field_zero" => { + field_constants.push(Stage6FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 0, + }); + } + "cpu.field_one" => { + field_constants.push(Stage6FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 1, + }); + } + "cpu.field_add" | "cpu.field_sub" | "cpu.field_mul" | "cpu.field_neg" => { + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage6FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: operation_name(op).replace("cpu.field_", "field."), + operand_names: operands.clone(), + operands, + }); + } + "cpu.field_pow" => { + let exponent = int_attr(op, "exponent")?; + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage6FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: format!("field.pow:{exponent}"), + operand_names: operands.clone(), + operands, + }); + } + "cpu.sumcheck_claim" => { + claims.push(Stage6SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_verify_claim" => { + claims.push(Stage6SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_batch" => { + batches.push(Stage6SumcheckBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + round_schedule: int_array_attr(op, "round_schedule")?, + }); + } + "cpu.sumcheck_driver" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage6ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage6SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_verify" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage6ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage6SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_instance_result" => { + instance_results.push(Stage6SumcheckInstanceResultPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + claim: symbol_attr(op, "claim")?, + relation: symbol_attr(op, "relation")?, + index: int_attr(op, "index")?, + point_arity: int_attr(op, "point_arity")?, + num_rounds: int_attr(op, "num_rounds")?, + round_offset: int_attr(op, "round_offset")?, + point_order: string_attr(op, "point_order")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_eval" => { + evals.push(Stage6SumcheckEvalPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + name: symbol_attr(op, "name")?, + index: int_attr(op, "index")?, + oracle: symbol_attr(op, "oracle")?, + }); + } + "cpu.point_zero" => { + point_zeros.push(Stage6PointZeroPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + arity: int_attr(op, "arity")?, + }); + } + "cpu.point_slice" => { + point_slices.push(Stage6PointSlicePlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + offset: int_attr(op, "offset")?, + length: int_attr(op, "length")?, + input: operand_symbol(op, 0)?, + }); + } + "cpu.point_concat" => { + point_concats.push(Stage6PointConcatPlan { + symbol: string_attr(op, "sym_name")?, + layout: string_attr(op, "layout")?, + arity: int_attr(op, "arity")?, + inputs: operand_symbols(op, 0)?, + }); + } + "cpu.opening_claim" => { + opening_claims.push(Stage6OpeningClaimPlan { + symbol: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + point_source: operand_symbol(op, 0)?, + eval_source: operand_symbol(op, 1)?, + }); + } + "cpu.opening_claim_equal" => { + opening_equalities.push(Stage6OpeningClaimEqualityPlan { + symbol: string_attr(op, "sym_name")?, + mode: string_attr(op, "mode")?, + lhs: operand_symbol(op, 0)?, + rhs: operand_symbol(op, 1)?, + }); + } + "cpu.opening_batch" => { + opening_batches.push(Stage6OpeningBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + }); + } + _ => {} + } + } + + Ok(Self { + params: params.ok_or_else(|| EmitError::new("missing cpu.params"))?, + role: module + .role() + .ok_or_else(|| EmitError::new("missing cpu party role"))?, + steps, + transcript_squeezes, + transcript_absorb_bytes, + opening_inputs, + field_constants, + field_exprs, + kernels, + claims, + batches, + drivers, + instance_results, + evals, + point_zeros, + point_slices, + point_concats, + opening_claims, + opening_equalities, + opening_batches, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + require_supported_symbol("field", &self.params.field, "bn254_fr")?; + require_supported_symbol("pcs", &self.params.pcs, "dory")?; + require_supported_symbol("transcript", &self.params.transcript, "blake2b_transcript")?; + self.verify_transcript_steps()?; + self.verify_field_flow()?; + self.verify_claim_batches()?; + match self.role { + Role::Prover => { + self.verify_kernel_definitions()?; + self.verify_prover_driver_bindings()?; + } + Role::Verifier => self.verify_verifier_driver_bindings()?, + } + self.verify_opening_flow() + } + + fn verify_transcript_steps(&self) -> Result<(), EmitError> { + for squeeze in &self.transcript_squeezes { + if !matches!( + squeeze.kind.as_str(), + "challenge_scalar" | "challenge_vector" + ) { + return Err(EmitError::new(format!( + "stage6 transcript squeeze @{} has unsupported kind `{}`", + squeeze.symbol, squeeze.kind + ))); + } + if squeeze.count == 0 { + return Err(EmitError::new(format!( + "stage6 transcript squeeze @{} has zero count", + squeeze.symbol + ))); + } + } + for absorb in &self.transcript_absorb_bytes { + if absorb.label.is_empty() { + return Err(EmitError::new(format!( + "stage6 transcript byte absorb @{} has empty label", + absorb.symbol + ))); + } + } + Ok(()) + } + + fn verify_field_flow(&self) -> Result<(), EmitError> { + for constant in &self.field_constants { + require_supported_symbol("field constant field", &constant.field, "bn254_fr")?; + } + let field_values = self.field_value_symbols(); + for expr in &self.field_exprs { + verify_count( + "field expr operands", + &expr.symbol, + expr.operand_names.len(), + expr.operands.len(), + )?; + for operand in &expr.operands { + if !field_values.contains(operand) { + return Err(EmitError::new(format!( + "field expr @{} references missing field value @{operand}", + expr.symbol + ))); + } + } + } + for claim in &self.claims { + if !field_values.contains(&claim.claim_value) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing claim value @{}", + claim.symbol, claim.claim_value + ))); + } + } + Ok(()) + } + + fn field_value_symbols(&self) -> BTreeSet { + let mut values = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + values.extend(symbols( + self.field_constants.iter().map(|constant| &constant.symbol), + )); + values.extend(symbols( + self.transcript_squeezes + .iter() + .filter(|squeeze| matches!(squeeze.kind.as_str(), "challenge_scalar" | "scalar")) + .map(|squeeze| &squeeze.symbol), + )); + values.extend(symbols(self.field_exprs.iter().map(|expr| &expr.symbol))); + values.extend(symbols(self.evals.iter().map(|eval| &eval.symbol))); + values + } + + fn verify_kernel_definitions(&self) -> Result<(), EmitError> { + for kernel in &self.kernels { + if kernel.backend != "cpu" { + return Err(EmitError::new(format!( + "stage6 kernel @{} targets unsupported backend `{}`", + kernel.symbol, kernel.backend + ))); + } + if kernel.kind != "sumcheck" { + return Err(EmitError::new(format!( + "stage6 kernel @{} has unsupported kind `{}`", + kernel.symbol, kernel.kind + ))); + } + let expected_abi = match kernel.relation.as_str() { + "jolt.stage6.bytecode_read_raf" => "jolt_stage6_bytecode_read_raf", + "jolt.stage6.booleanity" => "jolt_stage6_booleanity", + "jolt.stage6.hamming_booleanity" => "jolt_stage6_hamming_booleanity", + "jolt.stage6.ram_ra_virtual" => "jolt_stage6_ram_ra_virtual", + "jolt.stage6.instruction_ra_virtual" => "jolt_stage6_instruction_ra_virtual", + "jolt.stage6.inc_claim_reduction" => "jolt_stage6_inc_claim_reduction", + "jolt.stage6.batched" => "jolt_stage6_batched", + _ => { + return Err(EmitError::new(format!( + "unsupported stage6 kernel relation @{}", + kernel.relation + ))); + } + }; + if kernel.abi != expected_abi { + return Err(EmitError::new(format!( + "stage6 kernel @{} ABI `{}` does not match relation @{}", + kernel.symbol, kernel.abi, kernel.relation + ))); + } + } + Ok(()) + } + + fn verify_claim_batches(&self) -> Result<(), EmitError> { + let claims = symbols(self.claims.iter().map(|claim| &claim.symbol)); + for batch in &self.batches { + verify_count( + "sumcheck batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "sumcheck batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "sumcheck batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !claims.contains(claim) { + return Err(EmitError::new(format!( + "sumcheck batch @{} references missing claim @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn verify_prover_driver_bindings(&self) -> Result<(), EmitError> { + let kernels = symbols(self.kernels.iter().map(|kernel| &kernel.symbol)); + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + let Some(kernel) = claim.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck claim @{} is missing kernel", + claim.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck claim @{} references missing kernel @{kernel}", + claim.symbol + ))); + } + } + for driver in &self.drivers { + let Some(kernel) = driver.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck driver @{} is missing kernel", + driver.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck driver @{} references missing kernel @{kernel}", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_verifier_driver_bindings(&self) -> Result<(), EmitError> { + if !self.kernels.is_empty() { + return Err(EmitError::new( + "verifier stage6 program must not contain kernels", + )); + } + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + if claim.kernel.is_some() || claim.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck claim @{} must carry relation and no kernel", + claim.symbol + ))); + } + } + for driver in &self.drivers { + if driver.kernel.is_some() || driver.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck driver @{} must carry relation and no kernel", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_opening_flow(&self) -> Result<(), EmitError> { + let mut point_sources = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + point_sources.extend(symbols( + self.instance_results + .iter() + .map(|instance| &instance.symbol), + )); + point_sources.extend(symbols( + self.opening_inputs.iter().map(|input| &input.symbol), + )); + point_sources.extend(symbols(self.point_zeros.iter().map(|zero| &zero.symbol))); + point_sources.extend(symbols(self.point_slices.iter().map(|slice| &slice.symbol))); + point_sources.extend(symbols( + self.point_concats.iter().map(|concat| &concat.symbol), + )); + for zero in &self.point_zeros { + require_supported_symbol("point zero field", &zero.field, "bn254_fr")?; + } + for slice in &self.point_slices { + if !point_sources.contains(&slice.input) { + return Err(EmitError::new(format!( + "point slice @{} uses missing point source @{}", + slice.symbol, slice.input + ))); + } + } + for concat in &self.point_concats { + for input in &concat.inputs { + if !point_sources.contains(input) { + return Err(EmitError::new(format!( + "point concat @{} uses missing point source @{input}", + concat.symbol + ))); + } + } + } + let eval_sources = self.field_value_symbols(); + let mut opening_sources = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + opening_sources.extend(symbols( + self.opening_claims.iter().map(|claim| &claim.symbol), + )); + for equality in &self.opening_equalities { + if !opening_sources.contains(&equality.lhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing lhs opening @{}", + equality.symbol, equality.lhs + ))); + } + if !opening_sources.contains(&equality.rhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing rhs opening @{}", + equality.symbol, equality.rhs + ))); + } + } + for claim in &self.claims { + for input in &claim.input_openings { + if !opening_sources.contains(input) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing opening @{input}", + claim.symbol + ))); + } + } + } + let drivers = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + for instance in &self.instance_results { + if !drivers.contains(&instance.source) { + return Err(EmitError::new(format!( + "sumcheck instance result @{} references missing driver @{}", + instance.symbol, instance.source + ))); + } + } + for eval in &self.evals { + if !drivers.contains(&eval.source) { + return Err(EmitError::new(format!( + "sumcheck eval @{} references missing driver @{}", + eval.symbol, eval.source + ))); + } + } + for claim in &self.opening_claims { + if !point_sources.contains(&claim.point_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing point source @{}", + claim.symbol, claim.point_source + ))); + } + if !eval_sources.contains(&claim.eval_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing eval source @{}", + claim.symbol, claim.eval_source + ))); + } + } + let openings = symbols(self.opening_claims.iter().map(|claim| &claim.symbol)); + for batch in &self.opening_batches { + verify_count( + "opening batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "opening batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "opening batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !openings.contains(claim) { + return Err(EmitError::new(format!( + "opening batch @{} references missing opening @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_stage6.rs", + Role::Verifier => "verify_stage6.rs", + } + } + + fn emit_source(&self) -> String { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + match self.role { + Role::Prover => { + source.push_str(Self::emit_prover_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_prover_types()); + } + Role::Verifier => { + source.push_str(Self::emit_verifier_imports()); + source.push_str("\n\n"); + source.push_str(&Self::emit_verifier_types()); + } + } + source.push('\n'); + source.push_str(&self.emit_constants()); + source.push('\n'); + source.push_str(self.emit_entrypoint()); + source + } + + fn emit_prover_imports() -> &'static str { + "use jolt_field::Fr;\n\ + use jolt_kernels::stage6::{execute_stage6_program, Stage6CpuProgramPlan, Stage6ExecutionArtifacts, Stage6ExecutionMode, Stage6FieldConstantPlan, Stage6FieldExprPlan, Stage6KernelError, Stage6KernelExecutor, Stage6KernelPlan, Stage6OpeningBatchPlan, Stage6OpeningClaimEqualityPlan, Stage6OpeningClaimPlan, Stage6OpeningInputPlan, Stage6Params, Stage6PointConcatPlan, Stage6PointSlicePlan, Stage6PointZeroPlan, Stage6ProgramStepPlan, Stage6SumcheckBatchPlan, Stage6SumcheckClaimPlan, Stage6SumcheckDriverPlan, Stage6SumcheckEvalPlan, Stage6SumcheckInstanceResultPlan, Stage6TranscriptAbsorbBytesPlan, Stage6TranscriptSqueezePlan};\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_prover_types() -> &'static str { + "pub type DefaultStage6Transcript = Blake2bTranscript;\n" + } + + fn emit_verifier_imports() -> &'static str { + "use super::common::{batch_claims, expected_stage67_booleanity, expected_stage67_bytecode_read_raf, expected_stage67_hamming_booleanity, expected_stage67_inc_claim_reduction, expected_stage67_instruction_ra_virtual, expected_stage67_ram_ra_virtual, find_batch, find_plan, normalize_bytecode_read_raf_point, normalize_instruction_read_raf_point, stage67_trace_rounds, Stage67BytecodeEntry, Stage67BytecodeSymbols, Stage67RelationSymbols};\n\ + use jolt_field::{Field, Fr};\n\ + use jolt_sumcheck::SumcheckError;\n\ + use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript};" + } + + #[expect(dead_code)] + fn emit_types() -> &'static str { + r#"#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6TranscriptAbsorbBytesPlan { + pub symbol: &'static str, + pub label: &'static str, + pub payload: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operand_names: &'static [&'static str], + pub operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6PointZeroPlan { + pub symbol: &'static str, + pub field: &'static str, + pub arity: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6OpeningClaimEqualityPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub lhs: &'static str, + pub rhs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6CpuProgramPlan { + pub role: &'static str, + pub params: Stage6Params, + pub steps: &'static [Stage6ProgramStepPlan], + pub transcript_squeezes: &'static [Stage6TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [Stage6TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [Stage6OpeningInputPlan], + pub field_constants: &'static [Stage6FieldConstantPlan], + pub field_exprs: &'static [Stage6FieldExprPlan], + pub kernels: &'static [Stage6KernelPlan], + pub claims: &'static [Stage6SumcheckClaimPlan], + pub batches: &'static [Stage6SumcheckBatchPlan], + pub drivers: &'static [Stage6SumcheckDriverPlan], + pub instance_results: &'static [Stage6SumcheckInstanceResultPlan], + pub evals: &'static [Stage6SumcheckEvalPlan], + pub point_zeros: &'static [Stage6PointZeroPlan], + pub point_slices: &'static [Stage6PointSlicePlan], + pub point_concats: &'static [Stage6PointConcatPlan], + pub opening_claims: &'static [Stage6OpeningClaimPlan], + pub opening_equalities: &'static [Stage6OpeningClaimEqualityPlan], + pub opening_batches: &'static [Stage6OpeningBatchPlan], +} +"# + } + + fn emit_verifier_type_aliases() -> &'static str { + r#"pub type Stage6NamedEval = super::common::StageNamedEval; +pub type Stage6SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage6ChallengeVector = super::common::StageChallengeVector; +pub type Stage6ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage6Proof = super::common::StageProof; +pub type Stage6OpeningInputValue = super::common::StageOpeningInputValue; + +pub use super::common::{ + FieldConstantPlan as Stage6FieldConstantPlan, FieldExprPlan as Stage6FieldExprPlan, + KernelPlan as Stage6KernelPlan, OpeningBatchPlan as Stage6OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage6OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage6OpeningClaimPlan, OpeningInputPlan as Stage6OpeningInputPlan, + PointConcatPlan as Stage6PointConcatPlan, PointSlicePlan as Stage6PointSlicePlan, + PointZeroPlan as Stage6PointZeroPlan, ProgramStepPlan as Stage6ProgramStepPlan, + StageParams as Stage6Params, StageProgramPlan as Stage6CpuProgramPlan, + SumcheckBatchPlan as Stage6SumcheckBatchPlan, + SumcheckClaimPlan as Stage6SumcheckClaimPlan, SumcheckDriverPlan as Stage6SumcheckDriverPlan, + SumcheckEvalPlan as Stage6SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage6SumcheckInstanceResultPlan, + TranscriptAbsorbBytesPlan as Stage6TranscriptAbsorbBytesPlan, + TranscriptSqueezePlan as Stage6TranscriptSqueezePlan, +}; +"# + } + + fn emit_verifier_types() -> String { + let mut source = Self::emit_verifier_type_aliases().to_owned(); + source.push_str( + r#" +pub type DefaultStage6Transcript = Blake2bTranscript; +pub type Stage6VerifierProgramPlan = Stage6CpuProgramPlan; + +#[derive(Clone, Debug)] +pub struct Stage6BytecodeEntry { + pub address: Fr, + pub imm: Fr, + pub circuit_flags: [bool; 14], + pub rd: Option, + pub rs1: Option, + pub rs2: Option, + pub lookup_table: Option, + pub is_interleaved: bool, + pub is_branch: bool, + pub left_is_rs1: bool, + pub left_is_pc: bool, + pub right_is_rs2: bool, + pub right_is_imm: bool, + pub is_noop: bool, +} + +impl Stage67BytecodeEntry for Stage6BytecodeEntry { + fn address(&self) -> Fr { self.address } + fn imm(&self) -> Fr { self.imm } + fn circuit_flags(&self) -> &[bool; 14] { &self.circuit_flags } + fn rd(&self) -> Option { self.rd } + fn rs1(&self) -> Option { self.rs1 } + fn rs2(&self) -> Option { self.rs2 } + fn lookup_table(&self) -> Option { self.lookup_table } + fn is_interleaved(&self) -> bool { self.is_interleaved } + fn is_branch(&self) -> bool { self.is_branch } + fn left_is_rs1(&self) -> bool { self.left_is_rs1 } + fn left_is_pc(&self) -> bool { self.left_is_pc } + fn right_is_rs2(&self) -> bool { self.right_is_rs2 } + fn right_is_imm(&self) -> bool { self.right_is_imm } + fn is_noop(&self) -> bool { self.is_noop } +} + + +#[derive(Clone, Debug)] +pub struct Stage6BytecodeReadRafData { + pub entries: Vec, + pub entry_bytecode_index: usize, + pub num_lookup_tables: usize, +} + +#[derive(Clone, Debug)] +pub struct Stage6VerifierData { + pub bytecode_read_raf: Option, +} + +const STAGE6_RELATION_SYMBOLS: Stage67RelationSymbols = Stage67RelationSymbols { + hamming_booleanity_relation: "jolt.stage6.hamming_booleanity", + hamming_booleanity_instance: "stage6.hamming_booleanity.instance", + booleanity_point: "stage6.booleanity.point", + stage5_instruction_ra0: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + booleanity_combined_point: "stage6.booleanity.combined_point", + booleanity_gamma: "stage6.booleanity.gamma", + booleanity_instruction_ra_prefix: "stage6.booleanity.eval.InstructionRa_", + booleanity_bytecode_ra_prefix: "stage6.booleanity.eval.BytecodeRa_", + booleanity_ram_ra_prefix: "stage6.booleanity.eval.RamRa_", + hamming_weight_eval: "stage6.hamming_booleanity.eval.HammingWeight", + hamming_lookup_output: "stage6.input.stage1.LookupOutput", + ram_ra_virtual_cycle: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + ram_ra_virtual_eval_prefix: "stage6.ram_ra_virtual.eval.RamRa_", + instruction_ra_virtual_cycle: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + instruction_ra_virtual_eval_prefix: "stage6.instruction_ra_virtual.eval.InstructionRa_", + instruction_ra_virtual_input_prefix: "stage6.input.stage5.instruction_read_raf.InstructionRa_", + instruction_ra_virtual_gamma: "stage6.instruction_ra_virtual.gamma", + inc_ram_stage2: "stage6.input.stage2.ram_read_write.RamInc", + inc_ram_stage4: "stage6.input.stage4.ram_val_check.RamInc", + inc_rd_stage4: "stage6.input.stage4.registers_read_write.RdInc", + inc_rd_stage5: "stage6.input.stage5.registers_val_evaluation.RdInc", + inc_gamma: "stage6.inc_claim_reduction.gamma", + inc_ram_eval: "stage6.inc_claim_reduction.eval.RamInc", + inc_rd_eval: "stage6.inc_claim_reduction.eval.RdInc", +}; + +const STAGE6_BYTECODE_SYMBOLS: Stage67BytecodeSymbols = Stage67BytecodeSymbols { + point: "stage6.bytecode_read_raf.point", + gamma: "stage6.bytecode_read_raf.gamma", + bytecode_ra_eval_prefix: "stage6.bytecode_read_raf.eval.BytecodeRa_", + entries: "stage6.bytecode_read_raf.entries", + entry_bytecode_index: "stage6.bytecode_read_raf.entry_bytecode_index", + stage_gammas: [ + "stage6.bytecode_read_raf.stage1_gamma", + "stage6.bytecode_read_raf.stage2_gamma", + "stage6.bytecode_read_raf.stage3_gamma", + "stage6.bytecode_read_raf.stage4_gamma", + "stage6.bytecode_read_raf.stage5_gamma", + ], + stage_cycle_points: [ + "stage6.input.stage1.Imm", + "stage6.input.stage2.OpFlagJump", + "stage6.input.stage3.spartan_shift.UnexpandedPC", + "stage6.input.stage4.Rs1Ra", + "stage6.input.stage5.registers_val_evaluation.RdWa", + ], + stage4_register_point: "stage6.input.stage4.Rs1Ra", + stage5_register_point: "stage6.input.stage5.registers_val_evaluation.RdWa", + entry_rd: "stage6.bytecode.entry.rd", + entry_rs1: "stage6.bytecode.entry.rs1", + entry_rs2: "stage6.bytecode.entry.rs2", + entry_lookup_table: "stage6.bytecode.entry.lookup_table", +}; + +#[derive(Debug)] +pub enum VerifyStage6Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage6Error); +"#, + ); + source + } + + fn emit_constants(&self) -> String { + let mut source = self.emit_shared_constants(); + source.push_str(&self.emit_kernel_constants()); + source.push_str(&self.emit_sumcheck_claim_constants()); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_sumcheck_driver_constants()); + source.push_str(&self.emit_tail_constants()); + push_format( + &mut source, + format_args!( + "pub const STAGE6_PROGRAM: {} = Stage6CpuProgramPlan {{\n\ + \x20 role: {},\n\ + \x20 params: STAGE6_PARAMS,\n\ + \x20 steps: STAGE6_PROGRAM_STEPS,\n\ + \x20 transcript_squeezes: STAGE6_TRANSCRIPT_SQUEEZES,\n\ + \x20 transcript_absorb_bytes: STAGE6_TRANSCRIPT_ABSORB_BYTES,\n\ + \x20 opening_inputs: STAGE6_OPENING_INPUTS,\n\ + \x20 field_constants: STAGE6_FIELD_CONSTANTS,\n\ + \x20 field_exprs: STAGE6_FIELD_EXPRS,\n\ + \x20 kernels: STAGE6_KERNELS,\n\ + \x20 claims: STAGE6_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE6_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE6_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE6_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE6_SUMCHECK_EVALS,\n\ + \x20 point_zeros: STAGE6_POINT_ZEROS,\n\ + \x20 point_slices: STAGE6_POINT_SLICES,\n\ + \x20 point_concats: STAGE6_POINT_CONCATS,\n\ + \x20 opening_claims: STAGE6_OPENING_CLAIMS,\n\ + \x20 opening_equalities: STAGE6_OPENING_EQUALITIES,\n\ + \x20 opening_batches: STAGE6_OPENING_BATCHES,\n\ + }};\n", + self.program_plan_type(), + rust_str(self.role_label()) + ), + ); + source + } + + fn emit_shared_constants(&self) -> String { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const STAGE6_PARAMS: Stage6Params = Stage6Params {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + source.push_str(&self.emit_program_step_constants()); + source.push_str(&self.emit_transcript_squeeze_constants()); + source.push_str(&self.emit_transcript_absorb_bytes_constants()); + source.push_str(&self.emit_opening_input_constants()); + source.push_str(&self.emit_field_constant_constants()); + source.push_str(&self.emit_field_expr_constants()); + source + } + + fn emit_program_step_constants(&self) -> String { + let steps = self + .steps + .iter() + .map(|step| { + format!( + " Stage6ProgramStepPlan {{ kind: {}, symbol: {} }},", + rust_str(&step.kind), + rust_str(&step.symbol), + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE6_PROGRAM_STEPS: &[Stage6ProgramStepPlan] = &[\n{steps}\n];\n\n") + } + + fn emit_transcript_squeeze_constants(&self) -> String { + let squeezes = self + .transcript_squeezes + .iter() + .map(|squeeze| { + format!( + " Stage6TranscriptSqueezePlan {{ symbol: {}, label: {}, kind: {}, count: {} }},", + rust_str(&squeeze.symbol), + rust_str(&squeeze.label), + rust_str(&squeeze.kind), + squeeze.count, + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE6_TRANSCRIPT_SQUEEZES: &[Stage6TranscriptSqueezePlan] = &[\n{squeezes}\n];\n\n" + ) + } + + fn emit_transcript_absorb_bytes_constants(&self) -> String { + let absorbs = self + .transcript_absorb_bytes + .iter() + .map(|absorb| { + format!( + " Stage6TranscriptAbsorbBytesPlan {{ symbol: {}, label: {}, payload: {} }},", + rust_str(&absorb.symbol), + rust_str(&absorb.label), + rust_str(&absorb.payload), + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE6_TRANSCRIPT_ABSORB_BYTES: &[Stage6TranscriptAbsorbBytesPlan] = &[\n{absorbs}\n];\n\n" + ) + } + + fn emit_opening_input_constants(&self) -> String { + let inputs = self + .opening_inputs + .iter() + .map(|input| { + format!( + " Stage6OpeningInputPlan {{ symbol: {}, source_stage: {}, source_claim: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {} }},", + rust_str(&input.symbol), + rust_str(&input.source_stage), + rust_str(&input.source_claim), + rust_str(&input.oracle), + rust_str(&input.domain), + input.point_arity, + rust_str(&input.claim_kind) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE6_OPENING_INPUTS: &[Stage6OpeningInputPlan] = &[\n{inputs}\n];\n\n") + } + + fn emit_field_constant_constants(&self) -> String { + let constants = self + .field_constants + .iter() + .map(|constant| { + format!( + " Stage6FieldConstantPlan {{ symbol: {}, field: {}, value: {} }},", + rust_str(&constant.symbol), + rust_str(&constant.field), + constant.value + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE6_FIELD_CONSTANTS: &[Stage6FieldConstantPlan] = &[\n{constants}\n];\n\n" + ) + } + + fn emit_field_expr_constants(&self) -> String { + if self.role == Role::Verifier { + let rows = self + .field_exprs + .chunks(8) + .map(|chunk| { + let exprs = chunk + .iter() + .map(|expr| { + format!( + "stage6_field_expr!({}, {}, {})", + rust_str(&expr.symbol), + rust_str(&expr.formula), + rust_str(&expr.operands.join("|")) + ) + }) + .collect::>() + .join(", "); + format!(" {exprs},") + }) + .collect::>() + .join("\n"); + return format!( + "macro_rules! stage6_field_expr {{\n ($symbol:literal, $formula:literal, $operands:literal) => {{\n Stage6FieldExprPlan {{ symbol: $symbol, kind: \"op\", formula: $formula, operands: $operands }}\n }};\n}}\n\n#[rustfmt::skip]\npub const STAGE6_FIELD_EXPRS: &[Stage6FieldExprPlan] = &[\n{rows}\n];\n" + ); + } + + let mut source = String::new(); + let mut arrays = Vec::new(); + let mut array_refs = Vec::new(); + for (index, expr) in self.field_exprs.iter().enumerate() { + let operands = intern_str_array( + &mut source, + &mut arrays, + "STAGE6_FIELD_EXPR_OPERANDS", + &expr.operands, + ); + let operand_names = intern_str_array( + &mut source, + &mut arrays, + "STAGE6_FIELD_EXPR_OPERANDS", + &expr.operand_names, + ); + array_refs.push((index, operand_names, operands)); + } + let exprs = self + .field_exprs + .iter() + .enumerate() + .map(|(index, expr)| { + let (_, operand_names, operands) = &array_refs[index]; + format!( + " Stage6FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operand_names: {operand_names}, operands: {operands} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE6_FIELD_EXPRS: &[Stage6FieldExprPlan] = &[\n{exprs}\n];\n" + ), + ); + source + } + + fn emit_kernel_constants(&self) -> String { + let kernels = self + .kernels + .iter() + .map(|kernel| { + format!( + " Stage6KernelPlan {{ symbol: {}, relation: {}, kind: {}, backend: {}, abi: {} }},", + rust_str(&kernel.symbol), + rust_str(&kernel.relation), + rust_str(&kernel.kind), + rust_str(&kernel.backend), + rust_str(&kernel.abi) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE6_KERNELS: &[Stage6KernelPlan] = &[\n{kernels}\n];\n\n") + } + + fn emit_sumcheck_claim_constants(&self) -> String { + if self.role == Role::Verifier { + let claims = self + .claims + .iter() + .map(|claim| { + format!( + " Stage6SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: {}, relation: {}, claim_value: {}, input_openings: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_option_str(claim.kernel.as_deref()), + rust_option_str(claim.relation.as_deref()), + rust_str(&claim.claim_value), + rust_str(&claim.input_openings.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE6_SUMCHECK_CLAIMS: &[Stage6SumcheckClaimPlan] = &[\n{claims}\n];\n" + ); + } + + let mut source = String::new(); + for (index, claim) in self.claims.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE6_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS"), + &claim.input_openings, + )); + } + let claims = self + .claims + .iter() + .enumerate() + .map(|(index, claim)| { + format!( + " Stage6SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: {}, relation: {}, claim_value: {}, input_openings: STAGE6_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_option_str(claim.kernel.as_deref()), + rust_option_str(claim.relation.as_deref()), + rust_str(&claim.claim_value) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE6_SUMCHECK_CLAIMS: &[Stage6SumcheckClaimPlan] = &[\n{claims}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE6_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage6SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {}, claim_label: {}, round_label: {}, round_schedule: STAGE6_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")), + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE6_SUMCHECK_BATCHES: &[Stage6SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + return source; + } + + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE6_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE6_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + source.push_str(&emit_usize_array( + &format!("STAGE6_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage6SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE6_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE6_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS, claim_label: {}, round_label: {}, round_schedule: STAGE6_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE6_SUMCHECK_BATCHES: &[Stage6SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_driver_constants(&self) -> String { + let mut source = String::new(); + for (index, driver) in self.drivers.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE6_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE"), + &driver.round_schedule, + )); + } + let drivers = self + .drivers + .iter() + .enumerate() + .map(|(index, driver)| { + format!( + " Stage6SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: {}, relation: {}, batch: {}, policy: {}, round_schedule: STAGE6_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_option_str(driver.kernel.as_deref()), + rust_option_str(driver.relation.as_deref()), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE6_SUMCHECK_DRIVERS: &[Stage6SumcheckDriverPlan] = &[\n{drivers}\n];\n" + ), + ); + source + } + + fn emit_tail_constants(&self) -> String { + let mut source = String::new(); + source.push_str(&self.emit_sumcheck_instance_result_constants()); + source.push_str(&self.emit_sumcheck_eval_constants()); + source.push_str(&self.emit_point_zero_constants()); + source.push_str(&self.emit_point_slice_constants()); + source.push_str(&self.emit_point_concat_constants()); + source.push_str(&self.emit_opening_claim_constants()); + source.push_str(&self.emit_opening_claim_equality_constants()); + source.push_str(&self.emit_opening_batch_constants()); + source + } + + fn emit_sumcheck_instance_result_constants(&self) -> String { + let instances = self + .instance_results + .iter() + .map(|instance| { + format!( + " Stage6SumcheckInstanceResultPlan {{ symbol: {}, source: {}, claim: {}, relation: {}, index: {}, point_arity: {}, num_rounds: {}, round_offset: {}, point_order: {}, degree: {} }},", + rust_str(&instance.symbol), + rust_str(&instance.source), + rust_str(&instance.claim), + rust_str(&instance.relation), + instance.index, + instance.point_arity, + instance.num_rounds, + instance.round_offset, + rust_str(&instance.point_order), + instance.degree + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE6_SUMCHECK_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = &[\n{instances}\n];\n\n" + ) + } + + fn emit_sumcheck_eval_constants(&self) -> String { + let rows = self + .evals + .chunks(4) + .map(|chunk| { + let evals = chunk + .iter() + .map(|eval| { + format!( + "stage6_sumcheck_eval!({}, {}, {}, {}, {})", + rust_str(&eval.symbol), + rust_str(&eval.source), + rust_str(&eval.name), + eval.index, + rust_str(&eval.oracle) + ) + }) + .collect::>() + .join(", "); + format!(" {evals},") + }) + .collect::>() + .join("\n"); + format!( + "macro_rules! stage6_sumcheck_eval {{\n ($symbol:literal, $source:literal, $name:literal, $index:literal, $oracle:literal) => {{\n Stage6SumcheckEvalPlan {{ symbol: $symbol, source: $source, name: $name, index: $index, oracle: $oracle }}\n }};\n}}\n\n#[rustfmt::skip]\npub const STAGE6_SUMCHECK_EVALS: &[Stage6SumcheckEvalPlan] = &[\n{rows}\n];\n\n" + ) + } + + fn emit_point_zero_constants(&self) -> String { + let zeros = self + .point_zeros + .iter() + .map(|zero| { + format!( + " Stage6PointZeroPlan {{ symbol: {}, field: {}, arity: {} }},", + rust_str(&zero.symbol), + rust_str(&zero.field), + zero.arity + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE6_POINT_ZEROS: &[Stage6PointZeroPlan] = &[\n{zeros}\n];\n\n") + } + + fn emit_point_slice_constants(&self) -> String { + let slices = self + .point_slices + .iter() + .map(|slice| { + format!( + " Stage6PointSlicePlan {{ symbol: {}, source: {}, offset: {}, length: {}, input: {} }},", + rust_str(&slice.symbol), + rust_str(&slice.source), + slice.offset, + slice.length, + rust_str(&slice.input) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE6_POINT_SLICES: &[Stage6PointSlicePlan] = &[\n{slices}\n];\n\n") + } + + fn emit_point_concat_constants(&self) -> String { + if self.role == Role::Verifier { + let concats = self + .point_concats + .iter() + .map(|concat| { + format!( + " Stage6PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: {} }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity, + rust_str(&concat.inputs.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE6_POINT_CONCATS: &[Stage6PointConcatPlan] = &[\n{concats}\n];\n" + ); + } + + let mut source = String::new(); + for (index, concat) in self.point_concats.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE6_POINT_CONCAT_{index}_INPUTS"), + &concat.inputs, + )); + } + let concats = self + .point_concats + .iter() + .enumerate() + .map(|(index, concat)| { + format!( + " Stage6PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: STAGE6_POINT_CONCAT_{index}_INPUTS }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE6_POINT_CONCATS: &[Stage6PointConcatPlan] = &[\n{concats}\n];\n" + ), + ); + source + } + + fn emit_opening_claim_constants(&self) -> String { + let claims = self + .opening_claims + .iter() + .map(|claim| { + format!( + " Stage6OpeningClaimPlan {{ symbol: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {}, point_source: {}, eval_source: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.oracle), + rust_str(&claim.domain), + claim.point_arity, + rust_str(&claim.claim_kind), + rust_str(&claim.point_source), + rust_str(&claim.eval_source) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE6_OPENING_CLAIMS: &[Stage6OpeningClaimPlan] = &[\n{claims}\n];\n\n") + } + + fn emit_opening_claim_equality_constants(&self) -> String { + let equalities = self + .opening_equalities + .iter() + .map(|equality| { + format!( + " Stage6OpeningClaimEqualityPlan {{ symbol: {}, mode: {}, lhs: {}, rhs: {} }},", + rust_str(&equality.symbol), + rust_str(&equality.mode), + rust_str(&equality.lhs), + rust_str(&equality.rhs) + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE6_OPENING_EQUALITIES: &[Stage6OpeningClaimEqualityPlan] = &[\n{equalities}\n];\n\n" + ) + } + + fn emit_opening_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let batches = self + .opening_batches + .iter() + .map(|batch| { + format!( + " Stage6OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {} }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE6_OPENING_BATCHES: &[Stage6OpeningBatchPlan] = &[\n{batches}\n];\n" + ); + } + + let mut source = String::new(); + for (index, batch) in self.opening_batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE6_OPENING_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE6_OPENING_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + } + let batches = self + .opening_batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage6OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE6_OPENING_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE6_OPENING_BATCH_{index}_CLAIM_OPERANDS }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE6_OPENING_BATCHES: &[Stage6OpeningBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_entrypoint(&self) -> &'static str { + match self.role { + Role::Prover => { + "pub fn execute_stage6_prover(\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage6KernelError>\n\ + where\n\ + \x20 E: Stage6KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage6_prover_with_program(&STAGE6_PROGRAM, executor, transcript)\n\ + }\n\ + \n\ + pub fn execute_stage6_prover_with_program(\n\ + \x20 program: &'static Stage6CpuProgramPlan,\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage6KernelError>\n\ + where\n\ + \x20 E: Stage6KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage6_program(program, Stage6ExecutionMode::Prover, executor, transcript)\n\ + }\n" + } + Role::Verifier => { + r#"pub fn verify_stage6( + proof: &Stage6Proof, + opening_inputs: &[Stage6OpeningInputValue], + verifier_data: Option<&Stage6VerifierData>, + transcript: &mut T, +) -> Result, VerifyStage6Error> +where + T: Transcript, +{ + verify_stage6_with_program(&STAGE6_PROGRAM, proof, opening_inputs, verifier_data, transcript) +} + +pub fn verify_stage6_with_program( + program: &'static Stage6VerifierProgramPlan, + proof: &Stage6Proof, + opening_inputs: &[Stage6OpeningInputValue], + verifier_data: Option<&Stage6VerifierData>, + transcript: &mut T, +) -> Result, VerifyStage6Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage6Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + store.seed_point_zeros(program.point_zeros); + let mut artifacts = Stage6ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage6Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage6_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "transcript_absorb_bytes" => { + let absorb = find_plan(program.transcript_absorb_bytes, step.symbol).ok_or( + VerifyStage6Error::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage6_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage6Error::MissingProof { + driver: step.symbol, + })?; + verify_stage6_driver( + program, + driver, + proof, + verifier_data, + &mut store, + transcript, + &mut artifacts, + )?; + } + _ => { + return Err(VerifyStage6Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage6 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage6_verifier_program() -> &'static Stage6VerifierProgramPlan { + &STAGE6_PROGRAM +} + +fn verify_stage6_squeeze( + program: &'static Stage6VerifierProgramPlan, + squeeze: &'static Stage6TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage6ExecutionArtifacts, +) -> Result<(), VerifyStage6Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage6Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage6Error::from)?; + artifacts.challenge_vectors.push(Stage6ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn absorb_stage6_bytes(absorb: &'static Stage6TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn verify_stage6_driver( + program: &'static Stage6VerifierProgramPlan, + driver: &'static Stage6SumcheckDriverPlan, + proof: &Stage6Proof, + verifier_data: Option<&Stage6VerifierData>, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage6ExecutionArtifacts, +) -> Result<(), VerifyStage6Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage6Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage6.batched" => { + verify_batched_stage6(program, driver, proof, verifier_data, store, transcript)? + } + _ => return Err(VerifyStage6Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage6( + program: &'static Stage6VerifierProgramPlan, + driver: &'static Stage6SumcheckDriverPlan, + proof: &Stage6SumcheckOutput, + verifier_data: Option<&Stage6VerifierData>, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage6Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim( + program, + driver, + verifier_data, + store, + evals, + point, + batching_coeffs, + ) + }, + |store, verified| observe_stage6_sumcheck_output(program, store, verified), + |driver, error| VerifyStage6Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage6_sumcheck_output( + program: &'static Stage6VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage6SumcheckOutput, +) -> Result<(), VerifyStage6Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "bytecode_read_raf" => point = normalize_bytecode_read_raf_point(&point, stage6_trace_rounds(program)?, "stage6.bytecode_read_raf.point")?, + "stage6_booleanity" => {} + "instruction_read_raf" => point = normalize_instruction_read_raf_point(&point, "stage6.instruction_read_raf.point")?, + _ => { + return Err(VerifyStage6Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage6Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage6Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage6Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage6Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage6Error::InvalidProof { driver, reason }, + |symbol| VerifyStage6Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage6VerifierProgramPlan, + driver: &'static Stage6SumcheckDriverPlan, + verifier_data: Option<&Stage6VerifierData>, + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage6Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage6Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(""); + let value = match relation { + "jolt.stage6.bytecode_read_raf" => { + let data = verifier_data + .and_then(|data| data.bytecode_read_raf.as_ref()) + .ok_or(VerifyStage6Error::MissingValue { + symbol: "stage6.bytecode_read_raf.data", + })?; + expected_bytecode_read_raf(program, data, store, evals, local_point)? + } + "jolt.stage6.booleanity" => { + expected_booleanity(program, store, evals, local_point)? + } + "jolt.stage6.hamming_booleanity" => { + expected_hamming_booleanity(store, evals, local_point)? + } + "jolt.stage6.ram_ra_virtual" => { + expected_ram_ra_virtual(store, evals, local_point)? + } + "jolt.stage6.instruction_ra_virtual" => { + expected_instruction_ra_virtual(program, store, evals, local_point)? + } + "jolt.stage6.inc_claim_reduction" => { + expected_inc_claim_reduction(store, evals, local_point)? + } + _ => return Err(VerifyStage6Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_bytecode_read_raf( + program: &'static Stage6VerifierProgramPlan, + data: &Stage6BytecodeReadRafData, + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + let log_t = stage6_trace_rounds(program)?; + Ok(expected_stage67_bytecode_read_raf( + &data.entries, + data.entry_bytecode_index, + data.num_lookup_tables, + store, + evals, + local_point, + log_t, + &STAGE6_BYTECODE_SYMBOLS, + )?) +} + +fn expected_booleanity( + program: &'static Stage6VerifierProgramPlan, + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + let log_t = stage6_trace_rounds(program)?; + Ok(expected_stage67_booleanity(store, evals, local_point, log_t, &STAGE6_RELATION_SYMBOLS)?) +} + +fn expected_hamming_booleanity( + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + Ok(expected_stage67_hamming_booleanity(store, evals, local_point, &STAGE6_RELATION_SYMBOLS)?) +} + +fn expected_ram_ra_virtual( + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + Ok(expected_stage67_ram_ra_virtual(store, evals, local_point, &STAGE6_RELATION_SYMBOLS)?) +} + +fn expected_instruction_ra_virtual( + program: &'static Stage6VerifierProgramPlan, + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + Ok(expected_stage67_instruction_ra_virtual(program.opening_inputs, store, evals, local_point, &STAGE6_RELATION_SYMBOLS)?) +} + +fn expected_inc_claim_reduction( + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + Ok(expected_stage67_inc_claim_reduction(store, evals, local_point, &STAGE6_RELATION_SYMBOLS)?) +} + +fn stage6_trace_rounds( + program: &'static Stage6VerifierProgramPlan, +) -> Result { + Ok(stage67_trace_rounds(program.instance_results, &STAGE6_RELATION_SYMBOLS)?) +} +"# + } + } + } + + fn role_label(&self) -> &'static str { + match self.role { + Role::Prover => "prover", + Role::Verifier => "verifier", + } + } + + fn program_plan_type(&self) -> &'static str { + match self.role { + Role::Prover => "Stage6CpuProgramPlan", + Role::Verifier => "Stage6VerifierProgramPlan", + } + } +} + +fn require_supported_symbol(kind: &str, actual: &str, expected: &str) -> Result<(), EmitError> { + if actual == expected { + Ok(()) + } else { + Err(EmitError::new(format!( + "unsupported {kind} @{actual}; expected @{expected}" + ))) + } +} + +fn emit_str_array(name: &str, values: &[String]) -> String { + if values.is_empty() { + return format!("pub const {name}: &[&str] = &[];\n\n"); + } + if let [value] = values { + return format!("pub const {name}: &[&str] = &[{}];\n\n", rust_str(value)); + } + let entries = values + .iter() + .map(|value| format!(" {},", rust_str(value))) + .collect::>() + .join("\n"); + format!("pub const {name}: &[&str] = &[\n{entries}\n];\n\n") +} + +fn emit_usize_array(name: &str, values: &[usize]) -> String { + let entries = values + .iter() + .map(|value| format!(" {value},")) + .collect::>() + .join("\n"); + format!("pub const {name}: &[usize] = &[\n{entries}\n];\n\n") +} + +fn intern_str_array( + source: &mut String, + arrays: &mut Vec<(Vec, String)>, + name_prefix: &str, + values: &[String], +) -> String { + if let Some((_, name)) = arrays + .iter() + .find(|(existing, _)| existing.as_slice() == values) + { + return name.clone(); + } + let name = format!("{name_prefix}_{}", arrays.len()); + source.push_str(&emit_str_array(&name, values)); + arrays.push((values.to_vec(), name.clone())); + name +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn rust_option_str(value: Option<&str>) -> String { + value.map_or_else( + || "None".to_owned(), + |value| format!("Some({})", rust_str(value)), + ) +} + +fn verify_count(kind: &str, symbol: &str, expected: usize, actual: usize) -> Result<(), EmitError> { + if expected == actual { + Ok(()) + } else { + Err(EmitError::new(format!( + "{kind} @{symbol} count mismatch: expected {expected}, got {actual}" + ))) + } +} + +fn symbols<'a>(values: impl Iterator) -> BTreeSet { + values.cloned().collect() +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn int_array_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "integer array"))?; + parse_int_array(&attribute).ok_or_else(|| attr_error(operation, attr, "integer array")) +} + +fn parse_int_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().parse().ok()) + .collect() +} + +fn operand_symbols( + operation: OperationRef<'_, '_>, + start_index: usize, +) -> Result, EmitError> { + (start_index..operation.operand_count()) + .map(|index| operand_symbol(operation, index)) + .collect() +} + +fn operand_symbol(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + EmitError::new(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + EmitError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name<'c: 'a, 'a>(operation: impl OperationLike<'c, 'a>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} diff --git a/crates/bolt/src/protocols/jolt/emit/rust/stage7.rs b/crates/bolt/src/protocols/jolt/emit/rust/stage7.rs new file mode 100644 index 0000000000..afc274ff7f --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/stage7.rs @@ -0,0 +1,2529 @@ +#![expect( + clippy::needless_raw_string_hashes, + reason = "generated Rust templates are kept as raw string blocks for copyable output" +)] + +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{Attribute, OperationRef}; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7CpuProgram { + pub role: Role, + pub params: Stage7Params, + pub steps: Vec, + pub transcript_squeezes: Vec, + pub transcript_absorb_bytes: Vec, + pub opening_inputs: Vec, + pub field_constants: Vec, + pub field_exprs: Vec, + pub kernels: Vec, + pub claims: Vec, + pub batches: Vec, + pub drivers: Vec, + pub instance_results: Vec, + pub evals: Vec, + pub point_zeros: Vec, + pub point_slices: Vec, + pub point_concats: Vec, + pub opening_claims: Vec, + pub opening_equalities: Vec, + pub opening_batches: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7Params { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7KernelPlan { + pub symbol: String, + pub relation: String, + pub kind: String, + pub backend: String, + pub abi: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7TranscriptSqueezePlan { + pub symbol: String, + pub label: String, + pub kind: String, + pub count: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7TranscriptAbsorbBytesPlan { + pub symbol: String, + pub label: String, + pub payload: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7ProgramStepPlan { + pub kind: String, + pub symbol: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7OpeningInputPlan { + pub symbol: String, + pub source_stage: String, + pub source_claim: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7FieldConstantPlan { + pub symbol: String, + pub field: String, + pub value: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7FieldExprPlan { + pub symbol: String, + pub kind: String, + pub formula: String, + pub operand_names: Vec, + pub operands: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckClaimPlan { + pub symbol: String, + pub stage: String, + pub domain: String, + pub num_rounds: usize, + pub degree: usize, + pub claim: String, + pub kernel: Option, + pub relation: Option, + pub claim_value: String, + pub input_openings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, + pub claim_label: String, + pub round_label: String, + pub round_schedule: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckDriverPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub kernel: Option, + pub relation: Option, + pub batch: String, + pub policy: String, + pub round_schedule: Vec, + pub claim_label: String, + pub round_label: String, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckInstanceResultPlan { + pub symbol: String, + pub source: String, + pub claim: String, + pub relation: String, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: String, + pub degree: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckEvalPlan { + pub symbol: String, + pub source: String, + pub name: String, + pub index: usize, + pub oracle: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7PointZeroPlan { + pub symbol: String, + pub field: String, + pub arity: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7PointSlicePlan { + pub symbol: String, + pub source: String, + pub offset: usize, + pub length: usize, + pub input: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7PointConcatPlan { + pub symbol: String, + pub layout: String, + pub arity: usize, + pub inputs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7OpeningClaimPlan { + pub symbol: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, + pub point_source: String, + pub eval_source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7OpeningClaimEqualityPlan { + pub symbol: String, + pub mode: String, + pub lhs: String, + pub rhs: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage7OpeningBatchPlan { + pub symbol: String, + pub stage: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, +} + +pub fn stage7_cpu_program(module: &BoltModule<'_, Cpu>) -> Result { + verify_cpu_schema(module)?; + let program = Stage7CpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +pub fn emit_stage7_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = stage7_cpu_program(module)?; + + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source(), + }) +} + +impl Stage7CpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let mut params = None; + let mut steps = Vec::new(); + let mut transcript_squeezes = Vec::new(); + let mut transcript_absorb_bytes = Vec::new(); + let mut opening_inputs = Vec::new(); + let mut field_constants = Vec::new(); + let mut field_exprs = Vec::new(); + let mut kernels = Vec::new(); + let mut claims = Vec::new(); + let mut batches = Vec::new(); + let mut drivers = Vec::new(); + let mut instance_results = Vec::new(); + let mut evals = Vec::new(); + let mut point_zeros = Vec::new(); + let mut point_slices = Vec::new(); + let mut point_concats = Vec::new(); + let mut opening_claims = Vec::new(); + let mut opening_equalities = Vec::new(); + let mut opening_batches = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(Stage7Params { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.kernel" => { + kernels.push(Stage7KernelPlan { + symbol: string_attr(op, "sym_name")?, + relation: symbol_attr(op, "relation")?, + kind: string_attr(op, "kind")?, + backend: string_attr(op, "backend")?, + abi: string_attr(op, "abi")?, + }); + } + "cpu.transcript_squeeze" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage7ProgramStepPlan { + kind: "transcript_squeeze".to_owned(), + symbol: symbol.clone(), + }); + transcript_squeezes.push(Stage7TranscriptSqueezePlan { + symbol, + label: string_attr(op, "label")?, + kind: string_attr(op, "kind")?, + count: int_attr(op, "count")?, + }); + } + "cpu.transcript_absorb_bytes" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage7ProgramStepPlan { + kind: "transcript_absorb_bytes".to_owned(), + symbol: symbol.clone(), + }); + transcript_absorb_bytes.push(Stage7TranscriptAbsorbBytesPlan { + symbol, + label: string_attr(op, "label")?, + payload: string_attr(op, "payload")?, + }); + } + "cpu.opening_input" => { + opening_inputs.push(Stage7OpeningInputPlan { + symbol: string_attr(op, "sym_name")?, + source_stage: symbol_attr(op, "source_stage")?, + source_claim: symbol_attr(op, "source_claim")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + }); + } + "cpu.field_const" => { + field_constants.push(Stage7FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: int_attr(op, "value")?, + }); + } + "cpu.field_zero" => { + field_constants.push(Stage7FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 0, + }); + } + "cpu.field_one" => { + field_constants.push(Stage7FieldConstantPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + value: 1, + }); + } + "cpu.field_add" | "cpu.field_sub" | "cpu.field_mul" | "cpu.field_neg" => { + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage7FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: operation_name(op).replace("cpu.field_", "field."), + operand_names: operands.clone(), + operands, + }); + } + "cpu.field_pow" => { + let exponent = int_attr(op, "exponent")?; + let operands = operand_symbols(op, 0)?; + field_exprs.push(Stage7FieldExprPlan { + symbol: string_attr(op, "sym_name")?, + kind: "op".to_owned(), + formula: format!("field.pow:{exponent}"), + operand_names: operands.clone(), + operands, + }); + } + "cpu.sumcheck_claim" => { + claims.push(Stage7SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_verify_claim" => { + claims.push(Stage7SumcheckClaimPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + domain: symbol_attr(op, "domain")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + claim: symbol_attr(op, "claim")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + claim_value: operand_symbol(op, 0)?, + input_openings: operand_symbols(op, 1)?, + }); + } + "cpu.sumcheck_batch" => { + batches.push(Stage7SumcheckBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + round_schedule: int_array_attr(op, "round_schedule")?, + }); + } + "cpu.sumcheck_driver" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage7ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage7SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: Some(symbol_attr(op, "kernel")?), + relation: None, + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_verify" => { + let symbol = string_attr(op, "sym_name")?; + steps.push(Stage7ProgramStepPlan { + kind: "sumcheck_driver".to_owned(), + symbol: symbol.clone(), + }); + drivers.push(Stage7SumcheckDriverPlan { + symbol, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + kernel: None, + relation: Some(symbol_attr(op, "relation")?), + batch: operand_symbol(op, 1)?, + policy: string_attr(op, "policy")?, + round_schedule: int_array_attr(op, "round_schedule")?, + claim_label: string_attr(op, "claim_label")?, + round_label: string_attr(op, "round_label")?, + num_rounds: int_attr(op, "num_rounds")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_instance_result" => { + instance_results.push(Stage7SumcheckInstanceResultPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + claim: symbol_attr(op, "claim")?, + relation: symbol_attr(op, "relation")?, + index: int_attr(op, "index")?, + point_arity: int_attr(op, "point_arity")?, + num_rounds: int_attr(op, "num_rounds")?, + round_offset: int_attr(op, "round_offset")?, + point_order: string_attr(op, "point_order")?, + degree: int_attr(op, "degree")?, + }); + } + "cpu.sumcheck_eval" => { + evals.push(Stage7SumcheckEvalPlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + name: symbol_attr(op, "name")?, + index: int_attr(op, "index")?, + oracle: symbol_attr(op, "oracle")?, + }); + } + "cpu.point_zero" => { + point_zeros.push(Stage7PointZeroPlan { + symbol: string_attr(op, "sym_name")?, + field: symbol_attr(op, "field")?, + arity: int_attr(op, "arity")?, + }); + } + "cpu.point_slice" => { + point_slices.push(Stage7PointSlicePlan { + symbol: string_attr(op, "sym_name")?, + source: symbol_attr(op, "source")?, + offset: int_attr(op, "offset")?, + length: int_attr(op, "length")?, + input: operand_symbol(op, 0)?, + }); + } + "cpu.point_concat" => { + point_concats.push(Stage7PointConcatPlan { + symbol: string_attr(op, "sym_name")?, + layout: string_attr(op, "layout")?, + arity: int_attr(op, "arity")?, + inputs: operand_symbols(op, 0)?, + }); + } + "cpu.opening_claim" => { + opening_claims.push(Stage7OpeningClaimPlan { + symbol: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + point_source: operand_symbol(op, 0)?, + eval_source: operand_symbol(op, 1)?, + }); + } + "cpu.opening_claim_equal" => { + opening_equalities.push(Stage7OpeningClaimEqualityPlan { + symbol: string_attr(op, "sym_name")?, + mode: string_attr(op, "mode")?, + lhs: operand_symbol(op, 0)?, + rhs: operand_symbol(op, 1)?, + }); + } + "cpu.opening_batch" => { + opening_batches.push(Stage7OpeningBatchPlan { + symbol: string_attr(op, "sym_name")?, + stage: symbol_attr(op, "stage")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + }); + } + _ => {} + } + } + + Ok(Self { + params: params.ok_or_else(|| EmitError::new("missing cpu.params"))?, + role: module + .role() + .ok_or_else(|| EmitError::new("missing cpu party role"))?, + steps, + transcript_squeezes, + transcript_absorb_bytes, + opening_inputs, + field_constants, + field_exprs, + kernels, + claims, + batches, + drivers, + instance_results, + evals, + point_zeros, + point_slices, + point_concats, + opening_claims, + opening_equalities, + opening_batches, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + require_supported_symbol("field", &self.params.field, "bn254_fr")?; + require_supported_symbol("pcs", &self.params.pcs, "dory")?; + require_supported_symbol("transcript", &self.params.transcript, "blake2b_transcript")?; + self.verify_transcript_steps()?; + self.verify_field_flow()?; + self.verify_claim_batches()?; + match self.role { + Role::Prover => { + self.verify_kernel_definitions()?; + self.verify_prover_driver_bindings()?; + } + Role::Verifier => self.verify_verifier_driver_bindings()?, + } + self.verify_opening_flow() + } + + fn verify_transcript_steps(&self) -> Result<(), EmitError> { + for squeeze in &self.transcript_squeezes { + if !matches!( + squeeze.kind.as_str(), + "challenge_scalar" | "challenge_vector" + ) { + return Err(EmitError::new(format!( + "stage7 transcript squeeze @{} has unsupported kind `{}`", + squeeze.symbol, squeeze.kind + ))); + } + if squeeze.count == 0 { + return Err(EmitError::new(format!( + "stage7 transcript squeeze @{} has zero count", + squeeze.symbol + ))); + } + } + for absorb in &self.transcript_absorb_bytes { + if absorb.label.is_empty() { + return Err(EmitError::new(format!( + "stage7 transcript byte absorb @{} has empty label", + absorb.symbol + ))); + } + } + Ok(()) + } + + fn verify_field_flow(&self) -> Result<(), EmitError> { + for constant in &self.field_constants { + require_supported_symbol("field constant field", &constant.field, "bn254_fr")?; + } + let field_values = self.field_value_symbols(); + for expr in &self.field_exprs { + verify_count( + "field expr operands", + &expr.symbol, + expr.operand_names.len(), + expr.operands.len(), + )?; + for operand in &expr.operands { + if !field_values.contains(operand) { + return Err(EmitError::new(format!( + "field expr @{} references missing field value @{operand}", + expr.symbol + ))); + } + } + } + for claim in &self.claims { + if !field_values.contains(&claim.claim_value) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing claim value @{}", + claim.symbol, claim.claim_value + ))); + } + } + Ok(()) + } + + fn field_value_symbols(&self) -> BTreeSet { + let mut values = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + values.extend(symbols( + self.field_constants.iter().map(|constant| &constant.symbol), + )); + values.extend(symbols( + self.transcript_squeezes + .iter() + .filter(|squeeze| matches!(squeeze.kind.as_str(), "challenge_scalar" | "scalar")) + .map(|squeeze| &squeeze.symbol), + )); + values.extend(symbols(self.field_exprs.iter().map(|expr| &expr.symbol))); + values.extend(symbols(self.evals.iter().map(|eval| &eval.symbol))); + values + } + + fn verify_kernel_definitions(&self) -> Result<(), EmitError> { + for kernel in &self.kernels { + if kernel.backend != "cpu" { + return Err(EmitError::new(format!( + "stage7 kernel @{} targets unsupported backend `{}`", + kernel.symbol, kernel.backend + ))); + } + if kernel.kind != "sumcheck" { + return Err(EmitError::new(format!( + "stage7 kernel @{} has unsupported kind `{}`", + kernel.symbol, kernel.kind + ))); + } + let expected_abi = match kernel.relation.as_str() { + "jolt.stage7.hamming_weight_claim_reduction" => { + "jolt_stage7_hamming_weight_claim_reduction" + } + "jolt.stage7.batched" => "jolt_stage7_batched", + _ => { + return Err(EmitError::new(format!( + "unsupported stage7 kernel relation @{}", + kernel.relation + ))); + } + }; + if kernel.abi != expected_abi { + return Err(EmitError::new(format!( + "stage7 kernel @{} ABI `{}` does not match relation @{}", + kernel.symbol, kernel.abi, kernel.relation + ))); + } + } + Ok(()) + } + + fn verify_claim_batches(&self) -> Result<(), EmitError> { + let claims = symbols(self.claims.iter().map(|claim| &claim.symbol)); + for batch in &self.batches { + verify_count( + "sumcheck batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "sumcheck batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "sumcheck batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !claims.contains(claim) { + return Err(EmitError::new(format!( + "sumcheck batch @{} references missing claim @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn verify_prover_driver_bindings(&self) -> Result<(), EmitError> { + let kernels = symbols(self.kernels.iter().map(|kernel| &kernel.symbol)); + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + let Some(kernel) = claim.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck claim @{} is missing kernel", + claim.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck claim @{} references missing kernel @{kernel}", + claim.symbol + ))); + } + } + for driver in &self.drivers { + let Some(kernel) = driver.kernel.as_deref() else { + return Err(EmitError::new(format!( + "prover sumcheck driver @{} is missing kernel", + driver.symbol + ))); + }; + if !kernels.contains(kernel) { + return Err(EmitError::new(format!( + "sumcheck driver @{} references missing kernel @{kernel}", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_verifier_driver_bindings(&self) -> Result<(), EmitError> { + if !self.kernels.is_empty() { + return Err(EmitError::new( + "verifier stage7 program must not contain kernels", + )); + } + let batches: BTreeMap<_, _> = self + .batches + .iter() + .map(|batch| (batch.symbol.as_str(), batch)) + .collect(); + for claim in &self.claims { + if claim.kernel.is_some() || claim.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck claim @{} must carry relation and no kernel", + claim.symbol + ))); + } + } + for driver in &self.drivers { + if driver.kernel.is_some() || driver.relation.is_none() { + return Err(EmitError::new(format!( + "verifier sumcheck driver @{} must carry relation and no kernel", + driver.symbol + ))); + } + let batch = batches.get(driver.batch.as_str()).ok_or_else(|| { + EmitError::new(format!( + "sumcheck driver @{} references missing batch @{}", + driver.symbol, driver.batch + )) + })?; + verify_count( + "sumcheck driver round_schedule", + &driver.symbol, + driver.num_rounds, + driver.round_schedule.iter().sum(), + )?; + if driver.round_schedule != batch.round_schedule { + return Err(EmitError::new(format!( + "sumcheck driver @{} round_schedule differs from batch @{}", + driver.symbol, batch.symbol + ))); + } + } + Ok(()) + } + + fn verify_opening_flow(&self) -> Result<(), EmitError> { + let mut point_sources = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + point_sources.extend(symbols( + self.instance_results + .iter() + .map(|instance| &instance.symbol), + )); + point_sources.extend(symbols( + self.opening_inputs.iter().map(|input| &input.symbol), + )); + point_sources.extend(symbols(self.point_zeros.iter().map(|zero| &zero.symbol))); + point_sources.extend(symbols(self.point_slices.iter().map(|slice| &slice.symbol))); + point_sources.extend(symbols( + self.point_concats.iter().map(|concat| &concat.symbol), + )); + for zero in &self.point_zeros { + require_supported_symbol("point zero field", &zero.field, "bn254_fr")?; + } + for slice in &self.point_slices { + if !point_sources.contains(&slice.input) { + return Err(EmitError::new(format!( + "point slice @{} uses missing point source @{}", + slice.symbol, slice.input + ))); + } + } + for concat in &self.point_concats { + for input in &concat.inputs { + if !point_sources.contains(input) { + return Err(EmitError::new(format!( + "point concat @{} uses missing point source @{input}", + concat.symbol + ))); + } + } + } + let eval_sources = self.field_value_symbols(); + let mut opening_sources = symbols(self.opening_inputs.iter().map(|input| &input.symbol)); + opening_sources.extend(symbols( + self.opening_claims.iter().map(|claim| &claim.symbol), + )); + for equality in &self.opening_equalities { + if !opening_sources.contains(&equality.lhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing lhs opening @{}", + equality.symbol, equality.lhs + ))); + } + if !opening_sources.contains(&equality.rhs) { + return Err(EmitError::new(format!( + "opening equality @{} uses missing rhs opening @{}", + equality.symbol, equality.rhs + ))); + } + } + for claim in &self.claims { + for input in &claim.input_openings { + if !opening_sources.contains(input) { + return Err(EmitError::new(format!( + "sumcheck claim @{} uses missing opening @{input}", + claim.symbol + ))); + } + } + } + let drivers = symbols(self.drivers.iter().map(|driver| &driver.symbol)); + for instance in &self.instance_results { + if !drivers.contains(&instance.source) { + return Err(EmitError::new(format!( + "sumcheck instance result @{} references missing driver @{}", + instance.symbol, instance.source + ))); + } + } + for eval in &self.evals { + if !drivers.contains(&eval.source) { + return Err(EmitError::new(format!( + "sumcheck eval @{} references missing driver @{}", + eval.symbol, eval.source + ))); + } + } + for claim in &self.opening_claims { + if !point_sources.contains(&claim.point_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing point source @{}", + claim.symbol, claim.point_source + ))); + } + if !eval_sources.contains(&claim.eval_source) { + return Err(EmitError::new(format!( + "opening claim @{} uses missing eval source @{}", + claim.symbol, claim.eval_source + ))); + } + } + let openings = symbols(self.opening_claims.iter().map(|claim| &claim.symbol)); + for batch in &self.opening_batches { + verify_count( + "opening batch", + &batch.symbol, + batch.count, + batch.ordered_claims.len(), + )?; + verify_count( + "opening batch operands", + &batch.symbol, + batch.count, + batch.claim_operands.len(), + )?; + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new(format!( + "opening batch @{} operand order does not match ordered_claims", + batch.symbol + ))); + } + for claim in &batch.ordered_claims { + if !openings.contains(claim) { + return Err(EmitError::new(format!( + "opening batch @{} references missing opening @{claim}", + batch.symbol + ))); + } + } + } + Ok(()) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_stage7.rs", + Role::Verifier => "verify_stage7.rs", + } + } + + fn emit_source(&self) -> String { + let mut source = String::new(); + source.push_str("#![allow(dead_code)]\n\n"); + match self.role { + Role::Prover => { + source.push_str(Self::emit_prover_imports()); + source.push_str("\n\n"); + source.push_str(Self::emit_prover_types()); + } + Role::Verifier => { + source.push_str(Self::emit_verifier_imports()); + source.push_str("\n\n"); + source.push_str(&Self::emit_verifier_types()); + } + } + source.push('\n'); + source.push_str(&self.emit_constants()); + source.push('\n'); + source.push_str(self.emit_entrypoint()); + source + } + + fn emit_prover_imports() -> &'static str { + "use jolt_field::Fr;\n\ + use jolt_kernels::stage7::{execute_stage7_program, Stage7CpuProgramPlan, Stage7ExecutionArtifacts, Stage7ExecutionMode, Stage7FieldConstantPlan, Stage7FieldExprPlan, Stage7KernelError, Stage7KernelExecutor, Stage7KernelPlan, Stage7OpeningBatchPlan, Stage7OpeningClaimEqualityPlan, Stage7OpeningClaimPlan, Stage7OpeningInputPlan, Stage7Params, Stage7PointConcatPlan, Stage7PointSlicePlan, Stage7PointZeroPlan, Stage7ProgramStepPlan, Stage7SumcheckBatchPlan, Stage7SumcheckClaimPlan, Stage7SumcheckDriverPlan, Stage7SumcheckEvalPlan, Stage7SumcheckInstanceResultPlan, Stage7TranscriptAbsorbBytesPlan, Stage7TranscriptSqueezePlan};\n\ + use jolt_transcript::{Blake2bTranscript, Transcript};" + } + + fn emit_prover_types() -> &'static str { + "pub type DefaultStage7Transcript = Blake2bTranscript;\n" + } + + fn emit_verifier_imports() -> &'static str { + "use super::common::{batch_claims, eval_by_name, find_batch, find_plan, normalize_bytecode_read_raf_point, normalize_instruction_read_raf_point, reverse_slice};\n\ + use jolt_field::{Field, Fr};\n\ + use jolt_poly::EqPolynomial;\n\ + use jolt_sumcheck::SumcheckError;\n\ + use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript};" + } + + #[expect(dead_code)] + fn emit_types() -> &'static str { + r#"#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7TranscriptAbsorbBytesPlan { + pub symbol: &'static str, + pub label: &'static str, + pub payload: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operand_names: &'static [&'static str], + pub operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7PointZeroPlan { + pub symbol: &'static str, + pub field: &'static str, + pub arity: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7OpeningClaimEqualityPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub lhs: &'static str, + pub rhs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7CpuProgramPlan { + pub role: &'static str, + pub params: Stage7Params, + pub steps: &'static [Stage7ProgramStepPlan], + pub transcript_squeezes: &'static [Stage7TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [Stage7TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [Stage7OpeningInputPlan], + pub field_constants: &'static [Stage7FieldConstantPlan], + pub field_exprs: &'static [Stage7FieldExprPlan], + pub kernels: &'static [Stage7KernelPlan], + pub claims: &'static [Stage7SumcheckClaimPlan], + pub batches: &'static [Stage7SumcheckBatchPlan], + pub drivers: &'static [Stage7SumcheckDriverPlan], + pub instance_results: &'static [Stage7SumcheckInstanceResultPlan], + pub evals: &'static [Stage7SumcheckEvalPlan], + pub point_zeros: &'static [Stage7PointZeroPlan], + pub point_slices: &'static [Stage7PointSlicePlan], + pub point_concats: &'static [Stage7PointConcatPlan], + pub opening_claims: &'static [Stage7OpeningClaimPlan], + pub opening_equalities: &'static [Stage7OpeningClaimEqualityPlan], + pub opening_batches: &'static [Stage7OpeningBatchPlan], +} +"# + } + + fn emit_verifier_type_aliases() -> &'static str { + r#"pub type Stage7NamedEval = super::common::StageNamedEval; +pub type Stage7SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage7ChallengeVector = super::common::StageChallengeVector; +pub type Stage7ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage7Proof = super::common::StageProof; +pub type Stage7OpeningInputValue = super::common::StageOpeningInputValue; + +pub use super::common::{ + FieldConstantPlan as Stage7FieldConstantPlan, FieldExprPlan as Stage7FieldExprPlan, + KernelPlan as Stage7KernelPlan, OpeningBatchPlan as Stage7OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage7OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage7OpeningClaimPlan, OpeningInputPlan as Stage7OpeningInputPlan, + PointConcatPlan as Stage7PointConcatPlan, PointSlicePlan as Stage7PointSlicePlan, + PointZeroPlan as Stage7PointZeroPlan, ProgramStepPlan as Stage7ProgramStepPlan, + StageParams as Stage7Params, StageProgramPlan as Stage7CpuProgramPlan, + SumcheckBatchPlan as Stage7SumcheckBatchPlan, + SumcheckClaimPlan as Stage7SumcheckClaimPlan, SumcheckDriverPlan as Stage7SumcheckDriverPlan, + SumcheckEvalPlan as Stage7SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage7SumcheckInstanceResultPlan, + TranscriptAbsorbBytesPlan as Stage7TranscriptAbsorbBytesPlan, + TranscriptSqueezePlan as Stage7TranscriptSqueezePlan, +}; +"# + } + + fn emit_verifier_types() -> String { + let mut source = Self::emit_verifier_type_aliases().to_owned(); + source.push_str( + r#" +pub type DefaultStage7Transcript = Blake2bTranscript; +pub type Stage7VerifierProgramPlan = Stage7CpuProgramPlan; + +#[derive(Debug)] +pub enum VerifyStage7Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage7Error); +"#, + ); + source + } + + fn emit_constants(&self) -> String { + let mut source = self.emit_shared_constants(); + source.push_str(&self.emit_kernel_constants()); + source.push_str(&self.emit_sumcheck_claim_constants()); + source.push_str(&self.emit_sumcheck_batch_constants()); + source.push_str(&self.emit_sumcheck_driver_constants()); + source.push_str(&self.emit_tail_constants()); + push_format( + &mut source, + format_args!( + "pub const STAGE7_PROGRAM: {} = Stage7CpuProgramPlan {{\n\ + \x20 role: {},\n\ + \x20 params: STAGE7_PARAMS,\n\ + \x20 steps: STAGE7_PROGRAM_STEPS,\n\ + \x20 transcript_squeezes: STAGE7_TRANSCRIPT_SQUEEZES,\n\ + \x20 transcript_absorb_bytes: STAGE7_TRANSCRIPT_ABSORB_BYTES,\n\ + \x20 opening_inputs: STAGE7_OPENING_INPUTS,\n\ + \x20 field_constants: STAGE7_FIELD_CONSTANTS,\n\ + \x20 field_exprs: STAGE7_FIELD_EXPRS,\n\ + \x20 kernels: STAGE7_KERNELS,\n\ + \x20 claims: STAGE7_SUMCHECK_CLAIMS,\n\ + \x20 batches: STAGE7_SUMCHECK_BATCHES,\n\ + \x20 drivers: STAGE7_SUMCHECK_DRIVERS,\n\ + \x20 instance_results: STAGE7_SUMCHECK_INSTANCE_RESULTS,\n\ + \x20 evals: STAGE7_SUMCHECK_EVALS,\n\ + \x20 point_zeros: STAGE7_POINT_ZEROS,\n\ + \x20 point_slices: STAGE7_POINT_SLICES,\n\ + \x20 point_concats: STAGE7_POINT_CONCATS,\n\ + \x20 opening_claims: STAGE7_OPENING_CLAIMS,\n\ + \x20 opening_equalities: STAGE7_OPENING_EQUALITIES,\n\ + \x20 opening_batches: STAGE7_OPENING_BATCHES,\n\ + }};\n", + self.program_plan_type(), + rust_str(self.role_label()) + ), + ); + source + } + + fn emit_shared_constants(&self) -> String { + let mut source = String::new(); + push_format( + &mut source, + format_args!( + "pub const STAGE7_PARAMS: Stage7Params = Stage7Params {{\n\ + \x20 field: {},\n\ + \x20 pcs: {},\n\ + \x20 transcript: {},\n\ + }};\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript) + ), + ); + source.push_str(&self.emit_program_step_constants()); + source.push_str(&self.emit_transcript_squeeze_constants()); + source.push_str(&self.emit_transcript_absorb_bytes_constants()); + source.push_str(&self.emit_opening_input_constants()); + source.push_str(&self.emit_field_constant_constants()); + source.push_str(&self.emit_field_expr_constants()); + source + } + + fn emit_program_step_constants(&self) -> String { + let steps = self + .steps + .iter() + .map(|step| { + format!( + " Stage7ProgramStepPlan {{ kind: {}, symbol: {} }},", + rust_str(&step.kind), + rust_str(&step.symbol), + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE7_PROGRAM_STEPS: &[Stage7ProgramStepPlan] = &[\n{steps}\n];\n\n") + } + + fn emit_transcript_squeeze_constants(&self) -> String { + let squeezes = self + .transcript_squeezes + .iter() + .map(|squeeze| { + format!( + " Stage7TranscriptSqueezePlan {{ symbol: {}, label: {}, kind: {}, count: {} }},", + rust_str(&squeeze.symbol), + rust_str(&squeeze.label), + rust_str(&squeeze.kind), + squeeze.count, + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE7_TRANSCRIPT_SQUEEZES: &[Stage7TranscriptSqueezePlan] = &[\n{squeezes}\n];\n\n" + ) + } + + fn emit_transcript_absorb_bytes_constants(&self) -> String { + let absorbs = self + .transcript_absorb_bytes + .iter() + .map(|absorb| { + format!( + " Stage7TranscriptAbsorbBytesPlan {{ symbol: {}, label: {}, payload: {} }},", + rust_str(&absorb.symbol), + rust_str(&absorb.label), + rust_str(&absorb.payload), + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE7_TRANSCRIPT_ABSORB_BYTES: &[Stage7TranscriptAbsorbBytesPlan] = &[\n{absorbs}\n];\n\n" + ) + } + + fn emit_opening_input_constants(&self) -> String { + let inputs = self + .opening_inputs + .iter() + .map(|input| { + format!( + " Stage7OpeningInputPlan {{ symbol: {}, source_stage: {}, source_claim: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {} }},", + rust_str(&input.symbol), + rust_str(&input.source_stage), + rust_str(&input.source_claim), + rust_str(&input.oracle), + rust_str(&input.domain), + input.point_arity, + rust_str(&input.claim_kind) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE7_OPENING_INPUTS: &[Stage7OpeningInputPlan] = &[\n{inputs}\n];\n\n") + } + + fn emit_field_constant_constants(&self) -> String { + let constants = self + .field_constants + .iter() + .map(|constant| { + format!( + " Stage7FieldConstantPlan {{ symbol: {}, field: {}, value: {} }},", + rust_str(&constant.symbol), + rust_str(&constant.field), + constant.value + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE7_FIELD_CONSTANTS: &[Stage7FieldConstantPlan] = &[\n{constants}\n];\n\n" + ) + } + + fn emit_field_expr_constants(&self) -> String { + if self.role == Role::Verifier { + let rows = self + .field_exprs + .chunks(8) + .map(|chunk| { + let exprs = chunk + .iter() + .map(|expr| { + format!( + "stage7_field_expr!({}, {}, {})", + rust_str(&expr.symbol), + rust_str(&expr.formula), + rust_str(&expr.operands.join("|")) + ) + }) + .collect::>() + .join(", "); + format!(" {exprs},") + }) + .collect::>() + .join("\n"); + return format!( + "macro_rules! stage7_field_expr {{\n ($symbol:literal, $formula:literal, $operands:literal) => {{\n Stage7FieldExprPlan {{ symbol: $symbol, kind: \"op\", formula: $formula, operands: $operands }}\n }};\n}}\n\n#[rustfmt::skip]\npub const STAGE7_FIELD_EXPRS: &[Stage7FieldExprPlan] = &[\n{rows}\n];\n" + ); + } + + let mut source = String::new(); + let mut arrays = Vec::new(); + let mut array_refs = Vec::new(); + for (index, expr) in self.field_exprs.iter().enumerate() { + let operands = intern_str_array( + &mut source, + &mut arrays, + "STAGE7_FIELD_EXPR_OPERANDS", + &expr.operands, + ); + let operand_names = intern_str_array( + &mut source, + &mut arrays, + "STAGE7_FIELD_EXPR_OPERANDS", + &expr.operand_names, + ); + array_refs.push((index, operand_names, operands)); + } + let exprs = self + .field_exprs + .iter() + .enumerate() + .map(|(index, expr)| { + let (_, operand_names, operands) = &array_refs[index]; + format!( + " Stage7FieldExprPlan {{ symbol: {}, kind: {}, formula: {}, operand_names: {operand_names}, operands: {operands} }},", + rust_str(&expr.symbol), + rust_str(&expr.kind), + rust_str(&expr.formula) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE7_FIELD_EXPRS: &[Stage7FieldExprPlan] = &[\n{exprs}\n];\n" + ), + ); + source + } + + fn emit_kernel_constants(&self) -> String { + let kernels = self + .kernels + .iter() + .map(|kernel| { + format!( + " Stage7KernelPlan {{ symbol: {}, relation: {}, kind: {}, backend: {}, abi: {} }},", + rust_str(&kernel.symbol), + rust_str(&kernel.relation), + rust_str(&kernel.kind), + rust_str(&kernel.backend), + rust_str(&kernel.abi) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE7_KERNELS: &[Stage7KernelPlan] = &[\n{kernels}\n];\n\n") + } + + fn emit_sumcheck_claim_constants(&self) -> String { + if self.role == Role::Verifier { + let claims = self + .claims + .iter() + .map(|claim| { + format!( + " Stage7SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: {}, relation: {}, claim_value: {}, input_openings: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_option_str(claim.kernel.as_deref()), + rust_option_str(claim.relation.as_deref()), + rust_str(&claim.claim_value), + rust_str(&claim.input_openings.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE7_SUMCHECK_CLAIMS: &[Stage7SumcheckClaimPlan] = &[\n{claims}\n];\n" + ); + } + + let mut source = String::new(); + for (index, claim) in self.claims.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE7_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS"), + &claim.input_openings, + )); + } + let claims = self + .claims + .iter() + .enumerate() + .map(|(index, claim)| { + format!( + " Stage7SumcheckClaimPlan {{ symbol: {}, stage: {}, domain: {}, num_rounds: {}, degree: {}, claim: {}, kernel: {}, relation: {}, claim_value: {}, input_openings: STAGE7_SUMCHECK_CLAIM_{index}_INPUT_OPENINGS }},", + rust_str(&claim.symbol), + rust_str(&claim.stage), + rust_str(&claim.domain), + claim.num_rounds, + claim.degree, + rust_str(&claim.claim), + rust_option_str(claim.kernel.as_deref()), + rust_option_str(claim.relation.as_deref()), + rust_str(&claim.claim_value) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE7_SUMCHECK_CLAIMS: &[Stage7SumcheckClaimPlan] = &[\n{claims}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE7_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage7SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {}, claim_label: {}, round_label: {}, round_schedule: STAGE7_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")), + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE7_SUMCHECK_BATCHES: &[Stage7SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + return source; + } + + let mut source = String::new(); + for (index, batch) in self.batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE7_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE7_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + source.push_str(&emit_usize_array( + &format!("STAGE7_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE"), + &batch.round_schedule, + )); + } + let batches = self + .batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage7SumcheckBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE7_SUMCHECK_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE7_SUMCHECK_BATCH_{index}_CLAIM_OPERANDS, claim_label: {}, round_label: {}, round_schedule: STAGE7_SUMCHECK_BATCH_{index}_ROUND_SCHEDULE }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.claim_label), + rust_str(&batch.round_label) + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE7_SUMCHECK_BATCHES: &[Stage7SumcheckBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_sumcheck_driver_constants(&self) -> String { + let mut source = String::new(); + for (index, driver) in self.drivers.iter().enumerate() { + source.push_str(&emit_usize_array( + &format!("STAGE7_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE"), + &driver.round_schedule, + )); + } + let drivers = self + .drivers + .iter() + .enumerate() + .map(|(index, driver)| { + format!( + " Stage7SumcheckDriverPlan {{ symbol: {}, stage: {}, proof_slot: {}, kernel: {}, relation: {}, batch: {}, policy: {}, round_schedule: STAGE7_SUMCHECK_DRIVER_{index}_ROUND_SCHEDULE, claim_label: {}, round_label: {}, num_rounds: {}, degree: {} }},", + rust_str(&driver.symbol), + rust_str(&driver.stage), + rust_str(&driver.proof_slot), + rust_option_str(driver.kernel.as_deref()), + rust_option_str(driver.relation.as_deref()), + rust_str(&driver.batch), + rust_str(&driver.policy), + rust_str(&driver.claim_label), + rust_str(&driver.round_label), + driver.num_rounds, + driver.degree + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE7_SUMCHECK_DRIVERS: &[Stage7SumcheckDriverPlan] = &[\n{drivers}\n];\n" + ), + ); + source + } + + fn emit_tail_constants(&self) -> String { + let mut source = String::new(); + source.push_str(&self.emit_sumcheck_instance_result_constants()); + source.push_str(&self.emit_sumcheck_eval_constants()); + source.push_str(&self.emit_point_zero_constants()); + source.push_str(&self.emit_point_slice_constants()); + source.push_str(&self.emit_point_concat_constants()); + source.push_str(&self.emit_opening_claim_constants()); + source.push_str(&self.emit_opening_claim_equality_constants()); + source.push_str(&self.emit_opening_batch_constants()); + source + } + + fn emit_sumcheck_instance_result_constants(&self) -> String { + let instances = self + .instance_results + .iter() + .map(|instance| { + format!( + " Stage7SumcheckInstanceResultPlan {{ symbol: {}, source: {}, claim: {}, relation: {}, index: {}, point_arity: {}, num_rounds: {}, round_offset: {}, point_order: {}, degree: {} }},", + rust_str(&instance.symbol), + rust_str(&instance.source), + rust_str(&instance.claim), + rust_str(&instance.relation), + instance.index, + instance.point_arity, + instance.num_rounds, + instance.round_offset, + rust_str(&instance.point_order), + instance.degree + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE7_SUMCHECK_INSTANCE_RESULTS: &[Stage7SumcheckInstanceResultPlan] = &[\n{instances}\n];\n\n" + ) + } + + fn emit_sumcheck_eval_constants(&self) -> String { + let rows = self + .evals + .chunks(4) + .map(|chunk| { + let evals = chunk + .iter() + .map(|eval| { + format!( + "stage7_sumcheck_eval!({}, {}, {}, {}, {})", + rust_str(&eval.symbol), + rust_str(&eval.source), + rust_str(&eval.name), + eval.index, + rust_str(&eval.oracle) + ) + }) + .collect::>() + .join(", "); + format!(" {evals},") + }) + .collect::>() + .join("\n"); + format!( + "macro_rules! stage7_sumcheck_eval {{\n ($symbol:literal, $source:literal, $name:literal, $index:literal, $oracle:literal) => {{\n Stage7SumcheckEvalPlan {{ symbol: $symbol, source: $source, name: $name, index: $index, oracle: $oracle }}\n }};\n}}\n\n#[rustfmt::skip]\npub const STAGE7_SUMCHECK_EVALS: &[Stage7SumcheckEvalPlan] = &[\n{rows}\n];\n\n" + ) + } + + fn emit_point_zero_constants(&self) -> String { + let zeros = self + .point_zeros + .iter() + .map(|zero| { + format!( + " Stage7PointZeroPlan {{ symbol: {}, field: {}, arity: {} }},", + rust_str(&zero.symbol), + rust_str(&zero.field), + zero.arity + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE7_POINT_ZEROS: &[Stage7PointZeroPlan] = &[\n{zeros}\n];\n\n") + } + + fn emit_point_slice_constants(&self) -> String { + let slices = self + .point_slices + .iter() + .map(|slice| { + format!( + " Stage7PointSlicePlan {{ symbol: {}, source: {}, offset: {}, length: {}, input: {} }},", + rust_str(&slice.symbol), + rust_str(&slice.source), + slice.offset, + slice.length, + rust_str(&slice.input) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE7_POINT_SLICES: &[Stage7PointSlicePlan] = &[\n{slices}\n];\n\n") + } + + fn emit_point_concat_constants(&self) -> String { + if self.role == Role::Verifier { + let concats = self + .point_concats + .iter() + .map(|concat| { + format!( + " Stage7PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: {} }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity, + rust_str(&concat.inputs.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE7_POINT_CONCATS: &[Stage7PointConcatPlan] = &[\n{concats}\n];\n" + ); + } + + let mut source = String::new(); + for (index, concat) in self.point_concats.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE7_POINT_CONCAT_{index}_INPUTS"), + &concat.inputs, + )); + } + let concats = self + .point_concats + .iter() + .enumerate() + .map(|(index, concat)| { + format!( + " Stage7PointConcatPlan {{ symbol: {}, layout: {}, arity: {}, inputs: STAGE7_POINT_CONCAT_{index}_INPUTS }},", + rust_str(&concat.symbol), + rust_str(&concat.layout), + concat.arity + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE7_POINT_CONCATS: &[Stage7PointConcatPlan] = &[\n{concats}\n];\n" + ), + ); + source + } + + fn emit_opening_claim_constants(&self) -> String { + let claims = self + .opening_claims + .iter() + .map(|claim| { + format!( + " Stage7OpeningClaimPlan {{ symbol: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {}, point_source: {}, eval_source: {} }},", + rust_str(&claim.symbol), + rust_str(&claim.oracle), + rust_str(&claim.domain), + claim.point_arity, + rust_str(&claim.claim_kind), + rust_str(&claim.point_source), + rust_str(&claim.eval_source) + ) + }) + .collect::>() + .join("\n"); + format!("pub const STAGE7_OPENING_CLAIMS: &[Stage7OpeningClaimPlan] = &[\n{claims}\n];\n\n") + } + + fn emit_opening_claim_equality_constants(&self) -> String { + let equalities = self + .opening_equalities + .iter() + .map(|equality| { + format!( + " Stage7OpeningClaimEqualityPlan {{ symbol: {}, mode: {}, lhs: {}, rhs: {} }},", + rust_str(&equality.symbol), + rust_str(&equality.mode), + rust_str(&equality.lhs), + rust_str(&equality.rhs) + ) + }) + .collect::>() + .join("\n"); + format!( + "pub const STAGE7_OPENING_EQUALITIES: &[Stage7OpeningClaimEqualityPlan] = &[\n{equalities}\n];\n\n" + ) + } + + fn emit_opening_batch_constants(&self) -> String { + if self.role == Role::Verifier { + let batches = self + .opening_batches + .iter() + .map(|batch| { + format!( + " Stage7OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: {}, claim_operands: {} }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + rust_str(&batch.ordered_claims.join("|")), + rust_str(&batch.claim_operands.join("|")) + ) + }) + .collect::>() + .join("\n"); + return format!( + "pub const STAGE7_OPENING_BATCHES: &[Stage7OpeningBatchPlan] = &[\n{batches}\n];\n" + ); + } + + let mut source = String::new(); + for (index, batch) in self.opening_batches.iter().enumerate() { + source.push_str(&emit_str_array( + &format!("STAGE7_OPENING_BATCH_{index}_ORDERED_CLAIMS"), + &batch.ordered_claims, + )); + source.push_str(&emit_str_array( + &format!("STAGE7_OPENING_BATCH_{index}_CLAIM_OPERANDS"), + &batch.claim_operands, + )); + } + let batches = self + .opening_batches + .iter() + .enumerate() + .map(|(index, batch)| { + format!( + " Stage7OpeningBatchPlan {{ symbol: {}, stage: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE7_OPENING_BATCH_{index}_ORDERED_CLAIMS, claim_operands: STAGE7_OPENING_BATCH_{index}_CLAIM_OPERANDS }},", + rust_str(&batch.symbol), + rust_str(&batch.stage), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count + ) + }) + .collect::>() + .join("\n"); + push_format( + &mut source, + format_args!( + "pub const STAGE7_OPENING_BATCHES: &[Stage7OpeningBatchPlan] = &[\n{batches}\n];\n" + ), + ); + source + } + + fn emit_entrypoint(&self) -> &'static str { + match self.role { + Role::Prover => { + "pub fn execute_stage7_prover(\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage7KernelError>\n\ + where\n\ + \x20 E: Stage7KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage7_prover_with_program(&STAGE7_PROGRAM, executor, transcript)\n\ + }\n\ + \n\ + pub fn execute_stage7_prover_with_program(\n\ + \x20 program: &'static Stage7CpuProgramPlan,\n\ + \x20 executor: &mut E,\n\ + \x20 transcript: &mut T,\n\ + ) -> Result, Stage7KernelError>\n\ + where\n\ + \x20 E: Stage7KernelExecutor,\n\ + \x20 T: Transcript,\n\ + {\n\ + \x20 execute_stage7_program(program, Stage7ExecutionMode::Prover, executor, transcript)\n\ + }\n" + } + Role::Verifier => { + r#"pub fn verify_stage7( + proof: &Stage7Proof, + opening_inputs: &[Stage7OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage7Error> +where + T: Transcript, +{ + verify_stage7_with_program(&STAGE7_PROGRAM, proof, opening_inputs, transcript) +} + +pub fn verify_stage7_with_program( + program: &'static Stage7VerifierProgramPlan, + proof: &Stage7Proof, + opening_inputs: &[Stage7OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage7Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage7Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + store.seed_point_zeros(program.point_zeros); + let mut artifacts = Stage7ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage7Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage7_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "transcript_absorb_bytes" => { + let absorb = find_plan(program.transcript_absorb_bytes, step.symbol).ok_or( + VerifyStage7Error::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage7_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage7Error::MissingProof { + driver: step.symbol, + })?; + verify_stage7_driver( + program, + driver, + proof, + &mut store, + transcript, + &mut artifacts, + )?; + } + _ => { + return Err(VerifyStage7Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage7 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage7_verifier_program() -> &'static Stage7VerifierProgramPlan { + &STAGE7_PROGRAM +} + +fn verify_stage7_squeeze( + program: &'static Stage7VerifierProgramPlan, + squeeze: &'static Stage7TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage7ExecutionArtifacts, +) -> Result<(), VerifyStage7Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage7Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage7Error::from)?; + artifacts.challenge_vectors.push(Stage7ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn absorb_stage7_bytes(absorb: &'static Stage7TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn verify_stage7_driver( + program: &'static Stage7VerifierProgramPlan, + driver: &'static Stage7SumcheckDriverPlan, + proof: &Stage7Proof, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage7ExecutionArtifacts, +) -> Result<(), VerifyStage7Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage7Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage7.batched" => { + verify_batched_stage7(program, driver, proof, store, transcript)? + } + _ => return Err(VerifyStage7Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage7( + program: &'static Stage7VerifierProgramPlan, + driver: &'static Stage7SumcheckDriverPlan, + proof: &Stage7SumcheckOutput, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage7Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim(program, driver, store, evals, point, batching_coeffs) + }, + |store, verified| observe_stage7_sumcheck_output(program, store, verified), + |driver, error| VerifyStage7Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage7_sumcheck_output( + program: &'static Stage7VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage7SumcheckOutput, +) -> Result<(), VerifyStage7Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "bytecode_read_raf" => point = normalize_bytecode_read_raf_point(&point, stage7_trace_rounds(program)?, "stage7.bytecode_read_raf.point")?, + "stage7_booleanity" => {} + "instruction_read_raf" => point = normalize_instruction_read_raf_point(&point, "stage7.instruction_read_raf.point")?, + _ => { + return Err(VerifyStage7Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage7Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage7Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage7Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage7Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage7Error::InvalidProof { driver, reason }, + |symbol| VerifyStage7Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage7VerifierProgramPlan, + driver: &'static Stage7SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage7NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage7Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage7Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(""); + let value = match relation { + "jolt.stage7.hamming_weight_claim_reduction" => { + expected_hamming_weight_claim_reduction(program, driver, store, evals, local_point)? + } + _ => return Err(VerifyStage7Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_hamming_weight_claim_reduction( + program: &'static Stage7VerifierProgramPlan, + driver: &'static Stage7SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage7NamedEval], + local_point: &[Fr], +) -> Result { + let rho_rev = reverse_slice(local_point); + let booleanity_point = super::common::store_point(store, "stage7.input.stage6.booleanity.InstructionRa_0")?; + let r_addr_bool = + booleanity_point + .get(..local_point.len()) + .ok_or(VerifyStage7Error::InvalidInputLength { + input: "stage7.input.stage6.booleanity.InstructionRa_0", + expected: local_point.len(), + actual: booleanity_point.len(), + })?; + let eq_bool = EqPolynomial::::mle(&rho_rev, r_addr_bool); + let gamma = super::common::store_scalar(store, "stage7.hamming_weight_claim_reduction.gamma")?; + let mut gamma_power = Fr::from_u64(1); + let mut expected = Fr::from_u64(0); + let mut eval_plans = program + .evals + .iter() + .filter(|eval| eval.source == driver.symbol) + .collect::>(); + eval_plans.sort_by_key(|eval| eval.index); + for eval_plan in eval_plans { + let g_i = eval_by_name(evals, eval_plan.name)?; + let virt_point = + stage7_virtualization_point(store, eval_plan.oracle, local_point.len())?; + let eq_virt = EqPolynomial::::mle(&rho_rev, virt_point); + expected += g_i * (gamma_power + gamma_power * gamma * eq_bool + + gamma_power * gamma.square() * eq_virt); + gamma_power *= gamma; + gamma_power *= gamma; + gamma_power *= gamma; + } + Ok(expected) +} + +fn stage7_virtualization_point<'a>( + store: &'a super::common::ValueStore, + oracle: &str, + log_k_chunk: usize, +) -> Result<&'a [Fr], VerifyStage7Error> { + let symbol = if oracle.starts_with("InstructionRa_") { + format!("stage7.input.stage6.instruction_ra_virtual.{oracle}") + } else if oracle.starts_with("BytecodeRa_") { + format!("stage7.input.stage6.bytecode_read_raf.{oracle}") + } else if oracle.starts_with("RamRa_") { + format!("stage7.input.stage6.ram_ra_virtual.{oracle}") + } else { + return Err(VerifyStage7Error::MissingValue { + symbol: "stage7.hamming_weight_claim_reduction.oracle", + }); + }; + let point = store.try_point(&symbol).ok_or(VerifyStage7Error::MissingValue { + symbol: "stage7.hamming_weight_claim_reduction.virtualization_point", + })?; + point + .get(..log_k_chunk) + .ok_or(VerifyStage7Error::InvalidInputLength { + input: "stage7.hamming_weight_claim_reduction.virtualization_point", + expected: log_k_chunk, + actual: point.len(), + }) +} + +fn stage7_trace_rounds( + program: &'static Stage7VerifierProgramPlan, +) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.relation == "jolt.stage7.hamming_booleanity") + .map(|instance| instance.num_rounds) + .ok_or(VerifyStage7Error::MissingValue { + symbol: "stage7.hamming_booleanity.instance", + }) +} +"# + } + } + } + + fn role_label(&self) -> &'static str { + match self.role { + Role::Prover => "prover", + Role::Verifier => "verifier", + } + } + + fn program_plan_type(&self) -> &'static str { + match self.role { + Role::Prover => "Stage7CpuProgramPlan", + Role::Verifier => "Stage7VerifierProgramPlan", + } + } +} + +fn require_supported_symbol(kind: &str, actual: &str, expected: &str) -> Result<(), EmitError> { + if actual == expected { + Ok(()) + } else { + Err(EmitError::new(format!( + "unsupported {kind} @{actual}; expected @{expected}" + ))) + } +} + +fn emit_str_array(name: &str, values: &[String]) -> String { + if values.is_empty() { + return format!("pub const {name}: &[&str] = &[];\n\n"); + } + if let [value] = values { + return format!("pub const {name}: &[&str] = &[{}];\n\n", rust_str(value)); + } + let entries = values + .iter() + .map(|value| format!(" {},", rust_str(value))) + .collect::>() + .join("\n"); + format!("pub const {name}: &[&str] = &[\n{entries}\n];\n\n") +} + +fn emit_usize_array(name: &str, values: &[usize]) -> String { + let entries = values + .iter() + .map(|value| format!(" {value},")) + .collect::>() + .join("\n"); + format!("pub const {name}: &[usize] = &[\n{entries}\n];\n\n") +} + +fn intern_str_array( + source: &mut String, + arrays: &mut Vec<(Vec, String)>, + name_prefix: &str, + values: &[String], +) -> String { + if let Some((_, name)) = arrays + .iter() + .find(|(existing, _)| existing.as_slice() == values) + { + return name.clone(); + } + let name = format!("{name_prefix}_{}", arrays.len()); + source.push_str(&emit_str_array(&name, values)); + arrays.push((values.to_vec(), name.clone())); + name +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn rust_option_str(value: Option<&str>) -> String { + value.map_or_else( + || "None".to_owned(), + |value| format!("Some({})", rust_str(value)), + ) +} + +fn verify_count(kind: &str, symbol: &str, expected: usize, actual: usize) -> Result<(), EmitError> { + if expected == actual { + Ok(()) + } else { + Err(EmitError::new(format!( + "{kind} @{symbol} count mismatch: expected {expected}, got {actual}" + ))) + } +} + +fn symbols<'a>(values: impl Iterator) -> BTreeSet { + values.cloned().collect() +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn int_array_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result, EmitError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "integer array"))?; + parse_int_array(&attribute).ok_or_else(|| attr_error(operation, attr, "integer array")) +} + +fn parse_int_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().parse().ok()) + .collect() +} + +fn operand_symbols( + operation: OperationRef<'_, '_>, + start_index: usize, +) -> Result, EmitError> { + (start_index..operation.operand_count()) + .map(|index| operand_symbol(operation, index)) + .collect() +} + +fn operand_symbol(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + EmitError::new(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + EmitError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name<'c: 'a, 'a>(operation: impl OperationLike<'c, 'a>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} diff --git a/crates/bolt/src/protocols/jolt/emit/rust/stage8.rs b/crates/bolt/src/protocols/jolt/emit/rust/stage8.rs new file mode 100644 index 0000000000..9914b4b4cf --- /dev/null +++ b/crates/bolt/src/protocols/jolt/emit/rust/stage8.rs @@ -0,0 +1,516 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::OperationRef; + +use crate::emit::rust::{push_format, EmitError, RustSourceFile}; +use crate::ir::{string_attribute_value, symbol_attribute_value, BoltModule, Cpu, Role}; +use crate::schema::verify_cpu_schema; + +const EVALUATION_POINT_SOURCE_SYMBOL: &str = "stage8.evaluation.point_source"; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage8CpuProgram { + pub role: Role, + pub params: Stage8Params, + pub function: String, + pub opening_inputs: Vec, + pub opening_claims: Vec, + pub opening_batches: Vec, + pub pcs_proofs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage8Params { + pub field: String, + pub pcs: String, + pub transcript: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage8OpeningInputPlan { + pub symbol: String, + pub source_stage: String, + pub source_claim: String, + pub oracle: String, + pub domain: String, + pub point_arity: usize, + pub claim_kind: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage8OpeningClaimPlan { + pub symbol: String, + pub oracle: String, + pub family: String, + pub domain: String, + pub point_arity: usize, + pub point_source: String, + pub eval_source: String, + pub source_stage: String, + pub source_claim: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage8OpeningBatchPlan { + pub symbol: String, + pub proof_slot: String, + pub policy: String, + pub count: usize, + pub ordered_claims: Vec, + pub claim_operands: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage8PcsProofPlan { + pub symbol: String, + pub mode: String, + pub pcs: String, + pub proof_slot: String, + pub transcript_label: String, + pub batch: String, +} + +pub fn stage8_cpu_program(module: &BoltModule<'_, Cpu>) -> Result { + verify_cpu_schema(module)?; + let program = Stage8CpuProgram::from_module(module)?; + program.verify_supported_target()?; + Ok(program) +} + +pub fn emit_stage8_rust(module: &BoltModule<'_, Cpu>) -> Result { + let program = stage8_cpu_program(module)?; + Ok(RustSourceFile { + filename: program.filename().to_owned(), + source: program.emit_source()?, + }) +} + +impl Stage8CpuProgram { + fn from_module(module: &BoltModule<'_, Cpu>) -> Result { + let role = module + .role() + .ok_or_else(|| EmitError::new("stage8 CPU module missing role"))?; + let mut params = None; + let mut function = None; + let mut opening_inputs = Vec::new(); + let mut opening_claims = Vec::new(); + let mut opening_batches = Vec::new(); + let mut pcs_proofs = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "cpu.params" => { + params = Some(Stage8Params { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + "cpu.function" => { + function = Some(string_attr(op, "sym_name")?); + } + "cpu.opening_input" => { + opening_inputs.push(Stage8OpeningInputPlan { + symbol: string_attr(op, "sym_name")?, + source_stage: symbol_attr(op, "source_stage")?, + source_claim: symbol_attr(op, "source_claim")?, + oracle: symbol_attr(op, "oracle")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + claim_kind: string_attr(op, "claim_kind")?, + }); + } + "cpu.pcs_opening_claim" => { + opening_claims.push(Stage8OpeningClaimPlan { + symbol: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + family: symbol_attr(op, "family")?, + domain: symbol_attr(op, "domain")?, + point_arity: int_attr(op, "point_arity")?, + point_source: operand_symbol(op, 0)?, + eval_source: operand_symbol(op, 1)?, + source_stage: String::new(), + source_claim: String::new(), + }); + } + "cpu.pcs_opening_batch" => { + opening_batches.push(Stage8OpeningBatchPlan { + symbol: string_attr(op, "sym_name")?, + proof_slot: symbol_attr(op, "proof_slot")?, + policy: string_attr(op, "policy")?, + count: int_attr(op, "count")?, + ordered_claims: symbol_array_attr(op, "ordered_claims")?, + claim_operands: operand_symbols(op, 0)?, + }); + } + "cpu.pcs_batch_open" | "cpu.pcs_batch_verify" => { + let mode = match operation_name(op).as_str() { + "cpu.pcs_batch_open" => "open", + "cpu.pcs_batch_verify" => "verify", + _ => unreachable!(), + }; + pcs_proofs.push(Stage8PcsProofPlan { + symbol: string_attr(op, "sym_name")?, + mode: mode.to_owned(), + pcs: symbol_attr(op, "pcs")?, + proof_slot: symbol_attr(op, "proof_slot")?, + transcript_label: string_attr(op, "transcript_label")?, + batch: operand_symbol(op, 1)?, + }); + } + _ => {} + } + } + + let input_by_symbol = opening_inputs + .iter() + .map(|input| (input.symbol.as_str(), input)) + .collect::>(); + for claim in &mut opening_claims { + let input = input_by_symbol + .get(claim.point_source.as_str()) + .ok_or_else(|| { + EmitError::new(format!( + "stage8 opening claim `{}` references missing point source `{}`", + claim.symbol, claim.point_source + )) + })?; + claim.source_stage = input.source_stage.clone(); + claim.source_claim = input.source_claim.clone(); + } + + Ok(Self { + role, + params: params.ok_or_else(|| EmitError::new("stage8 program missing cpu.params"))?, + function: function + .ok_or_else(|| EmitError::new("stage8 program missing cpu.function"))?, + opening_inputs, + opening_claims, + opening_batches, + pcs_proofs, + }) + } + + fn verify_supported_target(&self) -> Result<(), EmitError> { + if self.function != "jolt.stage8" { + return Err(EmitError::new(format!( + "stage8 emitter expected function `jolt.stage8`, got `{}`", + self.function + ))); + } + if self.opening_batches.len() != 1 { + return Err(EmitError::new(format!( + "stage8 emitter expects one PCS opening batch, got {}", + self.opening_batches.len() + ))); + } + if self.pcs_proofs.len() != 1 { + return Err(EmitError::new(format!( + "stage8 emitter expects one PCS proof op, got {}", + self.pcs_proofs.len() + ))); + } + let expected_mode = match self.role { + Role::Prover => "open", + Role::Verifier => "verify", + }; + if self.pcs_proofs[0].mode != expected_mode { + return Err(EmitError::new(format!( + "stage8 {} artifact expected PCS mode `{expected_mode}`, got `{}`", + self.role, self.pcs_proofs[0].mode + ))); + } + let batch = &self.opening_batches[0]; + if batch.count != self.opening_claims.len() { + return Err(EmitError::new(format!( + "stage8 opening batch count {} does not match {} opening claims", + batch.count, + self.opening_claims.len() + ))); + } + if batch.ordered_claims != batch.claim_operands { + return Err(EmitError::new( + "stage8 opening batch ordered claims do not match SSA operands", + )); + } + if !self + .opening_inputs + .iter() + .any(|input| input.symbol == EVALUATION_POINT_SOURCE_SYMBOL) + { + return Err(EmitError::new(format!( + "stage8 program missing `{EVALUATION_POINT_SOURCE_SYMBOL}` opening-point source" + ))); + } + let input_symbols = self + .opening_inputs + .iter() + .map(|input| input.symbol.as_str()) + .collect::>(); + for claim in &self.opening_claims { + if !input_symbols.contains(claim.point_source.as_str()) { + return Err(EmitError::new(format!( + "stage8 claim `{}` point source `{}` is not an opening input", + claim.symbol, claim.point_source + ))); + } + if claim.point_source != claim.eval_source { + return Err(EmitError::new(format!( + "stage8 claim `{}` must take point and eval from the same opening input", + claim.symbol + ))); + } + } + Ok(()) + } + + fn filename(&self) -> &'static str { + match self.role { + Role::Prover => "prove_stage8.rs", + Role::Verifier => "verify_stage8.rs", + } + } + + fn emit_source(&self) -> Result { + let mut source = String::new(); + source.push_str("#![allow(clippy::too_many_lines)]\n\n"); + source.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n"); + source.push_str( + "pub struct Stage8Params {\n pub field: &'static str,\n pub pcs: &'static str,\n pub transcript: &'static str,\n}\n\n", + ); + source.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n"); + source.push_str( + "pub struct Stage8OpeningInputPlan {\n pub symbol: &'static str,\n pub source_stage: &'static str,\n pub source_claim: &'static str,\n pub oracle: &'static str,\n pub domain: &'static str,\n pub point_arity: usize,\n pub claim_kind: &'static str,\n}\n\n", + ); + source.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n"); + source.push_str( + "pub struct Stage8OpeningClaimPlan {\n pub symbol: &'static str,\n pub oracle: &'static str,\n pub family: &'static str,\n pub domain: &'static str,\n pub point_arity: usize,\n pub point_source: &'static str,\n pub eval_source: &'static str,\n pub source_stage: &'static str,\n pub source_claim: &'static str,\n}\n\n", + ); + source.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n"); + source.push_str( + "pub struct Stage8OpeningBatchPlan {\n pub symbol: &'static str,\n pub proof_slot: &'static str,\n pub policy: &'static str,\n pub count: usize,\n pub ordered_claims: &'static [&'static str],\n}\n\n", + ); + source.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n"); + source.push_str( + "pub struct Stage8PcsProofPlan {\n pub symbol: &'static str,\n pub mode: &'static str,\n pub pcs: &'static str,\n pub proof_slot: &'static str,\n pub transcript_label: &'static str,\n pub batch: &'static str,\n}\n\n", + ); + source.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n"); + source.push_str( + "pub struct Stage8EvaluationProgramPlan {\n pub role: &'static str,\n pub function: &'static str,\n pub params: Stage8Params,\n pub evaluation_point_source: Stage8OpeningInputPlan,\n pub opening_inputs: &'static [Stage8OpeningInputPlan],\n pub opening_claims: &'static [Stage8OpeningClaimPlan],\n pub opening_batch: Stage8OpeningBatchPlan,\n pub pcs_proof: Stage8PcsProofPlan,\n}\n\n", + ); + + push_format( + &mut source, + format_args!( + "pub const STAGE8_PARAMS: Stage8Params = Stage8Params {{ field: {}, pcs: {}, transcript: {} }};\n\n", + rust_str(&self.params.field), + rust_str(&self.params.pcs), + rust_str(&self.params.transcript), + ), + ); + let point_source = self + .opening_inputs + .iter() + .find(|input| input.symbol == EVALUATION_POINT_SOURCE_SYMBOL) + .ok_or_else(|| { + EmitError::new(format!( + "evaluation program missing `{EVALUATION_POINT_SOURCE_SYMBOL}` opening-point source" + )) + })?; + push_format( + &mut source, + format_args!( + "pub const STAGE8_EVALUATION_POINT_SOURCE: Stage8OpeningInputPlan = {};\n\n", + opening_input_literal(point_source), + ), + ); + source.push_str("pub const STAGE8_OPENING_INPUTS: &[Stage8OpeningInputPlan] = &[\n"); + for input in &self.opening_inputs { + push_format( + &mut source, + format_args!(" {},\n", opening_input_literal(input)), + ); + } + source.push_str("];\n\n"); + source.push_str("pub const STAGE8_OPENING_CLAIMS: &[Stage8OpeningClaimPlan] = &[\n"); + for claim in &self.opening_claims { + push_format( + &mut source, + format_args!( + " Stage8OpeningClaimPlan {{ symbol: {}, oracle: {}, family: {}, domain: {}, point_arity: {}, point_source: {}, eval_source: {}, source_stage: {}, source_claim: {} }},\n", + rust_str(&claim.symbol), + rust_str(&claim.oracle), + rust_str(&claim.family), + rust_str(&claim.domain), + claim.point_arity, + rust_str(&claim.point_source), + rust_str(&claim.eval_source), + rust_str(&claim.source_stage), + rust_str(&claim.source_claim), + ), + ); + } + source.push_str("];\n\n"); + let batch = &self.opening_batches[0]; + push_format( + &mut source, + format_args!( + "pub const STAGE8_OPENING_BATCH_ORDERED_CLAIMS: &[&str] = &{};\n\n", + rust_str_array(&batch.ordered_claims), + ), + ); + push_format( + &mut source, + format_args!( + "pub const STAGE8_OPENING_BATCH: Stage8OpeningBatchPlan = Stage8OpeningBatchPlan {{ symbol: {}, proof_slot: {}, policy: {}, count: {}, ordered_claims: STAGE8_OPENING_BATCH_ORDERED_CLAIMS }};\n\n", + rust_str(&batch.symbol), + rust_str(&batch.proof_slot), + rust_str(&batch.policy), + batch.count, + ), + ); + let proof = &self.pcs_proofs[0]; + push_format( + &mut source, + format_args!( + "pub const STAGE8_PCS_PROOF: Stage8PcsProofPlan = Stage8PcsProofPlan {{ symbol: {}, mode: {}, pcs: {}, proof_slot: {}, transcript_label: {}, batch: {} }};\n\n", + rust_str(&proof.symbol), + rust_str(&proof.mode), + rust_str(&proof.pcs), + rust_str(&proof.proof_slot), + rust_str(&proof.transcript_label), + rust_str(&proof.batch), + ), + ); + push_format( + &mut source, + format_args!( + "pub const STAGE8_PROGRAM: Stage8EvaluationProgramPlan = Stage8EvaluationProgramPlan {{\n role: {},\n function: {},\n params: STAGE8_PARAMS,\n evaluation_point_source: STAGE8_EVALUATION_POINT_SOURCE,\n opening_inputs: STAGE8_OPENING_INPUTS,\n opening_claims: STAGE8_OPENING_CLAIMS,\n opening_batch: STAGE8_OPENING_BATCH,\n pcs_proof: STAGE8_PCS_PROOF,\n}};\n", + rust_str(self.role.as_str()), + rust_str(&self.function), + ), + ); + Ok(source) + } +} + +fn opening_input_literal(input: &Stage8OpeningInputPlan) -> String { + format!( + "Stage8OpeningInputPlan {{ symbol: {}, source_stage: {}, source_claim: {}, oracle: {}, domain: {}, point_arity: {}, claim_kind: {} }}", + rust_str(&input.symbol), + rust_str(&input.source_stage), + rust_str(&input.source_claim), + rust_str(&input.oracle), + rust_str(&input.domain), + input.point_arity, + rust_str(&input.claim_kind), + ) +} + +fn rust_str(value: &str) -> String { + format!("{value:?}") +} + +fn rust_str_array(values: &[String]) -> String { + let values = values + .iter() + .map(|value| rust_str(value)) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +fn symbol_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol reference")) +} + +fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + let value = operation + .attribute(attr) + .ok() + .and_then(|attr| attr.to_string().strip_suffix(" : i64").map(str::to_owned)) + .ok_or_else(|| attr_error(operation, attr, "integer"))?; + value + .parse() + .map_err(|_| attr_error(operation, attr, "integer")) +} + +fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, EmitError> { + let value = operation + .attribute(attr) + .ok() + .map(|attr| attr.to_string()) + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&value).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(str::to_owned)) + .collect() +} + +fn operand_symbols( + operation: OperationRef<'_, '_>, + start_index: usize, +) -> Result, EmitError> { + (start_index..operation.operand_count()) + .map(|index| operand_symbol(operation, index)) + .collect() +} + +fn operand_symbol(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + EmitError::new(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + EmitError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> EmitError { + EmitError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +fn operation_name<'c: 'a, 'a>(operation: impl OperationLike<'c, 'a>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} diff --git a/crates/bolt/src/protocols/jolt/mod.rs b/crates/bolt/src/protocols/jolt/mod.rs new file mode 100644 index 0000000000..91da996a0e --- /dev/null +++ b/crates/bolt/src/protocols/jolt/mod.rs @@ -0,0 +1,41 @@ +pub mod artifacts; +pub mod emit; +pub mod oracles; +pub mod params; +pub mod phases; +pub mod validate; + +pub use artifacts::{ + assemble_jolt_generated_crates, assemble_jolt_workspace_generated_crates, jolt_artifact_config, + jolt_rust_artifact, validate_jolt_rust_artifact_imports, write_jolt_generated_crates, + JoltArtifactCrate, JoltGeneratedCrate, JoltGeneratedFile, JoltProtocolStage, JoltRustArtifact, +}; +pub use emit::rust::{ + commitment_cpu_program, emit_commitment_rust, emit_stage1_rust, emit_stage2_rust, + emit_stage3_rust, emit_stage4_rust, emit_stage5_rust, emit_stage6_rust, emit_stage7_rust, + emit_stage8_rust, stage1_cpu_program, stage2_cpu_program, stage3_cpu_program, + stage4_cpu_program, stage5_cpu_program, stage6_cpu_program, stage7_cpu_program, + stage8_cpu_program, CommitmentBatchPlan, CommitmentCpuProgram, CommitmentParams, + OptionalCommitmentPlan, OptionalSkipPolicy, OracleGeneration, OraclePlan, Stage1CpuProgram, + Stage1KernelPlan, Stage1OpeningBatchPlan, Stage1OpeningClaimPlan, Stage1Params, + Stage1SumcheckBatchPlan, Stage1SumcheckClaimPlan, Stage1SumcheckDriverPlan, + Stage1SumcheckEvalPlan, Stage2CpuProgram, Stage3CpuProgram, Stage4CpuProgram, Stage5CpuProgram, + Stage6CpuProgram, Stage7CpuProgram, Stage8CpuProgram, TranscriptStep, +}; +pub use params::JoltProtocolParams; +pub use phases::commitment::{ + build_commitment_protocol, lower_commitment_to_compute, lower_compute_to_cpu, +}; +pub use phases::stage1::{ + build_stage1_outer_protocol, lower_stage1_to_compute, resolve_compute_kernels, +}; +pub use phases::stage2::{build_stage2_protocol, lower_stage2_to_compute}; +pub use phases::stage3::{build_stage3_protocol, lower_stage3_to_compute}; +pub use phases::stage4::{build_stage4_protocol, lower_stage4_to_compute}; +pub use phases::stage5::{build_stage5_protocol, lower_stage5_to_compute}; +pub use phases::stage6::{build_stage6_protocol, lower_stage6_to_compute}; +pub use phases::stage7::{build_stage7_protocol, lower_stage7_to_compute}; +pub use phases::stage8::{build_stage8_protocol, lower_stage8_to_compute}; +pub use validate::{ + verify_jolt_concrete_schema, verify_jolt_party_schema, verify_jolt_protocol_schema, +}; diff --git a/crates/bolt/src/protocols/jolt/oracles.rs b/crates/bolt/src/protocols/jolt/oracles.rs new file mode 100644 index 0000000000..46e66304b2 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/oracles.rs @@ -0,0 +1,227 @@ +use crate::ir::{BoltModule, Protocol}; +use crate::mlir::{MeliorContext, MlirError}; + +use super::params::JoltProtocolParams; + +pub const FIELD_SYMBOL: &str = "bn254_fr"; +pub const HASH_SYMBOL: &str = "blake2b"; +pub const TRANSCRIPT_SYMBOL: &str = "blake2b_transcript"; +pub const PCS_SYMBOL: &str = "dory"; +pub const TRACE_DOMAIN_SYMBOL: &str = "jolt.trace_domain"; +pub const MAIN_WITNESS_COMMIT_DOMAIN_SYMBOL: &str = "jolt.main_witness_commit_domain"; +pub const MAIN_WITNESS_FAMILY_SYMBOL: &str = "jolt.main_witness_polys"; +pub const ADVICE_FAMILY_SYMBOL: &str = "jolt.advice_polys"; + +pub fn main_witness_oracles(params: &JoltProtocolParams) -> Vec { + let mut oracles = vec!["RdInc".to_owned(), "RamInc".to_owned()]; + oracles.extend((0..params.instruction_d).map(|index| format!("InstructionRa_{index}"))); + oracles.extend((0..params.ram_d).map(|index| format!("RamRa_{index}"))); + oracles.extend((0..params.bytecode_d).map(|index| format!("BytecodeRa_{index}"))); + oracles +} + +pub fn main_witness_oracle_attr(params: &JoltProtocolParams) -> String { + symbol_array_attr(&main_witness_oracles(params)) +} + +pub fn append_foundation_ops<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + context.append_op( + module, + "field.define", + Some(FIELD_SYMBOL), + &[("modulus_bits", "254 : i64"), ("role", r#""scalar""#)], + )?; + context.append_op( + module, + "hash.function", + Some(HASH_SYMBOL), + &[("algorithm", r#""blake2b""#)], + )?; + context.append_op( + module, + "transcript.scheme", + Some(TRANSCRIPT_SYMBOL), + &[("hash", "@blake2b")], + )?; + context.append_op( + module, + "pcs.scheme", + Some(PCS_SYMBOL), + &[("field", "@bn254_fr")], + )?; + context.append_op( + module, + "poly.domain", + Some(TRACE_DOMAIN_SYMBOL), + &[ + ("field", "@bn254_fr"), + ("log_size", &format!("{} : i64", params.log_t)), + ], + )?; + context.append_op( + module, + "poly.domain", + Some(MAIN_WITNESS_COMMIT_DOMAIN_SYMBOL), + &[ + ("field", "@bn254_fr"), + ( + "log_size", + &format!("{} : i64", params.log_t + params.log_k_chunk), + ), + ], + )?; + Ok(()) +} + +pub fn append_committed_oracles<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_oracle( + context, + module, + OracleSpec { + symbol: "RdInc".to_owned(), + domain: "@jolt.trace_domain", + commit_domain: "@jolt.main_witness_commit_domain", + layout: "dense_trace", + visibility: "committed", + extra_attrs: Vec::new(), + }, + )?; + append_oracle( + context, + module, + OracleSpec { + symbol: "RamInc".to_owned(), + domain: "@jolt.trace_domain", + commit_domain: "@jolt.main_witness_commit_domain", + layout: "dense_trace", + visibility: "committed", + extra_attrs: Vec::new(), + }, + )?; + for index in 0..params.instruction_d { + append_indexed_oracle( + context, + module, + "InstructionRa", + index, + "@jolt.main_witness_commit_domain", + )?; + } + for index in 0..params.ram_d { + append_indexed_oracle( + context, + module, + "RamRa", + index, + "@jolt.main_witness_commit_domain", + )?; + } + for index in 0..params.bytecode_d { + append_indexed_oracle( + context, + module, + "BytecodeRa", + index, + "@jolt.main_witness_commit_domain", + )?; + } + append_oracle( + context, + module, + OracleSpec { + symbol: "UntrustedAdvice".to_owned(), + domain: "@jolt.trace_domain", + commit_domain: "@jolt.trace_domain", + layout: "dense_trace", + visibility: "optional_committed", + extra_attrs: Vec::new(), + }, + )?; + append_oracle( + context, + module, + OracleSpec { + symbol: "TrustedAdvice".to_owned(), + domain: "@jolt.trace_domain", + commit_domain: "@jolt.trace_domain", + layout: "dense_trace", + visibility: "optional_committed", + extra_attrs: Vec::new(), + }, + )?; + Ok(()) +} + +fn append_indexed_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + family: &str, + index: usize, + domain: &str, +) -> Result<(), MlirError> { + append_oracle( + context, + module, + OracleSpec { + symbol: format!("{family}_{index}"), + domain, + commit_domain: "@jolt.main_witness_commit_domain", + layout: "onehot_expanded", + visibility: "committed", + extra_attrs: vec![ + ("family", format!("@{family}")), + ("index", format!("{index} : i64")), + ], + }, + ) +} + +struct OracleSpec<'a> { + symbol: String, + domain: &'a str, + commit_domain: &'a str, + layout: &'a str, + visibility: &'a str, + extra_attrs: Vec<(&'a str, String)>, +} + +fn append_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + spec: OracleSpec<'_>, +) -> Result<(), MlirError> { + let mut attrs = vec![ + ("field", "@bn254_fr".to_owned()), + ("domain", spec.domain.to_owned()), + ("commit_domain", spec.commit_domain.to_owned()), + ("visibility", format!("\"{}\"", spec.visibility)), + ("layout", format!("\"{}\"", spec.layout)), + ]; + attrs.extend(spec.extra_attrs); + context.append_op_with_owned_attrs( + module, + "piop.oracle", + Some(&spec.symbol), + &attrs + .into_iter() + .map(|(name, value)| (name.to_owned(), value)) + .collect::>(), + ) +} + +fn symbol_array_attr(values: &[String]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} diff --git a/crates/bolt/src/protocols/jolt/params.rs b/crates/bolt/src/protocols/jolt/params.rs new file mode 100644 index 0000000000..595f42d8c7 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/params.rs @@ -0,0 +1,308 @@ +use melior::ir::OperationRef; + +use crate::schema::{ + int_attr as mlir_int_attr, require_attrs, symbol_attr as mlir_symbol_attr, SchemaError, +}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct JoltProtocolParams { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, + pub xlen: usize, + pub log_t: usize, + pub trace_length: usize, + pub log_k_bytecode: usize, + pub bytecode_k: usize, + pub log_k_ram: usize, + pub ram_k: usize, + pub log_k_chunk: usize, + pub k_chunk: usize, + pub lookups_ra_virtual_log_k_chunk: usize, + pub instruction_log_k: usize, + pub register_log_k: usize, + pub lookup_table_count: usize, + pub instruction_d: usize, + pub instruction_ra_virtual_d: usize, + pub bytecode_d: usize, + pub ram_d: usize, + pub num_committed: usize, + pub num_r1cs_constraints: usize, + pub num_r1cs_inputs: usize, + pub num_vars_padded: usize, +} + +impl JoltProtocolParams { + pub fn new(log_t: usize, log_k_bytecode: usize, log_k_ram: usize) -> Self { + let log_k_chunk = if log_t < 25 { 4 } else { 8 }; + let instruction_log_k = 128; + let lookups_ra_virtual_log_k_chunk = if log_t < 25 { + instruction_log_k / 8 + } else { + instruction_log_k / 4 + }; + let instruction_d = instruction_log_k / log_k_chunk; + let instruction_ra_virtual_d = instruction_log_k / lookups_ra_virtual_log_k_chunk; + let bytecode_d = log_k_bytecode.div_ceil(log_k_chunk); + let ram_d = log_k_ram.div_ceil(log_k_chunk); + Self { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + xlen: 64, + log_t, + trace_length: 1usize << log_t, + log_k_bytecode, + bytecode_k: 1usize << log_k_bytecode, + log_k_ram, + ram_k: 1usize << log_k_ram, + log_k_chunk, + k_chunk: 1usize << log_k_chunk, + lookups_ra_virtual_log_k_chunk, + instruction_log_k, + register_log_k: 7, + lookup_table_count: 40, + instruction_d, + instruction_ra_virtual_d, + bytecode_d, + ram_d, + num_committed: 2 + instruction_d + bytecode_d + ram_d, + num_r1cs_constraints: 19, + num_r1cs_inputs: 35, + num_vars_padded: 64, + } + } + + pub fn fixture() -> Self { + // Max shape supported by current jolt-kernels Stage 6 RA-virtual + // sumcheck (`degree_bound > 5` is hardcoded unsupported, so ram_d + // and bytecode_d are capped at 4 under log_k_chunk = 4). + Self::new(18, 14, 14) + } + + pub fn attrs(&self) -> Vec<(String, String)> { + vec![ + symbol_attr("field", self.field), + symbol_attr("pcs", self.pcs), + symbol_attr("transcript", self.transcript), + int_attr("xlen", self.xlen), + int_attr("log_t", self.log_t), + int_attr("trace_length", self.trace_length), + int_attr("log_k_bytecode", self.log_k_bytecode), + int_attr("bytecode_k", self.bytecode_k), + int_attr("log_k_ram", self.log_k_ram), + int_attr("ram_k", self.ram_k), + int_attr("log_k_chunk", self.log_k_chunk), + int_attr("k_chunk", self.k_chunk), + int_attr( + "lookups_ra_virtual_log_k_chunk", + self.lookups_ra_virtual_log_k_chunk, + ), + int_attr("instruction_log_k", self.instruction_log_k), + int_attr("register_log_k", self.register_log_k), + int_attr("lookup_table_count", self.lookup_table_count), + int_attr("instruction_d", self.instruction_d), + int_attr("instruction_ra_virtual_d", self.instruction_ra_virtual_d), + int_attr("bytecode_d", self.bytecode_d), + int_attr("ram_d", self.ram_d), + int_attr("num_committed", self.num_committed), + int_attr("num_r1cs_constraints", self.num_r1cs_constraints), + int_attr("num_r1cs_inputs", self.num_r1cs_inputs), + int_attr("num_vars_padded", self.num_vars_padded), + ] + } +} + +fn symbol_attr(name: &str, value: &str) -> (String, String) { + (name.to_owned(), format!("@{value}")) +} + +fn int_attr(name: &str, value: usize) -> (String, String) { + (name.to_owned(), format!("{value} : i64")) +} + +#[derive(Clone, Debug)] +pub(crate) struct ParsedJoltProtocolParams { + pub(crate) field: String, + pub(crate) pcs: String, + pub(crate) transcript: String, + pub(crate) log_t: usize, + pub(crate) trace_length: usize, + pub(crate) log_k_bytecode: usize, + pub(crate) bytecode_k: usize, + pub(crate) log_k_ram: usize, + pub(crate) ram_k: usize, + pub(crate) log_k_chunk: usize, + pub(crate) k_chunk: usize, + pub(crate) lookups_ra_virtual_log_k_chunk: usize, + pub(crate) instruction_log_k: usize, + pub(crate) instruction_d: usize, + pub(crate) instruction_ra_virtual_d: usize, + pub(crate) bytecode_d: usize, + pub(crate) ram_d: usize, + pub(crate) num_committed: usize, +} + +impl ParsedJoltProtocolParams { + pub(crate) fn from_op(operation: OperationRef<'_, '_>) -> Result { + require_jolt_params_attrs(operation)?; + Ok(Self { + field: mlir_symbol_attr(operation, "field")?, + pcs: mlir_symbol_attr(operation, "pcs")?, + transcript: mlir_symbol_attr(operation, "transcript")?, + log_t: mlir_int_attr(operation, "log_t")?, + trace_length: mlir_int_attr(operation, "trace_length")?, + log_k_bytecode: mlir_int_attr(operation, "log_k_bytecode")?, + bytecode_k: mlir_int_attr(operation, "bytecode_k")?, + log_k_ram: mlir_int_attr(operation, "log_k_ram")?, + ram_k: mlir_int_attr(operation, "ram_k")?, + log_k_chunk: mlir_int_attr(operation, "log_k_chunk")?, + k_chunk: mlir_int_attr(operation, "k_chunk")?, + lookups_ra_virtual_log_k_chunk: mlir_int_attr( + operation, + "lookups_ra_virtual_log_k_chunk", + )?, + instruction_log_k: mlir_int_attr(operation, "instruction_log_k")?, + instruction_d: mlir_int_attr(operation, "instruction_d")?, + instruction_ra_virtual_d: mlir_int_attr(operation, "instruction_ra_virtual_d")?, + bytecode_d: mlir_int_attr(operation, "bytecode_d")?, + ram_d: mlir_int_attr(operation, "ram_d")?, + num_committed: mlir_int_attr(operation, "num_committed")?, + }) + } + + pub(crate) fn validate(&self) -> Result<(), SchemaError> { + require_power_relation("trace_length", self.trace_length, "log_t", self.log_t)?; + require_power_relation( + "bytecode_k", + self.bytecode_k, + "log_k_bytecode", + self.log_k_bytecode, + )?; + require_power_relation("ram_k", self.ram_k, "log_k_ram", self.log_k_ram)?; + require_power_relation("k_chunk", self.k_chunk, "log_k_chunk", self.log_k_chunk)?; + + if self.log_k_chunk != 4 && self.log_k_chunk != 8 { + return Err(SchemaError::new(format!( + "log_k_chunk must be 4 or 8, got {}", + self.log_k_chunk + ))); + } + if self.instruction_log_k != 128 { + return Err(SchemaError::new(format!( + "instruction_log_k must be 128, got {}", + self.instruction_log_k + ))); + } + if self.lookups_ra_virtual_log_k_chunk < self.log_k_chunk { + return Err(SchemaError::new(format!( + "lookups_ra_virtual_log_k_chunk must be >= log_k_chunk; got {} < {}", + self.lookups_ra_virtual_log_k_chunk, self.log_k_chunk + ))); + } + if !self + .lookups_ra_virtual_log_k_chunk + .is_multiple_of(self.log_k_chunk) + { + return Err(SchemaError::new(format!( + "lookups_ra_virtual_log_k_chunk must be a multiple of log_k_chunk; got {} and {}", + self.lookups_ra_virtual_log_k_chunk, self.log_k_chunk + ))); + } + if !self + .instruction_log_k + .is_multiple_of(self.lookups_ra_virtual_log_k_chunk) + { + return Err(SchemaError::new(format!( + "instruction_log_k must be divisible by lookups_ra_virtual_log_k_chunk; got {} and {}", + self.instruction_log_k, self.lookups_ra_virtual_log_k_chunk + ))); + } + + let instruction_d = self.instruction_log_k / self.log_k_chunk; + let instruction_ra_virtual_d = self.instruction_log_k / self.lookups_ra_virtual_log_k_chunk; + let bytecode_d = self.log_k_bytecode.div_ceil(self.log_k_chunk); + let ram_d = self.log_k_ram.div_ceil(self.log_k_chunk); + require_eq("instruction_d", self.instruction_d, instruction_d)?; + require_eq( + "instruction_ra_virtual_d", + self.instruction_ra_virtual_d, + instruction_ra_virtual_d, + )?; + require_eq("bytecode_d", self.bytecode_d, bytecode_d)?; + require_eq("ram_d", self.ram_d, ram_d)?; + require_eq( + "num_committed", + self.num_committed, + 2 + instruction_d + bytecode_d + ram_d, + )?; + Ok(()) + } + + pub(crate) fn main_witness_oracles(&self) -> Vec { + let mut oracles = vec!["RdInc".to_owned(), "RamInc".to_owned()]; + oracles.extend((0..self.instruction_d).map(|index| format!("InstructionRa_{index}"))); + oracles.extend((0..self.ram_d).map(|index| format!("RamRa_{index}"))); + oracles.extend((0..self.bytecode_d).map(|index| format!("BytecodeRa_{index}"))); + oracles + } +} + +fn require_jolt_params_attrs(operation: OperationRef<'_, '_>) -> Result<(), SchemaError> { + require_attrs( + operation, + &[ + "sym_name", + "field", + "pcs", + "transcript", + "xlen", + "log_t", + "trace_length", + "log_k_bytecode", + "bytecode_k", + "log_k_ram", + "ram_k", + "log_k_chunk", + "k_chunk", + "lookups_ra_virtual_log_k_chunk", + "instruction_log_k", + "register_log_k", + "lookup_table_count", + "instruction_d", + "instruction_ra_virtual_d", + "bytecode_d", + "ram_d", + "num_committed", + "num_r1cs_constraints", + "num_r1cs_inputs", + "num_vars_padded", + ], + ) +} + +fn require_power_relation( + value_name: &str, + value: usize, + log_name: &str, + log_value: usize, +) -> Result<(), SchemaError> { + let expected = 1usize << log_value; + if value == expected { + Ok(()) + } else { + Err(SchemaError::new(format!( + "{value_name} must equal 2^{log_name}; got {value}, expected {expected}" + ))) + } +} + +fn require_eq(name: &str, actual: usize, expected: usize) -> Result<(), SchemaError> { + if actual == expected { + Ok(()) + } else { + Err(SchemaError::new(format!( + "{name} must be {expected}, got {actual}" + ))) + } +} diff --git a/crates/bolt/src/protocols/jolt/phases/commitment.rs b/crates/bolt/src/protocols/jolt/phases/commitment.rs new file mode 100644 index 0000000000..10327884cd --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/commitment.rs @@ -0,0 +1,1663 @@ +use std::collections::BTreeMap; + +use melior::ir::block::BlockLike; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::{OperationRef, Value}; + +use crate::ir::{BoltModule, Compute, Cpu, Party, Protocol, Role}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{ + int_attr, operation_name, symbol_array_attr, symbol_attr, verify_compute_schema, + verify_cpu_schema, SchemaError, +}; + +use super::super::oracles::{self, ADVICE_FAMILY_SYMBOL, MAIN_WITNESS_FAMILY_SYMBOL, PCS_SYMBOL}; +use super::super::params::JoltProtocolParams; +use super::super::validate::{verify_jolt_party_schema, verify_jolt_protocol_schema}; +use super::lowering::{ + copy_attrs, field_lowering_attrs as compute_field_attrs, string_attr, + transcript_squeeze_cpu_result_types, +}; + +pub fn build_commitment_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.commitment_phase", None); + oracles::append_foundation_ops(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.commitment_phase"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + oracles::append_committed_oracles(context, &module, params)?; + context.append_op( + &module, + "piop.oracle_family", + Some(MAIN_WITNESS_FAMILY_SYMBOL), + &[ + ( + "ordered_oracles", + &oracles::main_witness_oracle_attr(params), + ), + ("count", &format!("{} : i64", params.num_committed)), + ("domain", "@jolt.main_witness_commit_domain"), + ("visibility", r#""committed""#), + ], + )?; + context.append_op( + &module, + "piop.oracle_family", + Some(ADVICE_FAMILY_SYMBOL), + &[ + ("ordered_oracles", "[@UntrustedAdvice, @TrustedAdvice]"), + ("count", "2 : i64"), + ("domain", "@jolt.trace_domain"), + ("visibility", r#""optional_committed""#), + ], + )?; + let state = context.append_typed_op( + &module, + "transcript.state", + Some("fs0"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let mut state = state + .result(0) + .map_err(|_| schema_error("transcript.state requires one result"))? + .into(); + let main_commitments = context.append_typed_op( + &module, + "commit.publish_batch", + Some("jolt.main_witness_commitments"), + &[ + ("oracle_family", "@jolt.main_witness_polys"), + ("label", r#""commitment""#), + ], + &[], + &["!commit.artifact"], + )?; + let main_commitments = main_commitments + .result(0) + .map_err(|_| schema_error("commit.publish_batch requires one result"))? + .into(); + let _pcs_commit = context.append_typed_op( + &module, + "pcs.commit_batch", + Some("jolt.dory_main_witness_commit"), + &[("scheme", &format!("@{PCS_SYMBOL}"))], + &[main_commitments], + &[], + )?; + let untrusted_advice = context.append_typed_op( + &module, + "commit.publish_optional", + Some("jolt.untrusted_advice_commitment"), + &[ + ("oracle", "@UntrustedAdvice"), + ("label", r#""untrusted_advice""#), + ("skip_policy", r#""missing_or_zero""#), + ], + &[], + &["!commit.artifact"], + )?; + let untrusted_advice = untrusted_advice + .result(0) + .map_err(|_| schema_error("commit.publish_optional requires one result"))? + .into(); + let trusted_advice = context.append_typed_op( + &module, + "commit.publish_optional", + Some("jolt.trusted_advice_commitment"), + &[ + ("oracle", "@TrustedAdvice"), + ("label", r#""trusted_advice""#), + ("skip_policy", r#""missing_or_zero""#), + ], + &[], + &["!commit.artifact"], + )?; + let trusted_advice = trusted_advice + .result(0) + .map_err(|_| schema_error("commit.publish_optional requires one result"))? + .into(); + let absorb = context.append_typed_op( + &module, + "transcript.absorb", + Some("jolt.absorb_main_witness_commitments"), + &[("label", r#""commitment""#)], + &[state, main_commitments], + &["!transcript.state_type"], + )?; + state = absorb + .result(0) + .map_err(|_| schema_error("transcript.absorb requires one result"))? + .into(); + let absorb = context.append_typed_op( + &module, + "transcript.absorb_optional", + Some("jolt.absorb_untrusted_advice"), + &[("label", r#""untrusted_advice""#)], + &[state, untrusted_advice], + &["!transcript.state_type"], + )?; + state = absorb + .result(0) + .map_err(|_| schema_error("transcript.absorb_optional requires one result"))? + .into(); + let _absorb = context.append_typed_op( + &module, + "transcript.absorb_optional", + Some("jolt.absorb_trusted_advice"), + &[("label", r#""trusted_advice""#)], + &[state, trusted_advice], + &["!transcript.state_type"], + )?; + verify_module(&module)?; + verify_jolt_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_commitment_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + verify_jolt_party_schema(module)?; + let role = module + .role() + .ok_or_else(|| schema_error("commitment lowering requires party role"))?; + let concrete = analyze_concrete(module)?; + let (batch_op, optional_op) = match role { + Role::Prover => ("compute.pcs_commit_batch", "compute.pcs_commit_optional"), + Role::Verifier => ("compute.pcs_receive_batch", "compute.pcs_receive_optional"), + }; + let module_name = module.name(); + let compute = context.new_module::(&module_name, Some(role.clone())); + context.append_op_with_owned_attrs( + &compute, + "compute.params", + Some("jolt.compute_params"), + &[ + ("field".to_owned(), symbol_ref(&concrete.params.field)), + ("pcs".to_owned(), symbol_ref(&concrete.params.pcs)), + ( + "transcript".to_owned(), + symbol_ref(&concrete.params.transcript), + ), + ], + )?; + context.append_op( + &compute, + "compute.function", + Some("jolt.commitment_phase"), + &[("source", "@jolt.commitment_phase")], + )?; + + let mut artifact_values = BTreeMap::new(); + let transcript_scheme = symbol_ref(&concrete.params.transcript); + let transcript_init = context.append_typed_op( + &compute, + "compute.transcript_init", + Some("fs0"), + &[("scheme", transcript_scheme.as_str())], + &[], + &["!compute.transcript_state"], + )?; + let mut transcript_value = first_result(transcript_init, "compute.transcript_init")?; + + for plan in &concrete.batch_plans { + let family_symbol = format!("{}.oracle_family.compute", plan.oracle_family); + let family_init = context.append_typed_op_with_owned_attrs( + &compute, + "compute.oracle_family_init", + Some(&family_symbol), + &[ + ("family".to_owned(), symbol_ref(&plan.oracle_family)), + ("count".to_owned(), int_attr_source(plan.count)), + ], + &[], + &["!compute.oracle_family"], + )?; + let mut family_value = first_result(family_init, "compute.oracle_family_init")?; + for (index, oracle) in plan.oracles.iter().enumerate() { + let oracle_buffer = concrete.oracle_buffers.get(oracle).ok_or_else(|| { + schema_error(format!( + "batch commitment references missing oracle buffer @{oracle}" + )) + })?; + let oracle_value = append_oracle_buffer( + context, + &compute, + &role, + &concrete.params, + oracle, + &oracle_buffer.domain, + oracle_buffer.num_vars, + )?; + let append_symbol = format!("{}.append_{index}.compute", plan.oracle_family); + let append = context.append_typed_op_with_owned_attrs( + &compute, + "compute.oracle_family_append", + Some(&append_symbol), + &[ + ("family".to_owned(), symbol_ref(&plan.oracle_family)), + ("oracle".to_owned(), symbol_ref(oracle)), + ("index".to_owned(), int_attr_source(index)), + ], + &[family_value, oracle_value], + &["!compute.oracle_family"], + )?; + family_value = first_result(append, "compute.oracle_family_append")?; + } + let symbol = format!("{}.compute", plan.artifact); + let attrs = vec![ + ("artifact".to_owned(), symbol_ref(&plan.artifact)), + ("count".to_owned(), int_attr_source(plan.count)), + ("domain".to_owned(), symbol_ref(&plan.domain)), + ("label".to_owned(), string_attr_source(&plan.label)), + ("num_vars".to_owned(), int_attr_source(plan.num_vars)), + ("oracle_family".to_owned(), symbol_ref(&plan.oracle_family)), + ( + "ordered_oracles".to_owned(), + symbol_array_attr_source(&plan.oracles), + ), + ("pcs".to_owned(), symbol_ref(&plan.pcs)), + ]; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + batch_op, + Some(&symbol), + &attrs, + &[family_value], + &["!compute.commitment_artifact"], + )?; + let value = first_result(operation, batch_op)?; + let inserted = artifact_values.insert(plan.artifact.clone(), value); + debug_assert!(inserted.is_none()); + } + for plan in &concrete.optional_plans { + let oracle_value = append_optional_oracle_buffer( + context, + &compute, + &role, + &plan.oracle, + &plan.domain, + plan.num_vars, + &plan.skip_policy, + )?; + let symbol = format!("{}.compute", plan.artifact); + let attrs = vec![ + ("artifact".to_owned(), symbol_ref(&plan.artifact)), + ("domain".to_owned(), symbol_ref(&plan.domain)), + ("label".to_owned(), string_attr_source(&plan.label)), + ("num_vars".to_owned(), int_attr_source(plan.num_vars)), + ("oracle".to_owned(), symbol_ref(&plan.oracle)), + ("pcs".to_owned(), symbol_ref(&plan.pcs)), + ( + "skip_policy".to_owned(), + string_attr_source(&plan.skip_policy), + ), + ]; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + optional_op, + Some(&symbol), + &attrs, + &[oracle_value], + &["!compute.commitment_artifact"], + )?; + let value = first_result(operation, optional_op)?; + let inserted = artifact_values.insert(plan.artifact.clone(), value); + debug_assert!(inserted.is_none()); + } + for step in &concrete.transcript_steps { + let artifact = artifact_values.get(&step.source).copied().ok_or_else(|| { + schema_error(format!( + "transcript absorb @{} references missing commitment artifact @{}", + step.symbol, step.source + )) + })?; + let symbol = format!("{}.compute", step.symbol); + let attrs = vec![ + ("label".to_owned(), string_attr_source(&step.label)), + ( + "optional".to_owned(), + bool_attr_source(step.optional).to_owned(), + ), + ]; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_absorb", + Some(&symbol), + &attrs, + &[transcript_value, artifact], + &["!compute.transcript_state"], + )?; + transcript_value = first_result(operation, "compute.transcript_absorb")?; + } + verify_module(&compute)?; + verify_compute_schema(&compute)?; + Ok(compute) +} + +pub fn lower_compute_to_cpu<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Compute>, +) -> Result, MlirError> { + verify_compute_schema(module)?; + let role = module + .role() + .ok_or_else(|| schema_error("CPU lowering requires compute party role"))?; + let module_name = module.name(); + let cpu = context.new_module::(&module_name, Some(role)); + let mut value_map = BTreeMap::new(); + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "compute.function" => { + let source = symbol_ref(&symbol_attr(op, "source")?); + let symbol = string_attr(op, "sym_name")?; + context.append_op(&cpu, "cpu.function", Some(&symbol), &[("source", &source)])?; + } + "compute.params" => { + let symbol = string_attr(op, "sym_name")?; + context.append_op_with_owned_attrs( + &cpu, + "cpu.params", + Some(&symbol), + &[ + ("field".to_owned(), symbol_ref(&symbol_attr(op, "field")?)), + ("pcs".to_owned(), symbol_ref(&symbol_attr(op, "pcs")?)), + ( + "transcript".to_owned(), + symbol_ref(&symbol_attr(op, "transcript")?), + ), + ], + )?; + } + "compute.kernel" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["relation", "kind", "backend", "abi"])?; + context.append_op_with_owned_attrs(&cpu, "cpu.kernel", Some(&symbol), &attrs)?; + } + "compute.transcript_init" => { + let attrs = vec![("scheme".to_owned(), symbol_ref(&symbol_attr(op, "scheme")?))]; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.transcript_init", + Some(&symbol), + &attrs, + &[], + &["!cpu.transcript_state"], + )?; + let value = first_result(operation, "cpu.transcript_init")?; + let inserted = value_map.insert(operation_result_key(op)?, value); + debug_assert!(inserted.is_none()); + } + "compute.oracle_dense_trace" + | "compute.oracle_one_hot_chunk" + | "compute.oracle_optional_advice" + | "compute.oracle_ref" => { + let target_op = operation_name(op).replacen("compute.", "cpu.", 1); + let attrs = copy_attrs( + op, + &[ + "oracle", + "source", + "domain", + "num_vars", + "trace_num_vars", + "chunk", + "num_chunks", + "chunk_bits", + "padding", + "layout", + "skip_policy", + ], + )?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + &target_op, + Some(&symbol), + &attrs, + &[], + &["!cpu.oracle_buffer"], + )?; + let value = first_result(operation, &target_op)?; + let inserted = value_map.insert(operation_result_key(op)?, value); + debug_assert!(inserted.is_none()); + } + "compute.oracle_family_init" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["family", "count"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.oracle_family_init", + Some(&symbol), + &attrs, + &[], + &["!cpu.oracle_family"], + )?; + let value = first_result(operation, "cpu.oracle_family_init")?; + let inserted = value_map.insert(operation_result_key(op)?, value); + debug_assert!(inserted.is_none()); + } + "compute.oracle_family_append" => { + let input = operand_key(op, 0)?; + let oracle = operand_key(op, 1)?; + let input = value_map.get(&input).copied().ok_or_else(|| { + schema_error("compute.oracle_family_append input operand was not lowered") + })?; + let oracle = value_map.get(&oracle).copied().ok_or_else(|| { + schema_error("compute.oracle_family_append oracle operand was not lowered") + })?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["family", "oracle", "index"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.oracle_family_append", + Some(&symbol), + &attrs, + &[input, oracle], + &["!cpu.oracle_family"], + )?; + let value = first_result(operation, "cpu.oracle_family_append")?; + let inserted = value_map.insert(operation_result_key(op)?, value); + debug_assert!(inserted.is_none()); + } + "compute.pcs_commit_batch" | "compute.pcs_receive_batch" => { + let target_op = match operation_name(op).as_str() { + "compute.pcs_commit_batch" => "cpu.pcs_commit_batch", + "compute.pcs_receive_batch" => "cpu.pcs_receive_batch", + _ => unreachable!(), + }; + let attrs = vec![ + ( + "artifact".to_owned(), + symbol_ref(&symbol_attr(op, "artifact")?), + ), + ("count".to_owned(), int_attr_source(int_attr(op, "count")?)), + ("domain".to_owned(), symbol_ref(&symbol_attr(op, "domain")?)), + ( + "label".to_owned(), + string_attr_source(&string_attr(op, "label")?), + ), + ( + "num_vars".to_owned(), + int_attr_source(int_attr(op, "num_vars")?), + ), + ( + "oracle_family".to_owned(), + symbol_ref(&symbol_attr(op, "oracle_family")?), + ), + ( + "ordered_oracles".to_owned(), + symbol_array_attr_source(&symbol_array_attr(op, "ordered_oracles")?), + ), + ("pcs".to_owned(), symbol_ref(&symbol_attr(op, "pcs")?)), + ]; + let oracles = operand_key(op, 0)?; + let oracles = value_map.get(&oracles).copied().ok_or_else(|| { + schema_error("compute.pcs batch oracle family was not lowered") + })?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + target_op, + Some(&symbol), + &attrs, + &[oracles], + &["!cpu.commitment_artifact"], + )?; + let value = first_result(operation, target_op)?; + let inserted = value_map.insert(operation_result_key(op)?, value); + debug_assert!(inserted.is_none()); + } + "compute.pcs_commit_optional" | "compute.pcs_receive_optional" => { + let target_op = match operation_name(op).as_str() { + "compute.pcs_commit_optional" => "cpu.pcs_commit_optional", + "compute.pcs_receive_optional" => "cpu.pcs_receive_optional", + _ => unreachable!(), + }; + let attrs = vec![ + ( + "artifact".to_owned(), + symbol_ref(&symbol_attr(op, "artifact")?), + ), + ("domain".to_owned(), symbol_ref(&symbol_attr(op, "domain")?)), + ( + "label".to_owned(), + string_attr_source(&string_attr(op, "label")?), + ), + ( + "num_vars".to_owned(), + int_attr_source(int_attr(op, "num_vars")?), + ), + ("oracle".to_owned(), symbol_ref(&symbol_attr(op, "oracle")?)), + ("pcs".to_owned(), symbol_ref(&symbol_attr(op, "pcs")?)), + ( + "skip_policy".to_owned(), + string_attr_source(&string_attr(op, "skip_policy")?), + ), + ]; + let oracle = operand_key(op, 0)?; + let oracle = value_map + .get(&oracle) + .copied() + .ok_or_else(|| schema_error("compute.pcs optional oracle was not lowered"))?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + target_op, + Some(&symbol), + &attrs, + &[oracle], + &["!cpu.commitment_artifact"], + )?; + let value = first_result(operation, target_op)?; + let inserted = value_map.insert(operation_result_key(op)?, value); + debug_assert!(inserted.is_none()); + } + "compute.transcript_absorb" => { + let input = operand_key(op, 0)?; + let artifact = operand_key(op, 1)?; + let input = value_map.get(&input).copied().ok_or_else(|| { + schema_error("compute.transcript_absorb input operand was not lowered") + })?; + let artifact = value_map.get(&artifact).copied().ok_or_else(|| { + schema_error("compute.transcript_absorb artifact operand was not lowered") + })?; + let attrs = vec![ + ( + "label".to_owned(), + string_attr_source(&string_attr(op, "label")?), + ), + ( + "optional".to_owned(), + bool_attr_source(bool_attr(op, "optional")?).to_owned(), + ), + ]; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.transcript_absorb", + Some(&symbol), + &attrs, + &[input, artifact], + &["!cpu.transcript_state"], + )?; + let output = first_result(operation, "cpu.transcript_absorb")?; + let inserted = value_map.insert(operation_result_key(op)?, output); + debug_assert!(inserted.is_none()); + } + "compute.transcript_absorb_bytes" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "payload"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.transcript_absorb_bytes", + Some(&symbol), + &attrs, + &operands, + &["!cpu.transcript_state"], + )?; + let output = first_result(operation, "cpu.transcript_absorb_bytes")?; + let inserted = value_map.insert(operation_result_key(op)?, output); + debug_assert!(inserted.is_none()); + } + "compute.transcript_squeeze" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "kind", "count"])?; + let result_types = transcript_squeeze_cpu_result_types(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.transcript_squeeze", + Some(&symbol), + &attrs, + &operands, + &result_types, + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "compute.opening_input" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source_stage", + "source_claim", + "oracle", + "domain", + "point_arity", + "claim_kind", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.opening_input", + Some(&symbol), + &attrs, + &[], + &["!cpu.point", "!cpu.field_value", "!cpu.opening_claim_type"], + )?; + for index in 0..3 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "compute.point_slice" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "offset", "length"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.point_slice", + Some(&symbol), + &attrs, + &operands, + &["!cpu.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.point_zero" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field", "arity"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.point_zero", + Some(&symbol), + &attrs, + &[], + &["!cpu.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.point_concat" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["layout", "arity"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.point_concat", + Some(&symbol), + &attrs, + &operands, + &["!cpu.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.field_const" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field", "value"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.field_const", + Some(&symbol), + &attrs, + &[], + &["!cpu.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.field_zero" | "compute.field_one" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + &operation_name(op).replace("compute.", "cpu."), + Some(&symbol), + &attrs, + &[], + &["!cpu.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.field_add" + | "compute.field_sub" + | "compute.field_mul" + | "compute.field_neg" + | "compute.field_pow" + | "compute.poly_lagrange_basis_eval" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = compute_field_attrs(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + &operation_name(op).replace("compute.", "cpu."), + Some(&symbol), + &attrs, + &operands, + &["!cpu.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_kernel_claim" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &["stage", "domain", "num_rounds", "degree", "claim", "kernel"], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.sumcheck_claim", + Some(&symbol), + &attrs, + &operands, + &["!cpu.sumcheck_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_verify_claim" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.sumcheck_verify_claim", + Some(&symbol), + &attrs, + &operands, + &["!cpu.sumcheck_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_batch" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.sumcheck_batch", + Some(&symbol), + &attrs, + &operands, + &["!cpu.sumcheck_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_kernel_driver" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "kernel", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.sumcheck_driver", + Some(&symbol), + &attrs, + &operands, + &[ + "!cpu.transcript_state", + "!cpu.point", + "!cpu.sumcheck_result_type", + "!cpu.sumcheck_proof_type", + ], + )?; + for index in 0..4 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "compute.sumcheck_verify" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.sumcheck_verify", + Some(&symbol), + &attrs, + &operands, + &[ + "!cpu.transcript_state", + "!cpu.point", + "!cpu.sumcheck_result_type", + "!cpu.sumcheck_proof_type", + ], + )?; + for index in 0..4 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "compute.sumcheck_eval" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "name", "index", "oracle"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.sumcheck_eval", + Some(&symbol), + &attrs, + &operands, + &["!cpu.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_instance_result" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.sumcheck_instance_result", + Some(&symbol), + &attrs, + &operands, + &["!cpu.point", "!cpu.sumcheck_result_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "compute.opening_claim" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["oracle", "domain", "point_arity", "claim_kind"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!cpu.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.opening_claim_equal" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["mode"])?; + let _operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.opening_claim_equal", + Some(&symbol), + &attrs, + &operands, + &[], + )?; + } + "compute.opening_batch" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &["stage", "proof_slot", "policy", "count", "ordered_claims"], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!cpu.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.pcs_opening_claim" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["oracle", "family", "domain", "point_arity"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.pcs_opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!cpu.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.pcs_opening_batch" => { + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["proof_slot", "policy", "count", "ordered_claims"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + "cpu.pcs_opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!cpu.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.pcs_batch_open" | "compute.pcs_batch_verify" => { + let target_op = match operation_name(op).as_str() { + "compute.pcs_batch_open" => "cpu.pcs_batch_open", + "compute.pcs_batch_verify" => "cpu.pcs_batch_verify", + _ => unreachable!(), + }; + let operands = lowered_operands(op, &value_map)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["pcs", "proof_slot", "transcript_label"])?; + let operation = context.append_typed_op_with_owned_attrs( + &cpu, + target_op, + Some(&symbol), + &attrs, + &operands, + &["!cpu.transcript_state", "!cpu.opening_proof_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + _ => {} + } + } + verify_module(&cpu)?; + verify_cpu_schema(&cpu)?; + Ok(cpu) +} + +#[derive(Clone, Debug)] +struct ConcreteCommitmentAst { + params: ParamsAst, + oracle_buffers: BTreeMap, + batch_plans: Vec, + optional_plans: Vec, + transcript_steps: Vec, +} + +#[derive(Clone, Debug)] +struct ParamsAst { + field: String, + pcs: String, + transcript: String, + log_t: usize, + log_k_chunk: usize, + instruction_d: usize, + bytecode_d: usize, + ram_d: usize, +} + +#[derive(Clone, Debug)] +struct DomainAst { + num_vars: usize, +} + +#[derive(Clone, Debug)] +struct OracleAst { + domain: String, + commit_domain: String, + layout: String, +} + +#[derive(Clone, Debug)] +struct OracleBufferAst { + domain: String, + num_vars: usize, +} + +#[derive(Clone, Debug)] +struct OracleFamilyAst { + oracles: Vec, + count: usize, + domain: String, +} + +#[derive(Clone, Debug)] +struct PublishedBatchAst { + artifact: String, + oracle_family: String, + label: String, +} + +#[derive(Clone, Debug)] +struct PublishedOptionalAst { + artifact: String, + oracle: String, + label: String, + skip_policy: String, +} + +#[derive(Clone, Debug)] +struct BatchPlanAst { + artifact: String, + pcs: String, + oracle_family: String, + oracles: Vec, + label: String, + domain: String, + num_vars: usize, + count: usize, +} + +#[derive(Clone, Debug)] +struct OptionalPlanAst { + artifact: String, + pcs: String, + oracle: String, + label: String, + domain: String, + num_vars: usize, + skip_policy: String, +} + +#[derive(Clone, Debug)] +struct TranscriptStepAst { + symbol: String, + label: String, + source: String, + optional: bool, +} + +fn analyze_concrete

(module: &BoltModule<'_, P>) -> Result +where + P: crate::ir::Phase, +{ + let mut params = None; + let mut domains = BTreeMap::new(); + let mut oracles = BTreeMap::new(); + let mut families = BTreeMap::new(); + let mut published_batches = Vec::new(); + let mut published_optional = Vec::new(); + let mut pcs_by_artifact = BTreeMap::new(); + let mut transcript_steps = Vec::new(); + + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "protocol.params" => { + params = Some(ParamsAst { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + log_t: int_attr(op, "log_t")?, + log_k_chunk: int_attr(op, "log_k_chunk")?, + instruction_d: int_attr(op, "instruction_d")?, + bytecode_d: int_attr(op, "bytecode_d")?, + ram_d: int_attr(op, "ram_d")?, + }); + } + "poly.domain" => { + let _ = domains.insert( + string_attr(op, "sym_name")?, + DomainAst { + num_vars: int_attr(op, "log_size")?, + }, + ); + } + "piop.oracle" => { + let _ = oracles.insert( + string_attr(op, "sym_name")?, + OracleAst { + domain: symbol_attr(op, "domain")?, + commit_domain: symbol_attr(op, "commit_domain")?, + layout: string_attr(op, "layout")?, + }, + ); + } + "piop.oracle_family" => { + let _ = families.insert( + string_attr(op, "sym_name")?, + OracleFamilyAst { + oracles: symbol_array_attr(op, "ordered_oracles")?, + count: int_attr(op, "count")?, + domain: symbol_attr(op, "domain")?, + }, + ); + } + "commit.publish_batch" => published_batches.push(PublishedBatchAst { + artifact: string_attr(op, "sym_name")?, + oracle_family: symbol_attr(op, "oracle_family")?, + label: string_attr(op, "label")?, + }), + "commit.publish_optional" => published_optional.push(PublishedOptionalAst { + artifact: string_attr(op, "sym_name")?, + oracle: symbol_attr(op, "oracle")?, + label: string_attr(op, "label")?, + skip_policy: string_attr(op, "skip_policy")?, + }), + "pcs.commit_batch" => { + let _ = pcs_by_artifact + .insert(pcs_commitment_artifact(op)?, symbol_attr(op, "scheme")?); + } + "transcript.absorb" | "transcript.absorb_optional" => { + transcript_steps.push(TranscriptStepAst { + symbol: string_attr(op, "sym_name")?, + label: string_attr(op, "label")?, + source: transcript_artifact_source(op)?, + optional: operation_name(op) == "transcript.absorb_optional", + }); + } + _ => {} + } + } + + let params = params.ok_or_else(|| schema_error("missing protocol.params"))?; + let mut oracle_buffers = BTreeMap::new(); + for (symbol, oracle) in &oracles { + let buffer_domain = oracle_buffer_domain(oracle); + let domain = domains.get(buffer_domain).ok_or_else(|| { + schema_error(format!( + "oracle @{symbol} references missing buffer domain @{buffer_domain}" + )) + })?; + let _ = oracle_buffers.insert( + symbol.clone(), + OracleBufferAst { + domain: buffer_domain.to_owned(), + num_vars: domain.num_vars, + }, + ); + } + + let mut batch_plans = Vec::new(); + for batch in published_batches { + let family = families.get(&batch.oracle_family).ok_or_else(|| { + schema_error(format!( + "commitment artifact @{} references missing oracle family @{}", + batch.artifact, batch.oracle_family + )) + })?; + let domain = domains.get(&family.domain).ok_or_else(|| { + schema_error(format!( + "oracle family @{} references missing domain @{}", + batch.oracle_family, family.domain + )) + })?; + batch_plans.push(BatchPlanAst { + pcs: pcs_by_artifact + .get(&batch.artifact) + .cloned() + .unwrap_or_else(|| params.pcs.clone()), + artifact: batch.artifact, + oracle_family: batch.oracle_family, + oracles: family.oracles.clone(), + label: batch.label, + domain: family.domain.clone(), + num_vars: domain.num_vars, + count: family.count, + }); + } + + let mut optional_plans = Vec::new(); + for optional in published_optional { + let oracle = oracles.get(&optional.oracle).ok_or_else(|| { + schema_error(format!( + "commitment artifact @{} references missing oracle @{}", + optional.artifact, optional.oracle + )) + })?; + let domain = domains.get(&oracle.commit_domain).ok_or_else(|| { + schema_error(format!( + "oracle @{} references missing commit domain @{}", + optional.oracle, oracle.commit_domain + )) + })?; + optional_plans.push(OptionalPlanAst { + pcs: params.pcs.clone(), + artifact: optional.artifact, + oracle: optional.oracle, + label: optional.label, + domain: oracle.commit_domain.clone(), + num_vars: domain.num_vars, + skip_policy: optional.skip_policy, + }); + } + + Ok(ConcreteCommitmentAst { + params, + oracle_buffers, + batch_plans, + optional_plans, + transcript_steps, + }) +} + +fn oracle_buffer_domain(oracle: &OracleAst) -> &str { + if oracle.layout == "onehot_expanded" { + &oracle.commit_domain + } else { + &oracle.domain + } +} + +fn append_oracle_buffer<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Compute>, + role: &Role, + params: &ParamsAst, + oracle: &str, + domain: &str, + num_vars: usize, +) -> Result, MlirError> { + let symbol = format!("jolt.oracle.{oracle}.compute"); + match role { + Role::Verifier => append_oracle_ref(context, module, &symbol, oracle, domain, num_vars), + Role::Prover => { + let recipe = oracle_recipe(oracle, params)?; + let attrs = recipe.attrs(oracle, domain, num_vars, params); + let operation = context.append_typed_op_with_owned_attrs( + module, + recipe.op_name(), + Some(&symbol), + &attrs, + &[], + &["!compute.oracle_buffer"], + )?; + first_result(operation, recipe.op_name()) + } + } +} + +fn append_optional_oracle_buffer<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Compute>, + role: &Role, + oracle: &str, + domain: &str, + num_vars: usize, + skip_policy: &str, +) -> Result, MlirError> { + let symbol = format!("jolt.oracle.{oracle}.compute"); + match role { + Role::Verifier => append_oracle_ref(context, module, &symbol, oracle, domain, num_vars), + Role::Prover => { + let operation = context.append_typed_op_with_owned_attrs( + module, + "compute.oracle_optional_advice", + Some(&symbol), + &[ + ("oracle".to_owned(), symbol_ref(oracle)), + ( + "source".to_owned(), + symbol_ref(&optional_advice_source(oracle)?), + ), + ("domain".to_owned(), symbol_ref(domain)), + ("num_vars".to_owned(), int_attr_source(num_vars)), + ("skip_policy".to_owned(), string_attr_source(skip_policy)), + ], + &[], + &["!compute.oracle_buffer"], + )?; + first_result(operation, "compute.oracle_optional_advice") + } + } +} + +fn append_oracle_ref<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Compute>, + symbol: &str, + oracle: &str, + domain: &str, + num_vars: usize, +) -> Result, MlirError> { + let operation = context.append_typed_op_with_owned_attrs( + module, + "compute.oracle_ref", + Some(symbol), + &[ + ("oracle".to_owned(), symbol_ref(oracle)), + ("domain".to_owned(), symbol_ref(domain)), + ("num_vars".to_owned(), int_attr_source(num_vars)), + ], + &[], + &["!compute.oracle_buffer"], + )?; + first_result(operation, "compute.oracle_ref") +} + +#[derive(Clone, Debug)] +enum OracleRecipe { + DenseTrace { + source: &'static str, + }, + OneHotChunk { + source: &'static str, + chunk: usize, + num_chunks: usize, + padding: &'static str, + }, +} + +impl OracleRecipe { + fn op_name(&self) -> &'static str { + match self { + Self::DenseTrace { .. } => "compute.oracle_dense_trace", + Self::OneHotChunk { .. } => "compute.oracle_one_hot_chunk", + } + } + + fn attrs( + &self, + oracle: &str, + domain: &str, + num_vars: usize, + params: &ParamsAst, + ) -> Vec<(String, String)> { + let mut attrs = vec![ + ("oracle".to_owned(), symbol_ref(oracle)), + ("domain".to_owned(), symbol_ref(domain)), + ("num_vars".to_owned(), int_attr_source(num_vars)), + ]; + match self { + Self::DenseTrace { source } => { + attrs.push(("source".to_owned(), symbol_ref(source))); + attrs.push(("padding".to_owned(), string_attr_source("zero"))); + } + Self::OneHotChunk { + source, + chunk, + num_chunks, + padding, + } => { + attrs.push(("source".to_owned(), symbol_ref(source))); + attrs.push(("trace_num_vars".to_owned(), int_attr_source(params.log_t))); + attrs.push(("chunk".to_owned(), int_attr_source(*chunk))); + attrs.push(("num_chunks".to_owned(), int_attr_source(*num_chunks))); + attrs.push(("chunk_bits".to_owned(), int_attr_source(params.log_k_chunk))); + attrs.push(("padding".to_owned(), string_attr_source(padding))); + attrs.push(("layout".to_owned(), string_attr_source("address_major"))); + } + } + attrs + } +} + +fn oracle_recipe(oracle: &str, params: &ParamsAst) -> Result { + if oracle == "RdInc" { + return Ok(OracleRecipe::DenseTrace { + source: "trace.rd_inc", + }); + } + if oracle == "RamInc" { + return Ok(OracleRecipe::DenseTrace { + source: "trace.ram_inc", + }); + } + if let Some(index) = parse_indexed_oracle(oracle, "InstructionRa") { + return Ok(OracleRecipe::OneHotChunk { + source: "trace.instruction_keys", + chunk: index, + num_chunks: params.instruction_d, + padding: "zero", + }); + } + if let Some(index) = parse_indexed_oracle(oracle, "RamRa") { + return Ok(OracleRecipe::OneHotChunk { + source: "trace.ram_addresses", + chunk: index, + num_chunks: params.ram_d, + padding: "none", + }); + } + if let Some(index) = parse_indexed_oracle(oracle, "BytecodeRa") { + return Ok(OracleRecipe::OneHotChunk { + source: "trace.bytecode_indices", + chunk: index, + num_chunks: params.bytecode_d, + padding: "zero", + }); + } + Err(schema_error(format!( + "unsupported commitment oracle @{oracle}" + ))) +} + +fn parse_indexed_oracle(oracle: &str, prefix: &str) -> Option { + oracle.strip_prefix(prefix)?.strip_prefix('_')?.parse().ok() +} + +fn optional_advice_source(oracle: &str) -> Result { + match oracle { + "UntrustedAdvice" => Ok("advice.untrusted".to_owned()), + "TrustedAdvice" => Ok("advice.trusted".to_owned()), + _ => Err(schema_error(format!( + "unsupported optional advice oracle @{oracle}" + ))), + } +} + +fn bool_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(|attribute| match attribute.to_string().as_str() { + "true" => Some(true), + "false" => Some(false), + _ => None, + }) + .ok() + .flatten() + .ok_or_else(|| { + schema_error(format!( + "{} attr `{attr}` is not a bool", + operation_name(operation) + )) + }) +} + +fn operation_result_key(operation: OperationRef<'_, '_>) -> Result { + operation_result_key_at(operation, 0) +} + +fn operation_result_key_at( + operation: OperationRef<'_, '_>, + index: usize, +) -> Result { + let result = operation.result(index).map_err(|_| { + schema_error(format!( + "{} requires result {index}", + operation_name(operation) + )) + })?; + result_key(result.owner(), result.result_number()) +} + +fn result_key(operation: OperationRef<'_, '_>, result_number: usize) -> Result { + Ok(format!( + "{}#{result_number}", + string_attr(operation, "sym_name")? + )) +} + +fn operand_key(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + schema_error(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + schema_error(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + result_key(owner.owner(), owner.result_number()).map_err(|_| { + schema_error(format!( + "{} operand {index} owner missing sym_name", + operation_name(operation) + )) + }) +} + +fn lowered_operands<'c, 'a>( + operation: OperationRef<'_, '_>, + value_map: &BTreeMap>, +) -> Result>, MlirError> { + (0..operation.operand_count()) + .map(|index| { + let key = operand_key(operation, index)?; + value_map.get(&key).copied().ok_or_else(|| { + schema_error(format!( + "{} operand {index} was not lowered", + operation_name(operation) + )) + }) + }) + .collect() +} + +fn insert_result_mapping<'c, 'a>( + value_map: &mut BTreeMap>, + source: OperationRef<'_, '_>, + target: OperationRef<'c, 'a>, + source_index: usize, + target_index: usize, +) -> Result<(), MlirError> { + let key = operation_result_key_at(source, source_index)?; + let value = target.result(target_index).map(Into::into).map_err(|_| { + schema_error(format!( + "{} requires result {target_index}", + operation_name(target) + )) + })?; + let inserted = value_map.insert(key, value); + debug_assert!(inserted.is_none()); + Ok(()) +} + +fn first_result<'c, 'a>( + operation: OperationRef<'c, 'a>, + operation_name: &str, +) -> Result, MlirError> { + operation + .result(0) + .map(Into::into) + .map_err(|_| schema_error(format!("{operation_name} requires one result"))) +} + +fn pcs_commitment_artifact(operation: OperationRef<'_, '_>) -> Result { + let artifact = operation.operand(0).map_err(|_| { + schema_error(format!( + "{} requires commitment artifact operand 0", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(artifact).map_err(|_| { + schema_error(format!( + "{} commitment operand must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn transcript_artifact_source(operation: OperationRef<'_, '_>) -> Result { + if let Ok(source) = symbol_attr(operation, "source") { + return Ok(source); + } + let artifact = operation.operand(1).map_err(|_| { + schema_error(format!( + "{} requires commitment artifact operand 1", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(artifact).map_err(|_| { + schema_error(format!( + "{} artifact operand must be an op result", + operation_name(operation) + )) + })?; + string_attr(owner.owner(), "sym_name") +} + +fn schema_error(message: impl Into) -> MlirError { + let error = SchemaError::new(message); + error.into() +} + +fn symbol_ref(value: &str) -> String { + format!("@{value}") +} + +fn string_attr_source(value: &str) -> String { + format!("{value:?}") +} + +fn symbol_array_attr_source(values: &[String]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn int_attr_source(value: usize) -> String { + format!("{value} : i64") +} + +fn bool_attr_source(value: bool) -> &'static str { + if value { + "true" + } else { + "false" + } +} diff --git a/crates/bolt/src/protocols/jolt/phases/lowering.rs b/crates/bolt/src/protocols/jolt/phases/lowering.rs new file mode 100644 index 0000000000..ed50ed20db --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/lowering.rs @@ -0,0 +1,641 @@ +use std::collections::BTreeMap; + +use melior::ir::block::BlockLike; +use melior::ir::operation::OperationResult; +use melior::ir::operation::{OperationLike, OperationRef}; +use melior::ir::Value; + +use crate::ir::{string_attribute_value, BoltModule, Compute, Party, Role}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{operation_name, verify_compute_schema, verify_party_schema, SchemaError}; + +pub(super) fn copy_attrs( + operation: OperationRef<'_, '_>, + attrs: &[&str], +) -> Result, MlirError> { + attrs + .iter() + .filter_map(|attr| { + operation + .attribute(attr) + .ok() + .map(|value| Ok(((*attr).to_owned(), value.to_string()))) + }) + .collect() +} + +pub(super) fn string_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| { + schema_error(format!( + "{} attr `{attr}` is not a string", + operation_name(operation) + )) + }) +} + +pub(super) fn transcript_squeeze_protocol_result_type( + kind: &str, +) -> Result<&'static str, MlirError> { + transcript_squeeze_value_type(kind, "!poly.point", "!field.scalar") +} + +pub(super) fn transcript_squeeze_compute_result_types( + operation: OperationRef<'_, '_>, +) -> Result<[&'static str; 2], MlirError> { + Ok([ + "!compute.transcript_state", + transcript_squeeze_value_type( + string_attr(operation, "kind")?.as_str(), + "!compute.point", + "!compute.field_value", + )?, + ]) +} + +pub(super) fn transcript_squeeze_cpu_result_types( + operation: OperationRef<'_, '_>, +) -> Result<[&'static str; 2], MlirError> { + Ok([ + "!cpu.transcript_state", + transcript_squeeze_value_type( + string_attr(operation, "kind")?.as_str(), + "!cpu.point", + "!cpu.field_value", + )?, + ]) +} + +pub(super) fn field_lowering_attrs( + operation: OperationRef<'_, '_>, +) -> Result, MlirError> { + match operation_name(operation).as_str() { + "field.pow" | "compute.field_pow" => copy_attrs(operation, &["exponent"]), + "poly.lagrange_basis_eval" | "compute.poly_lagrange_basis_eval" => { + copy_attrs(operation, &["domain_start", "domain_size", "index"]) + } + _ => Ok(Vec::new()), + } +} + +pub(super) fn lower_party_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, + function_symbol: &str, + source_symbol: &str, + stage_label: &str, +) -> Result, MlirError> { + verify_party_schema(module)?; + let role = module + .role() + .ok_or_else(|| schema_error(format!("{stage_label} lowering requires party role")))?; + let compute = context.new_module::(&module.name(), Some(role.clone())); + let params_attrs = compute_params_attrs(module)?; + context.append_op_with_owned_attrs( + &compute, + "compute.params", + Some("jolt.compute_params"), + ¶ms_attrs, + )?; + context.append_op( + &compute, + "compute.function", + Some(function_symbol), + &[("source", &format!("@{source_symbol}"))], + )?; + + let mut value_map = BTreeMap::new(); + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "piop.relation" => { + let attrs = copy_attrs( + op, + &["kind", "domain", "num_rounds", "degree", "output_count"], + )?; + let symbol = string_attr(op, "sym_name")?; + context.append_op_with_owned_attrs( + &compute, + "compute.relation", + Some(&symbol), + &attrs, + )?; + } + "transcript.state" => { + let attrs = copy_attrs(op, &["scheme"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_init", + Some(&symbol), + &attrs, + &[], + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "transcript.absorb_bytes" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "payload"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_absorb_bytes", + Some(&symbol), + &attrs, + &operands, + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "transcript.squeeze" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "kind", "count"])?; + let result_types = transcript_squeeze_compute_result_types(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_squeeze", + Some(&symbol), + &attrs, + &operands, + &result_types, + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "field.const" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field", "value"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.field_const", + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "field.zero" | "field.one" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + &format!("compute.{}", operation_name(op).replace('.', "_")), + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "field.add" | "field.sub" | "field.mul" | "field.neg" | "field.pow" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = field_lowering_attrs(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + &format!("compute.{}", operation_name(op).replace('.', "_")), + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "poly.lagrange_basis_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["domain_start", "domain_size", "index"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.poly_lagrange_basis_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.opening_input" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source_stage", + "source_claim", + "oracle", + "domain", + "point_arity", + "claim_kind", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_input", + Some(&symbol), + &attrs, + &[], + &[ + "!compute.point", + "!compute.field_value", + "!compute.opening_claim_type", + ], + )?; + for index in 0..3 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "poly.point_slice" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "offset", "length"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.point_slice", + Some(&symbol), + &attrs, + &operands, + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "poly.point_zero" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field", "arity"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.point_zero", + Some(&symbol), + &attrs, + &[], + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "poly.point_concat" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["layout", "arity"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.point_concat", + Some(&symbol), + &attrs, + &operands, + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + let target_op = match &role { + Role::Prover => "compute.sumcheck_claim", + Role::Verifier => "compute.sumcheck_verify_claim", + }; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_batch" => { + let operands = lowered_operands(op, &value_map, 1)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + let target_op = match &role { + Role::Prover => "compute.sumcheck_driver", + Role::Verifier => "compute.sumcheck_verify", + }; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &[ + "!compute.transcript_state", + "!compute.point", + "!compute.sumcheck_result_type", + "!compute.sumcheck_proof_type", + ], + )?; + for index in 0..4 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "piop.sumcheck_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "name", "index", "oracle"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_instance_result" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_instance_result", + Some(&symbol), + &attrs, + &operands, + &["!compute.point", "!compute.sumcheck_result_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "piop.opening_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["oracle", "domain", "point_arity", "claim_kind"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.opening_claim_equal" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["mode"])?; + let _operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_claim_equal", + Some(&symbol), + &attrs, + &operands, + &[], + )?; + } + "piop.opening_batch" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &["stage", "proof_slot", "policy", "count", "ordered_claims"], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "pcs.opening_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["oracle", "family", "domain", "point_arity"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.pcs_opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "pcs.opening_batch" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["proof_slot", "policy", "count", "ordered_claims"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.pcs_opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "pcs.batch_open" | "pcs.batch_verify" => { + let target_op = match &role { + Role::Prover => "compute.pcs_batch_open", + Role::Verifier => "compute.pcs_batch_verify", + }; + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["pcs", "proof_slot", "transcript_label"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &["!compute.transcript_state", "!compute.opening_proof_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + _ => {} + } + } + + verify_module(&compute)?; + verify_compute_schema(&compute)?; + Ok(compute) +} + +fn compute_params_attrs( + module: &BoltModule<'_, P>, +) -> Result, MlirError> { + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + if operation_name(op) == "protocol.params" { + return copy_attrs(op, &["field", "pcs", "transcript"]); + } + } + Err(schema_error("module missing protocol.params")) +} + +fn transcript_squeeze_value_type( + kind: &str, + point_type: &'static str, + scalar_type: &'static str, +) -> Result<&'static str, MlirError> { + match kind { + "challenge_vector" => Ok(point_type), + "challenge_scalar" | "scalar" => Ok(scalar_type), + kind => Err(schema_error(format!( + "unsupported transcript squeeze kind `{kind}`" + ))), + } +} + +fn schema_error(message: impl Into) -> MlirError { + SchemaError::new(message).into() +} + +fn operation_result_key_at( + operation: OperationRef<'_, '_>, + index: usize, +) -> Result { + let result = operation.result(index).map_err(|_| { + schema_error(format!( + "{} requires result {index}", + operation_name(operation) + )) + })?; + result_key(operation, result.result_number()).map_err(|_| { + schema_error(format!( + "{} result {index} owner missing sym_name", + operation_name(operation) + )) + }) +} + +fn result_key(operation: OperationRef<'_, '_>, result_number: usize) -> Result { + let symbol = string_attr(operation, "sym_name")?; + Ok(format!("{symbol}#{result_number}")) +} + +fn operand_key(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + schema_error(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + schema_error(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + result_key(owner.owner(), owner.result_number()).map_err(|_| { + schema_error(format!( + "{} operand {index} owner missing sym_name", + operation_name(operation) + )) + }) +} + +fn lowered_operands<'c, 'a>( + operation: OperationRef<'_, '_>, + value_map: &BTreeMap>, + start_index: usize, +) -> Result>, MlirError> { + (start_index..operation.operand_count()) + .map(|index| { + let key = operand_key(operation, index)?; + value_map.get(&key).copied().ok_or_else(|| { + schema_error(format!( + "{} operand {index} was not lowered", + operation_name(operation) + )) + }) + }) + .collect() +} + +fn insert_result_mapping<'c, 'a>( + value_map: &mut BTreeMap>, + source: OperationRef<'_, '_>, + target: OperationRef<'c, 'a>, + source_index: usize, + target_index: usize, +) -> Result<(), MlirError> { + let key = operation_result_key_at(source, source_index)?; + let value = target.result(target_index).map(Into::into).map_err(|_| { + schema_error(format!( + "{} requires result {target_index}", + operation_name(target) + )) + })?; + let _ = value_map.insert(key, value); + Ok(()) +} diff --git a/crates/bolt/src/protocols/jolt/phases/mod.rs b/crates/bolt/src/protocols/jolt/phases/mod.rs new file mode 100644 index 0000000000..19f9646db3 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/mod.rs @@ -0,0 +1,11 @@ +mod lowering; + +pub mod commitment; +pub mod stage1; +pub mod stage2; +pub mod stage3; +pub mod stage4; +pub mod stage5; +pub mod stage6; +pub mod stage7; +pub mod stage8; diff --git a/crates/bolt/src/protocols/jolt/phases/stage1.rs b/crates/bolt/src/protocols/jolt/phases/stage1.rs new file mode 100644 index 0000000000..7501e62768 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/stage1.rs @@ -0,0 +1,1723 @@ +use std::collections::BTreeMap; + +use melior::ir::block::BlockLike; +use melior::ir::operation::OperationRef; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::Value; + +use crate::ir::{BoltModule, Compute, Party, Protocol, Role}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{ + operation_name, symbol_attr, verify_compute_schema, verify_party_schema, + verify_protocol_schema, SchemaError, +}; + +use super::super::oracles; +use super::super::params::JoltProtocolParams; +use super::lowering::{ + copy_attrs, field_lowering_attrs as field_compute_attrs, string_attr, + transcript_squeeze_compute_result_types, +}; + +const R1CS_INPUT_ORACLES: [&str; 35] = [ + "LeftInstructionInput", + "RightInstructionInput", + "Product", + "ShouldBranch", + "PC", + "UnexpandedPC", + "Imm", + "RamAddress", + "Rs1Value", + "Rs2Value", + "RdWriteValue", + "RamReadValue", + "RamWriteValue", + "LeftLookupOperand", + "RightLookupOperand", + "NextUnexpandedPC", + "NextPC", + "NextIsVirtual", + "NextIsFirstInSequence", + "LookupOutput", + "ShouldJump", + "OpFlagAddOperands", + "OpFlagSubtractOperands", + "OpFlagMultiplyOperands", + "OpFlagLoad", + "OpFlagStore", + "OpFlagJump", + "OpFlagWriteLookupOutputToRD", + "OpFlagVirtualInstruction", + "OpFlagAssert", + "OpFlagDoNotUpdateUnexpandedPC", + "OpFlagAdvice", + "OpFlagIsCompressed", + "OpFlagIsFirstInSequence", + "OpFlagIsLastInSequence", +]; +const OUTER_UNISKIP_FIRST_ROUND_DEGREE_BOUND: usize = 27; + +pub fn build_stage1_outer_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.stage1_outer", None); + oracles::append_foundation_ops(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.stage1_outer"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + append_stage1_virtual_oracles(context, &module, params)?; + append_stage1_relations(context, &module, params)?; + + let fs0 = context.append_typed_op( + &module, + "transcript.state", + Some("fs0"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let state = first_result(fs0, "transcript.state")?; + let tau = context.append_typed_op( + &module, + "transcript.squeeze", + Some("stage1.tau"), + &[ + ("label", r#""outer_tau""#), + ("kind", r#""challenge_vector""#), + ("count", &int_attr(params.log_t + 2)), + ], + &[state], + &["!transcript.state_type", "!poly.point"], + )?; + let state = first_result(tau, "transcript.squeeze")?; + + let stage = context.append_typed_op( + &module, + "piop.stage", + Some("stage1"), + &[ + ("name", r#""spartan_outer""#), + ("order", "1 : i64"), + ("roles", r#"["prover", "verifier"]"#), + ], + &[], + &["!piop.stage_type"], + )?; + let stage = first_result(stage, "piop.stage")?; + let zero_claim = append_field_zero(context, &module, "stage1.zero")?; + + let (state, uniskip_opening, uniskip_eval) = + append_uniskip_sumcheck(context, &module, params, state, stage, zero_claim)?; + let _state = append_remaining_sumcheck( + context, + &module, + params, + state, + stage, + uniskip_eval, + uniskip_opening, + )?; + + verify_module(&module)?; + verify_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_stage1_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + verify_party_schema(module)?; + let role = module + .role() + .ok_or_else(|| schema_error("stage1 lowering requires party role"))?; + let params = stage_params(module)?; + let compute = context.new_module::(&module.name(), Some(role.clone())); + context.append_op_with_owned_attrs( + &compute, + "compute.params", + Some("jolt.compute_params"), + &[ + ("field".to_owned(), symbol_ref(¶ms.field)), + ("pcs".to_owned(), symbol_ref(¶ms.pcs)), + ("transcript".to_owned(), symbol_ref(¶ms.transcript)), + ], + )?; + context.append_op( + &compute, + "compute.function", + Some("jolt.stage1_outer"), + &[("source", "@jolt.stage1_outer")], + )?; + + let mut value_map = BTreeMap::new(); + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "piop.relation" => { + let attrs = copy_attrs( + op, + &["kind", "domain", "num_rounds", "degree", "output_count"], + )?; + let symbol = string_attr(op, "sym_name")?; + context.append_op_with_owned_attrs( + &compute, + "compute.relation", + Some(&symbol), + &attrs, + )?; + } + "transcript.state" => { + let attrs = copy_attrs(op, &["scheme"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_init", + Some(&symbol), + &attrs, + &[], + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "transcript.absorb_bytes" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "payload"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_absorb_bytes", + Some(&symbol), + &attrs, + &operands, + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "transcript.squeeze" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "kind", "count"])?; + let result_types = transcript_squeeze_compute_result_types(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_squeeze", + Some(&symbol), + &attrs, + &operands, + &result_types, + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "field.const" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field", "value"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.field_const", + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "field.zero" | "field.one" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + &format!("compute.{}", operation_name(op).replace('.', "_")), + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "field.add" | "field.sub" | "field.mul" | "field.neg" | "field.pow" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = field_compute_attrs(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + &format!("compute.{}", operation_name(op).replace('.', "_")), + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "poly.lagrange_basis_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["domain_start", "domain_size", "index"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.poly_lagrange_basis_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + let target_op = match &role { + Role::Prover => "compute.sumcheck_claim", + Role::Verifier => "compute.sumcheck_verify_claim", + }; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_batch" => { + let operands = lowered_operands(op, &value_map, 1)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + let target_op = match &role { + Role::Prover => "compute.sumcheck_driver", + Role::Verifier => "compute.sumcheck_verify", + }; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &[ + "!compute.transcript_state", + "!compute.point", + "!compute.sumcheck_result_type", + "!compute.sumcheck_proof_type", + ], + )?; + for index in 0..4 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "piop.sumcheck_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "name", "index", "oracle"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_instance_result" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_instance_result", + Some(&symbol), + &attrs, + &operands, + &["!compute.point", "!compute.sumcheck_result_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "piop.opening_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["oracle", "domain", "point_arity", "claim_kind"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.opening_claim_equal" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["mode"])?; + let _operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_claim_equal", + Some(&symbol), + &attrs, + &operands, + &[], + )?; + } + "piop.opening_batch" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &["stage", "proof_slot", "policy", "count", "ordered_claims"], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + _ => {} + } + } + + verify_module(&compute)?; + verify_compute_schema(&compute)?; + Ok(compute) +} + +pub fn resolve_compute_kernels<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Compute>, +) -> Result, MlirError> { + verify_compute_schema(module)?; + let role = module + .role() + .ok_or_else(|| schema_error("kernel resolution requires compute party role"))?; + let kernelized = context.new_module::(&module.name(), Some(role)); + let mut value_map = BTreeMap::new(); + let mut kernels = BTreeMap::new(); + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "compute.params" => { + let attrs = copy_attrs(op, &["field", "pcs", "transcript"])?; + let symbol = string_attr(op, "sym_name")?; + context.append_op_with_owned_attrs( + &kernelized, + "compute.params", + Some(&symbol), + &attrs, + )?; + } + "compute.function" => { + let attrs = copy_attrs(op, &["source"])?; + let symbol = string_attr(op, "sym_name")?; + context.append_op_with_owned_attrs( + &kernelized, + "compute.function", + Some(&symbol), + &attrs, + )?; + } + "compute.relation" => { + let attrs = copy_attrs( + op, + &["kind", "domain", "num_rounds", "degree", "output_count"], + )?; + let symbol = string_attr(op, "sym_name")?; + context.append_op_with_owned_attrs( + &kernelized, + "compute.relation", + Some(&symbol), + &attrs, + )?; + } + "compute.transcript_init" => { + let attrs = copy_attrs(op, &["scheme"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.transcript_init", + Some(&symbol), + &attrs, + &[], + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.transcript_absorb_bytes" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["label", "payload"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.transcript_absorb_bytes", + Some(&symbol), + &attrs, + &operands, + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.transcript_squeeze" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["label", "kind", "count"])?; + let symbol = string_attr(op, "sym_name")?; + let result_types = transcript_squeeze_compute_result_types(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.transcript_squeeze", + Some(&symbol), + &attrs, + &operands, + &result_types, + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "compute.opening_input" => { + let attrs = copy_attrs( + op, + &[ + "source_stage", + "source_claim", + "oracle", + "domain", + "point_arity", + "claim_kind", + ], + )?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.opening_input", + Some(&symbol), + &attrs, + &[], + &[ + "!compute.point", + "!compute.field_value", + "!compute.opening_claim_type", + ], + )?; + for index in 0..3 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "compute.point_slice" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["source", "offset", "length"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.point_slice", + Some(&symbol), + &attrs, + &operands, + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.point_zero" => { + let attrs = copy_attrs(op, &["field", "arity"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.point_zero", + Some(&symbol), + &attrs, + &[], + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.point_concat" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["layout", "arity"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.point_concat", + Some(&symbol), + &attrs, + &operands, + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.field_const" => { + let attrs = copy_attrs(op, &["field", "value"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.field_const", + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.field_zero" | "compute.field_one" => { + let attrs = copy_attrs(op, &["field"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + &operation_name(op), + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.field_add" + | "compute.field_sub" + | "compute.field_mul" + | "compute.field_neg" + | "compute.field_pow" + | "compute.poly_lagrange_basis_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = field_compute_attrs(op)?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + &operation_name(op), + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_claim" => { + let relation = symbol_attr(op, "relation")?; + let kernel = ensure_kernel(context, &kernelized, &mut kernels, &relation)?; + let operands = lowered_operands(op, &value_map, 0)?; + let mut attrs = + copy_attrs(op, &["stage", "domain", "num_rounds", "degree", "claim"])?; + attrs.push(("kernel".to_owned(), symbol_ref(&kernel))); + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.sumcheck_kernel_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_verify_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs( + op, + &[ + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.sumcheck_verify_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_batch" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.sumcheck_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_driver" => { + let relation = symbol_attr(op, "relation")?; + let kernel = ensure_kernel(context, &kernelized, &mut kernels, &relation)?; + let operands = lowered_operands(op, &value_map, 0)?; + let mut attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + attrs.push(("kernel".to_owned(), symbol_ref(&kernel))); + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.sumcheck_kernel_driver", + Some(&symbol), + &attrs, + &operands, + &[ + "!compute.transcript_state", + "!compute.point", + "!compute.sumcheck_result_type", + "!compute.sumcheck_proof_type", + ], + )?; + for index in 0..4 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "compute.sumcheck_verify" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.sumcheck_verify", + Some(&symbol), + &attrs, + &operands, + &[ + "!compute.transcript_state", + "!compute.point", + "!compute.sumcheck_result_type", + "!compute.sumcheck_proof_type", + ], + )?; + for index in 0..4 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "compute.sumcheck_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["source", "name", "index", "oracle"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.sumcheck_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.sumcheck_instance_result" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs( + op, + &[ + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.sumcheck_instance_result", + Some(&symbol), + &attrs, + &operands, + &["!compute.point", "!compute.sumcheck_result_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "compute.opening_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["oracle", "domain", "point_arity", "claim_kind"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.opening_claim_equal" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["mode"])?; + let symbol = string_attr(op, "sym_name")?; + let _operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.opening_claim_equal", + Some(&symbol), + &attrs, + &operands, + &[], + )?; + } + "compute.opening_batch" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs( + op, + &["stage", "proof_slot", "policy", "count", "ordered_claims"], + )?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.pcs_opening_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["oracle", "family", "domain", "point_arity"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.pcs_opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.pcs_opening_batch" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["proof_slot", "policy", "count", "ordered_claims"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + "compute.pcs_opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "compute.pcs_batch_open" | "compute.pcs_batch_verify" => { + let operands = lowered_operands(op, &value_map, 0)?; + let attrs = copy_attrs(op, &["pcs", "proof_slot", "transcript_label"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &kernelized, + &operation_name(op), + Some(&symbol), + &attrs, + &operands, + &["!compute.transcript_state", "!compute.opening_proof_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + _ => {} + } + } + + verify_module(&kernelized)?; + verify_compute_schema(&kernelized)?; + Ok(kernelized) +} + +fn append_stage1_virtual_oracles<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + context.append_op( + module, + "poly.domain", + Some("jolt.stage1_uniskip_domain"), + &[("field", "@bn254_fr"), ("log_size", "1 : i64")], + )?; + append_virtual_oracle( + context, + module, + "UnivariateSkip", + "jolt.stage1_uniskip_domain", + )?; + for oracle in R1CS_INPUT_ORACLES { + append_virtual_oracle(context, module, oracle, "jolt.trace_domain")?; + } + context.append_op( + module, + "piop.oracle_family", + Some("jolt.stage1_r1cs_virtuals"), + &[ + ("ordered_oracles", &symbol_array_attr(&R1CS_INPUT_ORACLES)), + ("count", &int_attr(params.num_r1cs_inputs)), + ("domain", "@jolt.trace_domain"), + ("visibility", r#""virtual""#), + ], + ) +} + +fn append_virtual_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + domain: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", &format!("@{domain}")), + ("commit_domain", &format!("@{domain}")), + ("visibility", r#""virtual""#), + ("layout", r#""virtual""#), + ], + ) +} + +fn append_stage1_relations<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.relation", + Some("jolt.stage1.outer.uniskip"), + &[ + ("kind", r#""sumcheck""#), + ("domain", "@jolt.stage1_uniskip_domain"), + ("num_rounds", "1 : i64"), + ("degree", &int_attr(OUTER_UNISKIP_FIRST_ROUND_DEGREE_BOUND)), + ("output_count", "1 : i64"), + ], + )?; + context.append_op( + module, + "piop.relation", + Some("jolt.stage1.outer.remaining"), + &[ + ("kind", r#""sumcheck""#), + ("domain", "@jolt.trace_domain"), + ("num_rounds", &int_attr(params.log_t + 1)), + ("degree", "3 : i64"), + ("output_count", &int_attr(R1CS_INPUT_ORACLES.len())), + ], + ) +} + +fn append_field_zero<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.zero", + Some(symbol), + &[("field", "@bn254_fr")], + &[], + &["!field.scalar"], + )?; + first_result(op, "field.zero") +} + +fn append_uniskip_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + zero_claim: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let claim = context.append_typed_op( + module, + "piop.sumcheck_claim", + Some("stage1.uniskip.input"), + &[ + ("stage", "@stage1"), + ("domain", "@jolt.stage1_uniskip_domain"), + ("num_rounds", "1 : i64"), + ("degree", &int_attr(OUTER_UNISKIP_FIRST_ROUND_DEGREE_BOUND)), + ("claim", "@stage1.zero"), + ("relation", "@jolt.stage1.outer.uniskip"), + ], + &[zero_claim], + &["!piop.sumcheck_claim_type"], + )?; + let claim = first_result(claim, "piop.sumcheck_claim")?; + let batch = context.append_typed_op( + module, + "piop.sumcheck_batch", + Some("stage1.uniskip.batch"), + &[ + ("stage", "@stage1"), + ("proof_slot", "@stage1.uni_skip_first_round"), + ("policy", r#""single_instance""#), + ("count", "1 : i64"), + ("ordered_claims", "[@stage1.uniskip.input]"), + ("claim_label", r#""uniskip_claim""#), + ("round_label", r#""uniskip_poly""#), + ("round_schedule", "[1]"), + ], + &[stage, claim], + &["!piop.sumcheck_batch_type"], + )?; + let batch = first_result(batch, "piop.sumcheck_batch")?; + let sumcheck = context.append_typed_op( + module, + "piop.sumcheck", + Some("stage1.uniskip.sumcheck"), + &[ + ("stage", "@stage1"), + ("proof_slot", "@stage1.uni_skip_first_round"), + ("relation", "@jolt.stage1.outer.uniskip"), + ("policy", r#""univariate_skip""#), + ("round_schedule", "[1]"), + ("claim_label", r#""uniskip_claim""#), + ("round_label", r#""uniskip_poly""#), + ("num_rounds", "1 : i64"), + ("degree", &int_attr(OUTER_UNISKIP_FIRST_ROUND_DEGREE_BOUND)), + ], + &[state, batch], + &[ + "!transcript.state_type", + "!poly.point", + "!piop.sumcheck_result_type", + "!piop.sumcheck_proof_type", + ], + )?; + let state = result(sumcheck, 0, "piop.sumcheck")?; + let point = result(sumcheck, 1, "piop.sumcheck")?; + let result_value = result(sumcheck, 2, "piop.sumcheck")?; + let (point, result_value) = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage1.uniskip.instance", + source: "stage1.uniskip.sumcheck", + claim: "stage1.uniskip.input", + relation: "jolt.stage1.outer.uniskip", + index: 0, + point_arity: 1, + num_rounds: 1, + round_offset: 0, + point_order: "as_is", + degree: OUTER_UNISKIP_FIRST_ROUND_DEGREE_BOUND, + }, + point, + result_value, + )?; + let eval = append_sumcheck_eval( + context, + module, + "stage1.uniskip.eval", + "stage1.uniskip.sumcheck", + "UnivariateSkip", + 0, + result_value, + )?; + let opening = append_piop_opening_claim( + context, + module, + point, + eval, + OpeningClaimSpec { + symbol: "stage1.uniskip.opening", + oracle: "UnivariateSkip", + domain: "jolt.stage1_uniskip_domain", + point_arity: 1, + }, + )?; + let _ = params; + Ok((state, opening, eval)) +} + +fn append_remaining_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + input_claim: Value<'c, 'a>, + uniskip_opening: Value<'c, 'a>, +) -> Result, MlirError> { + let num_rounds = params.log_t + 1; + let claim = context.append_typed_op( + module, + "piop.sumcheck_claim", + Some("stage1.outer_remaining.input"), + &[ + ("stage", "@stage1"), + ("domain", "@jolt.trace_domain"), + ("num_rounds", &int_attr(num_rounds)), + ("degree", "3 : i64"), + ("claim", "@stage1.uniskip.eval"), + ("relation", "@jolt.stage1.outer.remaining"), + ], + &[input_claim, uniskip_opening], + &["!piop.sumcheck_claim_type"], + )?; + let claim = first_result(claim, "piop.sumcheck_claim")?; + let batch = context.append_typed_op( + module, + "piop.sumcheck_batch", + Some("stage1.outer_remaining.batch"), + &[ + ("stage", "@stage1"), + ("proof_slot", "@stage1.sumcheck"), + ("policy", r#""jolt_core_front_loaded""#), + ("count", "1 : i64"), + ("ordered_claims", "[@stage1.outer_remaining.input]"), + ("claim_label", r#""sumcheck_claim""#), + ("round_label", r#""sumcheck_poly""#), + ("round_schedule", &format!("[{}]", num_rounds)), + ], + &[stage, claim], + &["!piop.sumcheck_batch_type"], + )?; + let batch = first_result(batch, "piop.sumcheck_batch")?; + let sumcheck = context.append_typed_op( + module, + "piop.sumcheck", + Some("stage1.outer_remaining.sumcheck"), + &[ + ("stage", "@stage1"), + ("proof_slot", "@stage1.sumcheck"), + ("relation", "@jolt.stage1.outer.remaining"), + ("policy", r#""jolt_core_front_loaded""#), + ("round_schedule", &format!("[{}]", num_rounds)), + ("claim_label", r#""sumcheck_claim""#), + ("round_label", r#""sumcheck_poly""#), + ("num_rounds", &int_attr(num_rounds)), + ("degree", "3 : i64"), + ], + &[state, batch], + &[ + "!transcript.state_type", + "!poly.point", + "!piop.sumcheck_result_type", + "!piop.sumcheck_proof_type", + ], + )?; + let state = result(sumcheck, 0, "piop.sumcheck")?; + let point = result(sumcheck, 1, "piop.sumcheck")?; + let result_value = result(sumcheck, 2, "piop.sumcheck")?; + let (point, result_value) = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage1.outer_remaining.instance", + source: "stage1.outer_remaining.sumcheck", + claim: "stage1.outer_remaining.input", + relation: "jolt.stage1.outer.remaining", + index: 0, + point_arity: params.log_t, + num_rounds, + round_offset: 1, + point_order: "reverse", + degree: 3, + }, + point, + result_value, + )?; + let mut claims = Vec::with_capacity(R1CS_INPUT_ORACLES.len()); + for (index, oracle) in R1CS_INPUT_ORACLES.iter().enumerate() { + let eval = append_sumcheck_eval( + context, + module, + &format!("stage1.outer_remaining.eval.{oracle}"), + "stage1.outer_remaining.sumcheck", + oracle, + index, + result_value, + )?; + claims.push(append_piop_opening_claim( + context, + module, + point, + eval, + OpeningClaimSpec { + symbol: &format!("stage1.outer_remaining.opening.{oracle}"), + oracle, + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?); + } + let _batch = context.append_typed_op( + module, + "piop.opening_batch", + Some("stage1.outer_remaining.openings"), + &[ + ("stage", "@stage1"), + ("proof_slot", "@stage1.virtual_openings"), + ("policy", r#""jolt_r1cs_input_order""#), + ("count", &int_attr(R1CS_INPUT_ORACLES.len())), + ("ordered_claims", &opening_claim_attr()), + ], + &claims, + &["!piop.opening_batch_type"], + )?; + Ok(state) +} + +fn append_sumcheck_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + oracle: &str, + index: usize, + result_value: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_eval", + Some(symbol), + &[ + ("source", &format!("@{source}")), + ("name", &format!("@{symbol}")), + ("index", &int_attr(index)), + ("oracle", &format!("@{oracle}")), + ], + &[result_value], + &["!field.scalar"], + )?; + first_result(op, "piop.sumcheck_eval") +} + +fn append_sumcheck_instance_result<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckInstanceResultSpec<'_>, + point: Value<'c, 'a>, + result_value: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_instance_result", + Some(spec.symbol), + &[ + ("source", &format!("@{}", spec.source)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ("index", &int_attr(spec.index)), + ("point_arity", &int_attr(spec.point_arity)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("round_offset", &int_attr(spec.round_offset)), + ("point_order", &format!("\"{}\"", spec.point_order)), + ("degree", &int_attr(spec.degree)), + ], + &[point, result_value], + &["!poly.point", "!piop.sumcheck_result_type"], + )?; + Ok(( + result(op, 0, "piop.sumcheck_instance_result")?, + result(op, 1, "piop.sumcheck_instance_result")?, + )) +} + +fn append_piop_opening_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + spec: OpeningClaimSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_claim", + Some(spec.symbol), + &[ + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", r#""virtual""#), + ], + &[point, eval], + &["!piop.opening_claim_type"], + )?; + first_result(op, "piop.opening_claim") +} + +struct OpeningClaimSpec<'a> { + symbol: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, +} + +struct SumcheckInstanceResultSpec<'a> { + symbol: &'a str, + source: &'a str, + claim: &'a str, + relation: &'a str, + index: usize, + point_arity: usize, + num_rounds: usize, + round_offset: usize, + point_order: &'a str, + degree: usize, +} + +fn first_result<'c, 'a>( + operation: OperationRef<'c, 'a>, + operation_name: &str, +) -> Result, MlirError> { + result(operation, 0, operation_name) +} + +fn result<'c, 'a>( + operation: OperationRef<'c, 'a>, + index: usize, + operation_name: &str, +) -> Result, MlirError> { + operation + .result(index) + .map(Into::into) + .map_err(|_| schema_error(format!("{operation_name} requires result {index}"))) +} + +#[derive(Clone, Debug)] +struct StageParamsAst { + field: String, + pcs: String, + transcript: String, +} + +fn stage_params(module: &BoltModule<'_, Party>) -> Result { + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + if operation_name(op) == "protocol.params" { + return Ok(StageParamsAst { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + } + Err(schema_error("stage1 lowering requires protocol.params")) +} + +fn operation_result_key_at( + operation: OperationRef<'_, '_>, + index: usize, +) -> Result { + let result = operation.result(index).map_err(|_| { + schema_error(format!( + "{} requires result {index}", + operation_name(operation) + )) + })?; + result_key(result.owner(), result.result_number()) +} + +fn result_key(operation: OperationRef<'_, '_>, result_number: usize) -> Result { + Ok(format!( + "{}#{result_number}", + string_attr(operation, "sym_name")? + )) +} + +fn operand_key(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + schema_error(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + schema_error(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + result_key(owner.owner(), owner.result_number()).map_err(|_| { + schema_error(format!( + "{} operand {index} owner missing sym_name", + operation_name(operation) + )) + }) +} + +fn lowered_operands<'c, 'a>( + operation: OperationRef<'_, '_>, + value_map: &BTreeMap>, + start_index: usize, +) -> Result>, MlirError> { + (start_index..operation.operand_count()) + .map(|index| { + let key = operand_key(operation, index)?; + value_map.get(&key).copied().ok_or_else(|| { + schema_error(format!( + "{} operand {index} was not lowered", + operation_name(operation) + )) + }) + }) + .collect() +} + +fn insert_result_mapping<'c, 'a>( + value_map: &mut BTreeMap>, + source: OperationRef<'_, '_>, + target: OperationRef<'c, 'a>, + source_index: usize, + target_index: usize, +) -> Result<(), MlirError> { + let key = operation_result_key_at(source, source_index)?; + let value = target.result(target_index).map(Into::into).map_err(|_| { + schema_error(format!( + "{} requires result {target_index}", + operation_name(target) + )) + })?; + let inserted = value_map.insert(key, value); + debug_assert!(inserted.is_none()); + Ok(()) +} + +fn ensure_kernel<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Compute>, + kernels: &mut BTreeMap, + relation: &str, +) -> Result { + if let Some(kernel) = kernels.get(relation) { + return Ok(kernel.clone()); + } + let spec = kernel_spec(relation)?; + context.append_op_with_owned_attrs( + module, + "compute.kernel", + Some(spec.symbol), + &[ + ("relation".to_owned(), symbol_ref(relation)), + ("kind".to_owned(), string_literal(spec.kind)), + ("backend".to_owned(), string_literal("cpu")), + ("abi".to_owned(), string_literal(spec.abi)), + ], + )?; + let inserted = kernels.insert(relation.to_owned(), spec.symbol.to_owned()); + debug_assert!(inserted.is_none()); + Ok(spec.symbol.to_owned()) +} + +fn kernel_spec(relation: &str) -> Result { + match relation { + "jolt.stage1.outer.uniskip" => Ok(KernelSpec { + symbol: "jolt.cpu.stage1.outer.uniskip", + kind: "sumcheck", + abi: "jolt_stage1_outer_uniskip", + }), + "jolt.stage1.outer.remaining" => Ok(KernelSpec { + symbol: "jolt.cpu.stage1.outer.remaining", + kind: "sumcheck", + abi: "jolt_stage1_outer_remaining", + }), + "jolt.stage2.product_virtual.uniskip" => Ok(KernelSpec { + symbol: "jolt.cpu.stage2.product_virtual.uniskip", + kind: "sumcheck", + abi: "jolt_stage2_product_virtual_uniskip", + }), + "jolt.stage2.ram.read_write" => Ok(KernelSpec { + symbol: "jolt.cpu.stage2.ram.read_write", + kind: "sumcheck", + abi: "jolt_stage2_ram_read_write", + }), + "jolt.stage2.product_virtual.remainder" => Ok(KernelSpec { + symbol: "jolt.cpu.stage2.product_virtual.remainder", + kind: "sumcheck", + abi: "jolt_stage2_product_virtual_remainder", + }), + "jolt.stage2.instruction_lookup.claim_reduction" => Ok(KernelSpec { + symbol: "jolt.cpu.stage2.instruction_lookup.claim_reduction", + kind: "sumcheck", + abi: "jolt_stage2_instruction_lookup_claim_reduction", + }), + "jolt.stage2.ram.raf_evaluation" => Ok(KernelSpec { + symbol: "jolt.cpu.stage2.ram.raf_evaluation", + kind: "sumcheck", + abi: "jolt_stage2_ram_raf_evaluation", + }), + "jolt.stage2.ram.output_check" => Ok(KernelSpec { + symbol: "jolt.cpu.stage2.ram.output_check", + kind: "sumcheck", + abi: "jolt_stage2_ram_output_check", + }), + "jolt.stage2.batched" => Ok(KernelSpec { + symbol: "jolt.cpu.stage2.batched", + kind: "sumcheck", + abi: "jolt_stage2_batched", + }), + "jolt.stage3.spartan_shift" => Ok(KernelSpec { + symbol: "jolt.cpu.stage3.spartan_shift", + kind: "sumcheck", + abi: "jolt_stage3_spartan_shift", + }), + "jolt.stage3.instruction_input" => Ok(KernelSpec { + symbol: "jolt.cpu.stage3.instruction_input", + kind: "sumcheck", + abi: "jolt_stage3_instruction_input", + }), + "jolt.stage3.registers_claim_reduction" => Ok(KernelSpec { + symbol: "jolt.cpu.stage3.registers_claim_reduction", + kind: "sumcheck", + abi: "jolt_stage3_registers_claim_reduction", + }), + "jolt.stage3.batched" => Ok(KernelSpec { + symbol: "jolt.cpu.stage3.batched", + kind: "sumcheck", + abi: "jolt_stage3_batched", + }), + "jolt.stage4.registers_read_write" => Ok(KernelSpec { + symbol: "jolt.cpu.stage4.registers_read_write", + kind: "sumcheck", + abi: "jolt_stage4_registers_read_write", + }), + "jolt.stage4.ram_val_check" => Ok(KernelSpec { + symbol: "jolt.cpu.stage4.ram_val_check", + kind: "sumcheck", + abi: "jolt_stage4_ram_val_check", + }), + "jolt.stage4.batched" => Ok(KernelSpec { + symbol: "jolt.cpu.stage4.batched", + kind: "sumcheck", + abi: "jolt_stage4_batched", + }), + "jolt.stage5.instruction_read_raf" => Ok(KernelSpec { + symbol: "jolt.cpu.stage5.instruction_read_raf", + kind: "sumcheck", + abi: "jolt_stage5_instruction_read_raf", + }), + "jolt.stage5.ram_ra_claim_reduction" => Ok(KernelSpec { + symbol: "jolt.cpu.stage5.ram_ra_claim_reduction", + kind: "sumcheck", + abi: "jolt_stage5_ram_ra_claim_reduction", + }), + "jolt.stage5.registers_val_evaluation" => Ok(KernelSpec { + symbol: "jolt.cpu.stage5.registers_val_evaluation", + kind: "sumcheck", + abi: "jolt_stage5_registers_val_evaluation", + }), + "jolt.stage5.batched" => Ok(KernelSpec { + symbol: "jolt.cpu.stage5.batched", + kind: "sumcheck", + abi: "jolt_stage5_batched", + }), + "jolt.stage6.bytecode_read_raf" => Ok(KernelSpec { + symbol: "jolt.cpu.stage6.bytecode_read_raf", + kind: "sumcheck", + abi: "jolt_stage6_bytecode_read_raf", + }), + "jolt.stage6.booleanity" => Ok(KernelSpec { + symbol: "jolt.cpu.stage6.booleanity", + kind: "sumcheck", + abi: "jolt_stage6_booleanity", + }), + "jolt.stage6.hamming_booleanity" => Ok(KernelSpec { + symbol: "jolt.cpu.stage6.hamming_booleanity", + kind: "sumcheck", + abi: "jolt_stage6_hamming_booleanity", + }), + "jolt.stage6.ram_ra_virtual" => Ok(KernelSpec { + symbol: "jolt.cpu.stage6.ram_ra_virtual", + kind: "sumcheck", + abi: "jolt_stage6_ram_ra_virtual", + }), + "jolt.stage6.instruction_ra_virtual" => Ok(KernelSpec { + symbol: "jolt.cpu.stage6.instruction_ra_virtual", + kind: "sumcheck", + abi: "jolt_stage6_instruction_ra_virtual", + }), + "jolt.stage6.inc_claim_reduction" => Ok(KernelSpec { + symbol: "jolt.cpu.stage6.inc_claim_reduction", + kind: "sumcheck", + abi: "jolt_stage6_inc_claim_reduction", + }), + "jolt.stage6.batched" => Ok(KernelSpec { + symbol: "jolt.cpu.stage6.batched", + kind: "sumcheck", + abi: "jolt_stage6_batched", + }), + "jolt.stage7.hamming_weight_claim_reduction" => Ok(KernelSpec { + symbol: "jolt.cpu.stage7.hamming_weight_claim_reduction", + kind: "sumcheck", + abi: "jolt_stage7_hamming_weight_claim_reduction", + }), + "jolt.stage7.batched" => Ok(KernelSpec { + symbol: "jolt.cpu.stage7.batched", + kind: "sumcheck", + abi: "jolt_stage7_batched", + }), + _ => Err(schema_error(format!( + "unsupported compute relation @{relation}" + ))), + } +} + +struct KernelSpec { + symbol: &'static str, + kind: &'static str, + abi: &'static str, +} + +fn int_attr(value: usize) -> String { + format!("{value} : i64") +} + +fn symbol_array_attr(values: &[&str]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn opening_claim_attr() -> String { + let values = R1CS_INPUT_ORACLES + .iter() + .map(|oracle| format!("@stage1.outer_remaining.opening.{oracle}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn schema_error(message: impl Into) -> MlirError { + let error = SchemaError::new(message); + error.into() +} + +fn symbol_ref(value: &str) -> String { + format!("@{value}") +} + +fn string_literal(value: &str) -> String { + format!("{value:?}") +} diff --git a/crates/bolt/src/protocols/jolt/phases/stage2.rs b/crates/bolt/src/protocols/jolt/phases/stage2.rs new file mode 100644 index 0000000000..2c67e00a81 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/stage2.rs @@ -0,0 +1,2082 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::OperationRef; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::Value; + +use crate::ir::{BoltModule, Compute, Party, Protocol, Role}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{ + operation_name, symbol_attr, verify_compute_schema, verify_party_schema, + verify_protocol_schema, SchemaError, +}; + +use super::super::oracles; +use super::super::params::JoltProtocolParams; +use super::lowering::{ + copy_attrs, field_lowering_attrs as field_compute_attrs, string_attr, + transcript_squeeze_compute_result_types, transcript_squeeze_protocol_result_type, +}; + +const PRODUCT_UNISKIP_DEGREE_BOUND: usize = 6; +const PRODUCT_UNISKIP_DOMAIN_START: isize = -1; +const PRODUCT_UNISKIP_DOMAIN_SIZE: usize = 3; +const RAM_RW_DEGREE: usize = 3; +const PRODUCT_REMAINDER_DEGREE: usize = 3; +const INSTRUCTION_CLAIM_REDUCTION_DEGREE: usize = 2; +const RAM_RAF_DEGREE: usize = 2; +const RAM_OUTPUT_DEGREE: usize = 3; + +const STAGE1_PRODUCT_OPENINGS: [&str; 3] = ["Product", "ShouldBranch", "ShouldJump"]; +const STAGE2_RAM_RW_INPUTS: [&str; 2] = ["RamReadValue", "RamWriteValue"]; +const STAGE2_INSTRUCTION_INPUTS: [&str; 5] = [ + "LookupOutput", + "LeftLookupOperand", + "RightLookupOperand", + "LeftInstructionInput", + "RightInstructionInput", +]; +const PRODUCT_REMAINDER_OUTPUTS: [&str; 8] = [ + "LeftInstructionInput", + "RightInstructionInput", + "OpFlagJump", + "OpFlagWriteLookupOutputToRD", + "LookupOutput", + "InstructionFlagBranch", + "NextIsNoop", + "OpFlagVirtualInstruction", +]; + +pub fn build_stage2_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.stage2", None); + oracles::append_foundation_ops(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.stage2"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + append_stage2_domains(context, &module, params)?; + append_stage2_oracles(context, &module)?; + append_stage2_relations(context, &module, params)?; + let inputs = append_stage2_opening_inputs(context, &module, params)?; + + let fs = context.append_typed_op( + &module, + "transcript.state", + Some("fs_after_stage1"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let state = first_result(fs, "transcript.state")?; + let stage = context.append_typed_op( + &module, + "piop.stage", + Some("stage2"), + &[ + ("name", r#""product_virtual_and_ram""#), + ("order", "2 : i64"), + ("roles", r#"["prover", "verifier"]"#), + ], + &[], + &["!piop.stage_type"], + )?; + let stage = first_result(stage, "piop.stage")?; + let (state, tau_high) = append_transcript_squeeze( + context, + &module, + state, + "stage2.product_virtual.tau_high", + "product_virtual_tau_high", + "challenge_scalar", + 1, + )?; + let (state, uniskip) = + append_product_uniskip(context, &module, params, state, stage, &inputs, tau_high)?; + let (state, ram_read_write_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage2.ram_read_write.gamma", + "ram_read_write_gamma", + "challenge_scalar", + 1, + )?; + let (state, instruction_lookup_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage2.instruction_lookup.gamma", + "instruction_lookup_gamma", + "challenge_scalar", + 1, + )?; + let (state, _ram_output_address) = append_transcript_squeeze( + context, + &module, + state, + "stage2.ram_output.r_address", + "ram_output_r_address", + "challenge_vector", + params.log_k_ram, + )?; + let _state = append_stage2_batched_sumcheck( + context, + &module, + params, + Stage2BatchedSumcheckInputs { + state, + stage, + openings: &inputs, + uniskip, + ram_read_write_gamma, + instruction_lookup_gamma, + }, + )?; + + verify_module(&module)?; + verify_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_stage2_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + verify_party_schema(module)?; + let role = module + .role() + .ok_or_else(|| schema_error("stage2 lowering requires party role"))?; + let params = stage_params(module)?; + let compute = context.new_module::(&module.name(), Some(role.clone())); + context.append_op_with_owned_attrs( + &compute, + "compute.params", + Some("jolt.compute_params"), + &[ + ("field".to_owned(), symbol_ref(¶ms.field)), + ("pcs".to_owned(), symbol_ref(¶ms.pcs)), + ("transcript".to_owned(), symbol_ref(¶ms.transcript)), + ], + )?; + context.append_op( + &compute, + "compute.function", + Some("jolt.stage2"), + &[("source", "@jolt.stage2")], + )?; + + let mut value_map = BTreeMap::new(); + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "piop.relation" => { + let attrs = copy_attrs( + op, + &["kind", "domain", "num_rounds", "degree", "output_count"], + )?; + let symbol = string_attr(op, "sym_name")?; + context.append_op_with_owned_attrs( + &compute, + "compute.relation", + Some(&symbol), + &attrs, + )?; + } + "transcript.state" => { + let attrs = copy_attrs(op, &["scheme"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_init", + Some(&symbol), + &attrs, + &[], + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "transcript.absorb_bytes" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "payload"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_absorb_bytes", + Some(&symbol), + &attrs, + &operands, + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "transcript.squeeze" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "kind", "count"])?; + let result_types = transcript_squeeze_compute_result_types(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_squeeze", + Some(&symbol), + &attrs, + &operands, + &result_types, + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "field.const" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field", "value"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.field_const", + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "field.zero" | "field.one" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + &format!("compute.{}", operation_name(op).replace('.', "_")), + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "field.add" | "field.sub" | "field.mul" | "field.neg" | "field.pow" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = field_compute_attrs(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + &format!("compute.{}", operation_name(op).replace('.', "_")), + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "poly.lagrange_basis_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["domain_start", "domain_size", "index"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.poly_lagrange_basis_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.opening_input" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source_stage", + "source_claim", + "oracle", + "domain", + "point_arity", + "claim_kind", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_input", + Some(&symbol), + &attrs, + &[], + &[ + "!compute.point", + "!compute.field_value", + "!compute.opening_claim_type", + ], + )?; + for index in 0..3 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "poly.point_slice" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "offset", "length"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.point_slice", + Some(&symbol), + &attrs, + &operands, + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "poly.point_concat" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["layout", "arity"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.point_concat", + Some(&symbol), + &attrs, + &operands, + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + let target_op = match &role { + Role::Prover => "compute.sumcheck_claim", + Role::Verifier => "compute.sumcheck_verify_claim", + }; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_batch" => { + let operands = lowered_operands(op, &value_map, 1)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + let target_op = match &role { + Role::Prover => "compute.sumcheck_driver", + Role::Verifier => "compute.sumcheck_verify", + }; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &[ + "!compute.transcript_state", + "!compute.point", + "!compute.sumcheck_result_type", + "!compute.sumcheck_proof_type", + ], + )?; + for index in 0..4 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "piop.sumcheck_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "name", "index", "oracle"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_instance_result" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_instance_result", + Some(&symbol), + &attrs, + &operands, + &["!compute.point", "!compute.sumcheck_result_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "piop.opening_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["oracle", "domain", "point_arity", "claim_kind"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.opening_claim_equal" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["mode"])?; + let _operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_claim_equal", + Some(&symbol), + &attrs, + &operands, + &[], + )?; + } + "piop.opening_batch" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &["stage", "proof_slot", "policy", "count", "ordered_claims"], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + _ => {} + } + } + + verify_module(&compute)?; + verify_compute_schema(&compute)?; + Ok(compute) +} + +fn append_stage2_domains<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + context.append_op( + module, + "poly.domain", + Some("jolt.stage2_uniskip_domain"), + &[("field", "@bn254_fr"), ("log_size", "1 : i64")], + )?; + context.append_op( + module, + "poly.domain", + Some("jolt.stage2_ram_rw_domain"), + &[ + ("field", "@bn254_fr"), + ("log_size", &int_attr(stage2_max_rounds(params))), + ], + )?; + context.append_op( + module, + "poly.domain", + Some("jolt.ram_address_domain"), + &[ + ("field", "@bn254_fr"), + ("log_size", &int_attr(params.log_k_ram)), + ], + ) +} + +fn append_stage2_oracles<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, +) -> Result<(), MlirError> { + let mut trace_oracles = BTreeSet::new(); + trace_oracles.extend(STAGE1_PRODUCT_OPENINGS); + trace_oracles.extend(STAGE2_RAM_RW_INPUTS); + trace_oracles.extend(STAGE2_INSTRUCTION_INPUTS); + trace_oracles.extend(PRODUCT_REMAINDER_OUTPUTS); + let _ = trace_oracles.insert("RamAddress"); + for oracle in trace_oracles { + append_virtual_oracle(context, module, oracle, "jolt.trace_domain")?; + } + append_virtual_oracle( + context, + module, + "UnivariateSkip", + "jolt.stage2_uniskip_domain", + )?; + append_virtual_oracle(context, module, "RamVal", "jolt.stage2_ram_rw_domain")?; + append_virtual_oracle(context, module, "RamRa", "jolt.stage2_ram_rw_domain")?; + append_virtual_oracle(context, module, "RamValFinal", "jolt.ram_address_domain")?; + context.append_op( + module, + "piop.oracle", + Some("RamInc"), + &[ + ("field", "@bn254_fr"), + ("domain", "@jolt.trace_domain"), + ("commit_domain", "@jolt.main_witness_commit_domain"), + ("visibility", r#""committed""#), + ("layout", r#""dense_trace""#), + ], + ) +} + +fn append_virtual_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + domain: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", &format!("@{domain}")), + ("commit_domain", &format!("@{domain}")), + ("visibility", r#""virtual""#), + ("layout", r#""virtual""#), + ], + ) +} + +fn append_stage2_relations<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + let max_rounds = stage2_max_rounds(params); + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage2.product_virtual.uniskip", + kind: "sumcheck", + domain: "jolt.stage2_uniskip_domain", + num_rounds: 1, + degree: PRODUCT_UNISKIP_DEGREE_BOUND, + output_count: 1, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage2.ram.read_write", + kind: "sumcheck", + domain: "jolt.stage2_ram_rw_domain", + num_rounds: max_rounds, + degree: RAM_RW_DEGREE, + output_count: 3, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage2.product_virtual.remainder", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: PRODUCT_REMAINDER_DEGREE, + output_count: PRODUCT_REMAINDER_OUTPUTS.len(), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage2.instruction_lookup.claim_reduction", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: INSTRUCTION_CLAIM_REDUCTION_DEGREE, + output_count: STAGE2_INSTRUCTION_INPUTS.len(), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage2.ram.raf_evaluation", + kind: "sumcheck", + domain: "jolt.ram_address_domain", + num_rounds: params.log_k_ram, + degree: RAM_RAF_DEGREE, + output_count: 1, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage2.ram.output_check", + kind: "sumcheck", + domain: "jolt.ram_address_domain", + num_rounds: params.log_k_ram, + degree: RAM_OUTPUT_DEGREE, + output_count: 1, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage2.batched", + kind: "batched_sumcheck", + domain: "jolt.stage2_ram_rw_domain", + num_rounds: max_rounds, + degree: RAM_RW_DEGREE, + output_count: 18, + }, + ) +} + +fn append_relation<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + spec: RelationSpec<'_>, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.relation", + Some(spec.symbol), + &[ + ("kind", &format!("\"{}\"", spec.kind)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("output_count", &int_attr(spec.output_count)), + ], + ) +} + +fn append_stage2_opening_inputs<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let product = append_stage1_opening_input(context, module, params, "Product")?; + let should_branch = append_stage1_opening_input(context, module, params, "ShouldBranch")?; + let should_jump = append_stage1_opening_input(context, module, params, "ShouldJump")?; + let ram_read_value = append_stage1_opening_input(context, module, params, "RamReadValue")?; + let ram_write_value = append_stage1_opening_input(context, module, params, "RamWriteValue")?; + let lookup_output = append_stage1_opening_input(context, module, params, "LookupOutput")?; + let left_lookup_operand = + append_stage1_opening_input(context, module, params, "LeftLookupOperand")?; + let right_lookup_operand = + append_stage1_opening_input(context, module, params, "RightLookupOperand")?; + let left_instruction_input = + append_stage1_opening_input(context, module, params, "LeftInstructionInput")?; + let right_instruction_input = + append_stage1_opening_input(context, module, params, "RightInstructionInput")?; + let ram_address = append_stage1_opening_input(context, module, params, "RamAddress")?; + + Ok(Stage2OpeningInputs { + product, + should_branch, + should_jump, + ram_read_value, + ram_write_value, + lookup_output, + left_lookup_operand, + right_lookup_operand, + left_instruction_input, + right_instruction_input, + ram_address, + }) +} + +fn append_stage1_opening_input<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + oracle: &str, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_input", + Some(&format!("stage2.input.stage1.{oracle}")), + &[ + ("source_stage", "@stage1"), + ( + "source_claim", + &format!("@stage1.outer_remaining.opening.{oracle}"), + ), + ("oracle", &format!("@{oracle}")), + ("domain", "@jolt.trace_domain"), + ("point_arity", &int_attr(params.log_t)), + ("claim_kind", r#""virtual""#), + ], + &[], + &["!poly.point", "!field.scalar", "!piop.opening_claim_type"], + )?; + Ok(Stage2OpeningInput { + point: result(op, 0, "piop.opening_input")?, + eval: result(op, 1, "piop.opening_input")?, + claim: result(op, 2, "piop.opening_input")?, + }) +} + +fn append_transcript_squeeze<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + symbol: &str, + label: &str, + kind: &str, + count: usize, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "transcript.squeeze", + Some(symbol), + &[ + ("label", &format!("\"{label}\"")), + ("kind", &format!("\"{kind}\"")), + ("count", &int_attr(count)), + ], + &[state], + &[ + "!transcript.state_type", + transcript_squeeze_protocol_result_type(kind)?, + ], + )?; + Ok(( + result(op, 0, "transcript.squeeze")?, + result(op, 1, "transcript.squeeze")?, + )) +} + +fn append_field_const<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + value: usize, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.const", + Some(symbol), + &[("field", "@bn254_fr"), ("value", &int_attr(value))], + &[], + &["!field.scalar"], + )?; + first_result(op, "field.const") +} + +fn append_field_binary<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + op_name: &str, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + op_name, + Some(symbol), + &[], + &[lhs, rhs], + &["!field.scalar"], + )?; + first_result(op, op_name) +} + +fn append_field_add<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.add", symbol, lhs, rhs) +} + +fn append_field_mul<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.mul", symbol, lhs, rhs) +} + +fn append_lagrange_basis_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + point: Value<'c, 'a>, + index: usize, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.lagrange_basis_eval", + Some(symbol), + &[ + ( + "domain_start", + &int_attr_signed(PRODUCT_UNISKIP_DOMAIN_START), + ), + ("domain_size", &int_attr(PRODUCT_UNISKIP_DOMAIN_SIZE)), + ("index", &int_attr(index)), + ], + &[point], + &["!field.scalar"], + )?; + first_result(op, "poly.lagrange_basis_eval") +} + +fn append_product_uniskip<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + _params: &JoltProtocolParams, + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + inputs: &Stage2OpeningInputs<'c, 'a>, + tau_high: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Stage2UniskipOutput<'c, 'a>), MlirError> { + let product_weight = append_lagrange_basis_eval( + context, + module, + "stage2.product_virtual.uniskip.weight.Product", + tau_high, + 0, + )?; + let branch_weight = append_lagrange_basis_eval( + context, + module, + "stage2.product_virtual.uniskip.weight.ShouldBranch", + tau_high, + 1, + )?; + let jump_weight = append_lagrange_basis_eval( + context, + module, + "stage2.product_virtual.uniskip.weight.ShouldJump", + tau_high, + 2, + )?; + let product_term = append_field_mul( + context, + module, + "stage2.product_virtual.uniskip.term.Product", + product_weight, + inputs.product.eval, + )?; + let branch_term = append_field_mul( + context, + module, + "stage2.product_virtual.uniskip.term.ShouldBranch", + branch_weight, + inputs.should_branch.eval, + )?; + let jump_term = append_field_mul( + context, + module, + "stage2.product_virtual.uniskip.term.ShouldJump", + jump_weight, + inputs.should_jump.eval, + )?; + let product_branch_sum = append_field_add( + context, + module, + "stage2.product_virtual.uniskip.partial.ProductShouldBranch", + product_term, + branch_term, + )?; + let input_claim = append_field_add( + context, + module, + "stage2.product_virtual.uniskip.claim_expr", + product_branch_sum, + jump_term, + )?; + let claim = append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage2.product_virtual.uniskip.input", + stage: "stage2", + domain: "jolt.stage2_uniskip_domain", + num_rounds: 1, + degree: PRODUCT_UNISKIP_DEGREE_BOUND, + claim: "stage2.product_virtual.weighted_stage1_outputs", + relation: "jolt.stage2.product_virtual.uniskip", + }, + input_claim, + &[ + inputs.product.claim, + inputs.should_branch.claim, + inputs.should_jump.claim, + ], + )?; + let batch = append_sumcheck_batch( + context, + module, + stage, + &[claim], + SumcheckBatchSpec { + symbol: "stage2.product_virtual.uniskip.batch", + stage: "stage2", + proof_slot: "stage2.product_virtual.uni_skip_first_round", + policy: "single_instance", + ordered_claims: &["stage2.product_virtual.uniskip.input"], + claim_label: "uniskip_claim", + round_label: "uniskip_poly", + round_schedule: "[1]".to_owned(), + }, + )?; + let (state, point, result_value) = append_sumcheck( + context, + module, + state, + batch, + SumcheckDriverSpec { + symbol: "stage2.product_virtual.uniskip.sumcheck", + stage: "stage2", + proof_slot: "stage2.product_virtual.uni_skip_first_round", + relation: "jolt.stage2.product_virtual.uniskip", + policy: "univariate_skip", + round_schedule: "[1]".to_owned(), + claim_label: "uniskip_claim", + round_label: "uniskip_poly", + num_rounds: 1, + degree: PRODUCT_UNISKIP_DEGREE_BOUND, + }, + )?; + let (point, result_value) = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage2.product_virtual.uniskip.instance", + source: "stage2.product_virtual.uniskip.sumcheck", + claim: "stage2.product_virtual.uniskip.input", + relation: "jolt.stage2.product_virtual.uniskip", + index: 0, + point_arity: 1, + num_rounds: 1, + round_offset: 0, + point_order: "as_is", + degree: PRODUCT_UNISKIP_DEGREE_BOUND, + }, + point, + result_value, + )?; + let eval = append_sumcheck_eval( + context, + module, + "stage2.product_virtual.uniskip.eval.UnivariateSkip", + "stage2.product_virtual.uniskip.sumcheck", + "UnivariateSkip", + 0, + result_value, + )?; + let opening = append_opening_claim( + context, + module, + point, + eval, + OpeningClaimSpec { + symbol: "stage2.product_virtual.uniskip.opening.UnivariateSkip", + oracle: "UnivariateSkip", + domain: "jolt.stage2_uniskip_domain", + point_arity: 1, + claim_kind: "virtual", + }, + )?; + Ok((state, Stage2UniskipOutput { opening, eval })) +} + +fn append_stage2_batched_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + spec: Stage2BatchedSumcheckInputs<'c, 'a, '_>, +) -> Result, MlirError> { + let inputs = spec.openings; + let uniskip = spec.uniskip; + let max_rounds = stage2_max_rounds(params); + let product_offset = max_rounds - params.log_t; + let ram_offset = params.log_t; + let ram_write_term = append_field_mul( + context, + module, + "stage2.ram_read_write.term.RamWriteValue", + spec.ram_read_write_gamma, + inputs.ram_write_value.eval, + )?; + let ram_read_write_claim = append_field_add( + context, + module, + "stage2.ram_read_write.claim_expr", + inputs.ram_read_value.eval, + ram_write_term, + )?; + let product_remainder_claim = uniskip.eval; + let gamma2 = append_field_mul( + context, + module, + "stage2.instruction_lookup.gamma2", + spec.instruction_lookup_gamma, + spec.instruction_lookup_gamma, + )?; + let gamma3 = append_field_mul( + context, + module, + "stage2.instruction_lookup.gamma3", + gamma2, + spec.instruction_lookup_gamma, + )?; + let gamma4 = append_field_mul( + context, + module, + "stage2.instruction_lookup.gamma4", + gamma2, + gamma2, + )?; + let left_lookup_term = append_field_mul( + context, + module, + "stage2.instruction_lookup.term.LeftLookupOperand", + spec.instruction_lookup_gamma, + inputs.left_lookup_operand.eval, + )?; + let right_lookup_term = append_field_mul( + context, + module, + "stage2.instruction_lookup.term.RightLookupOperand", + gamma2, + inputs.right_lookup_operand.eval, + )?; + let left_input_term = append_field_mul( + context, + module, + "stage2.instruction_lookup.term.LeftInstructionInput", + gamma3, + inputs.left_instruction_input.eval, + )?; + let right_input_term = append_field_mul( + context, + module, + "stage2.instruction_lookup.term.RightInstructionInput", + gamma4, + inputs.right_instruction_input.eval, + )?; + let instruction_sum_0 = append_field_add( + context, + module, + "stage2.instruction_lookup.partial.LookupOutputLeftOperand", + inputs.lookup_output.eval, + left_lookup_term, + )?; + let instruction_sum_1 = append_field_add( + context, + module, + "stage2.instruction_lookup.partial.RightOperand", + instruction_sum_0, + right_lookup_term, + )?; + let instruction_sum_2 = append_field_add( + context, + module, + "stage2.instruction_lookup.partial.LeftInstructionInput", + instruction_sum_1, + left_input_term, + )?; + let instruction_claim = append_field_add( + context, + module, + "stage2.instruction_lookup.claim_reduction.claim_expr", + instruction_sum_2, + right_input_term, + )?; + let ram_raf_claim = inputs.ram_address.eval; + let ram_output_claim = append_field_const(context, module, "stage2.ram_output.zero", 0)?; + let claims = [ + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage2.ram_read_write.input", + stage: "stage2", + domain: "jolt.stage2_ram_rw_domain", + num_rounds: max_rounds, + degree: RAM_RW_DEGREE, + claim: "stage2.ram_read_write.weighted_values", + relation: "jolt.stage2.ram.read_write", + }, + ram_read_write_claim, + &[inputs.ram_read_value.claim, inputs.ram_write_value.claim], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage2.product_virtual.remainder.input", + stage: "stage2", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: PRODUCT_REMAINDER_DEGREE, + claim: "stage2.product_virtual.uniskip.opening", + relation: "jolt.stage2.product_virtual.remainder", + }, + product_remainder_claim, + &[uniskip.opening], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage2.instruction_lookup.claim_reduction.input", + stage: "stage2", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: INSTRUCTION_CLAIM_REDUCTION_DEGREE, + claim: "stage2.instruction_lookup.weighted_operands", + relation: "jolt.stage2.instruction_lookup.claim_reduction", + }, + instruction_claim, + &[ + inputs.lookup_output.claim, + inputs.left_lookup_operand.claim, + inputs.right_lookup_operand.claim, + inputs.left_instruction_input.claim, + inputs.right_instruction_input.claim, + ], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage2.ram_raf.input", + stage: "stage2", + domain: "jolt.ram_address_domain", + num_rounds: params.log_k_ram, + degree: RAM_RAF_DEGREE, + claim: "stage2.ram_raf.ram_address", + relation: "jolt.stage2.ram.raf_evaluation", + }, + ram_raf_claim, + &[inputs.ram_address.claim], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage2.ram_output.input", + stage: "stage2", + domain: "jolt.ram_address_domain", + num_rounds: params.log_k_ram, + degree: RAM_OUTPUT_DEGREE, + claim: "zero", + relation: "jolt.stage2.ram.output_check", + }, + ram_output_claim, + &[], + )?, + ]; + let batch = append_sumcheck_batch( + context, + module, + spec.stage, + &claims, + SumcheckBatchSpec { + symbol: "stage2.batch", + stage: "stage2", + proof_slot: "stage2.sumcheck", + policy: "jolt_core_stage2_aligned", + ordered_claims: &[ + "stage2.ram_read_write.input", + "stage2.product_virtual.remainder.input", + "stage2.instruction_lookup.claim_reduction.input", + "stage2.ram_raf.input", + "stage2.ram_output.input", + ], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: format!("[{}, {}]", params.log_t, params.log_k_ram), + }, + )?; + let (state, point, result_value) = append_sumcheck( + context, + module, + spec.state, + batch, + SumcheckDriverSpec { + symbol: "stage2.sumcheck", + stage: "stage2", + proof_slot: "stage2.sumcheck", + relation: "jolt.stage2.batched", + policy: "jolt_core_stage2_aligned", + round_schedule: format!("[{}, {}]", params.log_t, params.log_k_ram), + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: max_rounds, + degree: RAM_RW_DEGREE, + }, + )?; + let ram_rw = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage2.ram_read_write.instance", + source: "stage2.sumcheck", + claim: "stage2.ram_read_write.input", + relation: "jolt.stage2.ram.read_write", + index: 0, + point_arity: max_rounds, + num_rounds: max_rounds, + round_offset: 0, + point_order: "reverse", + degree: RAM_RW_DEGREE, + }, + point, + result_value, + )?; + let product = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage2.product_virtual.remainder.instance", + source: "stage2.sumcheck", + claim: "stage2.product_virtual.remainder.input", + relation: "jolt.stage2.product_virtual.remainder", + index: 1, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: product_offset, + point_order: "reverse", + degree: PRODUCT_REMAINDER_DEGREE, + }, + point, + result_value, + )?; + let instruction = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage2.instruction_lookup.claim_reduction.instance", + source: "stage2.sumcheck", + claim: "stage2.instruction_lookup.claim_reduction.input", + relation: "jolt.stage2.instruction_lookup.claim_reduction", + index: 2, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: product_offset, + point_order: "reverse", + degree: INSTRUCTION_CLAIM_REDUCTION_DEGREE, + }, + point, + result_value, + )?; + let ram_raf = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage2.ram_raf.instance", + source: "stage2.sumcheck", + claim: "stage2.ram_raf.input", + relation: "jolt.stage2.ram.raf_evaluation", + index: 3, + point_arity: params.log_k_ram, + num_rounds: params.log_k_ram, + round_offset: ram_offset, + point_order: "reverse", + degree: RAM_RAF_DEGREE, + }, + point, + result_value, + )?; + let ram_output = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage2.ram_output.instance", + source: "stage2.sumcheck", + claim: "stage2.ram_output.input", + relation: "jolt.stage2.ram.output_check", + index: 4, + point_arity: params.log_k_ram, + num_rounds: params.log_k_ram, + round_offset: ram_offset, + point_order: "reverse", + degree: RAM_OUTPUT_DEGREE, + }, + point, + result_value, + )?; + append_stage2_output_openings( + context, + module, + params, + Stage2OutputOpeningSpec { + outputs: &[ + InstanceOutput { + prefix: "stage2.product_virtual.remainder", + instance: product, + eval_source: "stage2.sumcheck", + outputs: &PRODUCT_REMAINDER_OUTPUTS, + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "virtual", + }, + InstanceOutput { + prefix: "stage2.instruction_lookup.claim_reduction", + instance: instruction, + eval_source: "stage2.sumcheck", + outputs: &STAGE2_INSTRUCTION_INPUTS, + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "virtual", + }, + ], + ram_rw, + ram_raf, + ram_output, + stage1_ram_address_point: inputs.ram_address.point, + }, + )?; + Ok(state) +} + +fn append_stage2_output_openings<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + spec: Stage2OutputOpeningSpec<'c, 'a, '_>, +) -> Result<(), MlirError> { + let mut claims = Vec::new(); + let mut claim_symbols = Vec::new(); + + for (index, &oracle) in ["RamVal", "RamRa"].iter().enumerate() { + let symbol = format!("stage2.ram_read_write.opening.{oracle}"); + let eval = append_sumcheck_eval( + context, + module, + &format!("stage2.ram_read_write.eval.{oracle}"), + "stage2.sumcheck", + oracle, + index, + spec.ram_rw.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + spec.ram_rw.0, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle, + domain: "jolt.stage2_ram_rw_domain", + point_arity: stage2_max_rounds(params), + claim_kind: "virtual", + }, + )?); + } + let ram_inc_point = append_point_slice( + context, + module, + "stage2.ram_read_write.point.RamInc", + "stage2.ram_read_write.instance", + params.log_k_ram, + params.log_t, + spec.ram_rw.0, + )?; + let ram_inc_eval = append_sumcheck_eval( + context, + module, + "stage2.ram_read_write.eval.RamInc", + "stage2.sumcheck", + "RamInc", + 2, + spec.ram_rw.1, + )?; + claim_symbols.push("stage2.ram_read_write.opening.RamInc".to_owned()); + claims.push(append_opening_claim( + context, + module, + ram_inc_point, + ram_inc_eval, + OpeningClaimSpec { + symbol: "stage2.ram_read_write.opening.RamInc", + oracle: "RamInc", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?); + + for output in spec.outputs { + for (index, &oracle) in output.outputs.iter().enumerate() { + let symbol = format!("{}.opening.{oracle}", output.prefix); + let eval = append_sumcheck_eval( + context, + module, + &format!("{}.eval.{oracle}", output.prefix), + output.eval_source, + oracle, + index, + output.instance.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + output.instance.0, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle, + domain: output.domain, + point_arity: output.point_arity, + claim_kind: output.claim_kind, + }, + )?); + } + } + + let ram_raf_point = append_point_concat( + context, + module, + "stage2.ram_raf.point.RamRa", + "address_then_cycle", + params.log_k_ram + params.log_t, + &[spec.ram_raf.0, spec.stage1_ram_address_point], + )?; + let ram_raf_eval = append_sumcheck_eval( + context, + module, + "stage2.ram_raf.eval.RamRa", + "stage2.sumcheck", + "RamRa", + 0, + spec.ram_raf.1, + )?; + claim_symbols.push("stage2.ram_raf.opening.RamRa".to_owned()); + claims.push(append_opening_claim( + context, + module, + ram_raf_point, + ram_raf_eval, + OpeningClaimSpec { + symbol: "stage2.ram_raf.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: params.log_k_ram + params.log_t, + claim_kind: "virtual", + }, + )?); + + let ram_output_eval = append_sumcheck_eval( + context, + module, + "stage2.ram_output.eval.RamValFinal", + "stage2.sumcheck", + "RamValFinal", + 0, + spec.ram_output.1, + )?; + claim_symbols.push("stage2.ram_output.opening.RamValFinal".to_owned()); + claims.push(append_opening_claim( + context, + module, + spec.ram_output.0, + ram_output_eval, + OpeningClaimSpec { + symbol: "stage2.ram_output.opening.RamValFinal", + oracle: "RamValFinal", + domain: "jolt.ram_address_domain", + point_arity: params.log_k_ram, + claim_kind: "virtual", + }, + )?); + + let claim_names = claim_symbols.iter().map(String::as_str).collect::>(); + let _batch = context.append_typed_op( + module, + "piop.opening_batch", + Some("stage2.openings"), + &[ + ("stage", "@stage2"), + ("proof_slot", "@stage2.openings"), + ("policy", r#""jolt_stage2_output_order""#), + ("count", &int_attr(claims.len())), + ("ordered_claims", &symbol_array_attr(&claim_names)), + ], + &claims, + &["!piop.opening_batch_type"], + )?; + Ok(()) +} + +fn append_sumcheck_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckClaimSpec<'_>, + input_claim: Value<'c, 'a>, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(inputs.len() + 1); + operands.push(input_claim); + operands.extend_from_slice(inputs); + let op = context.append_typed_op( + module, + "piop.sumcheck_claim", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ], + &operands, + &["!piop.sumcheck_claim_type"], + )?; + first_result(op, "piop.sumcheck_claim") +} + +fn append_sumcheck_batch<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + stage: Value<'c, 'a>, + claims: &[Value<'c, 'a>], + spec: SumcheckBatchSpec<'_>, +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(claims.len() + 1); + operands.push(stage); + operands.extend_from_slice(claims); + let op = context.append_typed_op( + module, + "piop.sumcheck_batch", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("policy", &format!("\"{}\"", spec.policy)), + ("count", &int_attr(spec.ordered_claims.len())), + ("ordered_claims", &symbol_array_attr(spec.ordered_claims)), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("round_schedule", &spec.round_schedule), + ], + &operands, + &["!piop.sumcheck_batch_type"], + )?; + first_result(op, "piop.sumcheck_batch") +} + +fn append_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + batch: Value<'c, 'a>, + spec: SumcheckDriverSpec<'_>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("relation", &format!("@{}", spec.relation)), + ("policy", &format!("\"{}\"", spec.policy)), + ("round_schedule", &spec.round_schedule), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ], + &[state, batch], + &[ + "!transcript.state_type", + "!poly.point", + "!piop.sumcheck_result_type", + "!piop.sumcheck_proof_type", + ], + )?; + Ok(( + result(op, 0, "piop.sumcheck")?, + result(op, 1, "piop.sumcheck")?, + result(op, 2, "piop.sumcheck")?, + )) +} + +fn append_sumcheck_instance_result<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckInstanceResultSpec<'_>, + point: Value<'c, 'a>, + result_value: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_instance_result", + Some(spec.symbol), + &[ + ("source", &format!("@{}", spec.source)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ("index", &int_attr(spec.index)), + ("point_arity", &int_attr(spec.point_arity)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("round_offset", &int_attr(spec.round_offset)), + ("point_order", &format!("\"{}\"", spec.point_order)), + ("degree", &int_attr(spec.degree)), + ], + &[point, result_value], + &["!poly.point", "!piop.sumcheck_result_type"], + )?; + Ok(( + result(op, 0, "piop.sumcheck_instance_result")?, + result(op, 1, "piop.sumcheck_instance_result")?, + )) +} + +fn append_sumcheck_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + oracle: &str, + index: usize, + result_value: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_eval", + Some(symbol), + &[ + ("source", &format!("@{source}")), + ("name", &format!("@{symbol}")), + ("index", &int_attr(index)), + ("oracle", &format!("@{oracle}")), + ], + &[result_value], + &["!field.scalar"], + )?; + first_result(op, "piop.sumcheck_eval") +} + +fn append_opening_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + spec: OpeningClaimSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_claim", + Some(spec.symbol), + &[ + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", &format!("\"{}\"", spec.claim_kind)), + ], + &[point, eval], + &["!piop.opening_claim_type"], + )?; + first_result(op, "piop.opening_claim") +} + +fn append_point_slice<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + offset: usize, + length: usize, + point: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_slice", + Some(symbol), + &[ + ("source", &format!("@{source}")), + ("offset", &int_attr(offset)), + ("length", &int_attr(length)), + ], + &[point], + &["!poly.point"], + )?; + first_result(op, "poly.point_slice") +} + +fn append_point_concat<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + layout: &str, + arity: usize, + points: &[Value<'c, 'a>], +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_concat", + Some(symbol), + &[ + ("layout", &format!("\"{layout}\"")), + ("arity", &int_attr(arity)), + ], + points, + &["!poly.point"], + )?; + first_result(op, "poly.point_concat") +} + +fn first_result<'c, 'a>( + operation: OperationRef<'c, 'a>, + operation_name: &str, +) -> Result, MlirError> { + result(operation, 0, operation_name) +} + +fn result<'c, 'a>( + operation: OperationRef<'c, 'a>, + index: usize, + operation_name: &str, +) -> Result, MlirError> { + operation + .result(index) + .map(Into::into) + .map_err(|_| schema_error(format!("{operation_name} requires result {index}"))) +} + +#[derive(Clone, Debug)] +struct StageParamsAst { + field: String, + pcs: String, + transcript: String, +} + +fn stage_params(module: &BoltModule<'_, Party>) -> Result { + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + if operation_name(op) == "protocol.params" { + return Ok(StageParamsAst { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + } + Err(schema_error("stage2 lowering requires protocol.params")) +} + +fn operation_result_key_at( + operation: OperationRef<'_, '_>, + index: usize, +) -> Result { + let result = operation.result(index).map_err(|_| { + schema_error(format!( + "{} requires result {index}", + operation_name(operation) + )) + })?; + result_key(result.owner(), result.result_number()) +} + +fn result_key(operation: OperationRef<'_, '_>, result_number: usize) -> Result { + Ok(format!( + "{}#{result_number}", + string_attr(operation, "sym_name")? + )) +} + +fn operand_key(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + schema_error(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + schema_error(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + result_key(owner.owner(), owner.result_number()).map_err(|_| { + schema_error(format!( + "{} operand {index} owner missing sym_name", + operation_name(operation) + )) + }) +} + +fn lowered_operands<'c, 'a>( + operation: OperationRef<'_, '_>, + value_map: &BTreeMap>, + start_index: usize, +) -> Result>, MlirError> { + (start_index..operation.operand_count()) + .map(|index| { + let key = operand_key(operation, index)?; + value_map.get(&key).copied().ok_or_else(|| { + schema_error(format!( + "{} operand {index} was not lowered", + operation_name(operation) + )) + }) + }) + .collect() +} + +fn insert_result_mapping<'c, 'a>( + value_map: &mut BTreeMap>, + source: OperationRef<'_, '_>, + target: OperationRef<'c, 'a>, + source_index: usize, + target_index: usize, +) -> Result<(), MlirError> { + let key = operation_result_key_at(source, source_index)?; + let value = target.result(target_index).map(Into::into).map_err(|_| { + schema_error(format!( + "{} requires result {target_index}", + operation_name(target) + )) + })?; + let inserted = value_map.insert(key, value); + debug_assert!(inserted.is_none()); + Ok(()) +} + +fn symbol_ref(symbol: &str) -> String { + format!("@{symbol}") +} + +fn stage2_max_rounds(params: &JoltProtocolParams) -> usize { + params.log_t + params.log_k_ram +} + +fn int_attr(value: usize) -> String { + format!("{value} : i64") +} + +fn int_attr_signed(value: isize) -> String { + format!("{value} : i64") +} + +fn symbol_array_attr(values: &[&str]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn schema_error(message: impl Into) -> MlirError { + SchemaError::new(message).into() +} + +#[derive(Clone, Copy)] +struct Stage2OpeningInput<'c, 'a> { + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + claim: Value<'c, 'a>, +} + +struct Stage2OpeningInputs<'c, 'a> { + product: Stage2OpeningInput<'c, 'a>, + should_branch: Stage2OpeningInput<'c, 'a>, + should_jump: Stage2OpeningInput<'c, 'a>, + ram_read_value: Stage2OpeningInput<'c, 'a>, + ram_write_value: Stage2OpeningInput<'c, 'a>, + lookup_output: Stage2OpeningInput<'c, 'a>, + left_lookup_operand: Stage2OpeningInput<'c, 'a>, + right_lookup_operand: Stage2OpeningInput<'c, 'a>, + left_instruction_input: Stage2OpeningInput<'c, 'a>, + right_instruction_input: Stage2OpeningInput<'c, 'a>, + ram_address: Stage2OpeningInput<'c, 'a>, +} + +#[derive(Clone, Copy)] +struct Stage2UniskipOutput<'c, 'a> { + opening: Value<'c, 'a>, + eval: Value<'c, 'a>, +} + +struct Stage2BatchedSumcheckInputs<'c, 'a, 'b> { + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + openings: &'b Stage2OpeningInputs<'c, 'a>, + uniskip: Stage2UniskipOutput<'c, 'a>, + ram_read_write_gamma: Value<'c, 'a>, + instruction_lookup_gamma: Value<'c, 'a>, +} + +struct RelationSpec<'a> { + symbol: &'a str, + kind: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + output_count: usize, +} + +struct SumcheckClaimSpec<'a> { + symbol: &'a str, + stage: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + claim: &'a str, + relation: &'a str, +} + +struct SumcheckBatchSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + policy: &'a str, + ordered_claims: &'a [&'a str], + claim_label: &'a str, + round_label: &'a str, + round_schedule: String, +} + +struct SumcheckDriverSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + relation: &'a str, + policy: &'a str, + round_schedule: String, + claim_label: &'a str, + round_label: &'a str, + num_rounds: usize, + degree: usize, +} + +struct SumcheckInstanceResultSpec<'a> { + symbol: &'a str, + source: &'a str, + claim: &'a str, + relation: &'a str, + index: usize, + point_arity: usize, + num_rounds: usize, + round_offset: usize, + point_order: &'a str, + degree: usize, +} + +struct OpeningClaimSpec<'a> { + symbol: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, + claim_kind: &'a str, +} + +struct Stage2OutputOpeningSpec<'c, 'a, 'b> { + outputs: &'b [InstanceOutput<'c, 'a, 'b>], + ram_rw: (Value<'c, 'a>, Value<'c, 'a>), + ram_raf: (Value<'c, 'a>, Value<'c, 'a>), + ram_output: (Value<'c, 'a>, Value<'c, 'a>), + stage1_ram_address_point: Value<'c, 'a>, +} + +struct InstanceOutput<'c, 'a, 'b> { + prefix: &'b str, + instance: (Value<'c, 'a>, Value<'c, 'a>), + eval_source: &'b str, + outputs: &'b [&'b str], + domain: &'b str, + point_arity: usize, + claim_kind: &'b str, +} diff --git a/crates/bolt/src/protocols/jolt/phases/stage3.rs b/crates/bolt/src/protocols/jolt/phases/stage3.rs new file mode 100644 index 0000000000..b0e3c1513d --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/stage3.rs @@ -0,0 +1,1762 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::OperationRef; +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::Value; + +use crate::ir::{BoltModule, Compute, Party, Protocol, Role}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{ + operation_name, symbol_attr, verify_compute_schema, verify_party_schema, + verify_protocol_schema, SchemaError, +}; + +use super::super::oracles; +use super::super::params::JoltProtocolParams; +use super::lowering::{ + copy_attrs, field_lowering_attrs as field_compute_attrs, string_attr, + transcript_squeeze_compute_result_types, transcript_squeeze_protocol_result_type, +}; + +const SPARTAN_SHIFT_DEGREE: usize = 2; +const INSTRUCTION_INPUT_DEGREE: usize = 3; +const REGISTERS_CLAIM_REDUCTION_DEGREE: usize = 2; +const STAGE3_BATCHED_DEGREE: usize = 3; + +const STAGE3_SHIFT_INPUTS: [&str; 4] = [ + "NextUnexpandedPC", + "NextPC", + "NextIsVirtual", + "NextIsFirstInSequence", +]; +const STAGE3_SHIFT_OUTPUTS: [&str; 5] = [ + "UnexpandedPC", + "PC", + "OpFlagVirtualInstruction", + "OpFlagIsFirstInSequence", + "InstructionFlagIsNoop", +]; +const STAGE3_INSTRUCTION_INPUT_OUTPUTS: [&str; 8] = [ + "InstructionFlagLeftOperandIsRs1Value", + "Rs1Value", + "InstructionFlagLeftOperandIsPC", + "UnexpandedPC", + "InstructionFlagRightOperandIsRs2Value", + "Rs2Value", + "InstructionFlagRightOperandIsImm", + "Imm", +]; +const STAGE3_REGISTER_INPUTS: [&str; 3] = ["RdWriteValue", "Rs1Value", "Rs2Value"]; + +pub fn build_stage3_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.stage3", None); + oracles::append_foundation_ops(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.stage3"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + append_stage3_oracles(context, &module)?; + append_stage3_relations(context, &module, params)?; + let inputs = append_stage3_opening_inputs(context, &module, params)?; + + let fs = context.append_typed_op( + &module, + "transcript.state", + Some("fs_after_stage2"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let state = first_result(fs, "transcript.state")?; + let stage = context.append_typed_op( + &module, + "piop.stage", + Some("stage3"), + &[ + ("name", r#""shift_instruction_input_and_registers""#), + ("order", "3 : i64"), + ("roles", r#"["prover", "verifier"]"#), + ], + &[], + &["!piop.stage_type"], + )?; + let stage = first_result(stage, "piop.stage")?; + + let (state, shift_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage3.spartan_shift.gamma", + "spartan_shift_gamma", + "challenge_scalar", + 1, + )?; + let (state, instruction_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage3.instruction_input.gamma", + "instruction_input_gamma", + "challenge_scalar", + 1, + )?; + let (state, registers_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage3.registers.gamma", + "registers_gamma", + "challenge_scalar", + 1, + )?; + let _state = append_stage3_batched_sumcheck( + context, + &module, + params, + Stage3BatchedSumcheckInputs { + state, + stage, + openings: &inputs, + shift_gamma, + instruction_gamma, + registers_gamma, + }, + )?; + + verify_module(&module)?; + verify_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_stage3_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + verify_party_schema(module)?; + let role = module + .role() + .ok_or_else(|| schema_error("stage3 lowering requires party role"))?; + let params = stage_params(module)?; + let compute = context.new_module::(&module.name(), Some(role.clone())); + context.append_op_with_owned_attrs( + &compute, + "compute.params", + Some("jolt.compute_params"), + &[ + ("field".to_owned(), symbol_ref(¶ms.field)), + ("pcs".to_owned(), symbol_ref(¶ms.pcs)), + ("transcript".to_owned(), symbol_ref(¶ms.transcript)), + ], + )?; + context.append_op( + &compute, + "compute.function", + Some("jolt.stage3"), + &[("source", "@jolt.stage3")], + )?; + + let mut value_map = BTreeMap::new(); + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + match operation_name(op).as_str() { + "piop.relation" => { + let attrs = copy_attrs( + op, + &["kind", "domain", "num_rounds", "degree", "output_count"], + )?; + let symbol = string_attr(op, "sym_name")?; + context.append_op_with_owned_attrs( + &compute, + "compute.relation", + Some(&symbol), + &attrs, + )?; + } + "transcript.state" => { + let attrs = copy_attrs(op, &["scheme"])?; + let symbol = string_attr(op, "sym_name")?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_init", + Some(&symbol), + &attrs, + &[], + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "transcript.absorb_bytes" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "payload"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_absorb_bytes", + Some(&symbol), + &attrs, + &operands, + &["!compute.transcript_state"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "transcript.squeeze" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["label", "kind", "count"])?; + let result_types = transcript_squeeze_compute_result_types(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.transcript_squeeze", + Some(&symbol), + &attrs, + &operands, + &result_types, + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "field.const" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field", "value"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.field_const", + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "field.zero" | "field.one" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["field"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + &format!("compute.{}", operation_name(op).replace('.', "_")), + Some(&symbol), + &attrs, + &[], + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "field.add" | "field.sub" | "field.mul" | "field.neg" | "field.pow" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = field_compute_attrs(op)?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + &format!("compute.{}", operation_name(op).replace('.', "_")), + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "poly.lagrange_basis_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["domain_start", "domain_size", "index"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.poly_lagrange_basis_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.opening_input" => { + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source_stage", + "source_claim", + "oracle", + "domain", + "point_arity", + "claim_kind", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_input", + Some(&symbol), + &attrs, + &[], + &[ + "!compute.point", + "!compute.field_value", + "!compute.opening_claim_type", + ], + )?; + for index in 0..3 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "poly.point_slice" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "offset", "length"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.point_slice", + Some(&symbol), + &attrs, + &operands, + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "poly.point_concat" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["layout", "arity"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.point_concat", + Some(&symbol), + &attrs, + &operands, + &["!compute.point"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + let target_op = match &role { + Role::Prover => "compute.sumcheck_claim", + Role::Verifier => "compute.sumcheck_verify_claim", + }; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_batch" => { + let operands = lowered_operands(op, &value_map, 1)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.sumcheck_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + let target_op = match &role { + Role::Prover => "compute.sumcheck_driver", + Role::Verifier => "compute.sumcheck_verify", + }; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + target_op, + Some(&symbol), + &attrs, + &operands, + &[ + "!compute.transcript_state", + "!compute.point", + "!compute.sumcheck_result_type", + "!compute.sumcheck_proof_type", + ], + )?; + for index in 0..4 { + insert_result_mapping(&mut value_map, op, operation, index, index)?; + } + } + "piop.sumcheck_eval" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["source", "name", "index", "oracle"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_eval", + Some(&symbol), + &attrs, + &operands, + &["!compute.field_value"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.sumcheck_instance_result" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &[ + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.sumcheck_instance_result", + Some(&symbol), + &attrs, + &operands, + &["!compute.point", "!compute.sumcheck_result_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + insert_result_mapping(&mut value_map, op, operation, 1, 1)?; + } + "piop.opening_claim" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["oracle", "domain", "point_arity", "claim_kind"])?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_claim", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_claim_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + "piop.opening_claim_equal" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs(op, &["mode"])?; + let _operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_claim_equal", + Some(&symbol), + &attrs, + &operands, + &[], + )?; + } + "piop.opening_batch" => { + let operands = lowered_operands(op, &value_map, 0)?; + let symbol = string_attr(op, "sym_name")?; + let attrs = copy_attrs( + op, + &["stage", "proof_slot", "policy", "count", "ordered_claims"], + )?; + let operation = context.append_typed_op_with_owned_attrs( + &compute, + "compute.opening_batch", + Some(&symbol), + &attrs, + &operands, + &["!compute.opening_batch_type"], + )?; + insert_result_mapping(&mut value_map, op, operation, 0, 0)?; + } + _ => {} + } + } + + verify_module(&compute)?; + verify_compute_schema(&compute)?; + Ok(compute) +} + +fn append_stage3_oracles<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, +) -> Result<(), MlirError> { + let mut trace_oracles = BTreeSet::new(); + trace_oracles.extend(STAGE3_SHIFT_INPUTS); + trace_oracles.extend(STAGE3_SHIFT_OUTPUTS); + trace_oracles.extend(STAGE3_INSTRUCTION_INPUT_OUTPUTS); + trace_oracles.extend(STAGE3_REGISTER_INPUTS); + trace_oracles.extend([ + "LeftInstructionInput", + "RightInstructionInput", + "NextIsNoop", + ]); + for oracle in trace_oracles { + append_virtual_oracle(context, module, oracle, "jolt.trace_domain")?; + } + Ok(()) +} + +fn append_virtual_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + domain: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", &format!("@{domain}")), + ("commit_domain", &format!("@{domain}")), + ("visibility", r#""virtual""#), + ("layout", r#""virtual""#), + ], + ) +} + +fn append_stage3_relations<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage3.spartan_shift", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: SPARTAN_SHIFT_DEGREE, + output_count: STAGE3_SHIFT_OUTPUTS.len(), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage3.instruction_input", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: INSTRUCTION_INPUT_DEGREE, + output_count: STAGE3_INSTRUCTION_INPUT_OUTPUTS.len(), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage3.registers_claim_reduction", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: REGISTERS_CLAIM_REDUCTION_DEGREE, + output_count: STAGE3_REGISTER_INPUTS.len(), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage3.batched", + kind: "batched_sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: STAGE3_BATCHED_DEGREE, + output_count: stage3_output_count(), + }, + ) +} + +fn append_relation<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + spec: RelationSpec<'_>, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.relation", + Some(spec.symbol), + &[ + ("kind", &format!("\"{}\"", spec.kind)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("output_count", &int_attr(spec.output_count)), + ], + ) +} + +fn append_stage3_opening_inputs<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result, MlirError> { + Ok(Stage3OpeningInputs { + next_unexpanded_pc: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage1.NextUnexpandedPC", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.NextUnexpandedPC", + oracle: "NextUnexpandedPC", + }, + )?, + next_pc: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage1.NextPC", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.NextPC", + oracle: "NextPC", + }, + )?, + next_is_virtual: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage1.NextIsVirtual", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.NextIsVirtual", + oracle: "NextIsVirtual", + }, + )?, + next_is_first_in_sequence: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage1.NextIsFirstInSequence", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.NextIsFirstInSequence", + oracle: "NextIsFirstInSequence", + }, + )?, + product_next_is_noop: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage2.product_virtual.NextIsNoop", + source_stage: "stage2", + source_claim: "stage2.product_virtual.remainder.opening.NextIsNoop", + oracle: "NextIsNoop", + }, + )?, + product_left_instruction_input: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage2.product_virtual.LeftInstructionInput", + source_stage: "stage2", + source_claim: "stage2.product_virtual.remainder.opening.LeftInstructionInput", + oracle: "LeftInstructionInput", + }, + )?, + product_right_instruction_input: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage2.product_virtual.RightInstructionInput", + source_stage: "stage2", + source_claim: "stage2.product_virtual.remainder.opening.RightInstructionInput", + oracle: "RightInstructionInput", + }, + )?, + instruction_left_instruction_input: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage2.instruction_lookup.LeftInstructionInput", + source_stage: "stage2", + source_claim: + "stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput", + oracle: "LeftInstructionInput", + }, + )?, + instruction_right_instruction_input: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage2.instruction_lookup.RightInstructionInput", + source_stage: "stage2", + source_claim: + "stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput", + oracle: "RightInstructionInput", + }, + )?, + rd_write_value: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage1.RdWriteValue", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.RdWriteValue", + oracle: "RdWriteValue", + }, + )?, + rs1_value: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage1.Rs1Value", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.Rs1Value", + oracle: "Rs1Value", + }, + )?, + rs2_value: append_stage_input( + context, + module, + params, + StageOpeningInputSpec { + symbol: "stage3.input.stage1.Rs2Value", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.Rs2Value", + oracle: "Rs2Value", + }, + )?, + }) +} + +fn append_stage_input<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + spec: StageOpeningInputSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_input", + Some(spec.symbol), + &[ + ("source_stage", &format!("@{}", spec.source_stage)), + ("source_claim", &format!("@{}", spec.source_claim)), + ("oracle", &format!("@{}", spec.oracle)), + ("domain", "@jolt.trace_domain"), + ("point_arity", &int_attr(params.log_t)), + ("claim_kind", r#""virtual""#), + ], + &[], + &["!poly.point", "!field.scalar", "!piop.opening_claim_type"], + )?; + Ok(Stage3OpeningInput { + eval: result(op, 1, "piop.opening_input")?, + claim: result(op, 2, "piop.opening_input")?, + }) +} + +fn append_transcript_squeeze<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + symbol: &str, + label: &str, + kind: &str, + count: usize, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "transcript.squeeze", + Some(symbol), + &[ + ("label", &format!("\"{label}\"")), + ("kind", &format!("\"{kind}\"")), + ("count", &int_attr(count)), + ], + &[state], + &[ + "!transcript.state_type", + transcript_squeeze_protocol_result_type(kind)?, + ], + )?; + Ok(( + result(op, 0, "transcript.squeeze")?, + result(op, 1, "transcript.squeeze")?, + )) +} + +fn append_field_one<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.one", + Some(symbol), + &[("field", "@bn254_fr")], + &[], + &["!field.scalar"], + )?; + first_result(op, "field.one") +} + +fn append_field_binary<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + op_name: &str, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + op_name, + Some(symbol), + &[], + &[lhs, rhs], + &["!field.scalar"], + )?; + first_result(op, op_name) +} + +fn append_field_add<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.add", symbol, lhs, rhs) +} + +fn append_field_sub<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.sub", symbol, lhs, rhs) +} + +fn append_field_mul<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.mul", symbol, lhs, rhs) +} + +fn append_field_pow<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + base: Value<'c, 'a>, + exponent: usize, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.pow", + Some(symbol), + &[("exponent", &int_attr(exponent))], + &[base], + &["!field.scalar"], + )?; + first_result(op, "field.pow") +} + +fn append_opening_claim_equal<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + left: Value<'c, '_>, + right: Value<'c, '_>, +) -> Result<(), MlirError> { + let _operation = context.append_typed_op( + module, + "piop.opening_claim_equal", + Some(symbol), + &[("mode", r#""point_and_eval""#)], + &[left, right], + &[], + )?; + Ok(()) +} + +fn append_stage3_batched_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + spec: Stage3BatchedSumcheckInputs<'c, 'a, '_>, +) -> Result, MlirError> { + let inputs = spec.openings; + let shift_gamma2 = append_field_pow( + context, + module, + "stage3.spartan_shift.gamma2", + spec.shift_gamma, + 2, + )?; + let shift_gamma3 = append_field_mul( + context, + module, + "stage3.spartan_shift.gamma3", + shift_gamma2, + spec.shift_gamma, + )?; + let shift_gamma4 = append_field_mul( + context, + module, + "stage3.spartan_shift.gamma4", + shift_gamma2, + shift_gamma2, + )?; + let one = append_field_one(context, module, "stage3.field.one")?; + let next_pc_term = append_field_mul( + context, + module, + "stage3.spartan_shift.term.NextPC", + spec.shift_gamma, + inputs.next_pc.eval, + )?; + let next_virtual_term = append_field_mul( + context, + module, + "stage3.spartan_shift.term.NextIsVirtual", + shift_gamma2, + inputs.next_is_virtual.eval, + )?; + let next_first_term = append_field_mul( + context, + module, + "stage3.spartan_shift.term.NextIsFirstInSequence", + shift_gamma3, + inputs.next_is_first_in_sequence.eval, + )?; + let one_minus_noop = append_field_sub( + context, + module, + "stage3.spartan_shift.one_minus.NextIsNoop", + one, + inputs.product_next_is_noop.eval, + )?; + let next_noop_term = append_field_mul( + context, + module, + "stage3.spartan_shift.term.NextIsNoop", + shift_gamma4, + one_minus_noop, + )?; + let shift_sum0 = append_field_add( + context, + module, + "stage3.spartan_shift.partial.NextUnexpandedPCNextPC", + inputs.next_unexpanded_pc.eval, + next_pc_term, + )?; + let shift_sum1 = append_field_add( + context, + module, + "stage3.spartan_shift.partial.NextIsVirtual", + shift_sum0, + next_virtual_term, + )?; + let shift_sum2 = append_field_add( + context, + module, + "stage3.spartan_shift.partial.NextIsFirstInSequence", + shift_sum1, + next_first_term, + )?; + let shift_claim = append_field_add( + context, + module, + "stage3.spartan_shift.claim_expr", + shift_sum2, + next_noop_term, + )?; + append_opening_claim_equal( + context, + module, + "stage3.instruction_input.left_claim_consistency", + inputs.product_left_instruction_input.claim, + inputs.instruction_left_instruction_input.claim, + )?; + append_opening_claim_equal( + context, + module, + "stage3.instruction_input.right_claim_consistency", + inputs.product_right_instruction_input.claim, + inputs.instruction_right_instruction_input.claim, + )?; + let instruction_left_term = append_field_mul( + context, + module, + "stage3.instruction_input.term.LeftInstructionInput", + spec.instruction_gamma, + inputs.product_left_instruction_input.eval, + )?; + let instruction_claim = append_field_add( + context, + module, + "stage3.instruction_input.claim_expr", + inputs.product_right_instruction_input.eval, + instruction_left_term, + )?; + let registers_gamma2 = append_field_pow( + context, + module, + "stage3.registers.gamma2", + spec.registers_gamma, + 2, + )?; + let rs1_term = append_field_mul( + context, + module, + "stage3.registers.term.Rs1Value", + spec.registers_gamma, + inputs.rs1_value.eval, + )?; + let rs2_term = append_field_mul( + context, + module, + "stage3.registers.term.Rs2Value", + registers_gamma2, + inputs.rs2_value.eval, + )?; + let registers_sum = append_field_add( + context, + module, + "stage3.registers.partial.RdWriteValueRs1Value", + inputs.rd_write_value.eval, + rs1_term, + )?; + let registers_claim = append_field_add( + context, + module, + "stage3.registers.claim_expr", + registers_sum, + rs2_term, + )?; + + let claims = [ + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage3.spartan_shift.input", + stage: "stage3", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: SPARTAN_SHIFT_DEGREE, + claim: "stage3.spartan_shift.weighted_next_values", + relation: "jolt.stage3.spartan_shift", + }, + shift_claim, + &[ + inputs.next_unexpanded_pc.claim, + inputs.next_pc.claim, + inputs.next_is_virtual.claim, + inputs.next_is_first_in_sequence.claim, + inputs.product_next_is_noop.claim, + ], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage3.instruction_input.input", + stage: "stage3", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: INSTRUCTION_INPUT_DEGREE, + claim: "stage3.instruction_input.weighted_inputs", + relation: "jolt.stage3.instruction_input", + }, + instruction_claim, + &[ + inputs.product_right_instruction_input.claim, + inputs.product_left_instruction_input.claim, + ], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage3.registers_claim_reduction.input", + stage: "stage3", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: REGISTERS_CLAIM_REDUCTION_DEGREE, + claim: "stage3.registers.weighted_register_values", + relation: "jolt.stage3.registers_claim_reduction", + }, + registers_claim, + &[ + inputs.rd_write_value.claim, + inputs.rs1_value.claim, + inputs.rs2_value.claim, + ], + )?, + ]; + let batch = append_sumcheck_batch( + context, + module, + spec.stage, + &claims, + SumcheckBatchSpec { + symbol: "stage3.batch", + stage: "stage3", + proof_slot: "stage3.sumcheck", + policy: "jolt_core_stage3_aligned", + ordered_claims: &[ + "stage3.spartan_shift.input", + "stage3.instruction_input.input", + "stage3.registers_claim_reduction.input", + ], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: format!("[{}]", params.log_t), + }, + )?; + let (state, point, result_value) = append_sumcheck( + context, + module, + spec.state, + batch, + SumcheckDriverSpec { + symbol: "stage3.sumcheck", + stage: "stage3", + proof_slot: "stage3.sumcheck", + relation: "jolt.stage3.batched", + policy: "jolt_core_stage3_aligned", + round_schedule: format!("[{}]", params.log_t), + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: params.log_t, + degree: STAGE3_BATCHED_DEGREE, + }, + )?; + + let shift = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage3.spartan_shift.instance", + source: "stage3.sumcheck", + claim: "stage3.spartan_shift.input", + relation: "jolt.stage3.spartan_shift", + index: 0, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: 0, + point_order: "reverse", + degree: SPARTAN_SHIFT_DEGREE, + }, + point, + result_value, + )?; + let instruction = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage3.instruction_input.instance", + source: "stage3.sumcheck", + claim: "stage3.instruction_input.input", + relation: "jolt.stage3.instruction_input", + index: 1, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: 0, + point_order: "reverse", + degree: INSTRUCTION_INPUT_DEGREE, + }, + point, + result_value, + )?; + let registers = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage3.registers_claim_reduction.instance", + source: "stage3.sumcheck", + claim: "stage3.registers_claim_reduction.input", + relation: "jolt.stage3.registers_claim_reduction", + index: 2, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: 0, + point_order: "reverse", + degree: REGISTERS_CLAIM_REDUCTION_DEGREE, + }, + point, + result_value, + )?; + append_stage3_output_openings( + context, + module, + &[ + InstanceOutput { + prefix: "stage3.spartan_shift", + instance: shift, + outputs: &STAGE3_SHIFT_OUTPUTS, + degree_offset: 0, + }, + InstanceOutput { + prefix: "stage3.instruction_input", + instance: instruction, + outputs: &STAGE3_INSTRUCTION_INPUT_OUTPUTS, + degree_offset: STAGE3_SHIFT_OUTPUTS.len(), + }, + InstanceOutput { + prefix: "stage3.registers_claim_reduction", + instance: registers, + outputs: &STAGE3_REGISTER_INPUTS, + degree_offset: STAGE3_SHIFT_OUTPUTS.len() + STAGE3_INSTRUCTION_INPUT_OUTPUTS.len(), + }, + ], + params.log_t, + )?; + Ok(state) +} + +fn append_stage3_output_openings<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + outputs: &[InstanceOutput<'c, 'a, '_>], + point_arity: usize, +) -> Result<(), MlirError> { + let mut claims = Vec::new(); + let mut claim_symbols = Vec::new(); + + for output in outputs { + for (index, &oracle) in output.outputs.iter().enumerate() { + let symbol = format!("{}.opening.{oracle}", output.prefix); + let eval = append_sumcheck_eval( + context, + module, + &format!("{}.eval.{oracle}", output.prefix), + "stage3.sumcheck", + oracle, + output.degree_offset + index, + output.instance.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + output.instance.0, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle, + domain: "jolt.trace_domain", + point_arity, + claim_kind: "virtual", + }, + )?); + } + } + + let claim_names = claim_symbols.iter().map(String::as_str).collect::>(); + let _batch = context.append_typed_op( + module, + "piop.opening_batch", + Some("stage3.openings"), + &[ + ("stage", "@stage3"), + ("proof_slot", "@stage3.openings"), + ("policy", r#""jolt_stage3_output_order""#), + ("count", &int_attr(claims.len())), + ("ordered_claims", &symbol_array_attr(&claim_names)), + ], + &claims, + &["!piop.opening_batch_type"], + )?; + Ok(()) +} + +fn append_sumcheck_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckClaimSpec<'_>, + input_claim: Value<'c, 'a>, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(inputs.len() + 1); + operands.push(input_claim); + operands.extend_from_slice(inputs); + let op = context.append_typed_op( + module, + "piop.sumcheck_claim", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ], + &operands, + &["!piop.sumcheck_claim_type"], + )?; + first_result(op, "piop.sumcheck_claim") +} + +fn append_sumcheck_batch<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + stage: Value<'c, 'a>, + claims: &[Value<'c, 'a>], + spec: SumcheckBatchSpec<'_>, +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(claims.len() + 1); + operands.push(stage); + operands.extend_from_slice(claims); + let op = context.append_typed_op( + module, + "piop.sumcheck_batch", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("policy", &format!("\"{}\"", spec.policy)), + ("count", &int_attr(spec.ordered_claims.len())), + ("ordered_claims", &symbol_array_attr(spec.ordered_claims)), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("round_schedule", &spec.round_schedule), + ], + &operands, + &["!piop.sumcheck_batch_type"], + )?; + first_result(op, "piop.sumcheck_batch") +} + +fn append_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + batch: Value<'c, 'a>, + spec: SumcheckDriverSpec<'_>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("relation", &format!("@{}", spec.relation)), + ("policy", &format!("\"{}\"", spec.policy)), + ("round_schedule", &spec.round_schedule), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ], + &[state, batch], + &[ + "!transcript.state_type", + "!poly.point", + "!piop.sumcheck_result_type", + "!piop.sumcheck_proof_type", + ], + )?; + Ok(( + result(op, 0, "piop.sumcheck")?, + result(op, 1, "piop.sumcheck")?, + result(op, 2, "piop.sumcheck")?, + )) +} + +fn append_sumcheck_instance_result<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckInstanceResultSpec<'_>, + point: Value<'c, 'a>, + result_value: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_instance_result", + Some(spec.symbol), + &[ + ("source", &format!("@{}", spec.source)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ("index", &int_attr(spec.index)), + ("point_arity", &int_attr(spec.point_arity)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("round_offset", &int_attr(spec.round_offset)), + ("point_order", &format!("\"{}\"", spec.point_order)), + ("degree", &int_attr(spec.degree)), + ], + &[point, result_value], + &["!poly.point", "!piop.sumcheck_result_type"], + )?; + Ok(( + result(op, 0, "piop.sumcheck_instance_result")?, + result(op, 1, "piop.sumcheck_instance_result")?, + )) +} + +fn append_sumcheck_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + oracle: &str, + index: usize, + result_value: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_eval", + Some(symbol), + &[ + ("source", &format!("@{source}")), + ("name", &format!("@{symbol}")), + ("index", &int_attr(index)), + ("oracle", &format!("@{oracle}")), + ], + &[result_value], + &["!field.scalar"], + )?; + first_result(op, "piop.sumcheck_eval") +} + +fn append_opening_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + spec: OpeningClaimSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_claim", + Some(spec.symbol), + &[ + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", &format!("\"{}\"", spec.claim_kind)), + ], + &[point, eval], + &["!piop.opening_claim_type"], + )?; + first_result(op, "piop.opening_claim") +} + +fn first_result<'c, 'a>( + operation: OperationRef<'c, 'a>, + operation_name: &str, +) -> Result, MlirError> { + result(operation, 0, operation_name) +} + +fn result<'c, 'a>( + operation: OperationRef<'c, 'a>, + index: usize, + operation_name: &str, +) -> Result, MlirError> { + operation + .result(index) + .map(Into::into) + .map_err(|_| schema_error(format!("{operation_name} requires result {index}"))) +} + +#[derive(Clone, Debug)] +struct StageParamsAst { + field: String, + pcs: String, + transcript: String, +} + +fn stage_params(module: &BoltModule<'_, Party>) -> Result { + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + if operation_name(op) == "protocol.params" { + return Ok(StageParamsAst { + field: symbol_attr(op, "field")?, + pcs: symbol_attr(op, "pcs")?, + transcript: symbol_attr(op, "transcript")?, + }); + } + } + Err(schema_error("stage3 lowering requires protocol.params")) +} + +fn operation_result_key_at( + operation: OperationRef<'_, '_>, + index: usize, +) -> Result { + let result = operation.result(index).map_err(|_| { + schema_error(format!( + "{} requires result {index}", + operation_name(operation) + )) + })?; + result_key(result.owner(), result.result_number()) +} + +fn result_key(operation: OperationRef<'_, '_>, result_number: usize) -> Result { + Ok(format!( + "{}#{result_number}", + string_attr(operation, "sym_name")? + )) +} + +fn operand_key(operation: OperationRef<'_, '_>, index: usize) -> Result { + let operand = operation.operand(index).map_err(|_| { + schema_error(format!( + "{} requires operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + schema_error(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + result_key(owner.owner(), owner.result_number()).map_err(|_| { + schema_error(format!( + "{} operand {index} owner missing sym_name", + operation_name(operation) + )) + }) +} + +fn lowered_operands<'c, 'a>( + operation: OperationRef<'_, '_>, + value_map: &BTreeMap>, + start_index: usize, +) -> Result>, MlirError> { + (start_index..operation.operand_count()) + .map(|index| { + let key = operand_key(operation, index)?; + value_map.get(&key).copied().ok_or_else(|| { + schema_error(format!( + "{} operand {index} was not lowered", + operation_name(operation) + )) + }) + }) + .collect() +} + +fn insert_result_mapping<'c, 'a>( + value_map: &mut BTreeMap>, + source: OperationRef<'_, '_>, + target: OperationRef<'c, 'a>, + source_index: usize, + target_index: usize, +) -> Result<(), MlirError> { + let key = operation_result_key_at(source, source_index)?; + let value = target.result(target_index).map(Into::into).map_err(|_| { + schema_error(format!( + "{} requires result {target_index}", + operation_name(target) + )) + })?; + let inserted = value_map.insert(key, value); + debug_assert!(inserted.is_none()); + Ok(()) +} + +fn symbol_ref(symbol: &str) -> String { + format!("@{symbol}") +} + +fn stage3_output_count() -> usize { + STAGE3_SHIFT_OUTPUTS.len() + + STAGE3_INSTRUCTION_INPUT_OUTPUTS.len() + + STAGE3_REGISTER_INPUTS.len() +} + +fn int_attr(value: usize) -> String { + format!("{value} : i64") +} + +fn symbol_array_attr(values: &[&str]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn schema_error(message: impl Into) -> MlirError { + SchemaError::new(message).into() +} + +#[derive(Clone, Copy)] +struct Stage3OpeningInput<'c, 'a> { + eval: Value<'c, 'a>, + claim: Value<'c, 'a>, +} + +struct Stage3OpeningInputs<'c, 'a> { + next_unexpanded_pc: Stage3OpeningInput<'c, 'a>, + next_pc: Stage3OpeningInput<'c, 'a>, + next_is_virtual: Stage3OpeningInput<'c, 'a>, + next_is_first_in_sequence: Stage3OpeningInput<'c, 'a>, + product_next_is_noop: Stage3OpeningInput<'c, 'a>, + product_left_instruction_input: Stage3OpeningInput<'c, 'a>, + product_right_instruction_input: Stage3OpeningInput<'c, 'a>, + instruction_left_instruction_input: Stage3OpeningInput<'c, 'a>, + instruction_right_instruction_input: Stage3OpeningInput<'c, 'a>, + rd_write_value: Stage3OpeningInput<'c, 'a>, + rs1_value: Stage3OpeningInput<'c, 'a>, + rs2_value: Stage3OpeningInput<'c, 'a>, +} + +struct StageOpeningInputSpec<'a> { + symbol: &'a str, + source_stage: &'a str, + source_claim: &'a str, + oracle: &'a str, +} + +struct Stage3BatchedSumcheckInputs<'c, 'a, 'b> { + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + openings: &'b Stage3OpeningInputs<'c, 'a>, + shift_gamma: Value<'c, 'a>, + instruction_gamma: Value<'c, 'a>, + registers_gamma: Value<'c, 'a>, +} + +struct RelationSpec<'a> { + symbol: &'a str, + kind: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + output_count: usize, +} + +struct SumcheckClaimSpec<'a> { + symbol: &'a str, + stage: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + claim: &'a str, + relation: &'a str, +} + +struct SumcheckBatchSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + policy: &'a str, + ordered_claims: &'a [&'a str], + claim_label: &'a str, + round_label: &'a str, + round_schedule: String, +} + +struct SumcheckDriverSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + relation: &'a str, + policy: &'a str, + round_schedule: String, + claim_label: &'a str, + round_label: &'a str, + num_rounds: usize, + degree: usize, +} + +struct SumcheckInstanceResultSpec<'a> { + symbol: &'a str, + source: &'a str, + claim: &'a str, + relation: &'a str, + index: usize, + point_arity: usize, + num_rounds: usize, + round_offset: usize, + point_order: &'a str, + degree: usize, +} + +struct OpeningClaimSpec<'a> { + symbol: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, + claim_kind: &'a str, +} + +struct InstanceOutput<'c, 'a, 'b> { + prefix: &'b str, + instance: (Value<'c, 'a>, Value<'c, 'a>), + outputs: &'b [&'b str], + degree_offset: usize, +} diff --git a/crates/bolt/src/protocols/jolt/phases/stage4.rs b/crates/bolt/src/protocols/jolt/phases/stage4.rs new file mode 100644 index 0000000000..adf6bc080a --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/stage4.rs @@ -0,0 +1,1264 @@ +use melior::ir::operation::OperationRef; +use melior::ir::Value; + +use crate::ir::{BoltModule, Compute, Party, Protocol}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{verify_protocol_schema, SchemaError}; + +use super::super::oracles; +use super::super::params::JoltProtocolParams; +use super::lowering::{lower_party_to_compute, transcript_squeeze_protocol_result_type}; + +const REGISTERS_RW_DEGREE: usize = 3; +const RAM_VAL_CHECK_DEGREE: usize = 3; +const STAGE4_BATCHED_DEGREE: usize = 3; + +const STAGE4_REGISTER_INPUTS: [&str; 3] = ["RdWriteValue", "Rs1Value", "Rs2Value"]; +const STAGE4_REGISTER_OUTPUTS: [&str; 5] = ["RegistersVal", "Rs1Ra", "Rs2Ra", "RdWa", "RdInc"]; +const STAGE4_RAM_VAL_OUTPUTS: [&str; 2] = ["RamRa", "RamInc"]; + +pub fn build_stage4_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.stage4", None); + oracles::append_foundation_ops(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.stage4"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + append_stage4_domains(context, &module, params)?; + append_stage4_oracles(context, &module)?; + append_stage4_relations(context, &module, params)?; + let inputs = append_stage4_opening_inputs(context, &module, params)?; + + let fs = context.append_typed_op( + &module, + "transcript.state", + Some("fs_after_stage3"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let state = first_result(fs, "transcript.state")?; + let stage = context.append_typed_op( + &module, + "piop.stage", + Some("stage4"), + &[ + ("name", r#""registers_rw_and_ram_val_check""#), + ("order", "4 : i64"), + ("roles", r#"["prover", "verifier"]"#), + ], + &[], + &["!piop.stage_type"], + )?; + let stage = first_result(stage, "piop.stage")?; + + let (state, registers_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage4.registers_read_write.gamma", + "registers_read_write_gamma", + "challenge_scalar", + 1, + )?; + let state = append_transcript_absorb_bytes( + context, + &module, + state, + "stage4.ram_val_check.domain_separator", + "ram_val_check_gamma", + "", + )?; + let (state, ram_val_check_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage4.ram_val_check.gamma", + "ram_val_check_gamma", + "challenge_scalar", + 1, + )?; + let _state = append_stage4_batched_sumcheck( + context, + &module, + params, + Stage4BatchedSumcheckInputs { + state, + stage, + openings: &inputs, + registers_gamma, + ram_val_check_gamma, + }, + )?; + + verify_module(&module)?; + verify_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_stage4_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + lower_party_to_compute(context, module, "jolt.stage4", "jolt.stage4", "stage4") +} + +fn append_stage4_domains<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + context.append_op( + module, + "poly.domain", + Some("jolt.stage4_registers_rw_domain"), + &[ + ("field", "@bn254_fr"), + ("log_size", &int_attr(stage4_registers_rw_rounds(params))), + ], + )?; + context.append_op( + module, + "poly.domain", + Some("jolt.stage2_ram_rw_domain"), + &[ + ("field", "@bn254_fr"), + ("log_size", &int_attr(params.log_k_ram + params.log_t)), + ], + )?; + context.append_op( + module, + "poly.domain", + Some("jolt.ram_address_domain"), + &[ + ("field", "@bn254_fr"), + ("log_size", &int_attr(params.log_k_ram)), + ], + ) +} + +fn append_stage4_oracles<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, +) -> Result<(), MlirError> { + for oracle in STAGE4_REGISTER_INPUTS { + append_virtual_oracle(context, module, oracle, "jolt.trace_domain")?; + } + append_virtual_oracle( + context, + module, + "RegistersVal", + "jolt.stage4_registers_rw_domain", + )?; + append_virtual_oracle(context, module, "Rs1Ra", "jolt.stage4_registers_rw_domain")?; + append_virtual_oracle(context, module, "Rs2Ra", "jolt.stage4_registers_rw_domain")?; + append_virtual_oracle(context, module, "RdWa", "jolt.stage4_registers_rw_domain")?; + append_virtual_oracle(context, module, "RamVal", "jolt.stage2_ram_rw_domain")?; + append_virtual_oracle(context, module, "RamRa", "jolt.stage2_ram_rw_domain")?; + append_virtual_oracle(context, module, "RamValFinal", "jolt.ram_address_domain")?; + append_virtual_oracle(context, module, "RamValInit", "jolt.ram_address_domain")?; + append_committed_trace_oracle(context, module, "RdInc")?; + append_committed_trace_oracle(context, module, "RamInc") +} + +fn append_virtual_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + domain: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", &format!("@{domain}")), + ("commit_domain", &format!("@{domain}")), + ("visibility", r#""virtual""#), + ("layout", r#""virtual""#), + ], + ) +} + +fn append_committed_trace_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", "@jolt.trace_domain"), + ("commit_domain", "@jolt.main_witness_commit_domain"), + ("visibility", r#""committed""#), + ("layout", r#""dense_trace""#), + ], + ) +} + +fn append_stage4_relations<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage4.registers_read_write", + kind: "sumcheck", + domain: "jolt.stage4_registers_rw_domain", + num_rounds: stage4_registers_rw_rounds(params), + degree: REGISTERS_RW_DEGREE, + output_count: STAGE4_REGISTER_OUTPUTS.len(), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage4.ram_val_check", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: RAM_VAL_CHECK_DEGREE, + output_count: STAGE4_RAM_VAL_OUTPUTS.len(), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage4.batched", + kind: "batched_sumcheck", + domain: "jolt.stage4_registers_rw_domain", + num_rounds: stage4_registers_rw_rounds(params), + degree: STAGE4_BATCHED_DEGREE, + output_count: stage4_output_count(), + }, + ) +} + +fn append_relation<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + spec: RelationSpec<'_>, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.relation", + Some(spec.symbol), + &[ + ("kind", &format!("\"{}\"", spec.kind)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("output_count", &int_attr(spec.output_count)), + ], + ) +} + +fn append_stage4_opening_inputs<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result, MlirError> { + Ok(Stage4OpeningInputs { + rd_write_value: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage4.input.stage3.registers.RdWriteValue", + source_stage: "stage3", + source_claim: "stage3.registers_claim_reduction.opening.RdWriteValue", + oracle: "RdWriteValue", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + rs1_registers: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage4.input.stage3.registers.Rs1Value", + source_stage: "stage3", + source_claim: "stage3.registers_claim_reduction.opening.Rs1Value", + oracle: "Rs1Value", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + rs2_registers: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage4.input.stage3.registers.Rs2Value", + source_stage: "stage3", + source_claim: "stage3.registers_claim_reduction.opening.Rs2Value", + oracle: "Rs2Value", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + rs1_instruction: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage4.input.stage3.instruction.Rs1Value", + source_stage: "stage3", + source_claim: "stage3.instruction_input.opening.Rs1Value", + oracle: "Rs1Value", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + rs2_instruction: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage4.input.stage3.instruction.Rs2Value", + source_stage: "stage3", + source_claim: "stage3.instruction_input.opening.Rs2Value", + oracle: "Rs2Value", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + ram_val: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage4.input.stage2.RamVal", + source_stage: "stage2", + source_claim: "stage2.ram_read_write.opening.RamVal", + oracle: "RamVal", + domain: "jolt.stage2_ram_rw_domain", + point_arity: params.log_k_ram + params.log_t, + }, + )?, + ram_val_final: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage4.input.stage2.RamValFinal", + source_stage: "stage2", + source_claim: "stage2.ram_output.opening.RamValFinal", + oracle: "RamValFinal", + domain: "jolt.ram_address_domain", + point_arity: params.log_k_ram, + }, + )?, + ram_val_init: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage4.input.initial_ram.RamValInit", + source_stage: "stage4_precomputed", + source_claim: "stage4.ram_val_check.initial_ram_eval", + oracle: "RamValInit", + domain: "jolt.ram_address_domain", + point_arity: params.log_k_ram, + }, + )?, + }) +} + +fn append_stage_input<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: StageOpeningInputSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_input", + Some(spec.symbol), + &[ + ("source_stage", &format!("@{}", spec.source_stage)), + ("source_claim", &format!("@{}", spec.source_claim)), + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", r#""virtual""#), + ], + &[], + &["!poly.point", "!field.scalar", "!piop.opening_claim_type"], + )?; + Ok(Stage4OpeningInput { + point: result(op, 0, "piop.opening_input")?, + eval: result(op, 1, "piop.opening_input")?, + claim: result(op, 2, "piop.opening_input")?, + }) +} + +fn append_transcript_squeeze<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + symbol: &str, + label: &str, + kind: &str, + count: usize, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "transcript.squeeze", + Some(symbol), + &[ + ("label", &format!("\"{label}\"")), + ("kind", &format!("\"{kind}\"")), + ("count", &int_attr(count)), + ], + &[state], + &[ + "!transcript.state_type", + transcript_squeeze_protocol_result_type(kind)?, + ], + )?; + Ok(( + result(op, 0, "transcript.squeeze")?, + result(op, 1, "transcript.squeeze")?, + )) +} + +fn append_transcript_absorb_bytes<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + symbol: &str, + label: &str, + payload: &str, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "transcript.absorb_bytes", + Some(symbol), + &[ + ("label", &format!("\"{label}\"")), + ("payload", &format!("\"{payload}\"")), + ], + &[state], + &["!transcript.state_type"], + )?; + first_result(op, "transcript.absorb_bytes") +} + +fn append_stage4_batched_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + spec: Stage4BatchedSumcheckInputs<'c, 'a, '_>, +) -> Result, MlirError> { + let inputs = spec.openings; + append_opening_claim_equal( + context, + module, + "stage4.registers.rs1_claim_consistency", + inputs.rs1_registers.claim, + inputs.rs1_instruction.claim, + )?; + append_opening_claim_equal( + context, + module, + "stage4.registers.rs2_claim_consistency", + inputs.rs2_registers.claim, + inputs.rs2_instruction.claim, + )?; + let registers_gamma2 = append_field_pow( + context, + module, + "stage4.registers_read_write.gamma2", + spec.registers_gamma, + 2, + )?; + let rs1_term = append_field_mul( + context, + module, + "stage4.registers_read_write.term.Rs1Value", + spec.registers_gamma, + inputs.rs1_registers.eval, + )?; + let rs2_term = append_field_mul( + context, + module, + "stage4.registers_read_write.term.Rs2Value", + registers_gamma2, + inputs.rs2_registers.eval, + )?; + let registers_sum = append_field_add( + context, + module, + "stage4.registers_read_write.partial.RdWriteValueRs1Value", + inputs.rd_write_value.eval, + rs1_term, + )?; + let registers_claim = append_field_add( + context, + module, + "stage4.registers_read_write.claim_expr", + registers_sum, + rs2_term, + )?; + + let ram_val_delta = append_field_sub( + context, + module, + "stage4.ram_val_check.delta.RamVal", + inputs.ram_val.eval, + inputs.ram_val_init.eval, + )?; + let ram_final_delta = append_field_sub( + context, + module, + "stage4.ram_val_check.delta.RamValFinal", + inputs.ram_val_final.eval, + inputs.ram_val_init.eval, + )?; + let ram_final_term = append_field_mul( + context, + module, + "stage4.ram_val_check.term.RamValFinal", + spec.ram_val_check_gamma, + ram_final_delta, + )?; + let ram_val_claim = append_field_add( + context, + module, + "stage4.ram_val_check.claim_expr", + ram_val_delta, + ram_final_term, + )?; + + let claims = [ + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage4.registers_read_write.input", + stage: "stage4", + domain: "jolt.stage4_registers_rw_domain", + num_rounds: stage4_registers_rw_rounds(params), + degree: REGISTERS_RW_DEGREE, + claim: "stage4.registers_read_write.weighted_values", + relation: "jolt.stage4.registers_read_write", + }, + registers_claim, + &[ + inputs.rd_write_value.claim, + inputs.rs1_registers.claim, + inputs.rs2_registers.claim, + ], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage4.ram_val_check.input", + stage: "stage4", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: RAM_VAL_CHECK_DEGREE, + claim: "stage4.ram_val_check.weighted_values", + relation: "jolt.stage4.ram_val_check", + }, + ram_val_claim, + &[ + inputs.ram_val.claim, + inputs.ram_val_final.claim, + inputs.ram_val_init.claim, + ], + )?, + ]; + let batch = append_sumcheck_batch( + context, + module, + spec.stage, + &claims, + SumcheckBatchSpec { + symbol: "stage4.batch", + stage: "stage4", + proof_slot: "stage4.sumcheck", + policy: "jolt_core_stage4_aligned", + ordered_claims: &[ + "stage4.registers_read_write.input", + "stage4.ram_val_check.input", + ], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: format!("[{}, {}]", params.log_t, params.register_log_k), + }, + )?; + let (state, point, result_value) = append_sumcheck( + context, + module, + spec.state, + batch, + SumcheckDriverSpec { + symbol: "stage4.sumcheck", + stage: "stage4", + proof_slot: "stage4.sumcheck", + relation: "jolt.stage4.batched", + policy: "jolt_core_stage4_aligned", + round_schedule: format!("[{}, {}]", params.log_t, params.register_log_k), + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: stage4_registers_rw_rounds(params), + degree: STAGE4_BATCHED_DEGREE, + }, + )?; + let registers = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage4.registers_read_write.instance", + source: "stage4.sumcheck", + claim: "stage4.registers_read_write.input", + relation: "jolt.stage4.registers_read_write", + index: 0, + point_arity: stage4_registers_rw_rounds(params), + num_rounds: stage4_registers_rw_rounds(params), + round_offset: 0, + point_order: "stage4_registers_rw", + degree: REGISTERS_RW_DEGREE, + }, + point, + result_value, + )?; + let ram_val_check = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage4.ram_val_check.instance", + source: "stage4.sumcheck", + claim: "stage4.ram_val_check.input", + relation: "jolt.stage4.ram_val_check", + index: 1, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: params.register_log_k, + point_order: "reverse", + degree: RAM_VAL_CHECK_DEGREE, + }, + point, + result_value, + )?; + append_stage4_output_openings(context, module, params, inputs, registers, ram_val_check)?; + Ok(state) +} + +fn append_stage4_output_openings<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + inputs: &Stage4OpeningInputs<'c, 'a>, + registers: (Value<'c, 'a>, Value<'c, 'a>), + ram_val_check: (Value<'c, 'a>, Value<'c, 'a>), +) -> Result<(), MlirError> { + let mut claims = Vec::new(); + let mut claim_symbols = Vec::new(); + + for (index, &oracle) in ["RegistersVal", "Rs1Ra", "Rs2Ra", "RdWa"] + .iter() + .enumerate() + { + let symbol = format!("stage4.registers_read_write.opening.{oracle}"); + let eval = append_sumcheck_eval( + context, + module, + &format!("stage4.registers_read_write.eval.{oracle}"), + "stage4.sumcheck", + oracle, + index, + registers.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + registers.0, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle, + domain: "jolt.stage4_registers_rw_domain", + point_arity: stage4_registers_rw_rounds(params), + claim_kind: "virtual", + }, + )?); + } + + let rd_inc_point = append_point_slice( + context, + module, + "stage4.registers_read_write.point.RdInc", + "stage4.registers_read_write.instance", + params.register_log_k, + params.log_t, + registers.0, + )?; + let rd_inc_eval = append_sumcheck_eval( + context, + module, + "stage4.registers_read_write.eval.RdInc", + "stage4.sumcheck", + "RdInc", + 4, + registers.1, + )?; + claim_symbols.push("stage4.registers_read_write.opening.RdInc".to_owned()); + claims.push(append_opening_claim( + context, + module, + rd_inc_point, + rd_inc_eval, + OpeningClaimSpec { + symbol: "stage4.registers_read_write.opening.RdInc", + oracle: "RdInc", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?); + + let ram_address_point = append_point_slice( + context, + module, + "stage4.ram_val_check.point.RamAddress", + "stage4.input.stage2.RamVal", + 0, + params.log_k_ram, + inputs.ram_val.point, + )?; + let ram_ra_point = append_point_concat( + context, + module, + "stage4.ram_val_check.point.RamRa", + "address_then_cycle", + params.log_k_ram + params.log_t, + &[ram_address_point, ram_val_check.0], + )?; + let ram_ra_eval = append_sumcheck_eval( + context, + module, + "stage4.ram_val_check.eval.RamRa", + "stage4.sumcheck", + "RamRa", + 0, + ram_val_check.1, + )?; + claim_symbols.push("stage4.ram_val_check.opening.RamRa".to_owned()); + claims.push(append_opening_claim( + context, + module, + ram_ra_point, + ram_ra_eval, + OpeningClaimSpec { + symbol: "stage4.ram_val_check.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: params.log_k_ram + params.log_t, + claim_kind: "virtual", + }, + )?); + + let ram_inc_eval = append_sumcheck_eval( + context, + module, + "stage4.ram_val_check.eval.RamInc", + "stage4.sumcheck", + "RamInc", + 1, + ram_val_check.1, + )?; + claim_symbols.push("stage4.ram_val_check.opening.RamInc".to_owned()); + claims.push(append_opening_claim( + context, + module, + ram_val_check.0, + ram_inc_eval, + OpeningClaimSpec { + symbol: "stage4.ram_val_check.opening.RamInc", + oracle: "RamInc", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?); + + let claim_names = claim_symbols.iter().map(String::as_str).collect::>(); + let _batch = context.append_typed_op( + module, + "piop.opening_batch", + Some("stage4.openings"), + &[ + ("stage", "@stage4"), + ("proof_slot", "@stage4.openings"), + ("policy", r#""jolt_stage4_output_order""#), + ("count", &int_attr(claims.len())), + ("ordered_claims", &symbol_array_attr(&claim_names)), + ], + &claims, + &["!piop.opening_batch_type"], + )?; + Ok(()) +} + +fn append_opening_claim_equal<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + left: Value<'c, '_>, + right: Value<'c, '_>, +) -> Result<(), MlirError> { + let _operation = context.append_typed_op( + module, + "piop.opening_claim_equal", + Some(symbol), + &[("mode", r#""point_and_eval""#)], + &[left, right], + &[], + )?; + Ok(()) +} + +fn append_field_binary<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + op_name: &str, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + op_name, + Some(symbol), + &[], + &[lhs, rhs], + &["!field.scalar"], + )?; + first_result(op, op_name) +} + +fn append_field_add<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.add", symbol, lhs, rhs) +} + +fn append_field_sub<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.sub", symbol, lhs, rhs) +} + +fn append_field_mul<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.mul", symbol, lhs, rhs) +} + +fn append_field_pow<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + base: Value<'c, 'a>, + exponent: usize, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.pow", + Some(symbol), + &[("exponent", &int_attr(exponent))], + &[base], + &["!field.scalar"], + )?; + first_result(op, "field.pow") +} + +fn append_sumcheck_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckClaimSpec<'_>, + input_claim: Value<'c, 'a>, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(inputs.len() + 1); + operands.push(input_claim); + operands.extend_from_slice(inputs); + let op = context.append_typed_op( + module, + "piop.sumcheck_claim", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ], + &operands, + &["!piop.sumcheck_claim_type"], + )?; + first_result(op, "piop.sumcheck_claim") +} + +fn append_sumcheck_batch<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + stage: Value<'c, 'a>, + claims: &[Value<'c, 'a>], + spec: SumcheckBatchSpec<'_>, +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(claims.len() + 1); + operands.push(stage); + operands.extend_from_slice(claims); + let op = context.append_typed_op( + module, + "piop.sumcheck_batch", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("policy", &format!("\"{}\"", spec.policy)), + ("count", &int_attr(spec.ordered_claims.len())), + ("ordered_claims", &symbol_array_attr(spec.ordered_claims)), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("round_schedule", &spec.round_schedule), + ], + &operands, + &["!piop.sumcheck_batch_type"], + )?; + first_result(op, "piop.sumcheck_batch") +} + +fn append_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + batch: Value<'c, 'a>, + spec: SumcheckDriverSpec<'_>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("relation", &format!("@{}", spec.relation)), + ("policy", &format!("\"{}\"", spec.policy)), + ("round_schedule", &spec.round_schedule), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ], + &[state, batch], + &[ + "!transcript.state_type", + "!poly.point", + "!piop.sumcheck_result_type", + "!piop.sumcheck_proof_type", + ], + )?; + Ok(( + result(op, 0, "piop.sumcheck")?, + result(op, 1, "piop.sumcheck")?, + result(op, 2, "piop.sumcheck")?, + )) +} + +fn append_sumcheck_instance_result<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckInstanceResultSpec<'_>, + point: Value<'c, 'a>, + result_value: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_instance_result", + Some(spec.symbol), + &[ + ("source", &format!("@{}", spec.source)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ("index", &int_attr(spec.index)), + ("point_arity", &int_attr(spec.point_arity)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("round_offset", &int_attr(spec.round_offset)), + ("point_order", &format!("\"{}\"", spec.point_order)), + ("degree", &int_attr(spec.degree)), + ], + &[point, result_value], + &["!poly.point", "!piop.sumcheck_result_type"], + )?; + Ok(( + result(op, 0, "piop.sumcheck_instance_result")?, + result(op, 1, "piop.sumcheck_instance_result")?, + )) +} + +fn append_sumcheck_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + oracle: &str, + index: usize, + result_value: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_eval", + Some(symbol), + &[ + ("source", &format!("@{source}")), + ("name", &format!("@{symbol}")), + ("index", &int_attr(index)), + ("oracle", &format!("@{oracle}")), + ], + &[result_value], + &["!field.scalar"], + )?; + first_result(op, "piop.sumcheck_eval") +} + +fn append_opening_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + spec: OpeningClaimSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_claim", + Some(spec.symbol), + &[ + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", &format!("\"{}\"", spec.claim_kind)), + ], + &[point, eval], + &["!piop.opening_claim_type"], + )?; + first_result(op, "piop.opening_claim") +} + +fn append_point_slice<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + offset: usize, + length: usize, + point: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_slice", + Some(symbol), + &[ + ("source", &format!("@{source}")), + ("offset", &int_attr(offset)), + ("length", &int_attr(length)), + ], + &[point], + &["!poly.point"], + )?; + first_result(op, "poly.point_slice") +} + +fn append_point_concat<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + layout: &str, + arity: usize, + points: &[Value<'c, 'a>], +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_concat", + Some(symbol), + &[ + ("layout", &format!("\"{layout}\"")), + ("arity", &int_attr(arity)), + ], + points, + &["!poly.point"], + )?; + first_result(op, "poly.point_concat") +} + +fn first_result<'c, 'a>( + operation: OperationRef<'c, 'a>, + operation_name: &str, +) -> Result, MlirError> { + result(operation, 0, operation_name) +} + +fn result<'c, 'a>( + operation: OperationRef<'c, 'a>, + index: usize, + operation_name: &str, +) -> Result, MlirError> { + operation + .result(index) + .map(Into::into) + .map_err(|_| schema_error(format!("{operation_name} requires result {index}"))) +} + +struct RelationSpec<'a> { + symbol: &'a str, + kind: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + output_count: usize, +} + +struct Stage4OpeningInputs<'c, 'a> { + rd_write_value: Stage4OpeningInput<'c, 'a>, + rs1_registers: Stage4OpeningInput<'c, 'a>, + rs2_registers: Stage4OpeningInput<'c, 'a>, + rs1_instruction: Stage4OpeningInput<'c, 'a>, + rs2_instruction: Stage4OpeningInput<'c, 'a>, + ram_val: Stage4OpeningInput<'c, 'a>, + ram_val_final: Stage4OpeningInput<'c, 'a>, + ram_val_init: Stage4OpeningInput<'c, 'a>, +} + +struct Stage4OpeningInput<'c, 'a> { + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + claim: Value<'c, 'a>, +} + +struct StageOpeningInputSpec<'a> { + symbol: &'a str, + source_stage: &'a str, + source_claim: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, +} + +struct Stage4BatchedSumcheckInputs<'c, 'a, 'b> { + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + openings: &'b Stage4OpeningInputs<'c, 'a>, + registers_gamma: Value<'c, 'a>, + ram_val_check_gamma: Value<'c, 'a>, +} + +struct SumcheckClaimSpec<'a> { + symbol: &'a str, + stage: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + claim: &'a str, + relation: &'a str, +} + +struct SumcheckBatchSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + policy: &'a str, + ordered_claims: &'a [&'a str], + claim_label: &'a str, + round_label: &'a str, + round_schedule: String, +} + +struct SumcheckDriverSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + relation: &'a str, + policy: &'a str, + round_schedule: String, + claim_label: &'a str, + round_label: &'a str, + num_rounds: usize, + degree: usize, +} + +struct SumcheckInstanceResultSpec<'a> { + symbol: &'a str, + source: &'a str, + claim: &'a str, + relation: &'a str, + index: usize, + point_arity: usize, + num_rounds: usize, + round_offset: usize, + point_order: &'a str, + degree: usize, +} + +struct OpeningClaimSpec<'a> { + symbol: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, + claim_kind: &'a str, +} + +fn stage4_registers_rw_rounds(params: &JoltProtocolParams) -> usize { + params.log_t + params.register_log_k +} + +fn stage4_output_count() -> usize { + STAGE4_REGISTER_OUTPUTS.len() + STAGE4_RAM_VAL_OUTPUTS.len() +} + +fn int_attr(value: usize) -> String { + format!("{value} : i64") +} + +fn symbol_array_attr(values: &[&str]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn schema_error(message: impl Into) -> MlirError { + SchemaError::new(message).into() +} diff --git a/crates/bolt/src/protocols/jolt/phases/stage5.rs b/crates/bolt/src/protocols/jolt/phases/stage5.rs new file mode 100644 index 0000000000..3a7b1171e8 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/stage5.rs @@ -0,0 +1,1387 @@ +use melior::ir::operation::OperationRef; +use melior::ir::Value; + +use crate::ir::{BoltModule, Compute, Party, Protocol}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{verify_protocol_schema, SchemaError}; + +use super::super::oracles; +use super::super::params::JoltProtocolParams; +use super::lowering::{lower_party_to_compute, transcript_squeeze_protocol_result_type}; + +const RAM_RA_CLAIM_REDUCTION_DEGREE: usize = 2; +const REGISTERS_VAL_EVALUATION_DEGREE: usize = 3; + +pub fn build_stage5_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.stage5", None); + oracles::append_foundation_ops(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.stage5"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + append_stage5_domains(context, &module, params)?; + append_stage5_oracles(context, &module, params)?; + append_stage5_relations(context, &module, params)?; + let inputs = append_stage5_opening_inputs(context, &module, params)?; + + let fs = context.append_typed_op( + &module, + "transcript.state", + Some("fs_after_stage4"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let state = first_result(fs, "transcript.state")?; + let stage = context.append_typed_op( + &module, + "piop.stage", + Some("stage5"), + &[ + ("name", r#""instruction_ram_and_register_value_reductions""#), + ("order", "5 : i64"), + ("roles", r#"["prover", "verifier"]"#), + ], + &[], + &["!piop.stage_type"], + )?; + let stage = first_result(stage, "piop.stage")?; + + let (state, instruction_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage5.instruction_read_raf.gamma", + "instruction_read_raf_gamma", + "challenge_scalar", + 1, + )?; + let (state, ram_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage5.ram_ra_claim_reduction.gamma", + "ram_ra_claim_reduction_gamma", + "challenge_scalar", + 1, + )?; + let _state = append_stage5_batched_sumcheck( + context, + &module, + params, + Stage5BatchedSumcheckInputs { + state, + stage, + openings: &inputs, + instruction_gamma, + ram_gamma, + }, + )?; + + verify_module(&module)?; + verify_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_stage5_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + lower_party_to_compute(context, module, "jolt.stage5", "jolt.stage5", "stage5") +} + +fn append_stage5_domains<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_domain( + context, + module, + "jolt.stage2_ram_rw_domain", + params.log_k_ram + params.log_t, + )?; + append_domain( + context, + module, + "jolt.stage4_registers_rw_domain", + params.register_log_k + params.log_t, + )?; + append_domain( + context, + module, + "jolt.stage5_instruction_read_raf_domain", + params.instruction_log_k + params.log_t, + )?; + append_domain( + context, + module, + "jolt.stage5_instruction_ra_chunk_domain", + params.lookups_ra_virtual_log_k_chunk + params.log_t, + ) +} + +fn append_domain<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + log_size: usize, +) -> Result<(), MlirError> { + context.append_op( + module, + "poly.domain", + Some(symbol), + &[("field", "@bn254_fr"), ("log_size", &int_attr(log_size))], + ) +} + +fn append_stage5_oracles<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_virtual_oracle(context, module, "LookupOutput", "jolt.trace_domain")?; + append_virtual_oracle(context, module, "LeftLookupOperand", "jolt.trace_domain")?; + append_virtual_oracle(context, module, "RightLookupOperand", "jolt.trace_domain")?; + append_virtual_oracle(context, module, "RamRa", "jolt.stage2_ram_rw_domain")?; + append_virtual_oracle( + context, + module, + "RegistersVal", + "jolt.stage4_registers_rw_domain", + )?; + append_virtual_oracle(context, module, "RdWa", "jolt.stage4_registers_rw_domain")?; + append_committed_trace_oracle(context, module, "RdInc")?; + append_virtual_oracle(context, module, "InstructionRafFlag", "jolt.trace_domain")?; + for index in 0..params.lookup_table_count { + append_virtual_oracle( + context, + module, + &format!("LookupTableFlag_{index}"), + "jolt.trace_domain", + )?; + } + for index in 0..params.instruction_ra_virtual_d { + append_virtual_oracle( + context, + module, + &format!("InstructionRa_{index}"), + "jolt.stage5_instruction_ra_chunk_domain", + )?; + } + Ok(()) +} + +fn append_virtual_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + domain: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", &format!("@{domain}")), + ("commit_domain", &format!("@{domain}")), + ("visibility", r#""virtual""#), + ("layout", r#""virtual""#), + ], + ) +} + +fn append_committed_trace_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", "@jolt.trace_domain"), + ("commit_domain", "@jolt.main_witness_commit_domain"), + ("visibility", r#""committed""#), + ("layout", r#""dense_trace""#), + ], + ) +} + +fn append_stage5_relations<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage5.instruction_read_raf", + kind: "sumcheck", + domain: "jolt.stage5_instruction_read_raf_domain", + num_rounds: stage5_instruction_rounds(params), + degree: instruction_read_raf_degree(params), + output_count: instruction_read_raf_output_count(params), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage5.ram_ra_claim_reduction", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: RAM_RA_CLAIM_REDUCTION_DEGREE, + output_count: 1, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage5.registers_val_evaluation", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: REGISTERS_VAL_EVALUATION_DEGREE, + output_count: 2, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage5.batched", + kind: "batched_sumcheck", + domain: "jolt.stage5_instruction_read_raf_domain", + num_rounds: stage5_instruction_rounds(params), + degree: instruction_read_raf_degree(params), + output_count: stage5_output_count(params), + }, + ) +} + +fn append_relation<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + spec: RelationSpec<'_>, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.relation", + Some(spec.symbol), + &[ + ("kind", &format!("\"{}\"", spec.kind)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("output_count", &int_attr(spec.output_count)), + ], + ) +} + +fn append_stage5_opening_inputs<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result, MlirError> { + Ok(Stage5OpeningInputs { + lookup_output_instruction: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage5.input.stage2.instruction.LookupOutput", + source_stage: "stage2", + source_claim: "stage2.instruction_lookup.claim_reduction.opening.LookupOutput", + oracle: "LookupOutput", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + lookup_output_product: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage5.input.stage2.product_virtual.LookupOutput", + source_stage: "stage2", + source_claim: "stage2.product_virtual.remainder.opening.LookupOutput", + oracle: "LookupOutput", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + left_lookup_operand: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage5.input.stage2.instruction.LeftLookupOperand", + source_stage: "stage2", + source_claim: "stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand", + oracle: "LeftLookupOperand", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + right_lookup_operand: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage5.input.stage2.instruction.RightLookupOperand", + source_stage: "stage2", + source_claim: + "stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand", + oracle: "RightLookupOperand", + domain: "jolt.trace_domain", + point_arity: params.log_t, + }, + )?, + ram_ra_raf: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage5.input.stage2.ram_raf.RamRa", + source_stage: "stage2", + source_claim: "stage2.ram_raf.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: params.log_k_ram + params.log_t, + }, + )?, + ram_ra_rw: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage5.input.stage2.ram_read_write.RamRa", + source_stage: "stage2", + source_claim: "stage2.ram_read_write.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: params.log_k_ram + params.log_t, + }, + )?, + ram_ra_val: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage5.input.stage4.ram_val_check.RamRa", + source_stage: "stage4", + source_claim: "stage4.ram_val_check.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: params.log_k_ram + params.log_t, + }, + )?, + registers_val: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage5.input.stage4.registers.RegistersVal", + source_stage: "stage4", + source_claim: "stage4.registers_read_write.opening.RegistersVal", + oracle: "RegistersVal", + domain: "jolt.stage4_registers_rw_domain", + point_arity: params.register_log_k + params.log_t, + }, + )?, + }) +} + +fn append_stage_input<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: StageOpeningInputSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_input", + Some(spec.symbol), + &[ + ("source_stage", &format!("@{}", spec.source_stage)), + ("source_claim", &format!("@{}", spec.source_claim)), + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", r#""virtual""#), + ], + &[], + &["!poly.point", "!field.scalar", "!piop.opening_claim_type"], + )?; + Ok(Stage5OpeningInput { + point: result(op, 0, "piop.opening_input")?, + eval: result(op, 1, "piop.opening_input")?, + claim: result(op, 2, "piop.opening_input")?, + }) +} + +fn append_transcript_squeeze<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + symbol: &str, + label: &str, + kind: &str, + count: usize, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "transcript.squeeze", + Some(symbol), + &[ + ("label", &format!("\"{label}\"")), + ("kind", &format!("\"{kind}\"")), + ("count", &int_attr(count)), + ], + &[state], + &[ + "!transcript.state_type", + transcript_squeeze_protocol_result_type(kind)?, + ], + )?; + Ok(( + result(op, 0, "transcript.squeeze")?, + result(op, 1, "transcript.squeeze")?, + )) +} + +fn append_stage5_batched_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + spec: Stage5BatchedSumcheckInputs<'c, 'a, '_>, +) -> Result, MlirError> { + let inputs = spec.openings; + append_opening_claim_equal( + context, + module, + "stage5.instruction.lookup_output_claim_consistency", + inputs.lookup_output_instruction.claim, + inputs.lookup_output_product.claim, + )?; + + let instruction_gamma2 = append_field_pow( + context, + module, + "stage5.instruction_read_raf.gamma2", + spec.instruction_gamma, + 2, + )?; + let left_term = append_field_mul( + context, + module, + "stage5.instruction_read_raf.term.LeftLookupOperand", + spec.instruction_gamma, + inputs.left_lookup_operand.eval, + )?; + let right_term = append_field_mul( + context, + module, + "stage5.instruction_read_raf.term.RightLookupOperand", + instruction_gamma2, + inputs.right_lookup_operand.eval, + )?; + let lookup_left_sum = append_field_add( + context, + module, + "stage5.instruction_read_raf.partial.LookupOutputLeftOperand", + inputs.lookup_output_instruction.eval, + left_term, + )?; + let instruction_claim = append_field_add( + context, + module, + "stage5.instruction_read_raf.claim_expr", + lookup_left_sum, + right_term, + )?; + + let ram_gamma2 = append_field_pow( + context, + module, + "stage5.ram_ra_claim_reduction.gamma2", + spec.ram_gamma, + 2, + )?; + let ram_rw_term = append_field_mul( + context, + module, + "stage5.ram_ra_claim_reduction.term.RamRaReadWrite", + spec.ram_gamma, + inputs.ram_ra_rw.eval, + )?; + let ram_val_term = append_field_mul( + context, + module, + "stage5.ram_ra_claim_reduction.term.RamRaValCheck", + ram_gamma2, + inputs.ram_ra_val.eval, + )?; + let ram_raf_rw_sum = append_field_add( + context, + module, + "stage5.ram_ra_claim_reduction.partial.RafReadWrite", + inputs.ram_ra_raf.eval, + ram_rw_term, + )?; + let ram_claim = append_field_add( + context, + module, + "stage5.ram_ra_claim_reduction.claim_expr", + ram_raf_rw_sum, + ram_val_term, + )?; + + let claims = [ + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage5.instruction_read_raf.input", + stage: "stage5", + domain: "jolt.stage5_instruction_read_raf_domain", + num_rounds: stage5_instruction_rounds(params), + degree: instruction_read_raf_degree(params), + claim: "stage5.instruction_read_raf.weighted_lookup_values", + relation: "jolt.stage5.instruction_read_raf", + }, + instruction_claim, + &[ + inputs.lookup_output_instruction.claim, + inputs.left_lookup_operand.claim, + inputs.right_lookup_operand.claim, + ], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage5.ram_ra_claim_reduction.input", + stage: "stage5", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: RAM_RA_CLAIM_REDUCTION_DEGREE, + claim: "stage5.ram_ra_claim_reduction.weighted_ram_ra", + relation: "jolt.stage5.ram_ra_claim_reduction", + }, + ram_claim, + &[ + inputs.ram_ra_raf.claim, + inputs.ram_ra_rw.claim, + inputs.ram_ra_val.claim, + ], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage5.registers_val_evaluation.input", + stage: "stage5", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: REGISTERS_VAL_EVALUATION_DEGREE, + claim: "stage5.registers_val_evaluation.registers_val", + relation: "jolt.stage5.registers_val_evaluation", + }, + inputs.registers_val.eval, + &[inputs.registers_val.claim], + )?, + ]; + let round_schedule = format!("[{}, {}]", params.instruction_log_k, params.log_t); + let batch = append_sumcheck_batch( + context, + module, + spec.stage, + &claims, + SumcheckBatchSpec { + symbol: "stage5.batch", + stage: "stage5", + proof_slot: "stage5.sumcheck", + policy: "jolt_core_stage5_aligned", + ordered_claims: &[ + "stage5.instruction_read_raf.input", + "stage5.ram_ra_claim_reduction.input", + "stage5.registers_val_evaluation.input", + ], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &round_schedule, + }, + )?; + let (state, point, result_value) = append_sumcheck( + context, + module, + spec.state, + batch, + SumcheckDriverSpec { + symbol: "stage5.sumcheck", + stage: "stage5", + proof_slot: "stage5.sumcheck", + relation: "jolt.stage5.batched", + policy: "jolt_core_stage5_aligned", + round_schedule: &round_schedule, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: stage5_instruction_rounds(params), + degree: instruction_read_raf_degree(params), + }, + )?; + let instruction = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage5.instruction_read_raf.instance", + source: "stage5.sumcheck", + claim: "stage5.instruction_read_raf.input", + relation: "jolt.stage5.instruction_read_raf", + index: 0, + point_arity: stage5_instruction_rounds(params), + num_rounds: stage5_instruction_rounds(params), + round_offset: 0, + point_order: "instruction_read_raf", + degree: instruction_read_raf_degree(params), + }, + point, + result_value, + )?; + let ram = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage5.ram_ra_claim_reduction.instance", + source: "stage5.sumcheck", + claim: "stage5.ram_ra_claim_reduction.input", + relation: "jolt.stage5.ram_ra_claim_reduction", + index: 1, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: params.instruction_log_k, + point_order: "reverse", + degree: RAM_RA_CLAIM_REDUCTION_DEGREE, + }, + point, + result_value, + )?; + let registers = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage5.registers_val_evaluation.instance", + source: "stage5.sumcheck", + claim: "stage5.registers_val_evaluation.input", + relation: "jolt.stage5.registers_val_evaluation", + index: 2, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: params.instruction_log_k, + point_order: "reverse", + degree: REGISTERS_VAL_EVALUATION_DEGREE, + }, + point, + result_value, + )?; + append_stage5_output_openings(context, module, params, inputs, instruction, ram, registers)?; + Ok(state) +} + +fn append_stage5_output_openings<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + inputs: &Stage5OpeningInputs<'c, 'a>, + instruction: (Value<'c, 'a>, Value<'c, 'a>), + ram: (Value<'c, 'a>, Value<'c, 'a>), + registers: (Value<'c, 'a>, Value<'c, 'a>), +) -> Result<(), MlirError> { + let mut claims = Vec::new(); + let mut claim_symbols = Vec::new(); + + let instruction_cycle = append_point_slice( + context, + module, + "stage5.instruction_read_raf.point.Cycle", + "stage5.instruction_read_raf.instance", + params.instruction_log_k, + params.log_t, + instruction.0, + )?; + for index in 0..params.lookup_table_count { + let oracle = format!("LookupTableFlag_{index}"); + let symbol = format!("stage5.instruction_read_raf.opening.{oracle}"); + let eval_symbol = format!("stage5.instruction_read_raf.eval.{oracle}"); + let eval = append_sumcheck_eval( + context, + module, + &eval_symbol, + "stage5.sumcheck", + &oracle, + index, + instruction.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + instruction_cycle, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle: &oracle, + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "virtual", + }, + )?); + } + + for index in 0..params.instruction_ra_virtual_d { + let oracle = format!("InstructionRa_{index}"); + let symbol = format!("stage5.instruction_read_raf.opening.{oracle}"); + let address_chunk = append_point_slice( + context, + module, + &format!("stage5.instruction_read_raf.point.{oracle}.address"), + "stage5.instruction_read_raf.instance", + index * params.lookups_ra_virtual_log_k_chunk, + params.lookups_ra_virtual_log_k_chunk, + instruction.0, + )?; + let ra_point = append_point_concat( + context, + module, + &format!("stage5.instruction_read_raf.point.{oracle}"), + "address_chunk_then_cycle", + params.lookups_ra_virtual_log_k_chunk + params.log_t, + &[address_chunk, instruction_cycle], + )?; + let eval_symbol = format!("stage5.instruction_read_raf.eval.{oracle}"); + let eval = append_sumcheck_eval( + context, + module, + &eval_symbol, + "stage5.sumcheck", + &oracle, + params.lookup_table_count + index, + instruction.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + ra_point, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle: &oracle, + domain: "jolt.stage5_instruction_ra_chunk_domain", + point_arity: params.lookups_ra_virtual_log_k_chunk + params.log_t, + claim_kind: "virtual", + }, + )?); + } + + let raf_flag_eval_index = params.lookup_table_count + params.instruction_ra_virtual_d; + let raf_flag_eval = append_sumcheck_eval( + context, + module, + "stage5.instruction_read_raf.eval.InstructionRafFlag", + "stage5.sumcheck", + "InstructionRafFlag", + raf_flag_eval_index, + instruction.1, + )?; + claim_symbols.push("stage5.instruction_read_raf.opening.InstructionRafFlag".to_owned()); + claims.push(append_opening_claim( + context, + module, + instruction_cycle, + raf_flag_eval, + OpeningClaimSpec { + symbol: "stage5.instruction_read_raf.opening.InstructionRafFlag", + oracle: "InstructionRafFlag", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "virtual", + }, + )?); + + let ram_address = append_point_slice( + context, + module, + "stage5.ram_ra_claim_reduction.point.RamAddress", + "stage5.input.stage2.ram_raf.RamRa", + 0, + params.log_k_ram, + inputs.ram_ra_raf.point, + )?; + let ram_ra_point = append_point_concat( + context, + module, + "stage5.ram_ra_claim_reduction.point.RamRa", + "address_then_cycle", + params.log_k_ram + params.log_t, + &[ram_address, ram.0], + )?; + let ram_ra_eval = append_sumcheck_eval( + context, + module, + "stage5.ram_ra_claim_reduction.eval.RamRa", + "stage5.sumcheck", + "RamRa", + 0, + ram.1, + )?; + claim_symbols.push("stage5.ram_ra_claim_reduction.opening.RamRa".to_owned()); + claims.push(append_opening_claim( + context, + module, + ram_ra_point, + ram_ra_eval, + OpeningClaimSpec { + symbol: "stage5.ram_ra_claim_reduction.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: params.log_k_ram + params.log_t, + claim_kind: "virtual", + }, + )?); + + let rd_inc_eval = append_sumcheck_eval( + context, + module, + "stage5.registers_val_evaluation.eval.RdInc", + "stage5.sumcheck", + "RdInc", + 0, + registers.1, + )?; + claim_symbols.push("stage5.registers_val_evaluation.opening.RdInc".to_owned()); + claims.push(append_opening_claim( + context, + module, + registers.0, + rd_inc_eval, + OpeningClaimSpec { + symbol: "stage5.registers_val_evaluation.opening.RdInc", + oracle: "RdInc", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?); + + let register_address = append_point_slice( + context, + module, + "stage5.registers_val_evaluation.point.RegisterAddress", + "stage5.input.stage4.registers.RegistersVal", + 0, + params.register_log_k, + inputs.registers_val.point, + )?; + let rd_wa_point = append_point_concat( + context, + module, + "stage5.registers_val_evaluation.point.RdWa", + "register_address_then_cycle", + params.register_log_k + params.log_t, + &[register_address, registers.0], + )?; + let rd_wa_eval = append_sumcheck_eval( + context, + module, + "stage5.registers_val_evaluation.eval.RdWa", + "stage5.sumcheck", + "RdWa", + 1, + registers.1, + )?; + claim_symbols.push("stage5.registers_val_evaluation.opening.RdWa".to_owned()); + claims.push(append_opening_claim( + context, + module, + rd_wa_point, + rd_wa_eval, + OpeningClaimSpec { + symbol: "stage5.registers_val_evaluation.opening.RdWa", + oracle: "RdWa", + domain: "jolt.stage4_registers_rw_domain", + point_arity: params.register_log_k + params.log_t, + claim_kind: "virtual", + }, + )?); + + let claim_names = claim_symbols.iter().map(String::as_str).collect::>(); + let _batch = context.append_typed_op( + module, + "piop.opening_batch", + Some("stage5.openings"), + &[ + ("stage", "@stage5"), + ("proof_slot", "@stage5.openings"), + ("policy", r#""jolt_stage5_output_order""#), + ("count", &int_attr(claims.len())), + ("ordered_claims", &symbol_array_attr(&claim_names)), + ], + &claims, + &["!piop.opening_batch_type"], + )?; + Ok(()) +} + +fn append_opening_claim_equal<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + left: Value<'c, '_>, + right: Value<'c, '_>, +) -> Result<(), MlirError> { + let _operation = context.append_typed_op( + module, + "piop.opening_claim_equal", + Some(symbol), + &[("mode", r#""point_and_eval""#)], + &[left, right], + &[], + )?; + Ok(()) +} + +fn append_field_binary<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + op_name: &str, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + op_name, + Some(symbol), + &[], + &[lhs, rhs], + &["!field.scalar"], + )?; + first_result(op, op_name) +} + +fn append_field_add<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.add", symbol, lhs, rhs) +} + +fn append_field_mul<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.mul", symbol, lhs, rhs) +} + +fn append_field_pow<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + base: Value<'c, 'a>, + exponent: usize, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.pow", + Some(symbol), + &[("exponent", &int_attr(exponent))], + &[base], + &["!field.scalar"], + )?; + first_result(op, "field.pow") +} + +fn append_sumcheck_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckClaimSpec<'_>, + input_claim: Value<'c, 'a>, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(inputs.len() + 1); + operands.push(input_claim); + operands.extend_from_slice(inputs); + let op = context.append_typed_op( + module, + "piop.sumcheck_claim", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ], + &operands, + &["!piop.sumcheck_claim_type"], + )?; + first_result(op, "piop.sumcheck_claim") +} + +fn append_sumcheck_batch<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + stage: Value<'c, 'a>, + claims: &[Value<'c, 'a>], + spec: SumcheckBatchSpec<'_>, +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(claims.len() + 1); + operands.push(stage); + operands.extend_from_slice(claims); + let op = context.append_typed_op( + module, + "piop.sumcheck_batch", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("policy", &format!("\"{}\"", spec.policy)), + ("count", &int_attr(spec.ordered_claims.len())), + ("ordered_claims", &symbol_array_attr(spec.ordered_claims)), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("round_schedule", spec.round_schedule), + ], + &operands, + &["!piop.sumcheck_batch_type"], + )?; + first_result(op, "piop.sumcheck_batch") +} + +fn append_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + batch: Value<'c, 'a>, + spec: SumcheckDriverSpec<'_>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("relation", &format!("@{}", spec.relation)), + ("policy", &format!("\"{}\"", spec.policy)), + ("round_schedule", spec.round_schedule), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ], + &[state, batch], + &[ + "!transcript.state_type", + "!poly.point", + "!piop.sumcheck_result_type", + "!piop.sumcheck_proof_type", + ], + )?; + Ok(( + result(op, 0, "piop.sumcheck")?, + result(op, 1, "piop.sumcheck")?, + result(op, 2, "piop.sumcheck")?, + )) +} + +fn append_sumcheck_instance_result<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckInstanceResultSpec<'_>, + point: Value<'c, 'a>, + result_value: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_instance_result", + Some(spec.symbol), + &[ + ("source", &format!("@{}", spec.source)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ("index", &int_attr(spec.index)), + ("point_arity", &int_attr(spec.point_arity)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("round_offset", &int_attr(spec.round_offset)), + ("point_order", &format!("\"{}\"", spec.point_order)), + ("degree", &int_attr(spec.degree)), + ], + &[point, result_value], + &["!poly.point", "!piop.sumcheck_result_type"], + )?; + Ok(( + result(op, 0, "piop.sumcheck_instance_result")?, + result(op, 1, "piop.sumcheck_instance_result")?, + )) +} + +fn append_sumcheck_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + oracle: &str, + index: usize, + result_value: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_eval", + Some(symbol), + &[ + ("source", &format!("@{}", source)), + ("name", &format!("@{}", symbol)), + ("index", &int_attr(index)), + ("oracle", &format!("@{}", oracle)), + ], + &[result_value], + &["!field.scalar"], + )?; + first_result(op, "piop.sumcheck_eval") +} + +fn append_point_slice<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + offset: usize, + length: usize, + input: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_slice", + Some(symbol), + &[ + ("source", &format!("@{}", source)), + ("offset", &int_attr(offset)), + ("length", &int_attr(length)), + ], + &[input], + &["!poly.point"], + )?; + first_result(op, "poly.point_slice") +} + +fn append_point_concat<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + layout: &str, + arity: usize, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_concat", + Some(symbol), + &[ + ("layout", &format!("\"{}\"", layout)), + ("arity", &int_attr(arity)), + ], + inputs, + &["!poly.point"], + )?; + first_result(op, "poly.point_concat") +} + +fn append_opening_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + spec: OpeningClaimSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_claim", + Some(spec.symbol), + &[ + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", &format!("\"{}\"", spec.claim_kind)), + ], + &[point, eval], + &["!piop.opening_claim_type"], + )?; + first_result(op, "piop.opening_claim") +} + +fn stage5_instruction_rounds(params: &JoltProtocolParams) -> usize { + params.instruction_log_k + params.log_t +} + +fn instruction_read_raf_degree(params: &JoltProtocolParams) -> usize { + params.instruction_ra_virtual_d + 2 +} + +fn instruction_read_raf_output_count(params: &JoltProtocolParams) -> usize { + params.lookup_table_count + params.instruction_ra_virtual_d + 1 +} + +fn stage5_output_count(params: &JoltProtocolParams) -> usize { + instruction_read_raf_output_count(params) + 3 +} + +fn int_attr(value: usize) -> String { + format!("{value} : i64") +} + +fn symbol_array_attr(values: &[&str]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn first_result<'c, 'a>( + op: OperationRef<'c, 'a>, + context: &str, +) -> Result, MlirError> { + result(op, 0, context) +} + +fn result<'c, 'a>( + op: OperationRef<'c, 'a>, + index: usize, + context: &str, +) -> Result, MlirError> { + op.result(index) + .map(Into::into) + .map_err(|_| schema_error(format!("{context} expected result {index}"))) +} + +fn schema_error(message: impl Into) -> MlirError { + SchemaError::new(message).into() +} + +struct Stage5OpeningInput<'c, 'a> { + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + claim: Value<'c, 'a>, +} + +struct Stage5OpeningInputs<'c, 'a> { + lookup_output_instruction: Stage5OpeningInput<'c, 'a>, + lookup_output_product: Stage5OpeningInput<'c, 'a>, + left_lookup_operand: Stage5OpeningInput<'c, 'a>, + right_lookup_operand: Stage5OpeningInput<'c, 'a>, + ram_ra_raf: Stage5OpeningInput<'c, 'a>, + ram_ra_rw: Stage5OpeningInput<'c, 'a>, + ram_ra_val: Stage5OpeningInput<'c, 'a>, + registers_val: Stage5OpeningInput<'c, 'a>, +} + +struct StageOpeningInputSpec<'a> { + symbol: &'a str, + source_stage: &'a str, + source_claim: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, +} + +struct Stage5BatchedSumcheckInputs<'c, 'a, 'b> { + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + openings: &'b Stage5OpeningInputs<'c, 'a>, + instruction_gamma: Value<'c, 'a>, + ram_gamma: Value<'c, 'a>, +} + +struct RelationSpec<'a> { + symbol: &'a str, + kind: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + output_count: usize, +} + +struct SumcheckClaimSpec<'a> { + symbol: &'a str, + stage: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + claim: &'a str, + relation: &'a str, +} + +struct SumcheckBatchSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + policy: &'a str, + ordered_claims: &'a [&'a str], + claim_label: &'a str, + round_label: &'a str, + round_schedule: &'a str, +} + +struct SumcheckDriverSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + relation: &'a str, + policy: &'a str, + round_schedule: &'a str, + claim_label: &'a str, + round_label: &'a str, + num_rounds: usize, + degree: usize, +} + +struct SumcheckInstanceResultSpec<'a> { + symbol: &'a str, + source: &'a str, + claim: &'a str, + relation: &'a str, + index: usize, + point_arity: usize, + num_rounds: usize, + round_offset: usize, + point_order: &'a str, + degree: usize, +} + +struct OpeningClaimSpec<'a> { + symbol: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, + claim_kind: &'a str, +} diff --git a/crates/bolt/src/protocols/jolt/phases/stage6.rs b/crates/bolt/src/protocols/jolt/phases/stage6.rs new file mode 100644 index 0000000000..2e474f42c5 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/stage6.rs @@ -0,0 +1,2365 @@ +use std::collections::BTreeSet; + +use melior::ir::operation::OperationRef; +use melior::ir::Value; + +use crate::ir::{BoltModule, Compute, Party, Protocol}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{verify_protocol_schema, SchemaError}; + +use super::super::oracles; +use super::super::params::JoltProtocolParams; +use super::lowering::{lower_party_to_compute, transcript_squeeze_protocol_result_type}; + +const BOOLEANITY_DEGREE: usize = 3; +const HAMMING_BOOLEANITY_DEGREE: usize = 3; +const INC_CLAIM_REDUCTION_DEGREE: usize = 2; + +#[derive(Clone, Copy)] +enum BytecodeStageGamma { + Stage1, + Stage2, + Stage3, + Stage4, + Stage5, +} + +pub fn build_stage6_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.stage6", None); + oracles::append_foundation_ops(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.stage6"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + append_stage6_domains(context, &module, params)?; + append_stage6_oracles(context, &module, params)?; + append_stage6_relations(context, &module, params)?; + let inputs = append_stage6_opening_inputs(context, &module, params)?; + + let fs = context.append_typed_op( + &module, + "transcript.state", + Some("fs_after_stage5"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let state = first_result(fs, "transcript.state")?; + let stage = context.append_typed_op( + &module, + "piop.stage", + Some("stage6"), + &[ + ( + "name", + r#""bytecode_booleanity_and_virtual_address_reductions""#, + ), + ("order", "6 : i64"), + ("roles", r#"["prover", "verifier"]"#), + ], + &[], + &["!piop.stage_type"], + )?; + let stage = first_result(stage, "piop.stage")?; + + let (state, bc_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.bytecode_read_raf.gamma", + "bc_raf_gamma", + "challenge_scalar", + 1, + )?; + let (state, bc_stage1_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.bytecode_read_raf.stage1_gamma", + "bc_raf_stage1_gamma", + "challenge_scalar", + 1, + )?; + let (state, bc_stage2_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.bytecode_read_raf.stage2_gamma", + "bc_raf_stage2_gamma", + "challenge_scalar", + 1, + )?; + let (state, bc_stage3_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.bytecode_read_raf.stage3_gamma", + "bc_raf_stage3_gamma", + "challenge_scalar", + 1, + )?; + let (state, bc_stage4_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.bytecode_read_raf.stage4_gamma", + "bc_raf_stage4_gamma", + "challenge_scalar", + 1, + )?; + let (state, bc_stage5_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.bytecode_read_raf.stage5_gamma", + "bc_raf_stage5_gamma", + "challenge_scalar", + 1, + )?; + let (state, booleanity_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.booleanity.gamma", + "booleanity_gamma", + "challenge_scalar", + 1, + )?; + append_booleanity_power_placeholders(context, &module, params, booleanity_gamma)?; + let (state, inst_ra_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.instruction_ra_virtual.gamma", + "inst_ra_virtual_gamma", + "challenge_scalar", + 1, + )?; + let (state, inc_gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage6.inc_claim_reduction.gamma", + "inc_reduction_gamma", + "challenge_scalar", + 1, + )?; + + let _state = append_stage6_batched_sumcheck( + context, + &module, + params, + Stage6BatchedSumcheckInputs { + state, + stage, + openings: &inputs, + bc_gamma, + bc_stage1_gamma, + bc_stage2_gamma, + bc_stage3_gamma, + bc_stage4_gamma, + bc_stage5_gamma, + inst_ra_gamma, + inc_gamma, + }, + )?; + + verify_module(&module)?; + verify_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_stage6_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + lower_party_to_compute(context, module, "jolt.stage6", "jolt.stage6", "stage6") +} + +fn append_stage6_domains<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_domain( + context, + module, + "jolt.stage2_ram_rw_domain", + params.log_k_ram + params.log_t, + )?; + append_domain( + context, + module, + "jolt.stage4_registers_rw_domain", + params.register_log_k + params.log_t, + )?; + append_domain( + context, + module, + "jolt.stage5_instruction_ra_chunk_domain", + params.lookups_ra_virtual_log_k_chunk + params.log_t, + )?; + append_domain( + context, + module, + "jolt.stage6_bytecode_read_raf_domain", + stage6_max_rounds(params), + )?; + append_domain( + context, + module, + "jolt.stage6_booleanity_domain", + booleanity_rounds(params), + ) +} + +fn append_domain<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + log_size: usize, +) -> Result<(), MlirError> { + context.append_op( + module, + "poly.domain", + Some(symbol), + &[("field", "@bn254_fr"), ("log_size", &int_attr(log_size))], + ) +} + +fn append_stage6_oracles<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + let mut trace_oracles = BTreeSet::new(); + trace_oracles.extend( + [ + "HammingWeight", + "Imm", + "InstructionFlagBranch", + "InstructionFlagIsNoop", + "InstructionFlagLeftOperandIsPC", + "InstructionFlagLeftOperandIsRs1Value", + "InstructionFlagRightOperandIsImm", + "InstructionFlagRightOperandIsRs2Value", + "InstructionRafFlag", + "LookupOutput", + "OpFlagAddOperands", + "OpFlagAdvice", + "OpFlagAssert", + "OpFlagDoNotUpdateUnexpandedPC", + "OpFlagIsCompressed", + "OpFlagIsFirstInSequence", + "OpFlagIsLastInSequence", + "OpFlagJump", + "OpFlagLoad", + "OpFlagMultiplyOperands", + "OpFlagStore", + "OpFlagSubtractOperands", + "OpFlagVirtualInstruction", + "OpFlagWriteLookupOutputToRD", + "PC", + "UnexpandedPC", + ] + .into_iter() + .map(str::to_owned), + ); + for index in 0..params.lookup_table_count { + let _inserted = trace_oracles.insert(format!("LookupTableFlag_{index}")); + } + for oracle in trace_oracles { + append_virtual_oracle(context, module, &oracle, "jolt.trace_domain")?; + } + + append_virtual_oracle(context, module, "RamRa", "jolt.stage2_ram_rw_domain")?; + for oracle in ["RdWa", "Rs1Ra", "Rs2Ra"] { + append_virtual_oracle(context, module, oracle, "jolt.stage4_registers_rw_domain")?; + } + + append_committed_trace_oracle(context, module, "RamInc")?; + append_committed_trace_oracle(context, module, "RdInc")?; + for index in 0..params.instruction_d { + append_committed_main_witness_oracle(context, module, &format!("InstructionRa_{index}"))?; + } + for index in 0..params.bytecode_d { + append_committed_main_witness_oracle(context, module, &format!("BytecodeRa_{index}"))?; + } + for index in 0..params.ram_d { + append_committed_main_witness_oracle(context, module, &format!("RamRa_{index}"))?; + } + Ok(()) +} + +fn append_virtual_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + domain: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", &format!("@{domain}")), + ("commit_domain", &format!("@{domain}")), + ("visibility", r#""virtual""#), + ("layout", r#""virtual""#), + ], + ) +} + +fn append_committed_trace_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", "@jolt.trace_domain"), + ("commit_domain", "@jolt.main_witness_commit_domain"), + ("visibility", r#""committed""#), + ("layout", r#""dense_trace""#), + ], + ) +} + +fn append_committed_main_witness_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", "@jolt.main_witness_commit_domain"), + ("commit_domain", "@jolt.main_witness_commit_domain"), + ("visibility", r#""committed""#), + ("layout", r#""onehot_expanded""#), + ], + ) +} + +fn append_stage6_relations<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage6.bytecode_read_raf", + kind: "sumcheck", + domain: "jolt.stage6_bytecode_read_raf_domain", + num_rounds: stage6_max_rounds(params), + degree: params.bytecode_d + 1, + output_count: params.bytecode_d, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage6.booleanity", + kind: "sumcheck", + domain: "jolt.stage6_booleanity_domain", + num_rounds: booleanity_rounds(params), + degree: BOOLEANITY_DEGREE, + output_count: total_ra_oracles(params), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage6.hamming_booleanity", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: HAMMING_BOOLEANITY_DEGREE, + output_count: 1, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage6.ram_ra_virtual", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: params.ram_d + 1, + output_count: params.ram_d, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage6.instruction_ra_virtual", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: n_committed_per_virtual(params) + 1, + output_count: params.instruction_d, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage6.inc_claim_reduction", + kind: "sumcheck", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: INC_CLAIM_REDUCTION_DEGREE, + output_count: 2, + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage6.batched", + kind: "batched_sumcheck", + domain: "jolt.stage6_bytecode_read_raf_domain", + num_rounds: stage6_max_rounds(params), + degree: stage6_batched_degree(params), + output_count: stage6_output_count(params), + }, + ) +} + +fn append_relation<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + spec: RelationSpec<'_>, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.relation", + Some(spec.symbol), + &[ + ("kind", &format!("\"{}\"", spec.kind)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("output_count", &int_attr(spec.output_count)), + ], + ) +} + +fn append_stage6_opening_inputs<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let mut bytecode_terms = Vec::new(); + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + "stage6.input.stage1.UnexpandedPC", + "stage1", + "stage1.outer_remaining.opening.UnexpandedPC", + "UnexpandedPC", + 0, + Some(BytecodeStageGamma::Stage1), + 0, + ), + )?; + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + "stage6.input.stage1.Imm", + "stage1", + "stage1.outer_remaining.opening.Imm", + "Imm", + 0, + Some(BytecodeStageGamma::Stage1), + 1, + ), + )?; + for (index, oracle) in STAGE1_OP_FLAGS.iter().enumerate() { + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + &format!("stage6.input.stage1.{oracle}"), + "stage1", + &format!("stage1.outer_remaining.opening.{oracle}"), + oracle, + 0, + Some(BytecodeStageGamma::Stage1), + 2 + index, + ), + )?; + } + for (oracle, stage_gamma_power) in [ + ("OpFlagJump", 0), + ("InstructionFlagBranch", 1), + ("OpFlagWriteLookupOutputToRD", 2), + ("OpFlagVirtualInstruction", 3), + ] { + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + &format!("stage6.input.stage2.{oracle}"), + "stage2", + &format!("stage2.product_virtual.remainder.opening.{oracle}"), + oracle, + 1, + Some(BytecodeStageGamma::Stage2), + stage_gamma_power, + ), + )?; + } + for (symbol, source_claim, oracle, stage_gamma_power) in [ + ( + "stage6.input.stage3.instruction_input.Imm", + "stage3.instruction_input.opening.Imm", + "Imm", + 0, + ), + ( + "stage6.input.stage3.spartan_shift.UnexpandedPC", + "stage3.spartan_shift.opening.UnexpandedPC", + "UnexpandedPC", + 1, + ), + ( + "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsRs1Value", + "stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value", + "InstructionFlagLeftOperandIsRs1Value", + 2, + ), + ( + "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsPC", + "stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC", + "InstructionFlagLeftOperandIsPC", + 3, + ), + ( + "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsRs2Value", + "stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value", + "InstructionFlagRightOperandIsRs2Value", + 4, + ), + ( + "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsImm", + "stage3.instruction_input.opening.InstructionFlagRightOperandIsImm", + "InstructionFlagRightOperandIsImm", + 5, + ), + ( + "stage6.input.stage3.spartan_shift.InstructionFlagIsNoop", + "stage3.spartan_shift.opening.InstructionFlagIsNoop", + "InstructionFlagIsNoop", + 6, + ), + ( + "stage6.input.stage3.spartan_shift.OpFlagVirtualInstruction", + "stage3.spartan_shift.opening.OpFlagVirtualInstruction", + "OpFlagVirtualInstruction", + 7, + ), + ( + "stage6.input.stage3.spartan_shift.OpFlagIsFirstInSequence", + "stage3.spartan_shift.opening.OpFlagIsFirstInSequence", + "OpFlagIsFirstInSequence", + 8, + ), + ] { + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + symbol, + "stage3", + source_claim, + oracle, + 2, + Some(BytecodeStageGamma::Stage3), + stage_gamma_power, + ), + )?; + } + for (oracle, stage_gamma_power) in [("RdWa", 0), ("Rs1Ra", 1), ("Rs2Ra", 2)] { + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec { + input: StageOpeningInputSpec { + symbol: &format!("stage6.input.stage4.{oracle}"), + source_stage: "stage4", + source_claim: &format!("stage4.registers_read_write.opening.{oracle}"), + oracle, + domain: "jolt.stage4_registers_rw_domain", + point_arity: params.register_log_k + params.log_t, + claim_kind: "virtual", + }, + gamma_power: 3, + stage_gamma: Some(BytecodeStageGamma::Stage4), + stage_gamma_power, + }, + )?; + } + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec { + input: StageOpeningInputSpec { + symbol: "stage6.input.stage5.registers_val_evaluation.RdWa", + source_stage: "stage5", + source_claim: "stage5.registers_val_evaluation.opening.RdWa", + oracle: "RdWa", + domain: "jolt.stage4_registers_rw_domain", + point_arity: params.register_log_k + params.log_t, + claim_kind: "virtual", + }, + gamma_power: 4, + stage_gamma: Some(BytecodeStageGamma::Stage5), + stage_gamma_power: 0, + }, + )?; + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + "stage6.input.stage5.InstructionRafFlag", + "stage5", + "stage5.instruction_read_raf.opening.InstructionRafFlag", + "InstructionRafFlag", + 4, + Some(BytecodeStageGamma::Stage5), + 1, + ), + )?; + for index in 0..params.lookup_table_count { + let oracle = format!("LookupTableFlag_{index}"); + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + &format!("stage6.input.stage5.{oracle}"), + "stage5", + &format!("stage5.instruction_read_raf.opening.{oracle}"), + &oracle, + 4, + Some(BytecodeStageGamma::Stage5), + 2 + index, + ), + )?; + } + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + "stage6.input.stage1.PC", + "stage1", + "stage1.outer_remaining.opening.PC", + "PC", + 5, + None, + 0, + ), + )?; + append_bytecode_term( + context, + module, + params, + &mut bytecode_terms, + BytecodeTermSpec::trace( + "stage6.input.stage3.spartan_shift.PC", + "stage3", + "stage3.spartan_shift.opening.PC", + "PC", + 6, + None, + 0, + ), + )?; + + let ram_ra_virtual = append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + source_stage: "stage5", + source_claim: "stage5.ram_ra_claim_reduction.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: params.log_k_ram + params.log_t, + claim_kind: "virtual", + }, + )?; + + let mut instruction_ra_virtual = Vec::with_capacity(params.instruction_ra_virtual_d); + for index in 0..params.instruction_ra_virtual_d { + instruction_ra_virtual.push(append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: &format!("stage6.input.stage5.instruction_read_raf.InstructionRa_{index}"), + source_stage: "stage5", + source_claim: &format!("stage5.instruction_read_raf.opening.InstructionRa_{index}"), + oracle: &format!("InstructionRa_{index}"), + domain: "jolt.stage5_instruction_ra_chunk_domain", + point_arity: params.lookups_ra_virtual_log_k_chunk + params.log_t, + claim_kind: "virtual", + }, + )?); + } + + Ok(Stage6OpeningInputs { + bytecode_terms, + hamming_lookup_output: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage6.input.stage1.LookupOutput", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.LookupOutput", + oracle: "LookupOutput", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "virtual", + }, + )?, + ram_ra_virtual, + instruction_ra_virtual, + ram_inc_stage2: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage6.input.stage2.ram_read_write.RamInc", + source_stage: "stage2", + source_claim: "stage2.ram_read_write.opening.RamInc", + oracle: "RamInc", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?, + ram_inc_stage4: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage6.input.stage4.ram_val_check.RamInc", + source_stage: "stage4", + source_claim: "stage4.ram_val_check.opening.RamInc", + oracle: "RamInc", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?, + rd_inc_stage4: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage6.input.stage4.registers_read_write.RdInc", + source_stage: "stage4", + source_claim: "stage4.registers_read_write.opening.RdInc", + oracle: "RdInc", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?, + rd_inc_stage5: append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage6.input.stage5.registers_val_evaluation.RdInc", + source_stage: "stage5", + source_claim: "stage5.registers_val_evaluation.opening.RdInc", + oracle: "RdInc", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?, + }) +} + +fn append_bytecode_term<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + terms: &mut Vec>, + mut spec: BytecodeTermSpec<'_>, +) -> Result<(), MlirError> { + if spec.input.point_arity == 0 { + spec.input.point_arity = params.log_t; + } + let input = append_stage_input(context, module, spec.input)?; + terms.push(Stage6BytecodeTerm { + eval: input.eval, + claim: input.claim, + gamma_power: spec.gamma_power, + stage_gamma: spec.stage_gamma, + stage_gamma_power: spec.stage_gamma_power, + }); + Ok(()) +} + +fn append_stage_input<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: StageOpeningInputSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_input", + Some(spec.symbol), + &[ + ("source_stage", &format!("@{}", spec.source_stage)), + ("source_claim", &format!("@{}", spec.source_claim)), + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", &format!("\"{}\"", spec.claim_kind)), + ], + &[], + &["!poly.point", "!field.scalar", "!piop.opening_claim_type"], + )?; + Ok(Stage6OpeningInput { + point: result(op, 0, "piop.opening_input")?, + eval: result(op, 1, "piop.opening_input")?, + claim: result(op, 2, "piop.opening_input")?, + }) +} + +fn append_transcript_squeeze<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + symbol: &str, + label: &str, + kind: &str, + count: usize, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "transcript.squeeze", + Some(symbol), + &[ + ("label", &format!("\"{label}\"")), + ("kind", &format!("\"{kind}\"")), + ("count", &int_attr(count)), + ], + &[state], + &[ + "!transcript.state_type", + transcript_squeeze_protocol_result_type(kind)?, + ], + )?; + Ok(( + result(op, 0, "transcript.squeeze")?, + result(op, 1, "transcript.squeeze")?, + )) +} + +fn append_booleanity_power_placeholders<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + booleanity_gamma: Value<'c, 'a>, +) -> Result<(), MlirError> { + let total = total_ra_oracles(params); + for index in 0..total { + let _ = append_field_pow( + context, + module, + &format!("stage6.booleanity.gamma_sq_{index}"), + booleanity_gamma, + 2 * index, + )?; + } + for index in 0..total { + let _ = append_field_pow( + context, + module, + &format!("stage6.booleanity.gamma_pow_{index}"), + booleanity_gamma, + index, + )?; + } + Ok(()) +} + +fn append_stage6_batched_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + spec: Stage6BatchedSumcheckInputs<'c, 'a, '_>, +) -> Result, MlirError> { + let inputs = spec.openings; + let bytecode_claim = append_bytecode_read_raf_claim(context, module, inputs, &spec)?; + let zero = append_field_zero(context, module, "stage6.zero")?; + let ram_ra_virtual_claim = inputs.ram_ra_virtual.eval; + let inst_ra_virtual_claim = + append_instruction_ra_virtual_claim(context, module, inputs, spec.inst_ra_gamma)?; + let inc_claim = append_inc_claim_reduction_claim(context, module, inputs, spec.inc_gamma)?; + + let bytecode_inputs = inputs + .bytecode_terms + .iter() + .map(|term| term.claim) + .collect::>(); + let instruction_inputs = inputs + .instruction_ra_virtual + .iter() + .map(|input| input.claim) + .collect::>(); + let inc_inputs = [ + inputs.ram_inc_stage2.claim, + inputs.ram_inc_stage4.claim, + inputs.rd_inc_stage4.claim, + inputs.rd_inc_stage5.claim, + ]; + let claims = [ + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage6.bytecode_read_raf.input", + stage: "stage6", + domain: "jolt.stage6_bytecode_read_raf_domain", + num_rounds: stage6_max_rounds(params), + degree: params.bytecode_d + 1, + claim: "stage6.bytecode_read_raf.weighted_prior_stage_values", + relation: "jolt.stage6.bytecode_read_raf", + }, + bytecode_claim, + &bytecode_inputs, + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage6.booleanity.input", + stage: "stage6", + domain: "jolt.stage6_booleanity_domain", + num_rounds: booleanity_rounds(params), + degree: BOOLEANITY_DEGREE, + claim: "stage6.booleanity.zero", + relation: "jolt.stage6.booleanity", + }, + zero, + &[], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage6.hamming_booleanity.input", + stage: "stage6", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: HAMMING_BOOLEANITY_DEGREE, + claim: "stage6.hamming_booleanity.zero", + relation: "jolt.stage6.hamming_booleanity", + }, + zero, + &[inputs.hamming_lookup_output.claim], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage6.ram_ra_virtual.input", + stage: "stage6", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: params.ram_d + 1, + claim: "stage6.ram_ra_virtual.weighted_ram_ra", + relation: "jolt.stage6.ram_ra_virtual", + }, + ram_ra_virtual_claim, + &[inputs.ram_ra_virtual.claim], + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage6.instruction_ra_virtual.input", + stage: "stage6", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: n_committed_per_virtual(params) + 1, + claim: "stage6.instruction_ra_virtual.weighted_instruction_ra", + relation: "jolt.stage6.instruction_ra_virtual", + }, + inst_ra_virtual_claim, + &instruction_inputs, + )?, + append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage6.inc_claim_reduction.input", + stage: "stage6", + domain: "jolt.trace_domain", + num_rounds: params.log_t, + degree: INC_CLAIM_REDUCTION_DEGREE, + claim: "stage6.inc_claim_reduction.weighted_increments", + relation: "jolt.stage6.inc_claim_reduction", + }, + inc_claim, + &inc_inputs, + )?, + ]; + let round_schedule = format!("[{}, {}]", params.log_k_bytecode, params.log_t); + let batch = append_sumcheck_batch( + context, + module, + spec.stage, + &claims, + SumcheckBatchSpec { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + ordered_claims: &[ + "stage6.bytecode_read_raf.input", + "stage6.booleanity.input", + "stage6.hamming_booleanity.input", + "stage6.ram_ra_virtual.input", + "stage6.instruction_ra_virtual.input", + "stage6.inc_claim_reduction.input", + ], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &round_schedule, + }, + )?; + let (state, point, result_value) = append_sumcheck( + context, + module, + spec.state, + batch, + SumcheckDriverSpec { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + relation: "jolt.stage6.batched", + policy: "jolt_core_stage6_aligned", + round_schedule: &round_schedule, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: stage6_max_rounds(params), + degree: stage6_batched_degree(params), + }, + )?; + let bytecode = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage6.bytecode_read_raf.instance", + source: "stage6.sumcheck", + claim: "stage6.bytecode_read_raf.input", + relation: "jolt.stage6.bytecode_read_raf", + index: 0, + point_arity: stage6_max_rounds(params), + num_rounds: stage6_max_rounds(params), + round_offset: 0, + point_order: "bytecode_read_raf", + degree: params.bytecode_d + 1, + }, + point, + result_value, + )?; + let booleanity = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage6.booleanity.instance", + source: "stage6.sumcheck", + claim: "stage6.booleanity.input", + relation: "jolt.stage6.booleanity", + index: 1, + point_arity: booleanity_rounds(params), + num_rounds: booleanity_rounds(params), + round_offset: params.log_k_bytecode.saturating_sub(params.log_k_chunk), + point_order: "stage6_booleanity", + degree: BOOLEANITY_DEGREE, + }, + point, + result_value, + )?; + let hamming = append_stage6_trace_instance_result( + context, + module, + params, + point, + result_value, + Stage6TraceInstanceSpec { + symbol: "stage6.hamming_booleanity.instance", + claim: "stage6.hamming_booleanity.input", + relation: "jolt.stage6.hamming_booleanity", + index: 2, + degree: HAMMING_BOOLEANITY_DEGREE, + }, + )?; + let ram = append_stage6_trace_instance_result( + context, + module, + params, + point, + result_value, + Stage6TraceInstanceSpec { + symbol: "stage6.ram_ra_virtual.instance", + claim: "stage6.ram_ra_virtual.input", + relation: "jolt.stage6.ram_ra_virtual", + index: 3, + degree: params.ram_d + 1, + }, + )?; + let instruction = append_stage6_trace_instance_result( + context, + module, + params, + point, + result_value, + Stage6TraceInstanceSpec { + symbol: "stage6.instruction_ra_virtual.instance", + claim: "stage6.instruction_ra_virtual.input", + relation: "jolt.stage6.instruction_ra_virtual", + index: 4, + degree: n_committed_per_virtual(params) + 1, + }, + )?; + let inc = append_stage6_trace_instance_result( + context, + module, + params, + point, + result_value, + Stage6TraceInstanceSpec { + symbol: "stage6.inc_claim_reduction.instance", + claim: "stage6.inc_claim_reduction.input", + relation: "jolt.stage6.inc_claim_reduction", + index: 5, + degree: INC_CLAIM_REDUCTION_DEGREE, + }, + )?; + append_stage6_output_openings( + context, + module, + params, + inputs, + bytecode, + booleanity, + hamming, + ram, + instruction, + inc, + )?; + Ok(state) +} + +fn append_bytecode_read_raf_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + inputs: &Stage6OpeningInputs<'c, 'a>, + spec: &Stage6BatchedSumcheckInputs<'c, 'a, '_>, +) -> Result, MlirError> { + let mut terms = Vec::with_capacity(inputs.bytecode_terms.len() + 1); + for (index, term) in inputs.bytecode_terms.iter().enumerate() { + terms.push(append_weighted_eval( + context, + module, + &format!("stage6.bytecode_read_raf.claim.term{index}"), + term.eval, + WeightedEvalSpec { + gamma: spec.bc_gamma, + gamma_power: term.gamma_power, + stage_gamma: term.stage_gamma.map(|gamma| stage_gamma_value(gamma, spec)), + stage_gamma_power: term.stage_gamma_power, + }, + )?); + } + terms.push(append_field_pow( + context, + module, + "stage6.bytecode_read_raf.claim.entry_constant", + spec.bc_gamma, + 7, + )?); + append_field_sum( + context, + module, + "stage6.bytecode_read_raf.claim_expr", + &terms, + ) +} + +fn append_instruction_ra_virtual_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + inputs: &Stage6OpeningInputs<'c, 'a>, + gamma: Value<'c, 'a>, +) -> Result, MlirError> { + let mut terms = Vec::with_capacity(inputs.instruction_ra_virtual.len()); + for (index, input) in inputs.instruction_ra_virtual.iter().enumerate() { + terms.push(append_weighted_eval( + context, + module, + &format!("stage6.instruction_ra_virtual.claim.term{index}"), + input.eval, + WeightedEvalSpec { + gamma, + gamma_power: index, + stage_gamma: None, + stage_gamma_power: 0, + }, + )?); + } + append_field_sum( + context, + module, + "stage6.instruction_ra_virtual.claim_expr", + &terms, + ) +} + +fn append_inc_claim_reduction_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + inputs: &Stage6OpeningInputs<'c, 'a>, + gamma: Value<'c, 'a>, +) -> Result, MlirError> { + let terms = [ + inputs.ram_inc_stage2.eval, + append_weighted_eval( + context, + module, + "stage6.inc_claim_reduction.claim.ram_inc_stage4", + inputs.ram_inc_stage4.eval, + WeightedEvalSpec { + gamma, + gamma_power: 1, + stage_gamma: None, + stage_gamma_power: 0, + }, + )?, + append_weighted_eval( + context, + module, + "stage6.inc_claim_reduction.claim.rd_inc_stage4", + inputs.rd_inc_stage4.eval, + WeightedEvalSpec { + gamma, + gamma_power: 2, + stage_gamma: None, + stage_gamma_power: 0, + }, + )?, + append_weighted_eval( + context, + module, + "stage6.inc_claim_reduction.claim.rd_inc_stage5", + inputs.rd_inc_stage5.eval, + WeightedEvalSpec { + gamma, + gamma_power: 3, + stage_gamma: None, + stage_gamma_power: 0, + }, + )?, + ]; + append_field_sum( + context, + module, + "stage6.inc_claim_reduction.claim_expr", + &terms, + ) +} + +fn append_weighted_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol_prefix: &str, + eval: Value<'c, 'a>, + spec: WeightedEvalSpec<'c, 'a>, +) -> Result, MlirError> { + let mut value = eval; + if spec.stage_gamma_power > 0 { + let power = append_field_pow( + context, + module, + &format!("{symbol_prefix}.stage_gamma_pow"), + spec.stage_gamma.ok_or_else(|| MlirError::Schema { + message: format!( + "{symbol_prefix} requires stage gamma when stage_gamma_power is non-zero" + ), + })?, + spec.stage_gamma_power, + )?; + value = append_field_mul( + context, + module, + &format!("{symbol_prefix}.stage_gamma_term"), + power, + value, + )?; + } + if spec.gamma_power > 0 { + let power = append_field_pow( + context, + module, + &format!("{symbol_prefix}.gamma_pow"), + spec.gamma, + spec.gamma_power, + )?; + value = append_field_mul( + context, + module, + &format!("{symbol_prefix}.gamma_term"), + power, + value, + )?; + } + Ok(value) +} + +fn stage_gamma_value<'c, 'a>( + gamma: BytecodeStageGamma, + spec: &Stage6BatchedSumcheckInputs<'c, 'a, '_>, +) -> Value<'c, 'a> { + match gamma { + BytecodeStageGamma::Stage1 => spec.bc_stage1_gamma, + BytecodeStageGamma::Stage2 => spec.bc_stage2_gamma, + BytecodeStageGamma::Stage3 => spec.bc_stage3_gamma, + BytecodeStageGamma::Stage4 => spec.bc_stage4_gamma, + BytecodeStageGamma::Stage5 => spec.bc_stage5_gamma, + } +} + +fn append_stage6_trace_instance_result<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + point: Value<'c, 'a>, + result_value: Value<'c, 'a>, + spec: Stage6TraceInstanceSpec<'_>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: spec.symbol, + source: "stage6.sumcheck", + claim: spec.claim, + relation: spec.relation, + index: spec.index, + point_arity: params.log_t, + num_rounds: params.log_t, + round_offset: params.log_k_bytecode, + point_order: "reverse", + degree: spec.degree, + }, + point, + result_value, + ) +} + +#[expect(clippy::too_many_arguments)] +fn append_stage6_output_openings<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + inputs: &Stage6OpeningInputs<'c, 'a>, + bytecode: (Value<'c, 'a>, Value<'c, 'a>), + booleanity: (Value<'c, 'a>, Value<'c, 'a>), + hamming: (Value<'c, 'a>, Value<'c, 'a>), + ram: (Value<'c, 'a>, Value<'c, 'a>), + instruction: (Value<'c, 'a>, Value<'c, 'a>), + inc: (Value<'c, 'a>, Value<'c, 'a>), +) -> Result<(), MlirError> { + let mut claims = Vec::new(); + let mut claim_symbols = Vec::new(); + + let bytecode_cycle = append_point_slice( + context, + module, + "stage6.bytecode_read_raf.point.Cycle", + "stage6.bytecode_read_raf.instance", + params.log_k_bytecode, + params.log_t, + bytecode.0, + )?; + for index in 0..params.bytecode_d { + let oracle = format!("BytecodeRa_{index}"); + let eval_symbol = format!("stage6.bytecode_read_raf.eval.{oracle}"); + let eval = append_sumcheck_eval( + context, + module, + &eval_symbol, + "stage6.sumcheck", + &oracle, + index, + bytecode.1, + )?; + let address = append_padded_address_chunk( + context, + module, + &format!("stage6.bytecode_read_raf.point.{oracle}.address"), + "stage6.bytecode_read_raf.instance", + params.log_k_bytecode, + index, + params.log_k_chunk, + bytecode.0, + )?; + let point = append_point_concat( + context, + module, + &format!("stage6.bytecode_read_raf.point.{oracle}"), + "address_chunk_then_cycle", + params.log_k_chunk + params.log_t, + &[address, bytecode_cycle], + )?; + let symbol = format!("stage6.bytecode_read_raf.opening.{oracle}"); + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + point, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle: &oracle, + domain: "jolt.main_witness_commit_domain", + point_arity: params.log_k_chunk + params.log_t, + claim_kind: "committed", + }, + )?); + } + + let mut eval_index = 0; + for index in 0..params.instruction_d { + append_booleanity_output_opening( + context, + module, + params, + &mut claims, + &mut claim_symbols, + booleanity, + &format!("InstructionRa_{index}"), + eval_index, + )?; + eval_index += 1; + } + for index in 0..params.bytecode_d { + append_booleanity_output_opening( + context, + module, + params, + &mut claims, + &mut claim_symbols, + booleanity, + &format!("BytecodeRa_{index}"), + eval_index, + )?; + eval_index += 1; + } + for index in 0..params.ram_d { + append_booleanity_output_opening( + context, + module, + params, + &mut claims, + &mut claim_symbols, + booleanity, + &format!("RamRa_{index}"), + eval_index, + )?; + eval_index += 1; + } + + let hamming_eval = append_sumcheck_eval( + context, + module, + "stage6.hamming_booleanity.eval.HammingWeight", + "stage6.sumcheck", + "HammingWeight", + 0, + hamming.1, + )?; + claim_symbols.push("stage6.hamming_booleanity.opening.HammingWeight".to_owned()); + claims.push(append_opening_claim( + context, + module, + hamming.0, + hamming_eval, + OpeningClaimSpec { + symbol: "stage6.hamming_booleanity.opening.HammingWeight", + oracle: "HammingWeight", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "virtual", + }, + )?); + + for index in 0..params.ram_d { + let oracle = format!("RamRa_{index}"); + let symbol = format!("stage6.ram_ra_virtual.opening.{oracle}"); + let address = append_padded_address_chunk( + context, + module, + &format!("stage6.ram_ra_virtual.point.{oracle}.address"), + "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + params.log_k_ram, + index, + params.log_k_chunk, + inputs.ram_ra_virtual.point, + )?; + let point = append_point_concat( + context, + module, + &format!("stage6.ram_ra_virtual.point.{oracle}"), + "address_chunk_then_cycle", + params.log_k_chunk + params.log_t, + &[address, ram.0], + )?; + let eval = append_sumcheck_eval( + context, + module, + &format!("stage6.ram_ra_virtual.eval.{oracle}"), + "stage6.sumcheck", + &oracle, + index, + ram.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + point, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle: &oracle, + domain: "jolt.main_witness_commit_domain", + point_arity: params.log_k_chunk + params.log_t, + claim_kind: "committed", + }, + )?); + } + for index in 0..params.instruction_d { + let oracle = format!("InstructionRa_{index}"); + let symbol = format!("stage6.instruction_ra_virtual.opening.{oracle}"); + let virtual_index = index / n_committed_per_virtual(params); + let chunk_index = index % n_committed_per_virtual(params); + let virtual_input = inputs.instruction_ra_virtual[virtual_index].point; + let address = append_padded_address_chunk( + context, + module, + &format!("stage6.instruction_ra_virtual.point.{oracle}.address"), + &format!("stage6.input.stage5.instruction_read_raf.InstructionRa_{virtual_index}"), + params.lookups_ra_virtual_log_k_chunk, + chunk_index, + params.log_k_chunk, + virtual_input, + )?; + let point = append_point_concat( + context, + module, + &format!("stage6.instruction_ra_virtual.point.{oracle}"), + "address_chunk_then_cycle", + params.log_k_chunk + params.log_t, + &[address, instruction.0], + )?; + let eval = append_sumcheck_eval( + context, + module, + &format!("stage6.instruction_ra_virtual.eval.{oracle}"), + "stage6.sumcheck", + &oracle, + index, + instruction.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + point, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle: &oracle, + domain: "jolt.main_witness_commit_domain", + point_arity: params.log_k_chunk + params.log_t, + claim_kind: "committed", + }, + )?); + } + + for (index, oracle) in ["RamInc", "RdInc"].iter().enumerate() { + let symbol = format!("stage6.inc_claim_reduction.opening.{oracle}"); + let eval = append_sumcheck_eval( + context, + module, + &format!("stage6.inc_claim_reduction.eval.{oracle}"), + "stage6.sumcheck", + oracle, + index, + inc.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + inc.0, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle, + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "committed", + }, + )?); + } + + let claim_names = claim_symbols.iter().map(String::as_str).collect::>(); + let _batch = context.append_typed_op( + module, + "piop.opening_batch", + Some("stage6.openings"), + &[ + ("stage", "@stage6"), + ("proof_slot", "@stage6.openings"), + ("policy", r#""jolt_stage6_output_order""#), + ("count", &int_attr(claims.len())), + ("ordered_claims", &symbol_array_attr(&claim_names)), + ], + &claims, + &["!piop.opening_batch_type"], + )?; + Ok(()) +} + +#[expect(clippy::too_many_arguments)] +fn append_booleanity_output_opening<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + claims: &mut Vec>, + claim_symbols: &mut Vec, + booleanity: (Value<'c, 'a>, Value<'c, 'a>), + oracle: &str, + eval_index: usize, +) -> Result<(), MlirError> { + let symbol = format!("stage6.booleanity.opening.{oracle}"); + let eval = append_sumcheck_eval( + context, + module, + &format!("stage6.booleanity.eval.{oracle}"), + "stage6.sumcheck", + oracle, + eval_index, + booleanity.1, + )?; + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + booleanity.0, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle, + domain: "jolt.main_witness_commit_domain", + point_arity: booleanity_rounds(params), + claim_kind: "committed", + }, + )?); + Ok(()) +} + +fn append_field_zero<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.zero", + Some(symbol), + &[("field", "@bn254_fr")], + &[], + &["!field.scalar"], + )?; + first_result(op, "field.zero") +} + +fn append_field_binary<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + op_name: &str, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + op_name, + Some(symbol), + &[], + &[lhs, rhs], + &["!field.scalar"], + )?; + first_result(op, op_name) +} + +fn append_field_add<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.add", symbol, lhs, rhs) +} + +fn append_field_mul<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.mul", symbol, lhs, rhs) +} + +fn append_field_pow<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + base: Value<'c, 'a>, + exponent: usize, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.pow", + Some(symbol), + &[("exponent", &int_attr(exponent))], + &[base], + &["!field.scalar"], + )?; + first_result(op, "field.pow") +} + +fn append_field_sum<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol_prefix: &str, + terms: &[Value<'c, 'a>], +) -> Result, MlirError> { + let Some((&first, rest)) = terms.split_first() else { + return append_field_zero(context, module, symbol_prefix); + }; + let mut value = first; + for (index, &term) in rest.iter().enumerate() { + value = append_field_add( + context, + module, + &format!("{symbol_prefix}.partial{index}"), + value, + term, + )?; + } + Ok(value) +} + +fn append_sumcheck_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckClaimSpec<'_>, + input_claim: Value<'c, 'a>, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(inputs.len() + 1); + operands.push(input_claim); + operands.extend_from_slice(inputs); + let op = context.append_typed_op( + module, + "piop.sumcheck_claim", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ], + &operands, + &["!piop.sumcheck_claim_type"], + )?; + first_result(op, "piop.sumcheck_claim") +} + +fn append_sumcheck_batch<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + stage: Value<'c, 'a>, + claims: &[Value<'c, 'a>], + spec: SumcheckBatchSpec<'_>, +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(claims.len() + 1); + operands.push(stage); + operands.extend_from_slice(claims); + let op = context.append_typed_op( + module, + "piop.sumcheck_batch", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("policy", &format!("\"{}\"", spec.policy)), + ("count", &int_attr(spec.ordered_claims.len())), + ("ordered_claims", &symbol_array_attr(spec.ordered_claims)), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("round_schedule", spec.round_schedule), + ], + &operands, + &["!piop.sumcheck_batch_type"], + )?; + first_result(op, "piop.sumcheck_batch") +} + +fn append_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + batch: Value<'c, 'a>, + spec: SumcheckDriverSpec<'_>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("relation", &format!("@{}", spec.relation)), + ("policy", &format!("\"{}\"", spec.policy)), + ("round_schedule", spec.round_schedule), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ], + &[state, batch], + &[ + "!transcript.state_type", + "!poly.point", + "!piop.sumcheck_result_type", + "!piop.sumcheck_proof_type", + ], + )?; + Ok(( + result(op, 0, "piop.sumcheck")?, + result(op, 1, "piop.sumcheck")?, + result(op, 2, "piop.sumcheck")?, + )) +} + +fn append_sumcheck_instance_result<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckInstanceResultSpec<'_>, + point: Value<'c, 'a>, + result_value: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_instance_result", + Some(spec.symbol), + &[ + ("source", &format!("@{}", spec.source)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ("index", &int_attr(spec.index)), + ("point_arity", &int_attr(spec.point_arity)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("round_offset", &int_attr(spec.round_offset)), + ("point_order", &format!("\"{}\"", spec.point_order)), + ("degree", &int_attr(spec.degree)), + ], + &[point, result_value], + &["!poly.point", "!piop.sumcheck_result_type"], + )?; + Ok(( + result(op, 0, "piop.sumcheck_instance_result")?, + result(op, 1, "piop.sumcheck_instance_result")?, + )) +} + +fn append_sumcheck_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + oracle: &str, + index: usize, + result_value: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_eval", + Some(symbol), + &[ + ("source", &format!("@{}", source)), + ("name", &format!("@{}", symbol)), + ("index", &int_attr(index)), + ("oracle", &format!("@{}", oracle)), + ], + &[result_value], + &["!field.scalar"], + )?; + first_result(op, "piop.sumcheck_eval") +} + +fn append_point_slice<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + offset: usize, + length: usize, + input: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_slice", + Some(symbol), + &[ + ("source", &format!("@{}", source)), + ("offset", &int_attr(offset)), + ("length", &int_attr(length)), + ], + &[input], + &["!poly.point"], + )?; + first_result(op, "poly.point_slice") +} + +#[expect(clippy::too_many_arguments)] +fn append_padded_address_chunk<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + address_len: usize, + chunk_index: usize, + chunk_len: usize, + input: Value<'c, 'a>, +) -> Result, MlirError> { + let pad_len = (chunk_len - (address_len % chunk_len)) % chunk_len; + let padded_offset = chunk_index * chunk_len; + let zero_len = pad_len.saturating_sub(padded_offset).min(chunk_len); + let source_offset = padded_offset.saturating_sub(pad_len); + let source_len = chunk_len - zero_len; + if source_offset + source_len > address_len { + return Err(schema_error(format!( + "address chunk {chunk_index} exceeds source point @{source}" + ))); + } + + let source_chunk = if source_len == 0 { + None + } else { + let source_symbol = if zero_len == 0 { + symbol.to_owned() + } else { + format!("{symbol}.source") + }; + Some(append_point_slice( + context, + module, + &source_symbol, + source, + source_offset, + source_len, + input, + )?) + }; + + if zero_len == 0 { + return source_chunk.ok_or_else(|| { + schema_error(format!("address chunk {chunk_index} has no source point")) + }); + } + + let zero = append_point_zero(context, module, &format!("{symbol}.zero_pad"), zero_len)?; + let inputs = match source_chunk { + Some(source_chunk) => vec![zero, source_chunk], + None => vec![zero], + }; + append_point_concat( + context, + module, + symbol, + "left_zero_padded_address_chunk", + chunk_len, + &inputs, + ) +} + +fn append_point_zero<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + arity: usize, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_zero", + Some(symbol), + &[("field", "@bn254_fr"), ("arity", &int_attr(arity))], + &[], + &["!poly.point"], + )?; + first_result(op, "poly.point_zero") +} + +fn append_point_concat<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + layout: &str, + arity: usize, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_concat", + Some(symbol), + &[ + ("layout", &format!("\"{}\"", layout)), + ("arity", &int_attr(arity)), + ], + inputs, + &["!poly.point"], + )?; + first_result(op, "poly.point_concat") +} + +fn append_opening_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + spec: OpeningClaimSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_claim", + Some(spec.symbol), + &[ + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", &format!("\"{}\"", spec.claim_kind)), + ], + &[point, eval], + &["!piop.opening_claim_type"], + )?; + first_result(op, "piop.opening_claim") +} + +fn stage6_max_rounds(params: &JoltProtocolParams) -> usize { + params.log_k_bytecode + params.log_t +} + +fn booleanity_rounds(params: &JoltProtocolParams) -> usize { + params.log_k_chunk + params.log_t +} + +fn n_committed_per_virtual(params: &JoltProtocolParams) -> usize { + params.lookups_ra_virtual_log_k_chunk / params.log_k_chunk +} + +fn total_ra_oracles(params: &JoltProtocolParams) -> usize { + params.instruction_d + params.bytecode_d + params.ram_d +} + +fn stage6_batched_degree(params: &JoltProtocolParams) -> usize { + [ + params.bytecode_d + 1, + BOOLEANITY_DEGREE, + HAMMING_BOOLEANITY_DEGREE, + params.ram_d + 1, + n_committed_per_virtual(params) + 1, + INC_CLAIM_REDUCTION_DEGREE, + ] + .into_iter() + .max() + .unwrap_or(INC_CLAIM_REDUCTION_DEGREE) +} + +fn stage6_output_count(params: &JoltProtocolParams) -> usize { + params.bytecode_d + total_ra_oracles(params) + 1 + params.ram_d + params.instruction_d + 2 +} + +fn int_attr(value: usize) -> String { + format!("{value} : i64") +} + +fn symbol_array_attr(values: &[&str]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn first_result<'c, 'a>( + op: OperationRef<'c, 'a>, + context: &str, +) -> Result, MlirError> { + result(op, 0, context) +} + +fn result<'c, 'a>( + op: OperationRef<'c, 'a>, + index: usize, + context: &str, +) -> Result, MlirError> { + op.result(index) + .map(Into::into) + .map_err(|_| schema_error(format!("{context} missing result {index}"))) +} + +fn schema_error(message: impl Into) -> MlirError { + SchemaError::new(message).into() +} + +const STAGE1_OP_FLAGS: [&str; 14] = [ + "OpFlagAddOperands", + "OpFlagSubtractOperands", + "OpFlagMultiplyOperands", + "OpFlagLoad", + "OpFlagStore", + "OpFlagJump", + "OpFlagWriteLookupOutputToRD", + "OpFlagVirtualInstruction", + "OpFlagAssert", + "OpFlagDoNotUpdateUnexpandedPC", + "OpFlagAdvice", + "OpFlagIsCompressed", + "OpFlagIsFirstInSequence", + "OpFlagIsLastInSequence", +]; + +struct Stage6BatchedSumcheckInputs<'c, 'a, 'b> { + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + openings: &'b Stage6OpeningInputs<'c, 'a>, + bc_gamma: Value<'c, 'a>, + bc_stage1_gamma: Value<'c, 'a>, + bc_stage2_gamma: Value<'c, 'a>, + bc_stage3_gamma: Value<'c, 'a>, + bc_stage4_gamma: Value<'c, 'a>, + bc_stage5_gamma: Value<'c, 'a>, + inst_ra_gamma: Value<'c, 'a>, + inc_gamma: Value<'c, 'a>, +} + +struct Stage6OpeningInputs<'c, 'a> { + bytecode_terms: Vec>, + hamming_lookup_output: Stage6OpeningInput<'c, 'a>, + ram_ra_virtual: Stage6OpeningInput<'c, 'a>, + instruction_ra_virtual: Vec>, + ram_inc_stage2: Stage6OpeningInput<'c, 'a>, + ram_inc_stage4: Stage6OpeningInput<'c, 'a>, + rd_inc_stage4: Stage6OpeningInput<'c, 'a>, + rd_inc_stage5: Stage6OpeningInput<'c, 'a>, +} + +struct Stage6OpeningInput<'c, 'a> { + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + claim: Value<'c, 'a>, +} + +struct Stage6BytecodeTerm<'c, 'a> { + eval: Value<'c, 'a>, + claim: Value<'c, 'a>, + gamma_power: usize, + stage_gamma: Option, + stage_gamma_power: usize, +} + +struct BytecodeTermSpec<'a> { + input: StageOpeningInputSpec<'a>, + gamma_power: usize, + stage_gamma: Option, + stage_gamma_power: usize, +} + +impl<'a> BytecodeTermSpec<'a> { + fn trace( + symbol: &'a str, + source_stage: &'a str, + source_claim: &'a str, + oracle: &'a str, + gamma_power: usize, + stage_gamma: Option, + stage_gamma_power: usize, + ) -> Self { + Self { + input: StageOpeningInputSpec { + symbol, + source_stage, + source_claim, + oracle, + domain: "jolt.trace_domain", + point_arity: 0, + claim_kind: "virtual", + }, + gamma_power, + stage_gamma, + stage_gamma_power, + } + } +} + +struct WeightedEvalSpec<'c, 'a> { + gamma: Value<'c, 'a>, + gamma_power: usize, + stage_gamma: Option>, + stage_gamma_power: usize, +} + +struct StageOpeningInputSpec<'a> { + symbol: &'a str, + source_stage: &'a str, + source_claim: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, + claim_kind: &'a str, +} + +struct RelationSpec<'a> { + symbol: &'a str, + kind: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + output_count: usize, +} + +struct SumcheckClaimSpec<'a> { + symbol: &'a str, + stage: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + claim: &'a str, + relation: &'a str, +} + +struct SumcheckBatchSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + policy: &'a str, + ordered_claims: &'a [&'a str], + claim_label: &'a str, + round_label: &'a str, + round_schedule: &'a str, +} + +struct SumcheckDriverSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + relation: &'a str, + policy: &'a str, + round_schedule: &'a str, + claim_label: &'a str, + round_label: &'a str, + num_rounds: usize, + degree: usize, +} + +struct SumcheckInstanceResultSpec<'a> { + symbol: &'a str, + source: &'a str, + claim: &'a str, + relation: &'a str, + index: usize, + point_arity: usize, + num_rounds: usize, + round_offset: usize, + point_order: &'a str, + degree: usize, +} + +struct Stage6TraceInstanceSpec<'a> { + symbol: &'a str, + claim: &'a str, + relation: &'a str, + index: usize, + degree: usize, +} + +struct OpeningClaimSpec<'a> { + symbol: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, + claim_kind: &'a str, +} diff --git a/crates/bolt/src/protocols/jolt/phases/stage7.rs b/crates/bolt/src/protocols/jolt/phases/stage7.rs new file mode 100644 index 0000000000..c37568c3d3 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/stage7.rs @@ -0,0 +1,1095 @@ +use melior::ir::operation::OperationRef; +use melior::ir::Value; + +use crate::ir::{BoltModule, Compute, Party, Protocol}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{verify_protocol_schema, SchemaError}; + +use super::super::oracles; +use super::super::params::JoltProtocolParams; +use super::lowering::{lower_party_to_compute, transcript_squeeze_protocol_result_type}; + +const HAMMING_WEIGHT_CLAIM_REDUCTION_DEGREE: usize = 2; + +pub fn build_stage7_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.stage7", None); + oracles::append_foundation_ops(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.stage7"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + append_stage7_domains(context, &module, params)?; + append_stage7_oracles(context, &module, params)?; + append_stage7_relations(context, &module, params)?; + let inputs = append_stage7_opening_inputs(context, &module, params)?; + + let fs = context.append_typed_op( + &module, + "transcript.state", + Some("fs_after_stage6"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let state = first_result(fs, "transcript.state")?; + let stage = context.append_typed_op( + &module, + "piop.stage", + Some("stage7"), + &[ + ("name", r#""hamming_weight_claim_reduction""#), + ("order", "7 : i64"), + ("roles", r#"["prover", "verifier"]"#), + ], + &[], + &["!piop.stage_type"], + )?; + let stage = first_result(stage, "piop.stage")?; + + let (state, gamma) = append_transcript_squeeze( + context, + &module, + state, + "stage7.hamming_weight_claim_reduction.gamma", + "hamming_weight_claim_reduction_gamma", + "challenge_scalar", + 1, + )?; + let _state = append_stage7_sumcheck( + context, + &module, + params, + Stage7SumcheckInputs { + state, + stage, + openings: &inputs, + gamma, + }, + )?; + + verify_module(&module)?; + verify_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_stage7_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + lower_party_to_compute(context, module, "jolt.stage7", "jolt.stage7", "stage7") +} + +fn append_stage7_domains<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_domain( + context, + module, + "jolt.stage7_hamming_weight_claim_reduction_domain", + params.log_k_chunk, + ) +} + +fn append_domain<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + log_size: usize, +) -> Result<(), MlirError> { + context.append_op( + module, + "poly.domain", + Some(symbol), + &[("field", "@bn254_fr"), ("log_size", &int_attr(log_size))], + ) +} + +fn append_stage7_oracles<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_virtual_oracle(context, module, "HammingWeight", "jolt.trace_domain")?; + for index in 0..params.instruction_d { + append_committed_main_witness_oracle(context, module, &format!("InstructionRa_{index}"))?; + } + for index in 0..params.bytecode_d { + append_committed_main_witness_oracle(context, module, &format!("BytecodeRa_{index}"))?; + } + for index in 0..params.ram_d { + append_committed_main_witness_oracle(context, module, &format!("RamRa_{index}"))?; + } + Ok(()) +} + +fn append_virtual_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, + domain: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", &format!("@{domain}")), + ("commit_domain", &format!("@{domain}")), + ("visibility", r#""virtual""#), + ("layout", r#""virtual""#), + ], + ) +} + +fn append_committed_main_witness_oracle<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + symbol: &str, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.oracle", + Some(symbol), + &[ + ("field", "@bn254_fr"), + ("domain", "@jolt.main_witness_commit_domain"), + ("commit_domain", "@jolt.main_witness_commit_domain"), + ("visibility", r#""committed""#), + ("layout", r#""onehot_expanded""#), + ], + ) +} + +fn append_stage7_relations<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result<(), MlirError> { + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage7.hamming_weight_claim_reduction", + kind: "sumcheck", + domain: "jolt.stage7_hamming_weight_claim_reduction_domain", + num_rounds: params.log_k_chunk, + degree: HAMMING_WEIGHT_CLAIM_REDUCTION_DEGREE, + output_count: total_ra_oracles(params), + }, + )?; + append_relation( + context, + module, + RelationSpec { + symbol: "jolt.stage7.batched", + kind: "batched_sumcheck", + domain: "jolt.stage7_hamming_weight_claim_reduction_domain", + num_rounds: params.log_k_chunk, + degree: HAMMING_WEIGHT_CLAIM_REDUCTION_DEGREE, + output_count: total_ra_oracles(params), + }, + ) +} + +fn append_relation<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Protocol>, + spec: RelationSpec<'_>, +) -> Result<(), MlirError> { + context.append_op( + module, + "piop.relation", + Some(spec.symbol), + &[ + ("kind", &format!("\"{}\"", spec.kind)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("output_count", &int_attr(spec.output_count)), + ], + ) +} + +fn append_stage7_opening_inputs<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let ram_hamming = append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: "stage7.input.stage6.hamming_booleanity.HammingWeight", + source_stage: "stage6", + source_claim: "stage6.hamming_booleanity.opening.HammingWeight", + oracle: "HammingWeight", + domain: "jolt.trace_domain", + point_arity: params.log_t, + claim_kind: "virtual", + }, + )?; + + let mut ra_inputs = Vec::with_capacity(total_ra_oracles(params)); + for index in 0..params.instruction_d { + let oracle = format!("InstructionRa_{index}"); + ra_inputs.push(append_ra_inputs( + context, + module, + params, + &oracle, + Stage7RaKind::Instruction, + &format!("stage6.instruction_ra_virtual.opening.{oracle}"), + &format!("stage7.input.stage6.instruction_ra_virtual.{oracle}"), + )?); + } + for index in 0..params.bytecode_d { + let oracle = format!("BytecodeRa_{index}"); + ra_inputs.push(append_ra_inputs( + context, + module, + params, + &oracle, + Stage7RaKind::Bytecode, + &format!("stage6.bytecode_read_raf.opening.{oracle}"), + &format!("stage7.input.stage6.bytecode_read_raf.{oracle}"), + )?); + } + for index in 0..params.ram_d { + let oracle = format!("RamRa_{index}"); + ra_inputs.push(append_ra_inputs( + context, + module, + params, + &oracle, + Stage7RaKind::Ram, + &format!("stage6.ram_ra_virtual.opening.{oracle}"), + &format!("stage7.input.stage6.ram_ra_virtual.{oracle}"), + )?); + } + + let booleanity_point = ra_inputs + .first() + .ok_or_else(|| schema_error("Stage 7 requires at least one RA oracle"))? + .booleanity + .point; + + Ok(Stage7OpeningInputs { + ra_inputs, + ram_hamming, + booleanity_point, + }) +} + +fn append_ra_inputs<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + oracle: &str, + kind: Stage7RaKind, + source_virtual_claim: &str, + virtual_input_symbol: &str, +) -> Result, MlirError> { + let booleanity = append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: &format!("stage7.input.stage6.booleanity.{oracle}"), + source_stage: "stage6", + source_claim: &format!("stage6.booleanity.opening.{oracle}"), + oracle, + domain: "jolt.main_witness_commit_domain", + point_arity: params.log_k_chunk + params.log_t, + claim_kind: "committed", + }, + )?; + let virtualization = append_stage_input( + context, + module, + StageOpeningInputSpec { + symbol: virtual_input_symbol, + source_stage: "stage6", + source_claim: source_virtual_claim, + oracle, + domain: "jolt.main_witness_commit_domain", + point_arity: params.log_k_chunk + params.log_t, + claim_kind: "committed", + }, + )?; + Ok(Stage7RaInput { + oracle: oracle.to_owned(), + kind, + booleanity, + virtualization, + }) +} + +fn append_stage_input<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: StageOpeningInputSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_input", + Some(spec.symbol), + &[ + ("source_stage", &format!("@{}", spec.source_stage)), + ("source_claim", &format!("@{}", spec.source_claim)), + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", &format!("\"{}\"", spec.claim_kind)), + ], + &[], + &["!poly.point", "!field.scalar", "!piop.opening_claim_type"], + )?; + Ok(Stage7OpeningInput { + point: result(op, 0, "piop.opening_input")?, + eval: result(op, 1, "piop.opening_input")?, + claim: result(op, 2, "piop.opening_input")?, + }) +} + +fn append_stage7_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + spec: Stage7SumcheckInputs<'c, 'a, '_>, +) -> Result, MlirError> { + let input_claim = append_hamming_weight_claim_reduction_input_claim( + context, + module, + spec.openings, + spec.gamma, + )?; + let mut input_openings = Vec::with_capacity(2 * spec.openings.ra_inputs.len() + 1); + input_openings.push(spec.openings.ram_hamming.claim); + for input in &spec.openings.ra_inputs { + input_openings.push(input.booleanity.claim); + input_openings.push(input.virtualization.claim); + } + let claim = append_sumcheck_claim( + context, + module, + SumcheckClaimSpec { + symbol: "stage7.hamming_weight_claim_reduction.input", + stage: "stage7", + domain: "jolt.stage7_hamming_weight_claim_reduction_domain", + num_rounds: params.log_k_chunk, + degree: HAMMING_WEIGHT_CLAIM_REDUCTION_DEGREE, + claim: "stage7.hamming_weight_claim_reduction.weighted_stage6_claims", + relation: "jolt.stage7.hamming_weight_claim_reduction", + }, + input_claim, + &input_openings, + )?; + let round_schedule = format!("[{}]", params.log_k_chunk); + let batch = append_sumcheck_batch( + context, + module, + spec.stage, + &[claim], + SumcheckBatchSpec { + symbol: "stage7.batch", + stage: "stage7", + proof_slot: "stage7.sumcheck", + policy: "jolt_core_stage7_aligned", + ordered_claims: &["stage7.hamming_weight_claim_reduction.input"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &round_schedule, + }, + )?; + let (state, point, result_value) = append_sumcheck( + context, + module, + spec.state, + batch, + SumcheckDriverSpec { + symbol: "stage7.sumcheck", + stage: "stage7", + proof_slot: "stage7.sumcheck", + relation: "jolt.stage7.batched", + policy: "jolt_core_stage7_aligned", + round_schedule: &round_schedule, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: params.log_k_chunk, + degree: HAMMING_WEIGHT_CLAIM_REDUCTION_DEGREE, + }, + )?; + let instance = append_sumcheck_instance_result( + context, + module, + SumcheckInstanceResultSpec { + symbol: "stage7.hamming_weight_claim_reduction.instance", + source: "stage7.sumcheck", + claim: "stage7.hamming_weight_claim_reduction.input", + relation: "jolt.stage7.hamming_weight_claim_reduction", + index: 0, + point_arity: params.log_k_chunk, + num_rounds: params.log_k_chunk, + round_offset: 0, + point_order: "reverse", + degree: HAMMING_WEIGHT_CLAIM_REDUCTION_DEGREE, + }, + point, + result_value, + )?; + append_stage7_output_openings(context, module, params, spec.openings, instance)?; + Ok(state) +} + +fn append_hamming_weight_claim_reduction_input_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + inputs: &Stage7OpeningInputs<'c, 'a>, + gamma: Value<'c, 'a>, +) -> Result, MlirError> { + let one = append_field_one(context, module, "stage7.field.one")?; + let mut terms = Vec::with_capacity(3 * inputs.ra_inputs.len()); + for (index, input) in inputs.ra_inputs.iter().enumerate() { + let hamming_eval = match input.kind { + Stage7RaKind::Instruction | Stage7RaKind::Bytecode => one, + Stage7RaKind::Ram => inputs.ram_hamming.eval, + }; + terms.push(append_weighted_eval( + context, + module, + &format!("stage7.hamming_weight_claim_reduction.claim.{index}.hw"), + hamming_eval, + gamma, + 3 * index, + )?); + terms.push(append_weighted_eval( + context, + module, + &format!("stage7.hamming_weight_claim_reduction.claim.{index}.booleanity"), + input.booleanity.eval, + gamma, + 3 * index + 1, + )?); + terms.push(append_weighted_eval( + context, + module, + &format!("stage7.hamming_weight_claim_reduction.claim.{index}.virtualization"), + input.virtualization.eval, + gamma, + 3 * index + 2, + )?); + } + append_field_sum( + context, + module, + "stage7.hamming_weight_claim_reduction.claim_expr", + &terms, + ) +} + +fn append_weighted_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol_prefix: &str, + eval: Value<'c, 'a>, + gamma: Value<'c, 'a>, + gamma_power: usize, +) -> Result, MlirError> { + if gamma_power == 0 { + return Ok(eval); + } + let power = append_field_pow( + context, + module, + &format!("{symbol_prefix}.gamma_pow"), + gamma, + gamma_power, + )?; + append_field_mul( + context, + module, + &format!("{symbol_prefix}.gamma_term"), + power, + eval, + ) +} + +fn append_stage7_output_openings<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + inputs: &Stage7OpeningInputs<'c, 'a>, + instance: (Value<'c, 'a>, Value<'c, 'a>), +) -> Result<(), MlirError> { + let cycle = append_point_slice( + context, + module, + "stage7.hamming_weight_claim_reduction.point.cycle", + "stage7.input.stage6.booleanity.InstructionRa_0", + params.log_k_chunk, + params.log_t, + inputs.booleanity_point, + )?; + let full_point = append_point_concat( + context, + module, + "stage7.hamming_weight_claim_reduction.point", + "address_chunk_then_cycle", + params.log_k_chunk + params.log_t, + &[instance.0, cycle], + )?; + + let mut claims = Vec::with_capacity(inputs.ra_inputs.len()); + let mut claim_symbols = Vec::with_capacity(inputs.ra_inputs.len()); + for (index, input) in inputs.ra_inputs.iter().enumerate() { + let eval = append_sumcheck_eval( + context, + module, + &format!( + "stage7.hamming_weight_claim_reduction.eval.{}", + input.oracle + ), + "stage7.sumcheck", + &input.oracle, + index, + instance.1, + )?; + let symbol = format!( + "stage7.hamming_weight_claim_reduction.opening.{}", + input.oracle + ); + claim_symbols.push(symbol.clone()); + claims.push(append_opening_claim( + context, + module, + full_point, + eval, + OpeningClaimSpec { + symbol: &symbol, + oracle: &input.oracle, + domain: "jolt.main_witness_commit_domain", + point_arity: params.log_k_chunk + params.log_t, + claim_kind: "committed", + }, + )?); + } + let claim_names = claim_symbols.iter().map(String::as_str).collect::>(); + let _batch = context.append_typed_op( + module, + "piop.opening_batch", + Some("stage7.openings"), + &[ + ("stage", "@stage7"), + ("proof_slot", "@stage7.openings"), + ("policy", r#""jolt_stage7_output_order""#), + ("count", &int_attr(claims.len())), + ("ordered_claims", &symbol_array_attr(&claim_names)), + ], + &claims, + &["!piop.opening_batch_type"], + )?; + Ok(()) +} + +fn append_transcript_squeeze<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + symbol: &str, + label: &str, + kind: &str, + count: usize, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "transcript.squeeze", + Some(symbol), + &[ + ("label", &format!("\"{label}\"")), + ("kind", &format!("\"{kind}\"")), + ("count", &int_attr(count)), + ], + &[state], + &[ + "!transcript.state_type", + transcript_squeeze_protocol_result_type(kind)?, + ], + )?; + Ok(( + result(op, 0, "transcript.squeeze")?, + result(op, 1, "transcript.squeeze")?, + )) +} + +fn append_field_one<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.one", + Some(symbol), + &[("field", "@bn254_fr")], + &[], + &["!field.scalar"], + )?; + first_result(op, "field.one") +} + +fn append_field_binary<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + op_name: &str, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + op_name, + Some(symbol), + &[], + &[lhs, rhs], + &["!field.scalar"], + )?; + first_result(op, op_name) +} + +fn append_field_add<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.add", symbol, lhs, rhs) +} + +fn append_field_mul<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + lhs: Value<'c, 'a>, + rhs: Value<'c, 'a>, +) -> Result, MlirError> { + append_field_binary(context, module, "field.mul", symbol, lhs, rhs) +} + +fn append_field_pow<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + base: Value<'c, 'a>, + exponent: usize, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "field.pow", + Some(symbol), + &[("exponent", &int_attr(exponent))], + &[base], + &["!field.scalar"], + )?; + first_result(op, "field.pow") +} + +fn append_field_sum<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol_prefix: &str, + terms: &[Value<'c, 'a>], +) -> Result, MlirError> { + let Some((&first, rest)) = terms.split_first() else { + return append_field_one(context, module, symbol_prefix); + }; + let mut value = first; + for (index, &term) in rest.iter().enumerate() { + value = append_field_add( + context, + module, + &format!("{symbol_prefix}.partial{index}"), + value, + term, + )?; + } + Ok(value) +} + +fn append_sumcheck_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckClaimSpec<'_>, + input_claim: Value<'c, 'a>, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(inputs.len() + 1); + operands.push(input_claim); + operands.extend_from_slice(inputs); + let op = context.append_typed_op( + module, + "piop.sumcheck_claim", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("domain", &format!("@{}", spec.domain)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ], + &operands, + &["!piop.sumcheck_claim_type"], + )?; + first_result(op, "piop.sumcheck_claim") +} + +fn append_sumcheck_batch<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + stage: Value<'c, 'a>, + claims: &[Value<'c, 'a>], + spec: SumcheckBatchSpec<'_>, +) -> Result, MlirError> { + let mut operands = Vec::with_capacity(claims.len() + 1); + operands.push(stage); + operands.extend_from_slice(claims); + let op = context.append_typed_op( + module, + "piop.sumcheck_batch", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("policy", &format!("\"{}\"", spec.policy)), + ("count", &int_attr(spec.ordered_claims.len())), + ("ordered_claims", &symbol_array_attr(spec.ordered_claims)), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("round_schedule", spec.round_schedule), + ], + &operands, + &["!piop.sumcheck_batch_type"], + )?; + first_result(op, "piop.sumcheck_batch") +} + +fn append_sumcheck<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + state: Value<'c, 'a>, + batch: Value<'c, 'a>, + spec: SumcheckDriverSpec<'_>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck", + Some(spec.symbol), + &[ + ("stage", &format!("@{}", spec.stage)), + ("proof_slot", &format!("@{}", spec.proof_slot)), + ("relation", &format!("@{}", spec.relation)), + ("policy", &format!("\"{}\"", spec.policy)), + ("round_schedule", spec.round_schedule), + ("claim_label", &format!("\"{}\"", spec.claim_label)), + ("round_label", &format!("\"{}\"", spec.round_label)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("degree", &int_attr(spec.degree)), + ], + &[state, batch], + &[ + "!transcript.state_type", + "!poly.point", + "!piop.sumcheck_result_type", + "!piop.sumcheck_proof_type", + ], + )?; + Ok(( + result(op, 0, "piop.sumcheck")?, + result(op, 1, "piop.sumcheck")?, + result(op, 2, "piop.sumcheck")?, + )) +} + +fn append_sumcheck_instance_result<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: SumcheckInstanceResultSpec<'_>, + point: Value<'c, 'a>, + result_value: Value<'c, 'a>, +) -> Result<(Value<'c, 'a>, Value<'c, 'a>), MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_instance_result", + Some(spec.symbol), + &[ + ("source", &format!("@{}", spec.source)), + ("claim", &format!("@{}", spec.claim)), + ("relation", &format!("@{}", spec.relation)), + ("index", &int_attr(spec.index)), + ("point_arity", &int_attr(spec.point_arity)), + ("num_rounds", &int_attr(spec.num_rounds)), + ("round_offset", &int_attr(spec.round_offset)), + ("point_order", &format!("\"{}\"", spec.point_order)), + ("degree", &int_attr(spec.degree)), + ], + &[point, result_value], + &["!poly.point", "!piop.sumcheck_result_type"], + )?; + Ok(( + result(op, 0, "piop.sumcheck_instance_result")?, + result(op, 1, "piop.sumcheck_instance_result")?, + )) +} + +fn append_sumcheck_eval<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + oracle: &str, + index: usize, + result_value: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.sumcheck_eval", + Some(symbol), + &[ + ("source", &format!("@{}", source)), + ("name", &format!("@{}", symbol)), + ("index", &int_attr(index)), + ("oracle", &format!("@{}", oracle)), + ], + &[result_value], + &["!field.scalar"], + )?; + first_result(op, "piop.sumcheck_eval") +} + +fn append_point_slice<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + source: &str, + offset: usize, + length: usize, + input: Value<'c, 'a>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_slice", + Some(symbol), + &[ + ("source", &format!("@{}", source)), + ("offset", &int_attr(offset)), + ("length", &int_attr(length)), + ], + &[input], + &["!poly.point"], + )?; + first_result(op, "poly.point_slice") +} + +fn append_point_concat<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + symbol: &str, + layout: &str, + arity: usize, + inputs: &[Value<'c, 'a>], +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "poly.point_concat", + Some(symbol), + &[ + ("layout", &format!("\"{}\"", layout)), + ("arity", &int_attr(arity)), + ], + inputs, + &["!poly.point"], + )?; + first_result(op, "poly.point_concat") +} + +fn append_opening_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + spec: OpeningClaimSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_claim", + Some(spec.symbol), + &[ + ("oracle", &format!("@{}", spec.oracle)), + ("domain", &format!("@{}", spec.domain)), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", &format!("\"{}\"", spec.claim_kind)), + ], + &[point, eval], + &["!piop.opening_claim_type"], + )?; + first_result(op, "piop.opening_claim") +} + +fn total_ra_oracles(params: &JoltProtocolParams) -> usize { + params.instruction_d + params.bytecode_d + params.ram_d +} + +fn int_attr(value: usize) -> String { + format!("{value} : i64") +} + +fn symbol_array_attr(values: &[&str]) -> String { + let values = values + .iter() + .map(|value| format!("@{value}")) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn first_result<'c, 'a>( + op: OperationRef<'c, 'a>, + context: &str, +) -> Result, MlirError> { + result(op, 0, context) +} + +fn result<'c, 'a>( + op: OperationRef<'c, 'a>, + index: usize, + context: &str, +) -> Result, MlirError> { + op.result(index) + .map(Into::into) + .map_err(|_| schema_error(format!("{context} missing result {index}"))) +} + +fn schema_error(message: impl Into) -> MlirError { + SchemaError::new(message).into() +} + +#[derive(Clone, Copy)] +enum Stage7RaKind { + Instruction, + Bytecode, + Ram, +} + +struct Stage7SumcheckInputs<'c, 'a, 'b> { + state: Value<'c, 'a>, + stage: Value<'c, 'a>, + openings: &'b Stage7OpeningInputs<'c, 'a>, + gamma: Value<'c, 'a>, +} + +struct Stage7OpeningInputs<'c, 'a> { + ra_inputs: Vec>, + ram_hamming: Stage7OpeningInput<'c, 'a>, + booleanity_point: Value<'c, 'a>, +} + +struct Stage7RaInput<'c, 'a> { + oracle: String, + kind: Stage7RaKind, + booleanity: Stage7OpeningInput<'c, 'a>, + virtualization: Stage7OpeningInput<'c, 'a>, +} + +struct Stage7OpeningInput<'c, 'a> { + point: Value<'c, 'a>, + eval: Value<'c, 'a>, + claim: Value<'c, 'a>, +} + +struct StageOpeningInputSpec<'a> { + symbol: &'a str, + source_stage: &'a str, + source_claim: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, + claim_kind: &'a str, +} + +struct RelationSpec<'a> { + symbol: &'a str, + kind: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + output_count: usize, +} + +struct SumcheckClaimSpec<'a> { + symbol: &'a str, + stage: &'a str, + domain: &'a str, + num_rounds: usize, + degree: usize, + claim: &'a str, + relation: &'a str, +} + +struct SumcheckBatchSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + policy: &'a str, + ordered_claims: &'a [&'a str], + claim_label: &'a str, + round_label: &'a str, + round_schedule: &'a str, +} + +struct SumcheckDriverSpec<'a> { + symbol: &'a str, + stage: &'a str, + proof_slot: &'a str, + relation: &'a str, + policy: &'a str, + round_schedule: &'a str, + claim_label: &'a str, + round_label: &'a str, + num_rounds: usize, + degree: usize, +} + +struct SumcheckInstanceResultSpec<'a> { + symbol: &'a str, + source: &'a str, + claim: &'a str, + relation: &'a str, + index: usize, + point_arity: usize, + num_rounds: usize, + round_offset: usize, + point_order: &'a str, + degree: usize, +} + +struct OpeningClaimSpec<'a> { + symbol: &'a str, + oracle: &'a str, + domain: &'a str, + point_arity: usize, + claim_kind: &'a str, +} diff --git a/crates/bolt/src/protocols/jolt/phases/stage8.rs b/crates/bolt/src/protocols/jolt/phases/stage8.rs new file mode 100644 index 0000000000..97ed373918 --- /dev/null +++ b/crates/bolt/src/protocols/jolt/phases/stage8.rs @@ -0,0 +1,293 @@ +use melior::ir::operation::{OperationLike, OperationRef}; +use melior::ir::Value; + +use crate::ir::{BoltModule, Compute, Party, Protocol}; +use crate::mlir::{verify_module, MeliorContext, MlirError}; +use crate::schema::{verify_protocol_schema, SchemaError}; + +use super::super::oracles; +use super::super::params::JoltProtocolParams; +use super::lowering::lower_party_to_compute; + +const EVALUATION_POINT_SOURCE_SYMBOL: &str = "stage8.evaluation.point_source"; + +pub fn build_stage8_protocol<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> Result, MlirError> { + let module = context.new_module::("jolt.stage8", None); + oracles::append_foundation_ops(context, &module, params)?; + oracles::append_committed_oracles(context, &module, params)?; + context.append_op_with_owned_attrs( + &module, + "protocol.params", + Some("jolt.params"), + ¶ms.attrs(), + )?; + context.append_op( + &module, + "protocol.boundary", + Some("jolt.stage8"), + &[("roles", r#"["prover", "verifier"]"#)], + )?; + + let fs = context.append_typed_op( + &module, + "transcript.state", + Some("fs_after_stage7"), + &[("scheme", "@blake2b_transcript")], + &[], + &["!transcript.state_type"], + )?; + let state = result(fs, 0, "transcript.state")?; + let _stage = context.append_typed_op( + &module, + "piop.stage", + Some("stage8"), + &[ + ("name", r#""evaluation_proof""#), + ("order", "8 : i64"), + ("roles", r#"["prover", "verifier"]"#), + ], + &[], + &["!piop.stage_type"], + )?; + + let _point_source = append_opening_input( + context, + &module, + Stage8OpeningInputSpec { + symbol: EVALUATION_POINT_SOURCE_SYMBOL, + source_stage: "stage7", + source_claim: "stage7.input.stage6.booleanity.InstructionRa_0", + oracle: "InstructionRa_0", + point_arity: params.log_t + params.log_k_chunk, + }, + )?; + let mut claims = Vec::new(); + let mut claim_symbols = Vec::new(); + append_evaluation_claim( + context, + &module, + params, + &mut claims, + &mut claim_symbols, + Stage8EvaluationClaimSpec { + oracle: "RamInc", + source_stage: "stage6", + source_claim: "stage6.inc_claim_reduction.eval.RamInc", + }, + )?; + append_evaluation_claim( + context, + &module, + params, + &mut claims, + &mut claim_symbols, + Stage8EvaluationClaimSpec { + oracle: "RdInc", + source_stage: "stage6", + source_claim: "stage6.inc_claim_reduction.eval.RdInc", + }, + )?; + for index in 0..params.instruction_d { + append_evaluation_claim( + context, + &module, + params, + &mut claims, + &mut claim_symbols, + Stage8EvaluationClaimSpec { + oracle: &format!("InstructionRa_{index}"), + source_stage: "stage7", + source_claim: &format!( + "stage7.hamming_weight_claim_reduction.eval.InstructionRa_{index}" + ), + }, + )?; + } + for index in 0..params.bytecode_d { + append_evaluation_claim( + context, + &module, + params, + &mut claims, + &mut claim_symbols, + Stage8EvaluationClaimSpec { + oracle: &format!("BytecodeRa_{index}"), + source_stage: "stage7", + source_claim: &format!( + "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_{index}" + ), + }, + )?; + } + for index in 0..params.ram_d { + append_evaluation_claim( + context, + &module, + params, + &mut claims, + &mut claim_symbols, + Stage8EvaluationClaimSpec { + oracle: &format!("RamRa_{index}"), + source_stage: "stage7", + source_claim: &format!("stage7.hamming_weight_claim_reduction.eval.RamRa_{index}"), + }, + )?; + } + + let opening_batch = context.append_typed_op( + &module, + "pcs.opening_batch", + Some("stage8.evaluation.openings"), + &[ + ("proof_slot", "@stage8.evaluation"), + ("policy", r#""jolt_stage8_joint_rlc""#), + ("count", &int_attr(claims.len())), + ("ordered_claims", &symbol_array_attr(&claim_symbols)), + ], + &claims, + &["!pcs.opening_batch_type"], + )?; + let opening_batch = result(opening_batch, 0, "pcs.opening_batch")?; + let _state = context.append_typed_op( + &module, + "pcs.batch_open", + Some("stage8.evaluation.proof"), + &[ + ("pcs", "@dory"), + ("proof_slot", "@stage8.evaluation"), + ("transcript_label", r#""rlc_claims""#), + ], + &[state, opening_batch], + &["!transcript.state_type", "!pcs.opening_proof_type"], + )?; + + verify_module(&module)?; + verify_protocol_schema(&module)?; + Ok(module) +} + +pub fn lower_stage8_to_compute<'c>( + context: &'c MeliorContext, + module: &BoltModule<'c, Party>, +) -> Result, MlirError> { + lower_party_to_compute(context, module, "jolt.stage8", "jolt.stage8", "stage8") +} + +fn append_evaluation_claim<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + params: &JoltProtocolParams, + claims: &mut Vec>, + claim_symbols: &mut Vec, + spec: Stage8EvaluationClaimSpec<'_>, +) -> Result<(), MlirError> { + let input_symbol = format!("stage8.input.{}.{}", spec.source_stage, spec.oracle); + let opening_input = append_opening_input( + context, + module, + Stage8OpeningInputSpec { + symbol: &input_symbol, + source_stage: spec.source_stage, + source_claim: spec.source_claim, + oracle: spec.oracle, + point_arity: params.log_t + params.log_k_chunk, + }, + )?; + let opening_symbol = format!("stage8.evaluation.opening.{}", spec.oracle); + let opening = context.append_typed_op( + module, + "pcs.opening_claim", + Some(&opening_symbol), + &[ + ("oracle", &format!("@{}", spec.oracle)), + ("family", "@jolt.main_witness_polys"), + ("domain", "@jolt.main_witness_commit_domain"), + ("point_arity", &int_attr(params.log_t + params.log_k_chunk)), + ], + &[opening_input.point, opening_input.eval], + &["!pcs.opening_claim_type"], + )?; + claims.push(result(opening, 0, "pcs.opening_claim")?); + claim_symbols.push(opening_symbol); + Ok(()) +} + +fn append_opening_input<'c, 'a>( + context: &'c MeliorContext, + module: &'a BoltModule<'c, Protocol>, + spec: Stage8OpeningInputSpec<'_>, +) -> Result, MlirError> { + let op = context.append_typed_op( + module, + "piop.opening_input", + Some(spec.symbol), + &[ + ("source_stage", &format!("@{}", spec.source_stage)), + ("source_claim", &format!("@{}", spec.source_claim)), + ("oracle", &format!("@{}", spec.oracle)), + ("domain", "@jolt.main_witness_commit_domain"), + ("point_arity", &int_attr(spec.point_arity)), + ("claim_kind", r#""committed""#), + ], + &[], + &["!poly.point", "!field.scalar", "!piop.opening_claim_type"], + )?; + Ok(Stage8OpeningInput { + point: result(op, 0, "piop.opening_input")?, + eval: result(op, 1, "piop.opening_input")?, + }) +} + +#[derive(Clone, Copy)] +struct Stage8OpeningInputSpec<'a> { + symbol: &'a str, + source_stage: &'a str, + source_claim: &'a str, + oracle: &'a str, + point_arity: usize, +} + +#[derive(Clone, Copy)] +struct Stage8EvaluationClaimSpec<'a> { + oracle: &'a str, + source_stage: &'a str, + source_claim: &'a str, +} + +struct Stage8OpeningInput<'c, 'a> { + point: Value<'c, 'a>, + eval: Value<'c, 'a>, +} + +fn result<'c, 'a>( + operation: OperationRef<'c, 'a>, + index: usize, + op_name: &str, +) -> Result, MlirError> { + operation.result(index).map(Into::into).map_err(|_| { + schema_error(format!( + "{op_name} requires result {index}, got {} results", + operation.result_count() + )) + }) +} + +fn symbol_array_attr(symbols: &[String]) -> String { + let symbols = symbols + .iter() + .map(|symbol| format!("@{symbol}")) + .collect::>() + .join(", "); + format!("[{symbols}]") +} + +fn int_attr(value: usize) -> String { + format!("{value} : i64") +} + +fn schema_error(message: impl Into) -> MlirError { + SchemaError::new(message).into() +} diff --git a/crates/bolt/src/protocols/jolt/validate.rs b/crates/bolt/src/protocols/jolt/validate.rs new file mode 100644 index 0000000000..8070f5ba3a --- /dev/null +++ b/crates/bolt/src/protocols/jolt/validate.rs @@ -0,0 +1,134 @@ +use melior::ir::operation::{OperationLike, OperationResult}; +use melior::ir::OperationRef; + +use crate::ir::{string_attribute_value, BoltModule, Concrete, Party, Protocol}; +use crate::schema::{ + find_symbol, int_attr, missing_module_op, missing_symbol, require_symbol_attr_eq, + symbol_array_attr, symbol_attr, verify_concrete_schema, verify_party_schema, + verify_protocol_schema, SchemaError, +}; + +use super::oracles::{MAIN_WITNESS_FAMILY_SYMBOL, PCS_SYMBOL}; +use super::params::ParsedJoltProtocolParams; + +pub fn verify_jolt_protocol_schema(module: &BoltModule<'_, Protocol>) -> Result<(), SchemaError> { + verify_protocol_schema(module)?; + validate_jolt_shape(module) +} + +pub fn verify_jolt_concrete_schema(module: &BoltModule<'_, Concrete>) -> Result<(), SchemaError> { + verify_concrete_schema(module)?; + validate_jolt_shape(module) +} + +pub fn verify_jolt_party_schema(module: &BoltModule<'_, Party>) -> Result<(), SchemaError> { + verify_party_schema(module)?; + validate_jolt_shape(module) +} + +fn validate_jolt_shape

(module: &BoltModule<'_, P>) -> Result<(), SchemaError> +where + P: crate::ir::Phase, +{ + let params_op = + find_symbol(module, "jolt.params").ok_or_else(|| missing_module_op("protocol.params"))?; + let params = ParsedJoltProtocolParams::from_op(params_op)?; + params.validate()?; + + require_symbol(module, ¶ms.field)?; + require_symbol(module, ¶ms.pcs)?; + require_symbol(module, ¶ms.transcript)?; + + let witness_family = find_symbol(module, MAIN_WITNESS_FAMILY_SYMBOL) + .ok_or_else(|| missing_symbol(MAIN_WITNESS_FAMILY_SYMBOL))?; + let witness_count = int_attr(witness_family, "count")?; + if witness_count != params.num_committed { + return Err(SchemaError::new(format!( + "main witness count {witness_count} does not match num_committed {}", + params.num_committed + ))); + } + let ordered_oracles = symbol_array_attr(witness_family, "ordered_oracles")?; + let expected_oracles = params.main_witness_oracles(); + if ordered_oracles != expected_oracles { + return Err(SchemaError::new(format!( + "main witness ordered_oracles mismatch: expected [{}], got [{}]", + expected_oracles.join(", "), + ordered_oracles.join(", ") + ))); + } + if ordered_oracles.len() != witness_count { + return Err(SchemaError::new(format!( + "main witness ordered_oracles length {} does not match count {witness_count}", + ordered_oracles.len() + ))); + } + for oracle in &ordered_oracles { + let oracle_op = find_symbol(module, oracle).ok_or_else(|| missing_symbol(oracle))?; + require_symbol_attr_eq(oracle_op, "field", ¶ms.field)?; + } + + let commit = find_symbol(module, "jolt.main_witness_commitments") + .ok_or_else(|| missing_symbol("jolt.main_witness_commitments"))?; + require_symbol_attr_eq(commit, "oracle_family", MAIN_WITNESS_FAMILY_SYMBOL)?; + + let pcs = find_symbol(module, "jolt.dory_main_witness_commit") + .ok_or_else(|| missing_symbol("jolt.dory_main_witness_commit"))?; + require_operand_owner_symbol_eq(pcs, 0, "jolt.main_witness_commitments")?; + let scheme = symbol_attr(pcs, "scheme")?; + if scheme != PCS_SYMBOL || scheme != params.pcs { + return Err(SchemaError::new(format!( + "PCS scheme `{scheme}` does not match params pcs `{}`", + params.pcs + ))); + } + + Ok(()) +} + +fn require_operand_owner_symbol_eq( + operation: OperationRef<'_, '_>, + index: usize, + expected: &str, +) -> Result<(), SchemaError> { + let operand = operation.operand(index).map_err(|_| { + SchemaError::new(format!( + "{} missing required operand {index}", + crate::schema::operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + SchemaError::new(format!( + "{} operand {index} must be an op result", + crate::schema::operation_name(operation) + )) + })?; + let actual = owner + .owner() + .attribute("sym_name") + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| { + SchemaError::new(format!( + "{} operand {index} owner missing sym_name", + crate::schema::operation_name(operation) + )) + })?; + if actual == expected { + Ok(()) + } else { + Err(SchemaError::new(format!( + "{} operand {index} expected @{expected}, got @{actual}", + crate::schema::operation_name(operation) + ))) + } +} + +fn require_symbol

(module: &BoltModule<'_, P>, symbol: &str) -> Result<(), SchemaError> +where + P: crate::ir::Phase, +{ + find_symbol(module, symbol) + .map(|_| ()) + .ok_or_else(|| missing_symbol(symbol)) +} diff --git a/crates/bolt/src/protocols/jolt/verifier_common.rs.template b/crates/bolt/src/protocols/jolt/verifier_common.rs.template new file mode 100644 index 0000000000..5c31bfa6ef --- /dev/null +++ b/crates/bolt/src/protocols/jolt/verifier_common.rs.template @@ -0,0 +1,1789 @@ +#![expect( + clippy::too_many_arguments, + reason = "generated verifier helpers mirror staged protocol ABIs" +)] + +use jolt_field::{Field, Fr}; +use jolt_poly::EqPolynomial; +use jolt_sumcheck::{ + CompressedLabeledRoundPoly, SumcheckClaim, SumcheckError, SumcheckProof, SumcheckVerifier, +}; +use jolt_transcript::{Label, Transcript}; +use serde::Serialize; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageParams { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TranscriptAbsorbBytesPlan { + pub symbol: &'static str, + pub label: &'static str, + pub payload: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operands: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static str, + pub claim_operands: &'static str, + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PointZeroPlan { + pub symbol: &'static str, + pub field: &'static str, + pub arity: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpeningClaimEqualityPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub lhs: &'static str, + pub rhs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static str, + pub claim_operands: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageProgramPlan { + pub role: &'static str, + pub params: StageParams, + pub steps: &'static [ProgramStepPlan], + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [OpeningInputPlan], + pub field_constants: &'static [FieldConstantPlan], + pub field_exprs: &'static [FieldExprPlan], + pub kernels: &'static [KernelPlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub point_zeros: &'static [PointZeroPlan], + pub point_slices: &'static [PointSlicePlan], + pub point_concats: &'static [PointConcatPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_equalities: &'static [OpeningClaimEqualityPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageProgramPlanNoPointZeros { + pub role: &'static str, + pub params: StageParams, + pub steps: &'static [ProgramStepPlan], + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [OpeningInputPlan], + pub field_constants: &'static [FieldConstantPlan], + pub field_exprs: &'static [FieldExprPlan], + pub kernels: &'static [KernelPlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub point_slices: &'static [PointSlicePlan], + pub point_concats: &'static [PointConcatPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_equalities: &'static [OpeningClaimEqualityPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageVerifierProgramPlan { + pub params: StageParams, + pub steps: &'static [ProgramStepPlan], + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub opening_inputs: &'static [OpeningInputPlan], + pub field_constants: &'static [FieldConstantPlan], + pub field_exprs: &'static [FieldExprPlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub point_slices: &'static [PointSlicePlan], + pub point_concats: &'static [PointConcatPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_equalities: &'static [OpeningClaimEqualityPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageVerifierProgramPlanNoEqualities { + pub params: StageParams, + pub steps: &'static [ProgramStepPlan], + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub opening_inputs: &'static [OpeningInputPlan], + pub field_constants: &'static [FieldConstantPlan], + pub field_exprs: &'static [FieldExprPlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub point_slices: &'static [PointSlicePlan], + pub point_concats: &'static [PointConcatPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct VerifierProgramPlanMinimal { + pub params: StageParams, + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct StageNamedEval { + pub name: &'static str, + pub oracle: &'static str, + pub value: F, +} + +#[derive(Clone, Debug, Serialize)] +pub struct StageSumcheckOutput { + pub driver: &'static str, + pub point: Vec, + pub evals: Vec>, + pub proof: SumcheckProof, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StageChallengeVector { + pub symbol: &'static str, + pub values: Vec, +} + +#[derive(Clone, Debug)] +pub struct StageExecutionArtifacts { + pub challenge_vectors: Vec>, + pub sumchecks: Vec>, + pub opening_batches: Vec<&'static OpeningBatchPlan>, +} + +impl Default for StageExecutionArtifacts { + fn default() -> Self { + Self { + challenge_vectors: Vec::new(), + sumchecks: Vec::new(), + opening_batches: Vec::new(), + } + } +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct StageProof { + pub sumchecks: Vec>, +} + +#[derive(Clone, Debug)] +pub struct StageOpeningInputValue { + pub symbol: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RuntimePlanError { + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + MissingClaim { + batch: &'static str, + claim: &'static str, + }, + MissingValue { + symbol: &'static str, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, + UnsupportedFieldExpr { + symbol: &'static str, + formula: &'static str, + }, +} + +macro_rules! impl_runtime_plan_error_conversion { + ($error:ident) => { + impl From for $error { + fn from(error: super::common::RuntimePlanError) -> Self { + match error { + super::common::RuntimePlanError::MissingBatch { driver, batch } => { + Self::MissingBatch { driver, batch } + } + super::common::RuntimePlanError::MissingClaim { batch, claim } => { + Self::MissingClaim { batch, claim } + } + super::common::RuntimePlanError::MissingValue { symbol } => { + Self::MissingValue { symbol } + } + super::common::RuntimePlanError::InvalidInputLength { + input, + expected, + actual, + } => Self::InvalidInputLength { + input, + expected, + actual, + }, + super::common::RuntimePlanError::InvalidProof { driver, reason } => { + Self::InvalidProof { driver, reason } + } + super::common::RuntimePlanError::UnsupportedFieldExpr { symbol, formula } => { + Self::UnsupportedFieldExpr { symbol, formula } + } + } + } + } + }; +} + +pub(crate) use impl_runtime_plan_error_conversion; + +pub trait SymbolPlan { + fn symbol(&self) -> &'static str; +} + +impl SymbolPlan for TranscriptSqueezePlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for TranscriptAbsorbBytesPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for SumcheckBatchPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for SumcheckClaimPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for SumcheckDriverPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for OpeningClaimPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +pub trait SumcheckClaimInfo: SymbolPlan { + fn num_rounds(&self) -> usize; + fn claim_value(&self) -> &'static str; +} + +impl SumcheckClaimInfo for SumcheckClaimPlan { + fn num_rounds(&self) -> usize { + self.num_rounds + } + + fn claim_value(&self) -> &'static str { + self.claim_value + } +} + +pub trait SumcheckDriverInfo: SymbolPlan { + fn batch(&self) -> &'static str; + fn num_rounds(&self) -> usize; + fn degree(&self) -> usize; + fn round_label(&self) -> &'static str; +} + +impl SumcheckDriverInfo for SumcheckDriverPlan { + fn batch(&self) -> &'static str { + self.batch + } + + fn num_rounds(&self) -> usize { + self.num_rounds + } + + fn degree(&self) -> usize { + self.degree + } + + fn round_label(&self) -> &'static str { + self.round_label + } +} + +#[derive(Clone, Debug, Default)] +pub struct ValueStore { + scalars: Vec<(&'static str, F)>, + points: Vec<(&'static str, Vec)>, +} + +impl ValueStore { + pub fn with_opening_inputs( + inputs: &[StageOpeningInputValue], + expected_inputs: &[OpeningInputPlan], + ) -> Result { + if inputs.len() != expected_inputs.len() { + return Err(RuntimePlanError::InvalidInputLength { + input: "opening_inputs", + expected: expected_inputs.len(), + actual: inputs.len(), + }); + } + for expected in expected_inputs { + let matching_count = inputs + .iter() + .filter(|input| input.symbol == expected.symbol) + .count(); + if matching_count != 1 { + return Err(RuntimePlanError::InvalidInputLength { + input: expected.symbol, + expected: 1, + actual: matching_count, + }); + } + if let Some(input) = inputs.iter().find(|input| input.symbol == expected.symbol) { + if input.point.len() != expected.point_arity { + return Err(RuntimePlanError::InvalidInputLength { + input: expected.symbol, + expected: expected.point_arity, + actual: input.point.len(), + }); + } + } + } + let mut store = Self::default(); + for input in inputs { + store.insert_scalar(input.symbol, input.eval); + store.insert_point(input.symbol, input.point.clone()); + } + Ok(store) + } + + pub fn seed_constants(&mut self, constants: &[FieldConstantPlan]) { + for constant in constants { + self.insert_scalar(constant.symbol, F::from_u64(constant.value as u64)); + } + } + + pub fn seed_point_zeros(&mut self, point_zeros: &[PointZeroPlan]) { + for zero in point_zeros { + self.insert_point(zero.symbol, vec![F::from_u64(0); zero.arity]); + } + } + + pub fn observe_challenge_vector( + &mut self, + plan: &TranscriptSqueezePlan, + values: &[F], + invalid_input_length: impl Fn(&'static str, usize, usize) -> E, + ) -> Result<(), E> { + self.insert_point(plan.symbol, values.to_vec()); + if matches!(plan.kind, "challenge_scalar" | "scalar") { + if values.len() != 1 { + return Err(invalid_input_length(plan.symbol, 1, values.len())); + } + self.insert_scalar(plan.symbol, values[0]); + } + Ok(()) + } + + pub fn observe_sumcheck_output( + &mut self, + instance_results: &[SumcheckInstanceResultPlan], + evals: &[SumcheckEvalPlan], + output: &StageSumcheckOutput, + normalize_point: impl Fn(&SumcheckInstanceResultPlan, Vec) -> Result, E>, + invalid_input_length: impl Fn(&'static str, usize, usize) -> E, + missing_value: impl Fn(&'static str) -> E, + ) -> Result<(), E> { + self.insert_point(output.driver, output.point.clone()); + for instance in instance_results + .iter() + .filter(|instance| instance.source == output.driver) + { + let end = instance.round_offset + instance.point_arity; + let point = output + .point + .get(instance.round_offset..end) + .ok_or_else(|| invalid_input_length(instance.symbol, end, output.point.len()))? + .to_vec(); + self.insert_point(instance.symbol, normalize_point(instance, point)?); + } + for eval in evals.iter().filter(|eval| eval.source == output.driver) { + let value = output + .evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| output.evals.get(eval.index)) + .ok_or_else(|| missing_value(eval.symbol))? + .value; + self.insert_scalar(eval.symbol, value); + self.insert_scalar(eval.name, value); + } + Ok(()) + } + + pub fn evaluate_available_points( + &mut self, + point_slices: &[PointSlicePlan], + point_concats: &[PointConcatPlan], + invalid_input_length: impl Fn(&'static str, usize, usize) -> E, + ) -> Result<(), E> { + loop { + let mut progress = 0usize; + for slice in point_slices { + if self.try_point(slice.symbol).is_some() { + continue; + } + let Some(input) = self.try_point(slice.input) else { + continue; + }; + let end = slice.offset + slice.length; + let point = input + .get(slice.offset..end) + .ok_or_else(|| invalid_input_length(slice.symbol, end, input.len()))? + .to_vec(); + self.insert_point(slice.symbol, point); + progress += 1; + } + for concat in point_concats { + if self.try_point(concat.symbol).is_some() { + continue; + } + let Some(point) = self.try_concat_point(concat) else { + continue; + }; + if point.len() != concat.arity { + return Err(invalid_input_length( + concat.symbol, + concat.arity, + point.len(), + )); + } + self.insert_point(concat.symbol, point); + progress += 1; + } + if progress == 0 { + return Ok(()); + } + } + } + + pub fn evaluate_available_field_exprs( + &mut self, + field_exprs: &[FieldExprPlan], + evaluate: impl Fn(&FieldExprPlan, &[F]) -> Result, + ) -> Result<(), E> { + loop { + let mut progress = 0usize; + for expr in field_exprs { + if self.try_scalar(expr.symbol).is_some() { + continue; + } + let Some(operands) = self.try_expr_operands(expr) else { + continue; + }; + self.insert_scalar(expr.symbol, evaluate(expr, &operands)?); + progress += 1; + } + if progress == 0 { + return Ok(()); + } + } + } + + pub fn verify_opening_equalities( + &self, + opening_equalities: &[OpeningClaimEqualityPlan], + invalid_proof: impl Fn(&'static str, &'static str) -> E, + missing_value: impl Fn(&'static str) -> E, + ) -> Result<(), E> { + for equality in opening_equalities { + match equality.mode { + "point_and_eval" => { + if self.point_or(equality.lhs, &missing_value)? + != self.point_or(equality.rhs, &missing_value)? + || self.scalar_or(equality.lhs, &missing_value)? + != self.scalar_or(equality.rhs, &missing_value)? + { + return Err(invalid_proof( + equality.symbol, + "opening claim equality failed", + )); + } + } + _ => { + return Err(invalid_proof( + equality.symbol, + "unsupported opening equality mode", + )); + } + } + } + Ok(()) + } + + pub fn insert_scalar(&mut self, symbol: &'static str, value: F) { + if let Some((_, existing)) = self.scalars.iter_mut().find(|(name, _)| *name == symbol) { + *existing = value; + } else { + self.scalars.push((symbol, value)); + } + } + + pub fn insert_point(&mut self, symbol: &'static str, point: Vec) { + if let Some((_, existing)) = self.points.iter_mut().find(|(name, _)| *name == symbol) { + *existing = point; + } else { + self.points.push((symbol, point)); + } + } + + pub fn scalar_or( + &self, + symbol: &'static str, + missing_value: impl FnOnce(&'static str) -> E, + ) -> Result { + self.try_scalar(symbol).ok_or_else(|| missing_value(symbol)) + } + + pub fn try_scalar(&self, symbol: &str) -> Option { + self.scalars + .iter() + .find(|(name, _)| *name == symbol) + .map(|(_, value)| *value) + } + + pub fn point_or( + &self, + symbol: &'static str, + missing_value: impl FnOnce(&'static str) -> E, + ) -> Result<&[F], E> { + self.try_point(symbol).ok_or_else(|| missing_value(symbol)) + } + + pub fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.points + .iter() + .find(|(name, _)| *name == symbol) + .map(|(_, point)| point.as_slice()) + } + + fn try_expr_operands(&self, expr: &FieldExprPlan) -> Option> { + if expr.operands.is_empty() { + return Some(Vec::new()); + } + expr.operands + .split('|') + .map(|operand| self.try_scalar(operand)) + .collect() + } + + fn try_concat_point(&self, concat: &PointConcatPlan) -> Option> { + let mut point = Vec::with_capacity(concat.arity); + for input in symbol_list(concat.inputs) { + point.extend_from_slice(self.try_point(input)?); + } + Some(point) + } +} + +pub fn symbol_list(symbols: &'static str) -> impl Iterator { + symbols.split('|').filter(|symbol| !symbol.is_empty()) +} + +pub fn find_plan<'a, T: SymbolPlan>(plans: &'a [T], symbol: &str) -> Option<&'a T> { + plans.iter().find(|plan| plan.symbol() == symbol) +} + +pub fn find_batch<'a>( + batches: &'a [SumcheckBatchPlan], + driver: &'static str, + batch: &'static str, +) -> Result<&'a SumcheckBatchPlan, RuntimePlanError> { + find_plan(batches, batch).ok_or(RuntimePlanError::MissingBatch { driver, batch }) +} + +pub fn batch_claims<'a, C: SymbolPlan>( + claims: &'a [C], + batch: &SumcheckBatchPlan, +) -> Result, RuntimePlanError> { + symbol_list(batch.claim_operands) + .map(|symbol| { + find_plan(claims, symbol).ok_or(RuntimePlanError::MissingClaim { + batch: batch.symbol, + claim: symbol, + }) + }) + .collect() +} + +pub fn batch_claim_values( + claims: &[&C], + field_exprs: &[FieldExprPlan], + store: &mut ValueStore, +) -> Result, RuntimePlanError> { + claims + .iter() + .map(|claim| { + store.evaluate_available_field_exprs(field_exprs, evaluate_field_expr)?; + store.scalar_or(claim.claim_value(), |symbol| { + RuntimePlanError::MissingValue { symbol } + }) + }) + .collect() +} + +pub fn verify_batched_sumcheck( + driver: &'static D, + proof: &StageSumcheckOutput, + claims: &'static [C], + batches: &'static [SumcheckBatchPlan], + field_exprs: &'static [FieldExprPlan], + opening_inputs: &'static [OpeningInputPlan], + opening_claims: &'static [OpeningClaimPlan], + opening_batches: &'static [OpeningBatchPlan], + store: &mut ValueStore, + transcript: &mut T, + expected_output: Expected, + observe_output: Observe, + map_sumcheck: MapSumcheck, +) -> Result, E> +where + T: Transcript, + E: From, + C: SumcheckClaimInfo, + D: SumcheckDriverInfo, + Expected: FnOnce(&ValueStore, &[StageNamedEval], &[Fr], &[Fr]) -> Result, + Observe: FnOnce(&mut ValueStore, &StageSumcheckOutput) -> Result<(), E>, + MapSumcheck: FnOnce(&'static str, SumcheckError) -> E, +{ + if proof.driver != driver.symbol() { + return Err(RuntimePlanError::InvalidProof { + driver: driver.symbol(), + reason: "driver symbol mismatch", + } + .into()); + } + let batch = find_batch(batches, driver.symbol(), driver.batch())?; + let claims = batch_claims(claims, batch)?; + let input_claims = batch_claim_values(&claims, field_exprs, store)?; + for claim in &input_claims { + append_labeled_scalar(transcript, batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let claimed_sum = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), coefficient)| { + claim.mul_pow_2(driver.num_rounds() - plan.num_rounds()) * *coefficient + }) + .sum::(); + let claim = SumcheckClaim::new(driver.num_rounds(), driver.degree(), claimed_sum); + let round_proofs = proof + .proof + .round_polynomials + .iter() + .map(|poly| CompressedLabeledRoundPoly::new(poly, driver.round_label().as_bytes())) + .collect::>(); + let output = SumcheckVerifier::verify(&claim, &round_proofs, transcript) + .map_err(|error| map_sumcheck(driver.symbol(), error))?; + if !proof.point.is_empty() && proof.point != output.point { + return Err(RuntimePlanError::InvalidProof { + driver: driver.symbol(), + reason: "batched point mismatch", + } + .into()); + } + let expected = expected_output(store, &proof.evals, &output.point, &batching_coeffs)?; + if output.value != expected { + return Err(RuntimePlanError::InvalidProof { + driver: driver.symbol(), + reason: "batched output claim mismatch", + } + .into()); + } + let verified = StageSumcheckOutput { + driver: driver.symbol(), + point: output.point, + evals: proof.evals.clone(), + proof: proof.proof.clone(), + }; + observe_output(store, &verified)?; + append_opening_claims( + opening_inputs, + opening_claims, + opening_batches, + store, + transcript, + &verified.evals, + |batch, claim| RuntimePlanError::MissingClaim { batch, claim }, + |symbol| RuntimePlanError::MissingValue { symbol }, + )?; + Ok(verified) +} + +pub fn eval_by_name( + evals: &[StageNamedEval], + name: &'static str, +) -> Result { + evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| eval.value) + .ok_or(RuntimePlanError::MissingValue { symbol: name }) +} + +pub fn indexed_evals_by_prefix( + evals: &[StageNamedEval], + prefix: &'static str, + count: usize, +) -> Result, RuntimePlanError> { + let mut values = vec![None; count]; + for eval in evals { + let Some(suffix) = eval.name.strip_prefix(prefix) else { + continue; + }; + let index = suffix + .parse::() + .map_err(|_| RuntimePlanError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval suffix", + })?; + if index >= count || values[index].is_some() { + return Err(RuntimePlanError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval", + }); + } + values[index] = Some(eval.value); + } + values + .into_iter() + .map(|value| value.ok_or(RuntimePlanError::MissingValue { symbol: prefix })) + .collect() +} + +pub fn indexed_evals_by_prefix_any( + evals: &[StageNamedEval], + prefix: &'static str, +) -> Result, RuntimePlanError> { + let mut indexed_values = Vec::new(); + for eval in evals { + let Some(suffix) = eval.name.strip_prefix(prefix) else { + continue; + }; + let index = suffix + .parse::() + .map_err(|_| RuntimePlanError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval suffix", + })?; + if indexed_values + .iter() + .any(|(existing_index, _)| *existing_index == index) + { + return Err(RuntimePlanError::InvalidProof { + driver: prefix, + reason: "duplicate indexed eval", + }); + } + indexed_values.push((index, eval.value)); + } + if indexed_values.is_empty() { + return Err(RuntimePlanError::MissingValue { symbol: prefix }); + } + indexed_values.sort_by_key(|(index, _)| *index); + for (expected, (actual, _)) in indexed_values.iter().enumerate() { + if *actual != expected { + return Err(RuntimePlanError::InvalidProof { + driver: prefix, + reason: "non-contiguous indexed eval", + }); + } + } + Ok(indexed_values.into_iter().map(|(_, value)| value).collect()) +} + +pub fn single_operand( + symbol: &'static str, + operands: &[F], +) -> Result { + require_operand_count(symbol, 1, operands.len())?; + Ok(operands[0]) +} + +pub fn require_operand_count( + input: &'static str, + expected: usize, + actual: usize, +) -> Result<(), RuntimePlanError> { + if expected == actual { + Ok(()) + } else { + Err(RuntimePlanError::InvalidInputLength { + input, + expected, + actual, + }) + } +} + +pub fn evaluate_field_expr( + expr: &FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => Ok(single_operand(expr.symbol, operands)?), + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + formula => { + if let Some(exponent) = formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + RuntimePlanError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + Err(RuntimePlanError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }) + } + } +} + +pub fn bytecode_gamma_powers(gamma: Fr) -> [Fr; 8] { + let mut powers = [Fr::from_u64(1); 8]; + for index in 1..powers.len() { + powers[index] = powers[index - 1] * gamma; + } + powers +} + +pub fn indexed_boolean_eq(index: usize, point: &[Fr]) -> Fr { + point + .iter() + .enumerate() + .map(|(bit, value)| { + if (index >> (point.len() - 1 - bit)) & 1 == 1 { + *value + } else { + Fr::from_u64(1) - *value + } + }) + .product() +} + +pub fn field_powers(base: Fr, count: usize) -> Vec { + let mut powers = Vec::with_capacity(count); + let mut power = Fr::from_u64(1); + for _ in 0..count { + powers.push(power); + power *= base; + } + powers +} + +pub fn prefix_point<'a, F: Field>( + point: &'a [F], + length: usize, + input: &'static str, +) -> Result<&'a [F], RuntimePlanError> { + point + .get(..length) + .filter(|prefix| prefix.len() == length) + .ok_or(RuntimePlanError::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +pub fn suffix_point<'a, F: Field>( + point: &'a [F], + length: usize, + input: &'static str, +) -> Result<&'a [F], RuntimePlanError> { + point + .get(point.len().saturating_sub(length)..) + .filter(|suffix| suffix.len() == length) + .ok_or(RuntimePlanError::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +pub fn normalize_bytecode_read_raf_point( + point: &[F], + log_t: usize, + input: &'static str, +) -> Result, RuntimePlanError> { + let log_k = point + .len() + .checked_sub(log_t) + .ok_or(RuntimePlanError::InvalidInputLength { + input, + expected: log_t, + actual: point.len(), + })?; + let mut normalized = point.to_vec(); + normalized[..log_k].reverse(); + normalized[log_k..].reverse(); + Ok(normalized) +} + +pub fn normalize_instruction_read_raf_point( + point: &[F], + input: &'static str, +) -> Result, RuntimePlanError> { + const LOG_K: usize = 128; + if point.len() < LOG_K { + return Err(RuntimePlanError::InvalidInputLength { + input, + expected: LOG_K, + actual: point.len(), + }); + } + let mut normalized = point.to_vec(); + normalized[LOG_K..].reverse(); + Ok(normalized) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage67RelationSymbols { + pub hamming_booleanity_relation: &'static str, + pub hamming_booleanity_instance: &'static str, + pub booleanity_point: &'static str, + pub stage5_instruction_ra0: &'static str, + pub booleanity_combined_point: &'static str, + pub booleanity_gamma: &'static str, + pub booleanity_instruction_ra_prefix: &'static str, + pub booleanity_bytecode_ra_prefix: &'static str, + pub booleanity_ram_ra_prefix: &'static str, + pub hamming_weight_eval: &'static str, + pub hamming_lookup_output: &'static str, + pub ram_ra_virtual_cycle: &'static str, + pub ram_ra_virtual_eval_prefix: &'static str, + pub instruction_ra_virtual_cycle: &'static str, + pub instruction_ra_virtual_eval_prefix: &'static str, + pub instruction_ra_virtual_input_prefix: &'static str, + pub instruction_ra_virtual_gamma: &'static str, + pub inc_ram_stage2: &'static str, + pub inc_ram_stage4: &'static str, + pub inc_rd_stage4: &'static str, + pub inc_rd_stage5: &'static str, + pub inc_gamma: &'static str, + pub inc_ram_eval: &'static str, + pub inc_rd_eval: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage67BytecodeSymbols { + pub point: &'static str, + pub gamma: &'static str, + pub bytecode_ra_eval_prefix: &'static str, + pub entries: &'static str, + pub entry_bytecode_index: &'static str, + pub stage_gammas: [&'static str; 5], + pub stage_cycle_points: [&'static str; 5], + pub stage4_register_point: &'static str, + pub stage5_register_point: &'static str, + pub entry_rd: &'static str, + pub entry_rs1: &'static str, + pub entry_rs2: &'static str, + pub entry_lookup_table: &'static str, +} + +pub trait Stage67BytecodeEntry { + fn address(&self) -> Fr; + fn imm(&self) -> Fr; + fn circuit_flags(&self) -> &[bool; 14]; + fn rd(&self) -> Option; + fn rs1(&self) -> Option; + fn rs2(&self) -> Option; + fn lookup_table(&self) -> Option; + fn is_interleaved(&self) -> bool; + fn is_branch(&self) -> bool; + fn left_is_rs1(&self) -> bool; + fn left_is_pc(&self) -> bool; + fn right_is_rs2(&self) -> bool; + fn right_is_imm(&self) -> bool; + fn is_noop(&self) -> bool; +} + +pub fn store_scalar(store: &ValueStore, symbol: &'static str) -> Result { + store.scalar_or(symbol, |symbol| RuntimePlanError::MissingValue { symbol }) +} + +pub fn store_point<'a>( + store: &'a ValueStore, + symbol: &'static str, +) -> Result<&'a [Fr], RuntimePlanError> { + store.point_or(symbol, |symbol| RuntimePlanError::MissingValue { symbol }) +} + +pub fn stage67_trace_rounds( + instance_results: &[SumcheckInstanceResultPlan], + symbols: &Stage67RelationSymbols, +) -> Result { + instance_results + .iter() + .find(|instance| instance.relation == symbols.hamming_booleanity_relation) + .map(|instance| instance.num_rounds) + .ok_or(RuntimePlanError::MissingValue { + symbol: symbols.hamming_booleanity_instance, + }) +} + +pub fn expected_stage67_bytecode_read_raf( + entries: &[E], + entry_bytecode_index: usize, + num_lookup_tables: usize, + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + log_t: usize, + symbols: &Stage67BytecodeSymbols, +) -> Result { + let opening_point = normalize_bytecode_read_raf_point(local_point, log_t, symbols.point)?; + let log_k = opening_point.len() - log_t; + let (r_address_prime, r_cycle_prime) = opening_point.split_at(log_k); + + let gamma = store_scalar(store, symbols.gamma)?; + let gamma_powers = bytecode_gamma_powers(gamma); + let int_eval = identity_polynomial_eval(r_address_prime); + let stage_value_evals = stage67_bytecode_stage_value_evals( + entries, + entry_bytecode_index, + num_lookup_tables, + store, + r_address_prime, + r_cycle_prime.len(), + symbols, + )?; + let stage_cycle_points = + stage67_bytecode_stage_cycle_points(store, r_cycle_prime.len(), symbols)?; + let int_contrib = [ + gamma_powers[5] * int_eval, + Fr::from_u64(0), + gamma_powers[4] * int_eval, + Fr::from_u64(0), + Fr::from_u64(0), + ]; + + let mut val = Fr::from_u64(0); + for index in 0..stage_value_evals.len() { + val += (stage_value_evals[index] + int_contrib[index]) + * EqPolynomial::::mle(&stage_cycle_points[index], r_cycle_prime) + * gamma_powers[index]; + } + + let entry_bits = (0..log_k) + .map(|index| Fr::from_u64(((entry_bytecode_index >> (log_k - 1 - index)) & 1) as u64)) + .collect::>(); + let zero_cycle = vec![Fr::from_u64(0); r_cycle_prime.len()]; + let entry_contrib = gamma_powers[7] + * EqPolynomial::::mle(&entry_bits, r_address_prime) + * EqPolynomial::::mle(&zero_cycle, r_cycle_prime); + let bytecode_ra = indexed_evals_by_prefix_any(evals, symbols.bytecode_ra_eval_prefix)? + .into_iter() + .product::(); + Ok((val + entry_contrib) * bytecode_ra) +} + +pub fn expected_stage67_booleanity( + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + log_t: usize, + symbols: &Stage67RelationSymbols, +) -> Result { + let log_k_chunk = + local_point + .len() + .checked_sub(log_t) + .ok_or(RuntimePlanError::InvalidInputLength { + input: symbols.booleanity_point, + expected: log_t, + actual: local_point.len(), + })?; + let stage5_point = store_point(store, symbols.stage5_instruction_ra0)?; + let stage5_address_len = + stage5_point + .len() + .checked_sub(log_t) + .ok_or(RuntimePlanError::InvalidInputLength { + input: symbols.stage5_instruction_ra0, + expected: log_t, + actual: stage5_point.len(), + })?; + if stage5_address_len < log_k_chunk { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.stage5_instruction_ra0, + expected: log_k_chunk + log_t, + actual: stage5_point.len(), + }); + } + + let mut stage5_addr = stage5_point[..stage5_address_len].to_vec(); + stage5_addr.reverse(); + let mut combined_r = stage5_addr[stage5_address_len - log_k_chunk..].to_vec(); + combined_r.extend(stage5_point[stage5_address_len..].iter().rev().copied()); + if combined_r.len() != local_point.len() { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.booleanity_combined_point, + expected: local_point.len(), + actual: combined_r.len(), + }); + } + let mut verifier_point = combined_r[..log_k_chunk].to_vec(); + verifier_point.reverse(); + verifier_point.extend(combined_r[log_k_chunk..].iter().rev().copied()); + let eq_eval = EqPolynomial::::mle(local_point, &verifier_point); + + let gamma = store_scalar(store, symbols.booleanity_gamma)?; + let gamma_sq = gamma.square(); + let mut gamma_power = Fr::from_u64(1); + let mut booleanity = Fr::from_u64(0); + for ra in stage67_booleanity_evals(evals, symbols)? { + booleanity += gamma_power * (ra.square() - ra); + gamma_power *= gamma_sq; + } + Ok(eq_eval * booleanity) +} + +pub fn expected_stage67_hamming_booleanity( + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + symbols: &Stage67RelationSymbols, +) -> Result { + let hamming = eval_by_name(evals, symbols.hamming_weight_eval)?; + let lookup_output_point = reverse_slice(store_point(store, symbols.hamming_lookup_output)?); + if lookup_output_point.len() != local_point.len() { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.hamming_lookup_output, + expected: local_point.len(), + actual: lookup_output_point.len(), + }); + } + let eq_eval = EqPolynomial::::mle(local_point, &lookup_output_point); + Ok((hamming.square() - hamming) * eq_eval) +} + +pub fn expected_stage67_ram_ra_virtual( + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + symbols: &Stage67RelationSymbols, +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle = suffix_point( + store_point(store, symbols.ram_ra_virtual_cycle)?, + r_cycle_reduced.len(), + symbols.ram_ra_virtual_cycle, + )?; + let eq_eval = EqPolynomial::::mle(r_cycle, &r_cycle_reduced); + let ram_ra = indexed_evals_by_prefix_any(evals, symbols.ram_ra_virtual_eval_prefix)? + .into_iter() + .product::(); + Ok(eq_eval * ram_ra) +} + +pub fn expected_stage67_instruction_ra_virtual( + opening_inputs: &[OpeningInputPlan], + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + symbols: &Stage67RelationSymbols, +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle = suffix_point( + store_point(store, symbols.instruction_ra_virtual_cycle)?, + r_cycle_reduced.len(), + symbols.instruction_ra_virtual_cycle, + )?; + let eq_eval = EqPolynomial::::mle(r_cycle, &r_cycle_reduced); + let committed_ra = + indexed_evals_by_prefix_any(evals, symbols.instruction_ra_virtual_eval_prefix)?; + let virtual_count = opening_inputs + .iter() + .filter(|input| { + input + .symbol + .starts_with(symbols.instruction_ra_virtual_input_prefix) + }) + .count(); + if virtual_count == 0 || committed_ra.len() % virtual_count != 0 { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.instruction_ra_virtual_eval_prefix, + expected: virtual_count, + actual: committed_ra.len(), + }); + } + let committed_per_virtual = committed_ra.len() / virtual_count; + let gamma = store_scalar(store, symbols.instruction_ra_virtual_gamma)?; + let mut gamma_power = Fr::from_u64(1); + let mut value = Fr::from_u64(0); + for chunk in committed_ra.chunks(committed_per_virtual) { + value += gamma_power * chunk.iter().copied().product::(); + gamma_power *= gamma; + } + Ok(eq_eval * value) +} + +pub fn expected_stage67_inc_claim_reduction( + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + symbols: &Stage67RelationSymbols, +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let ram_inc_stage2 = suffix_point( + store_point(store, symbols.inc_ram_stage2)?, + r_cycle_reduced.len(), + symbols.inc_ram_stage2, + )?; + let ram_inc_stage4 = suffix_point( + store_point(store, symbols.inc_ram_stage4)?, + r_cycle_reduced.len(), + symbols.inc_ram_stage4, + )?; + let rd_inc_stage4 = suffix_point( + store_point(store, symbols.inc_rd_stage4)?, + r_cycle_reduced.len(), + symbols.inc_rd_stage4, + )?; + let rd_inc_stage5 = suffix_point( + store_point(store, symbols.inc_rd_stage5)?, + r_cycle_reduced.len(), + symbols.inc_rd_stage5, + )?; + let gamma = store_scalar(store, symbols.inc_gamma)?; + let eq_ram_combined = EqPolynomial::::mle(ram_inc_stage2, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(ram_inc_stage4, &r_cycle_reduced); + let eq_rd_combined = EqPolynomial::::mle(rd_inc_stage4, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(rd_inc_stage5, &r_cycle_reduced); + let ram_inc = eval_by_name(evals, symbols.inc_ram_eval)?; + let rd_inc = eval_by_name(evals, symbols.inc_rd_eval)?; + Ok(ram_inc * eq_ram_combined + gamma.square() * rd_inc * eq_rd_combined) +} + +fn stage67_booleanity_evals( + evals: &[StageNamedEval], + symbols: &Stage67RelationSymbols, +) -> Result, RuntimePlanError> { + let mut values = indexed_evals_by_prefix_any(evals, symbols.booleanity_instruction_ra_prefix)?; + values.extend(indexed_evals_by_prefix_any( + evals, + symbols.booleanity_bytecode_ra_prefix, + )?); + values.extend(indexed_evals_by_prefix_any( + evals, + symbols.booleanity_ram_ra_prefix, + )?); + Ok(values) +} + +fn stage67_bytecode_stage_cycle_points( + store: &ValueStore, + log_t: usize, + symbols: &Stage67BytecodeSymbols, +) -> Result<[Vec; 5], RuntimePlanError> { + let point = |index| { + let symbol = symbols.stage_cycle_points[index]; + suffix_point(store_point(store, symbol)?, log_t, symbol).map(|point| point.to_vec()) + }; + Ok([point(0)?, point(1)?, point(2)?, point(3)?, point(4)?]) +} + +fn stage67_bytecode_stage_value_evals( + entries: &[E], + entry_bytecode_index: usize, + num_lookup_tables: usize, + store: &ValueStore, + r_address: &[Fr], + log_t: usize, + symbols: &Stage67BytecodeSymbols, +) -> Result<[Fr; 5], RuntimePlanError> { + let expected_len = + 1usize + .checked_shl(r_address.len() as u32) + .ok_or(RuntimePlanError::InvalidInputLength { + input: symbols.entries, + expected: usize::BITS as usize, + actual: r_address.len(), + })?; + if entries.len() != expected_len { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.entries, + expected: expected_len, + actual: entries.len(), + }); + } + if entry_bytecode_index >= expected_len { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.entry_bytecode_index, + expected: expected_len, + actual: entry_bytecode_index + 1, + }); + } + + let stage1_gamma_powers = field_powers(store_scalar(store, symbols.stage_gammas[0])?, 16); + let stage2_gamma_powers = field_powers(store_scalar(store, symbols.stage_gammas[1])?, 4); + let stage3_gamma_powers = field_powers(store_scalar(store, symbols.stage_gammas[2])?, 9); + let stage4_gamma_powers = field_powers(store_scalar(store, symbols.stage_gammas[3])?, 3); + let stage5_gamma_powers = field_powers( + store_scalar(store, symbols.stage_gammas[4])?, + num_lookup_tables + 2, + ); + + let stage4_register_point = + stage67_register_prefix_point(store, symbols.stage4_register_point, log_t)?; + let stage5_register_point = + stage67_register_prefix_point(store, symbols.stage5_register_point, log_t)?; + + let mut evals = [Fr::from_u64(0); 5]; + for (index, entry) in entries.iter().enumerate() { + let eq = indexed_boolean_eq(index, r_address); + let values = stage67_bytecode_entry_stage_values( + entry, + num_lookup_tables, + stage4_register_point, + stage5_register_point, + &stage1_gamma_powers, + &stage2_gamma_powers, + &stage3_gamma_powers, + &stage4_gamma_powers, + &stage5_gamma_powers, + symbols, + )?; + for stage in 0..evals.len() { + evals[stage] += eq * values[stage]; + } + } + Ok(evals) +} + +fn stage67_bytecode_entry_stage_values( + entry: &E, + num_lookup_tables: usize, + stage4_register_point: &[Fr], + stage5_register_point: &[Fr], + stage1_gamma_powers: &[Fr], + stage2_gamma_powers: &[Fr], + stage3_gamma_powers: &[Fr], + stage4_gamma_powers: &[Fr], + stage5_gamma_powers: &[Fr], + symbols: &Stage67BytecodeSymbols, +) -> Result<[Fr; 5], RuntimePlanError> { + let flags = entry.circuit_flags(); + let mut stage1 = entry.address() + entry.imm() * stage1_gamma_powers[1]; + for (flag, gamma) in flags.iter().zip(stage1_gamma_powers.iter().skip(2)) { + if *flag { + stage1 += *gamma; + } + } + + let mut stage2 = Fr::from_u64(0); + if flags[5] { + stage2 += stage2_gamma_powers[0]; + } + if entry.is_branch() { + stage2 += stage2_gamma_powers[1]; + } + if flags[6] { + stage2 += stage2_gamma_powers[2]; + } + if flags[7] { + stage2 += stage2_gamma_powers[3]; + } + + let mut stage3 = entry.imm() + entry.address() * stage3_gamma_powers[1]; + if entry.left_is_rs1() { + stage3 += stage3_gamma_powers[2]; + } + if entry.left_is_pc() { + stage3 += stage3_gamma_powers[3]; + } + if entry.right_is_rs2() { + stage3 += stage3_gamma_powers[4]; + } + if entry.right_is_imm() { + stage3 += stage3_gamma_powers[5]; + } + if entry.is_noop() { + stage3 += stage3_gamma_powers[6]; + } + if flags[7] { + stage3 += stage3_gamma_powers[7]; + } + if flags[12] { + stage3 += stage3_gamma_powers[8]; + } + + let stage4 = stage67_register_eq(entry.rd(), stage4_register_point, symbols.entry_rd)? + * stage4_gamma_powers[0] + + stage67_register_eq(entry.rs1(), stage4_register_point, symbols.entry_rs1)? + * stage4_gamma_powers[1] + + stage67_register_eq(entry.rs2(), stage4_register_point, symbols.entry_rs2)? + * stage4_gamma_powers[2]; + + let mut stage5 = stage67_register_eq(entry.rd(), stage5_register_point, symbols.entry_rd)? + * stage5_gamma_powers[0]; + if !entry.is_interleaved() { + stage5 += stage5_gamma_powers[1]; + } + if let Some(table) = entry.lookup_table() { + if table >= num_lookup_tables { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.entry_lookup_table, + expected: num_lookup_tables, + actual: table + 1, + }); + } + stage5 += stage5_gamma_powers[2 + table]; + } + + Ok([stage1, stage2, stage3, stage4, stage5]) +} + +fn stage67_register_eq( + index: Option, + point: &[Fr], + input: &'static str, +) -> Result { + let Some(index) = index else { + return Ok(Fr::from_u64(0)); + }; + let register_count = + 1usize + .checked_shl(point.len() as u32) + .ok_or(RuntimePlanError::InvalidInputLength { + input, + expected: usize::BITS as usize, + actual: point.len(), + })?; + if index >= register_count { + return Err(RuntimePlanError::InvalidInputLength { + input, + expected: register_count, + actual: index + 1, + }); + } + Ok(indexed_boolean_eq(index, point)) +} + +fn stage67_register_prefix_point<'a>( + store: &'a ValueStore, + symbol: &'static str, + log_t: usize, +) -> Result<&'a [Fr], RuntimePlanError> { + let point = store_point(store, symbol)?; + let register_len = + point + .len() + .checked_sub(log_t) + .ok_or(RuntimePlanError::InvalidInputLength { + input: symbol, + expected: log_t, + actual: point.len(), + })?; + prefix_point(point, register_len, symbol) +} + +pub fn operand_polynomial_eval(point: &[Fr], left: bool) -> Fr { + let stride_offset = usize::from(!left); + let operand_bits = point.len() / 2; + (0..operand_bits) + .map(|index| point[2 * index + stride_offset].mul_pow_2(operand_bits - 1 - index)) + .sum() +} + +pub fn identity_polynomial_eval(point: &[Fr]) -> Fr { + point + .iter() + .enumerate() + .map(|(index, value)| value.mul_pow_2(point.len() - 1 - index)) + .sum() +} + +pub fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &Fr) +where + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +pub fn append_opening_claims( + opening_inputs: &[OpeningInputPlan], + opening_claims: &[OpeningClaimPlan], + opening_batches: &[OpeningBatchPlan], + store: &mut ValueStore, + transcript: &mut T, + evals: &[StageNamedEval], + missing_claim: impl Fn(&'static str, &'static str) -> E, + missing_value: impl Fn(&'static str) -> E, +) -> Result<(), E> +where + T: Transcript, +{ + if opening_batches.is_empty() { + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } + return Ok(()); + } + let mut seen = opening_inputs + .iter() + .filter_map(|input| { + store + .try_point(input.symbol) + .map(|point| (input.claim_kind, input.oracle, point.to_vec())) + }) + .collect::>(); + for batch in opening_batches { + for symbol in symbol_list(batch.claim_operands) { + let claim = opening_claims + .iter() + .find(|claim| claim.symbol == symbol) + .ok_or_else(|| missing_claim(batch.symbol, symbol))?; + let point = store.point_or(claim.point_source, &missing_value)?.to_vec(); + if seen.iter().any(|(kind, oracle, seen_point)| { + *kind == claim.claim_kind && *oracle == claim.oracle && seen_point == &point + }) { + continue; + } + let value = store.scalar_or(claim.eval_source, &missing_value)?; + append_labeled_scalar(transcript, "opening_claim", &value); + seen.push((claim.claim_kind, claim.oracle, point)); + } + } + Ok(()) +} + +pub fn lt_polynomial_eval(x: &[Fr], y: &[Fr]) -> Fr { + let mut lt_eval = Fr::from_u64(0); + let mut eq_term = Fr::from_u64(1); + for (x_i, y_i) in x.iter().zip(y.iter()) { + lt_eval += (Fr::from_u64(1) - *x_i) * *y_i * eq_term; + eq_term *= Fr::from_u64(1) - *x_i - *y_i + *x_i * *y_i + *x_i * *y_i; + } + lt_eval +} + +pub fn pow_field(base: F, mut exponent: usize) -> F { + let mut result = F::one(); + let mut power = base; + while exponent != 0 { + if exponent & 1 == 1 { + result *= power; + } + power = power.square(); + exponent >>= 1; + } + result +} + +pub fn reverse_slice(values: &[Fr]) -> Vec { + values.iter().rev().copied().collect() +} diff --git a/crates/bolt/src/protocols/mod.rs b/crates/bolt/src/protocols/mod.rs new file mode 100644 index 0000000000..5e01eb0c28 --- /dev/null +++ b/crates/bolt/src/protocols/mod.rs @@ -0,0 +1 @@ +pub mod jolt; diff --git a/crates/bolt/src/schema.rs b/crates/bolt/src/schema.rs new file mode 100644 index 0000000000..46e963c4e7 --- /dev/null +++ b/crates/bolt/src/schema.rs @@ -0,0 +1,1466 @@ +use std::collections::BTreeSet; +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use melior::ir::block::BlockLike; +use melior::ir::operation::OperationLike; +use melior::ir::operation::OperationResult; +use melior::ir::{Attribute, OperationRef}; + +use crate::ir::{ + string_attribute_value, symbol_attribute_value, BoltModule, Compute, Concrete, Cpu, Party, + Protocol, Role, +}; +use crate::mlir::MlirError; +use crate::pass::{verify_concrete_transcript, VerifyError}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SchemaError { + message: String, +} + +impl SchemaError { + pub(crate) fn new(message: impl Into) -> Self { + Self { + message: message.into(), + } + } +} + +impl Display for SchemaError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str(&self.message) + } +} + +impl Error for SchemaError {} + +impl From for MlirError { + fn from(error: SchemaError) -> Self { + Self::Schema { + message: error.to_string(), + } + } +} + +impl From for SchemaError { + fn from(error: VerifyError) -> Self { + Self::new(error.to_string()) + } +} + +pub fn verify_protocol_schema(module: &BoltModule<'_, Protocol>) -> Result<(), SchemaError> { + verify_schema(module, ModulePhase::Protocol) +} + +pub fn verify_concrete_schema(module: &BoltModule<'_, Concrete>) -> Result<(), SchemaError> { + verify_schema(module, ModulePhase::Concrete)?; + verify_concrete_transcript(module)?; + Ok(()) +} + +pub fn verify_party_schema(module: &BoltModule<'_, Party>) -> Result<(), SchemaError> { + verify_schema(module, ModulePhase::Party)?; + verify_concrete_transcript(module)?; + Ok(()) +} + +pub fn verify_compute_schema(module: &BoltModule<'_, Compute>) -> Result<(), SchemaError> { + verify_schema(module, ModulePhase::Compute) +} + +pub fn verify_cpu_schema(module: &BoltModule<'_, Cpu>) -> Result<(), SchemaError> { + verify_schema(module, ModulePhase::Cpu) +} + +#[derive(Clone, Copy)] +enum ModulePhase { + Protocol, + Concrete, + Party, + Compute, + Cpu, +} + +fn verify_schema

(module: &BoltModule<'_, P>, phase: ModulePhase) -> Result<(), SchemaError> +where + P: crate::ir::Phase, +{ + let phase_attr = module + .as_mlir_module() + .as_operation() + .attribute("bolt.phase") + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| SchemaError::new("module missing required attr `bolt.phase`"))?; + if phase_attr != P::NAME { + return Err(SchemaError::new(format!( + "module phase `{phase_attr}` does not match expected `{}`", + P::NAME + ))); + } + + let mut kernel_symbols = BTreeSet::new(); + let mut kernel_refs = Vec::new(); + let role = module.role(); + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + validate_op(op, phase)?; + if matches!(role, Some(Role::Verifier)) + && matches!(phase, ModulePhase::Compute | ModulePhase::Cpu) + { + validate_verifier_lowering_op(op)?; + } + match operation_name(op).as_str() { + "compute.kernel" | "cpu.kernel" => { + let _ = kernel_symbols.insert(string_attr(op, "sym_name")?); + } + "compute.sumcheck_kernel_claim" + | "compute.sumcheck_kernel_driver" + | "cpu.sumcheck_claim" + | "cpu.sumcheck_driver" => { + kernel_refs.push(symbol_attr(op, "kernel")?); + } + _ => {} + } + } + + if matches!(phase, ModulePhase::Compute | ModulePhase::Cpu) { + for kernel in kernel_refs { + if !kernel_symbols.contains(&kernel) { + return Err(SchemaError::new(format!( + "kernel reference @{kernel} has no matching kernel definition" + ))); + } + } + } + + Ok(()) +} + +fn validate_verifier_lowering_op(operation: OperationRef<'_, '_>) -> Result<(), SchemaError> { + let name = operation_name(operation); + match name.as_str() { + "compute.kernel" + | "compute.sumcheck_claim" + | "compute.sumcheck_driver" + | "compute.sumcheck_kernel_claim" + | "compute.sumcheck_kernel_driver" + | "compute.generate_oracle" + | "compute.generate_oracle_family" + | "cpu.kernel" + | "cpu.sumcheck_claim" + | "cpu.sumcheck_driver" => Err(SchemaError::new(format!( + "verifier lowering must use verifier-specific ops, got `{name}`" + ))), + _ => Ok(()), + } +} + +fn validate_op(operation: OperationRef<'_, '_>, _phase: ModulePhase) -> Result<(), SchemaError> { + let name = operation_name(operation); + match name.as_str() { + "field.define" => require_attrs(operation, &["sym_name", "modulus_bits", "role"]), + "field.const" => { + require_attrs(operation, &["sym_name", "field", "value"])?; + require_shape(operation, 0, 1) + } + "field.zero" | "field.one" => { + require_attrs(operation, &["sym_name", "field"])?; + require_shape(operation, 0, 1) + } + "field.add" | "field.sub" | "field.mul" => { + require_attrs(operation, &["sym_name"])?; + require_shape(operation, 2, 1) + } + "field.neg" => { + require_attrs(operation, &["sym_name"])?; + require_shape(operation, 1, 1) + } + "field.pow" => { + require_attrs(operation, &["sym_name", "exponent"])?; + require_shape(operation, 1, 1) + } + "hash.function" => require_attrs(operation, &["sym_name", "algorithm"]), + "transcript.scheme" => require_attrs(operation, &["sym_name", "hash"]), + "pcs.scheme" => require_attrs(operation, &["sym_name", "field"]), + "poly.domain" => require_attrs(operation, &["sym_name", "field", "log_size"]), + "poly.point_slice" => { + require_attrs(operation, &["sym_name", "source", "offset", "length"])?; + require_shape(operation, 1, 1) + } + "poly.point_zero" => { + require_attrs(operation, &["sym_name", "field", "arity"])?; + require_shape(operation, 0, 1) + } + "poly.point_concat" => { + require_attrs(operation, &["sym_name", "layout", "arity"])?; + require_min_shape(operation, 1, 1) + } + "poly.lagrange_basis_eval" => { + require_attrs( + operation, + &["sym_name", "domain_start", "domain_size", "index"], + )?; + require_shape(operation, 1, 1) + } + "protocol.params" => require_attrs(operation, &["sym_name", "field", "pcs", "transcript"]), + "protocol.boundary" => require_attrs(operation, &["sym_name", "roles"]), + "piop.oracle" => require_attrs( + operation, + &[ + "sym_name", + "field", + "domain", + "commit_domain", + "visibility", + "layout", + ], + ), + "piop.oracle_family" => require_attrs( + operation, + &[ + "sym_name", + "ordered_oracles", + "visibility", + "count", + "domain", + ], + ), + "commit.publish_batch" => { + require_attrs(operation, &["sym_name", "oracle_family", "label"])?; + require_shape(operation, 0, 1) + } + "commit.publish_optional" => { + require_attrs(operation, &["sym_name", "oracle", "label", "skip_policy"])?; + require_shape(operation, 0, 1) + } + "pcs.commit_batch" => { + require_attrs(operation, &["sym_name", "scheme"])?; + require_shape(operation, 1, 0) + } + "transcript.absorb" | "transcript.absorb_optional" => { + require_attrs(operation, &["sym_name", "label"])?; + require_shape(operation, 2, 1) + } + "transcript.absorb_bytes" => { + require_attrs(operation, &["sym_name", "label", "payload"])?; + require_shape(operation, 1, 1) + } + "transcript.squeeze" => { + require_attrs(operation, &["sym_name", "label", "kind", "count"])?; + require_shape(operation, 1, 2) + } + "transcript.state" => { + require_attrs(operation, &["sym_name", "scheme"])?; + require_shape(operation, 0, 1) + } + "piop.stage" => { + require_attrs(operation, &["sym_name", "name", "order", "roles"])?; + require_shape(operation, 0, 1) + } + "piop.relation" => require_attrs( + operation, + &[ + "sym_name", + "kind", + "domain", + "num_rounds", + "degree", + "output_count", + ], + ), + "piop.sumcheck_claim" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + require_min_shape(operation, 1, 1) + } + "piop.opening_input" => { + require_attrs( + operation, + &[ + "sym_name", + "source_stage", + "source_claim", + "oracle", + "domain", + "point_arity", + "claim_kind", + ], + )?; + require_shape(operation, 0, 3) + } + "piop.sumcheck_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + require_min_shape(operation, 1, 1)?; + require_counted_operands(operation, 1, "ordered_claims") + } + "piop.sumcheck" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + require_shape(operation, 2, 4) + } + "piop.sumcheck_eval" => { + require_attrs( + operation, + &["sym_name", "source", "name", "index", "oracle"], + )?; + require_shape(operation, 1, 1) + } + "piop.sumcheck_instance_result" => { + require_attrs( + operation, + &[ + "sym_name", + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + require_shape(operation, 2, 2) + } + "piop.opening_claim" => { + require_attrs( + operation, + &["sym_name", "oracle", "domain", "point_arity", "claim_kind"], + )?; + require_shape(operation, 2, 1) + } + "piop.opening_claim_equal" => { + require_attrs(operation, &["sym_name", "mode"])?; + require_shape(operation, 2, 0)?; + require_opening_claim_equality(operation) + } + "piop.opening_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + ], + )?; + require_min_shape(operation, 0, 1)?; + require_counted_operands(operation, 0, "ordered_claims") + } + "party.function" => require_attrs(operation, &["sym_name", "source", "role"]), + "compute.params" => require_attrs(operation, &["sym_name", "field", "pcs", "transcript"]), + "compute.function" => require_attrs(operation, &["sym_name", "source"]), + "compute.relation" => require_attrs( + operation, + &[ + "sym_name", + "kind", + "domain", + "num_rounds", + "degree", + "output_count", + ], + ), + "compute.kernel" => require_attrs( + operation, + &["sym_name", "relation", "kind", "backend", "abi"], + ), + "compute.oracle_dense_trace" => { + require_attrs( + operation, + &[ + "sym_name", "oracle", "source", "domain", "num_vars", "padding", + ], + )?; + require_shape(operation, 0, 1) + } + "compute.oracle_one_hot_chunk" => { + require_attrs( + operation, + &[ + "sym_name", + "oracle", + "source", + "domain", + "num_vars", + "trace_num_vars", + "chunk", + "num_chunks", + "chunk_bits", + "padding", + "layout", + ], + )?; + require_shape(operation, 0, 1) + } + "compute.oracle_optional_advice" => { + require_attrs( + operation, + &[ + "sym_name", + "oracle", + "source", + "domain", + "num_vars", + "skip_policy", + ], + )?; + require_shape(operation, 0, 1) + } + "compute.oracle_ref" => { + require_attrs(operation, &["sym_name", "oracle", "domain", "num_vars"])?; + require_shape(operation, 0, 1) + } + "compute.oracle_family_init" => { + require_attrs(operation, &["sym_name", "family", "count"])?; + require_shape(operation, 0, 1) + } + "compute.oracle_family_append" => { + require_attrs(operation, &["sym_name", "family", "oracle", "index"])?; + require_shape(operation, 2, 1) + } + "compute.pcs_commit_batch" | "compute.pcs_receive_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "artifact", + "pcs", + "oracle_family", + "ordered_oracles", + "label", + "domain", + "num_vars", + "count", + ], + )?; + require_shape(operation, 1, 1) + } + "compute.pcs_commit_optional" | "compute.pcs_receive_optional" => { + require_attrs( + operation, + &[ + "sym_name", + "artifact", + "pcs", + "oracle", + "label", + "domain", + "num_vars", + "skip_policy", + ], + )?; + require_shape(operation, 1, 1) + } + "compute.transcript_init" => { + require_attrs(operation, &["sym_name", "scheme"])?; + require_shape(operation, 0, 1) + } + "compute.transcript_absorb" => { + require_attrs(operation, &["sym_name", "label", "optional"])?; + require_shape(operation, 2, 1) + } + "compute.transcript_absorb_bytes" => { + require_attrs(operation, &["sym_name", "label", "payload"])?; + require_shape(operation, 1, 1) + } + "compute.transcript_squeeze" => { + require_attrs(operation, &["sym_name", "label", "kind", "count"])?; + require_shape(operation, 1, 2) + } + "compute.opening_input" => { + require_attrs( + operation, + &[ + "sym_name", + "source_stage", + "source_claim", + "oracle", + "domain", + "point_arity", + "claim_kind", + ], + )?; + require_shape(operation, 0, 3) + } + "compute.point_slice" => { + require_attrs(operation, &["sym_name", "source", "offset", "length"])?; + require_shape(operation, 1, 1) + } + "compute.point_zero" => { + require_attrs(operation, &["sym_name", "field", "arity"])?; + require_shape(operation, 0, 1) + } + "compute.point_concat" => { + require_attrs(operation, &["sym_name", "layout", "arity"])?; + require_min_shape(operation, 1, 1) + } + "compute.field_const" => { + require_attrs(operation, &["sym_name", "field", "value"])?; + require_shape(operation, 0, 1) + } + "compute.field_zero" | "compute.field_one" => { + require_attrs(operation, &["sym_name", "field"])?; + require_shape(operation, 0, 1) + } + "compute.field_add" | "compute.field_sub" | "compute.field_mul" => { + require_attrs(operation, &["sym_name"])?; + require_shape(operation, 2, 1) + } + "compute.field_neg" => { + require_attrs(operation, &["sym_name"])?; + require_shape(operation, 1, 1) + } + "compute.field_pow" => { + require_attrs(operation, &["sym_name", "exponent"])?; + require_shape(operation, 1, 1) + } + "compute.poly_lagrange_basis_eval" => { + require_attrs( + operation, + &["sym_name", "domain_start", "domain_size", "index"], + )?; + require_shape(operation, 1, 1) + } + "compute.sumcheck_claim" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + require_min_shape(operation, 1, 1) + } + "compute.sumcheck_kernel_claim" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "kernel", + ], + )?; + require_min_shape(operation, 1, 1) + } + "compute.sumcheck_verify_claim" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + require_min_shape(operation, 1, 1) + } + "compute.sumcheck_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + require_min_shape(operation, 0, 1)?; + require_counted_operands(operation, 0, "ordered_claims") + } + "compute.sumcheck_driver" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + require_shape(operation, 2, 4) + } + "compute.sumcheck_kernel_driver" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "kernel", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + require_shape(operation, 2, 4) + } + "compute.sumcheck_verify" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + require_shape(operation, 2, 4) + } + "compute.sumcheck_eval" => { + require_attrs( + operation, + &["sym_name", "source", "name", "index", "oracle"], + )?; + require_shape(operation, 1, 1) + } + "compute.sumcheck_instance_result" => { + require_attrs( + operation, + &[ + "sym_name", + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + require_shape(operation, 2, 2) + } + "compute.opening_claim" => { + require_attrs( + operation, + &["sym_name", "oracle", "domain", "point_arity", "claim_kind"], + )?; + require_shape(operation, 2, 1) + } + "compute.opening_claim_equal" => { + require_attrs(operation, &["sym_name", "mode"])?; + require_shape(operation, 2, 0)?; + require_opening_claim_equality(operation) + } + "compute.opening_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + ], + )?; + require_min_shape(operation, 0, 1)?; + require_counted_operands(operation, 0, "ordered_claims") + } + "compute.pcs_opening_claim" => { + require_attrs( + operation, + &["sym_name", "oracle", "family", "domain", "point_arity"], + )?; + require_shape(operation, 2, 1) + } + "compute.pcs_opening_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "proof_slot", + "policy", + "count", + "ordered_claims", + ], + )?; + require_min_shape(operation, 0, 1)?; + require_counted_operands(operation, 0, "ordered_claims") + } + "compute.pcs_batch_open" | "compute.pcs_batch_verify" => { + require_attrs( + operation, + &["sym_name", "pcs", "proof_slot", "transcript_label"], + )?; + require_shape(operation, 2, 2) + } + "cpu.params" => require_attrs(operation, &["sym_name", "field", "pcs", "transcript"]), + "cpu.function" => require_attrs(operation, &["sym_name", "source"]), + "cpu.oracle_dense_trace" => { + require_attrs( + operation, + &[ + "sym_name", "oracle", "source", "domain", "num_vars", "padding", + ], + )?; + require_shape(operation, 0, 1) + } + "cpu.oracle_one_hot_chunk" => { + require_attrs( + operation, + &[ + "sym_name", + "oracle", + "source", + "domain", + "num_vars", + "trace_num_vars", + "chunk", + "num_chunks", + "chunk_bits", + "padding", + "layout", + ], + )?; + require_shape(operation, 0, 1) + } + "cpu.oracle_optional_advice" => { + require_attrs( + operation, + &[ + "sym_name", + "oracle", + "source", + "domain", + "num_vars", + "skip_policy", + ], + )?; + require_shape(operation, 0, 1) + } + "cpu.oracle_ref" => { + require_attrs(operation, &["sym_name", "oracle", "domain", "num_vars"])?; + require_shape(operation, 0, 1) + } + "cpu.oracle_family_init" => { + require_attrs(operation, &["sym_name", "family", "count"])?; + require_shape(operation, 0, 1) + } + "cpu.oracle_family_append" => { + require_attrs(operation, &["sym_name", "family", "oracle", "index"])?; + require_shape(operation, 2, 1) + } + "cpu.pcs_commit_batch" | "cpu.pcs_receive_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "artifact", + "pcs", + "oracle_family", + "ordered_oracles", + "label", + "domain", + "num_vars", + "count", + ], + )?; + require_shape(operation, 1, 1) + } + "cpu.pcs_commit_optional" | "cpu.pcs_receive_optional" => { + require_attrs( + operation, + &[ + "sym_name", + "artifact", + "pcs", + "oracle", + "label", + "domain", + "num_vars", + "skip_policy", + ], + )?; + require_shape(operation, 1, 1) + } + "cpu.transcript_init" => { + require_attrs(operation, &["sym_name", "scheme"])?; + require_shape(operation, 0, 1) + } + "cpu.transcript_absorb" => { + require_attrs(operation, &["sym_name", "label", "optional"])?; + require_shape(operation, 2, 1) + } + "cpu.transcript_absorb_bytes" => { + require_attrs(operation, &["sym_name", "label", "payload"])?; + require_shape(operation, 1, 1) + } + "cpu.transcript_squeeze" => { + require_attrs(operation, &["sym_name", "label", "kind", "count"])?; + require_shape(operation, 1, 2) + } + "cpu.opening_input" => { + require_attrs( + operation, + &[ + "sym_name", + "source_stage", + "source_claim", + "oracle", + "domain", + "point_arity", + "claim_kind", + ], + )?; + require_shape(operation, 0, 3) + } + "cpu.point_slice" => { + require_attrs(operation, &["sym_name", "source", "offset", "length"])?; + require_shape(operation, 1, 1) + } + "cpu.point_zero" => { + require_attrs(operation, &["sym_name", "field", "arity"])?; + require_shape(operation, 0, 1) + } + "cpu.point_concat" => { + require_attrs(operation, &["sym_name", "layout", "arity"])?; + require_min_shape(operation, 1, 1) + } + "cpu.field_const" => { + require_attrs(operation, &["sym_name", "field", "value"])?; + require_shape(operation, 0, 1) + } + "cpu.field_zero" | "cpu.field_one" => { + require_attrs(operation, &["sym_name", "field"])?; + require_shape(operation, 0, 1) + } + "cpu.field_add" | "cpu.field_sub" | "cpu.field_mul" => { + require_attrs(operation, &["sym_name"])?; + require_shape(operation, 2, 1) + } + "cpu.field_neg" => { + require_attrs(operation, &["sym_name"])?; + require_shape(operation, 1, 1) + } + "cpu.field_pow" => { + require_attrs(operation, &["sym_name", "exponent"])?; + require_shape(operation, 1, 1) + } + "cpu.poly_lagrange_basis_eval" => { + require_attrs( + operation, + &["sym_name", "domain_start", "domain_size", "index"], + )?; + require_shape(operation, 1, 1) + } + "cpu.kernel" => require_attrs( + operation, + &["sym_name", "relation", "kind", "backend", "abi"], + ), + "cpu.sumcheck_claim" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "kernel", + ], + )?; + require_min_shape(operation, 1, 1) + } + "cpu.sumcheck_verify_claim" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "domain", + "num_rounds", + "degree", + "claim", + "relation", + ], + )?; + require_min_shape(operation, 1, 1) + } + "cpu.sumcheck_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + "claim_label", + "round_label", + "round_schedule", + ], + )?; + require_min_shape(operation, 0, 1)?; + require_counted_operands(operation, 0, "ordered_claims") + } + "cpu.sumcheck_driver" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "kernel", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + require_shape(operation, 2, 4) + } + "cpu.sumcheck_verify" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "relation", + "policy", + "round_schedule", + "claim_label", + "round_label", + "num_rounds", + "degree", + ], + )?; + require_shape(operation, 2, 4) + } + "cpu.sumcheck_eval" => { + require_attrs( + operation, + &["sym_name", "source", "name", "index", "oracle"], + )?; + require_shape(operation, 1, 1) + } + "cpu.sumcheck_instance_result" => { + require_attrs( + operation, + &[ + "sym_name", + "source", + "claim", + "relation", + "index", + "point_arity", + "num_rounds", + "round_offset", + "point_order", + "degree", + ], + )?; + require_shape(operation, 2, 2) + } + "cpu.opening_claim" => { + require_attrs( + operation, + &["sym_name", "oracle", "domain", "point_arity", "claim_kind"], + )?; + require_shape(operation, 2, 1) + } + "cpu.opening_claim_equal" => { + require_attrs(operation, &["sym_name", "mode"])?; + require_shape(operation, 2, 0)?; + require_opening_claim_equality(operation) + } + "cpu.opening_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "stage", + "proof_slot", + "policy", + "count", + "ordered_claims", + ], + )?; + require_min_shape(operation, 0, 1)?; + require_counted_operands(operation, 0, "ordered_claims") + } + "cpu.pcs_opening_claim" => { + require_attrs( + operation, + &["sym_name", "oracle", "family", "domain", "point_arity"], + )?; + require_shape(operation, 2, 1) + } + "cpu.pcs_opening_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "proof_slot", + "policy", + "count", + "ordered_claims", + ], + )?; + require_min_shape(operation, 0, 1)?; + require_counted_operands(operation, 0, "ordered_claims") + } + "cpu.pcs_batch_open" | "cpu.pcs_batch_verify" => { + require_attrs( + operation, + &["sym_name", "pcs", "proof_slot", "transcript_label"], + )?; + require_shape(operation, 2, 2) + } + "pcs.opening_claim" => { + require_attrs( + operation, + &["sym_name", "oracle", "family", "domain", "point_arity"], + )?; + require_shape(operation, 2, 1) + } + "pcs.opening_batch" => { + require_attrs( + operation, + &[ + "sym_name", + "proof_slot", + "policy", + "count", + "ordered_claims", + ], + )?; + require_min_shape(operation, 0, 1)?; + require_counted_operands(operation, 0, "ordered_claims") + } + "pcs.batch_open" | "pcs.batch_verify" => { + require_attrs( + operation, + &["sym_name", "pcs", "proof_slot", "transcript_label"], + )?; + require_shape(operation, 2, 2) + } + _ if is_bolt_dialect_op(&name) => Err(SchemaError::new(format!( + "unknown Bolt op `{name}` in schema verifier" + ))), + _ => Ok(()), + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct OpeningClaimMetadata { + owner: String, + oracle: String, + domain: String, + point_arity: usize, + claim_kind: String, +} + +fn require_opening_claim_equality(operation: OperationRef<'_, '_>) -> Result<(), SchemaError> { + let mode = string_attr(operation, "mode")?; + if mode != "point_and_eval" { + return Err(SchemaError::new(format!( + "{} attr `mode` expected \"point_and_eval\", got \"{mode}\"", + operation_name(operation) + ))); + } + + let left = opening_claim_metadata(operation, 0)?; + let right = opening_claim_metadata(operation, 1)?; + if left.oracle != right.oracle + || left.domain != right.domain + || left.point_arity != right.point_arity + || left.claim_kind != right.claim_kind + { + return Err(SchemaError::new(format!( + "{} compares incompatible claims @{} and @{}", + operation_name(operation), + left.owner, + right.owner + ))); + } + Ok(()) +} + +fn opening_claim_metadata( + equality_op: OperationRef<'_, '_>, + operand_index: usize, +) -> Result { + let operand = equality_op.operand(operand_index).map_err(|_| { + SchemaError::new(format!( + "{} missing required operand {operand_index}", + operation_name(equality_op) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + SchemaError::new(format!( + "{} operand {operand_index} must be an op result", + operation_name(equality_op) + )) + })?; + let operation = owner.owner(); + let result_number = owner.result_number(); + let expected_result = match operation_name(operation).as_str() { + "piop.opening_input" | "compute.opening_input" | "cpu.opening_input" => 2, + "piop.opening_claim" | "compute.opening_claim" | "cpu.opening_claim" => 0, + name => { + return Err(SchemaError::new(format!( + "{} operand {operand_index} must be an opening claim, got result from `{name}`", + operation_name(equality_op) + ))); + } + }; + if result_number != expected_result { + return Err(SchemaError::new(format!( + "{} operand {operand_index} must use opening claim result {expected_result}, got result {result_number}", + operation_name(equality_op) + ))); + } + + Ok(OpeningClaimMetadata { + owner: string_attr(operation, "sym_name")?, + oracle: symbol_attr(operation, "oracle")?, + domain: symbol_attr(operation, "domain")?, + point_arity: int_attr(operation, "point_arity")?, + claim_kind: string_attr(operation, "claim_kind")?, + }) +} + +fn require_shape( + operation: OperationRef<'_, '_>, + operands: usize, + results: usize, +) -> Result<(), SchemaError> { + if operation.operand_count() != operands { + return Err(SchemaError::new(format!( + "{} expected {operands} operands, got {}", + operation_name(operation), + operation.operand_count() + ))); + } + if operation.result_count() != results { + return Err(SchemaError::new(format!( + "{} expected {results} results, got {}", + operation_name(operation), + operation.result_count() + ))); + } + Ok(()) +} + +fn require_min_shape( + operation: OperationRef<'_, '_>, + min_operands: usize, + results: usize, +) -> Result<(), SchemaError> { + if operation.operand_count() < min_operands { + return Err(SchemaError::new(format!( + "{} expected at least {min_operands} operands, got {}", + operation_name(operation), + operation.operand_count() + ))); + } + if operation.result_count() != results { + return Err(SchemaError::new(format!( + "{} expected {results} results, got {}", + operation_name(operation), + operation.result_count() + ))); + } + Ok(()) +} + +fn require_counted_operands( + operation: OperationRef<'_, '_>, + fixed_operands: usize, + ordered_attr: &str, +) -> Result<(), SchemaError> { + let count = int_attr(operation, "count")?; + let dynamic_count = operation.operand_count().saturating_sub(fixed_operands); + if count != dynamic_count { + return Err(SchemaError::new(format!( + "{} attr `count` expected {dynamic_count}, got {count}", + operation_name(operation) + ))); + } + let ordered = symbol_array_attr(operation, ordered_attr)?; + if ordered.len() != count { + return Err(SchemaError::new(format!( + "{} attr `{ordered_attr}` length {} does not match count {count}", + operation_name(operation), + ordered.len() + ))); + } + for (index, expected) in ordered.iter().enumerate() { + let operand_index = fixed_operands + index; + let actual = operand_owner_symbol(operation, operand_index)?; + if &actual != expected { + return Err(SchemaError::new(format!( + "{} operand {operand_index} expected @{expected}, got @{actual}", + operation_name(operation) + ))); + } + } + Ok(()) +} + +pub(crate) fn require_attrs( + operation: OperationRef<'_, '_>, + attrs: &[&str], +) -> Result<(), SchemaError> { + for attr in attrs { + if !operation.has_attribute(attr) { + return Err(SchemaError::new(format!( + "{} missing required attr `{attr}`", + operation_name(operation) + ))); + } + } + Ok(()) +} + +pub(crate) fn operand_owner_symbol( + operation: OperationRef<'_, '_>, + index: usize, +) -> Result { + let operand = operation.operand(index).map_err(|_| { + SchemaError::new(format!( + "{} missing required operand {index}", + operation_name(operation) + )) + })?; + let owner = OperationResult::try_from(operand).map_err(|_| { + SchemaError::new(format!( + "{} operand {index} must be an op result", + operation_name(operation) + )) + })?; + owner + .owner() + .attribute("sym_name") + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| { + SchemaError::new(format!( + "{} operand {index} owner missing sym_name", + operation_name(operation) + )) + }) +} + +pub(crate) fn require_symbol_attr_eq( + operation: OperationRef<'_, '_>, + attr: &str, + expected: &str, +) -> Result<(), SchemaError> { + let actual = symbol_attr(operation, attr)?; + if actual == expected { + Ok(()) + } else { + Err(SchemaError::new(format!( + "{} attr `{attr}` expected @{expected}, got @{actual}", + operation_name(operation) + ))) + } +} + +pub(crate) fn find_symbol<'c, P>( + module: &'c BoltModule<'_, P>, + symbol: &str, +) -> Option> +where + P: crate::ir::Phase, +{ + let mut operation = module.as_mlir_module().body().first_operation(); + while let Some(op) = operation { + operation = op.next_in_block(); + if op + .attribute("sym_name") + .ok() + .and_then(string_attribute_value) + .as_deref() + == Some(symbol) + { + return Some(op); + } + } + None +} + +pub(crate) fn symbol_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result { + operation + .attribute(attr) + .ok() + .and_then(symbol_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "symbol")) +} + +fn string_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .ok() + .and_then(string_attribute_value) + .ok_or_else(|| attr_error(operation, attr, "string")) +} + +pub(crate) fn symbol_array_attr( + operation: OperationRef<'_, '_>, + attr: &str, +) -> Result, SchemaError> { + let attribute = operation + .attribute(attr) + .map(|attribute| attribute.to_string()) + .ok() + .ok_or_else(|| attr_error(operation, attr, "symbol array"))?; + parse_symbol_array(&attribute).ok_or_else(|| attr_error(operation, attr, "symbol array")) +} + +fn parse_symbol_array(attribute: &str) -> Option> { + let inner = attribute.strip_prefix('[')?.strip_suffix(']')?.trim(); + if inner.is_empty() { + return Some(Vec::new()); + } + inner + .split(',') + .map(|item| item.trim().strip_prefix('@').map(ToOwned::to_owned)) + .collect() +} + +pub(crate) fn int_attr(operation: OperationRef<'_, '_>, attr: &str) -> Result { + operation + .attribute(attr) + .map(parse_integer_attr) + .ok() + .flatten() + .ok_or_else(|| attr_error(operation, attr, "integer")) +} + +fn parse_integer_attr(attribute: Attribute<'_>) -> Option { + attribute + .to_string() + .split_whitespace() + .next() + .and_then(|value| value.parse().ok()) +} + +fn attr_error(operation: OperationRef<'_, '_>, attr: &str, expected: &str) -> SchemaError { + SchemaError::new(format!( + "{} attr `{attr}` is not a {expected}", + operation_name(operation) + )) +} + +pub(crate) fn operation_name(operation: OperationRef<'_, '_>) -> String { + operation + .name() + .as_string_ref() + .as_str() + .unwrap_or("") + .to_owned() +} + +pub(crate) fn missing_module_op(name: &str) -> SchemaError { + SchemaError::new(format!("module missing required op `{name}`")) +} + +pub(crate) fn missing_symbol(symbol: &str) -> SchemaError { + SchemaError::new(format!("module missing required symbol @{symbol}")) +} + +fn is_bolt_dialect_op(name: &str) -> bool { + matches!( + name.split_once('.').map(|(dialect, _)| dialect), + Some( + "field" + | "poly" + | "hash" + | "transcript" + | "commit" + | "pcs" + | "protocol" + | "piop" + | "party" + | "compute" + | "cpu" + ) + ) +} diff --git a/crates/bolt/tests/commitment_ir.rs b/crates/bolt/tests/commitment_ir.rs new file mode 100644 index 0000000000..f4073cb052 --- /dev/null +++ b/crates/bolt/tests/commitment_ir.rs @@ -0,0 +1,4148 @@ +#![expect( + clippy::expect_used, + clippy::unwrap_used, + reason = "integration tests use explicit panic messages" +)] + +use bolt::protocols::jolt::{ + assemble_jolt_generated_crates, assemble_jolt_workspace_generated_crates, + build_commitment_protocol, build_stage1_outer_protocol, build_stage2_protocol, + build_stage3_protocol, build_stage4_protocol, build_stage5_protocol, build_stage6_protocol, + build_stage7_protocol, build_stage8_protocol, commitment_cpu_program, emit_commitment_rust, + emit_stage1_rust, emit_stage2_rust, emit_stage3_rust, emit_stage4_rust, emit_stage5_rust, + emit_stage6_rust, emit_stage7_rust, emit_stage8_rust, jolt_artifact_config, jolt_rust_artifact, + lower_commitment_to_compute, lower_compute_to_cpu, lower_stage1_to_compute, + lower_stage2_to_compute, lower_stage3_to_compute, lower_stage4_to_compute, + lower_stage5_to_compute, lower_stage6_to_compute, lower_stage7_to_compute, + lower_stage8_to_compute, resolve_compute_kernels, stage1_cpu_program, stage2_cpu_program, + stage3_cpu_program, stage4_cpu_program, stage5_cpu_program, stage6_cpu_program, + stage7_cpu_program, stage8_cpu_program, validate_jolt_rust_artifact_imports, + verify_jolt_protocol_schema, write_jolt_generated_crates, JoltGeneratedCrate, + JoltProtocolParams, JoltProtocolStage, +}; +use bolt::{ + assemble_generated_crates, lower_piop_and_fiat_shamir, project_prover_party, + project_verifier_party, protocol_rust_artifact, validate_rust_artifact_imports, + verify_compute_schema, verify_concrete_transcript, verify_cpu_schema, verify_protocol_schema, + Concrete, Cpu, GeneratedFile, MeliorContext, ProtocolArtifactConfig, ProtocolRuntimeModule, + ProtocolStage, ProtocolStageKind, ProtocolStandaloneDependency, Role, RustSourceFile, + RustTypeRef, TextMlir, +}; +use std::fmt::Write as _; +use std::path::Path; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[test] +fn bolt_irdl_dialects_are_registered() { + let context = MeliorContext::new(); + assert!(!context.context().allow_unregistered_dialects()); + + let registered = r#" +module @registered { + "field.define"() {modulus_bits = 254 : i64, role = "scalar", sym_name = "bn254_fr"} : () -> () +} +"#; + let _ = context + .parse_module::(registered) + .expect("registered dialect op parses"); + + let unknown = r#" +module @unknown { + "unknown.dialect_op"() : () -> () +} +"#; + let _ = context + .parse_module::(unknown) + .expect_err("unknown dialect rejected"); +} + +#[test] +fn commitment_protocol_uses_bolt_semantic_dialects() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_commitment_protocol(&context, ¶ms).expect("build protocol"); + let text = protocol.to_text_mlir(); + + assert!(text.contains("\"protocol.params\"()")); + assert!(text.contains("sym_name = \"jolt.params\"")); + assert!(text.contains("trace_length = 262144")); + assert!(text.contains("num_committed = 42")); + assert!(text.contains("\"field.define\"()")); + assert!(text.contains("sym_name = \"bn254_fr\"")); + assert!(text.contains("\"poly.domain\"()")); + assert!(text.contains("sym_name = \"jolt.main_witness_commit_domain\"")); + assert!(text.contains("\"protocol.boundary\"()")); + assert!(text.contains("sym_name = \"jolt.commitment_phase\"")); + assert!(text.contains("\"piop.oracle\"()")); + assert!(text.contains("sym_name = \"InstructionRa_0\"")); + assert!(text.contains("\"piop.oracle_family\"()")); + assert!(text.contains("sym_name = \"jolt.main_witness_polys\"")); + assert!(text.contains("ordered_oracles = [@RdInc, @RamInc, @InstructionRa_0")); + assert!(text.contains("\"commit.publish_batch\"()")); + assert!(text.contains("\"pcs.commit_batch\"(%")); + assert!(!text.contains("commitment = @jolt.main_witness_commitments")); + assert!(text.contains("\"transcript.absorb\"(%")); + + let parsed = context + .parse_module::(&text) + .expect("parse protocol MLIR"); + assert!(parsed.to_text_mlir().contains("\"protocol.boundary\"")); +} + +#[test] +fn concrete_commitment_phase_threads_transcript_state() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_commitment_protocol(&context, ¶ms).expect("build protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower Fiat-Shamir state"); + verify_concrete_transcript(&concrete).expect("valid transcript state threading"); + + let text = concrete.to_text_mlir(); + assert!(text.contains("!transcript.state_type")); + assert!(text.contains("\"transcript.state\"()")); + assert!(text.contains("sym_name = \"fs0\"")); + assert!(text.contains("\"transcript.absorb\"(%")); + assert!(text.contains("\"transcript.absorb_optional\"(%")); + assert!(!text.contains("in = @fs")); + assert!(!text.contains("out = @fs")); +} + +#[test] +fn transcript_absorb_bytes_threads_and_lowers_to_cpu() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = context + .parse_module::(&transcript_absorb_bytes_protocol(¶ms)) + .expect("parse absorb-bytes protocol"); + verify_protocol_schema(&protocol).expect("absorb-bytes protocol schema is valid"); + + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower absorb-bytes protocol"); + verify_concrete_transcript(&concrete).expect("absorb-bytes threads transcript state"); + + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + // Stage4 does not have its own lowering entrypoint yet; this exercises the + // shared operation mapping that each stage lowering uses. + let compute = lower_stage3_to_compute(&context, &prover).expect("lower to compute"); + verify_compute_schema(&compute).expect("compute schema accepts absorb-bytes"); + assert!(compute + .to_text_mlir() + .contains("\"compute.transcript_absorb_bytes\"(%")); + + let kernelized = resolve_compute_kernels(&context, &compute).expect("kernelize compute"); + assert!(kernelized + .to_text_mlir() + .contains("\"compute.transcript_absorb_bytes\"(%")); + + let cpu = lower_compute_to_cpu(&context, &kernelized).expect("lower to CPU"); + verify_cpu_schema(&cpu).expect("CPU schema accepts absorb-bytes"); + let cpu_text = cpu.to_text_mlir(); + assert!(cpu_text.contains("\"cpu.transcript_absorb_bytes\"(%")); + assert!(cpu_text.contains("label = \"ram_val_check_gamma\"")); + assert!(cpu_text.contains("payload = \"\"")); +} + +#[test] +fn concrete_projects_to_party_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_commitment_protocol(&context, ¶ms).expect("build protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower Fiat-Shamir state"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_text = prover.to_text_mlir(); + let verifier_text = verifier.to_text_mlir(); + + assert!(prover_text.contains("bolt.phase = \"party\"")); + assert!(prover_text.contains("bolt.role = \"prover\"")); + assert!(prover_text.contains("\"party.function\"()")); + assert!(prover_text.contains("role = \"prover\"")); + assert!(prover_text.contains("\"transcript.absorb\"(%")); + assert!(!prover_text.contains("in = @fs")); + assert!(verifier_text.contains("bolt.phase = \"party\"")); + assert!(verifier_text.contains("bolt.role = \"verifier\"")); + assert!(verifier_text.contains("\"party.function\"()")); + assert!(verifier_text.contains("role = \"verifier\"")); + assert!(verifier_text.contains("\"transcript.absorb\"(%")); + assert!(!verifier_text.contains("in = @fs")); +} + +#[test] +fn commitment_compute_lowers_to_cpu_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_commitment_protocol(&context, ¶ms).expect("build protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower Fiat-Shamir state"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_commitment_to_compute(&context, &prover).expect("lower compute"); + let verifier_compute = + lower_commitment_to_compute(&context, &verifier).expect("lower verifier compute"); + let prover_cpu = lower_compute_to_cpu(&context, &prover_compute).expect("lower to CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_compute).expect("lower verifier to CPU"); + let compute_text = prover_compute.to_text_mlir(); + let text = prover_cpu.to_text_mlir(); + let verifier_compute_text = verifier_compute.to_text_mlir(); + let verifier_text = verifier_cpu.to_text_mlir(); + + assert!(compute_text.contains("\"compute.oracle_dense_trace\"()")); + assert!(compute_text.contains("\"compute.oracle_one_hot_chunk\"()")); + assert!(compute_text.contains("\"compute.oracle_family_append\"(%")); + assert!(compute_text.contains("\"compute.pcs_commit_batch\"(%")); + assert!(compute_text.contains("artifact = @jolt.main_witness_commitments")); + assert!(compute_text.contains("ordered_oracles = [@RdInc, @RamInc, @InstructionRa_0")); + assert!(compute_text.contains("\"compute.pcs_commit_optional\"(%")); + assert!(compute_text.contains("skip_policy = \"missing_or_zero\"")); + assert!(compute_text.contains("!compute.transcript_state")); + assert!(compute_text.contains("\"compute.transcript_absorb\"(%")); + assert!(!compute_text.contains("in = @fs")); + assert!(text.contains("\"cpu.function\"()")); + assert!(!text.contains("\"compute.function\"()")); + assert!(text.contains("\"cpu.oracle_family_append\"(%")); + assert!(text.contains("\"cpu.pcs_commit_batch\"(%")); + assert!(text.contains("\"cpu.pcs_commit_optional\"(%")); + assert!(text.contains("skip_policy = \"missing_or_zero\"")); + assert!(text.contains("!cpu.transcript_state")); + assert!(text.contains("\"cpu.transcript_absorb\"(%")); + assert!(!text.contains("in = @fs")); + assert!(verifier_compute_text.contains("\"compute.oracle_ref\"()")); + assert!(verifier_compute_text.contains("\"compute.pcs_receive_batch\"(%")); + assert!(verifier_compute_text.contains("\"compute.pcs_receive_optional\"(%")); + assert!(!verifier_compute_text.contains("\"compute.pcs_commit_batch\"(%")); + assert!(verifier_text.contains("\"cpu.pcs_receive_batch\"(%")); + assert!(verifier_text.contains("\"cpu.pcs_receive_optional\"(%")); + assert!(!verifier_text.contains("\"cpu.pcs_commit_batch\"(%")); + + let parsed = context + .parse_module::(&text) + .expect("parse CPU MLIR"); + assert!(parsed.to_text_mlir().contains("\"cpu.pcs_commit_batch\"")); + let parsed = context + .parse_module::(&verifier_text) + .expect("parse verifier CPU MLIR"); + assert!(parsed.to_text_mlir().contains("\"cpu.pcs_receive_batch\"")); +} + +#[test] +fn generic_protocol_schema_accepts_non_jolt_params() { + let context = MeliorContext::new(); + let generic = context.new_module::("generic", None); + context + .append_op( + &generic, + "protocol.params", + Some("generic.params"), + &[ + ("field", "@some_field"), + ("pcs", "@some_pcs"), + ("transcript", "@some_transcript"), + ], + ) + .expect("append generic params"); + + assert!( + generic.verify(), + "generic protocol params pass IRDL verification" + ); + verify_protocol_schema(&generic).expect("generic schema does not require Jolt attrs"); +} + +#[test] +fn protocol_schema_rejects_bad_derived_params() { + let context = MeliorContext::new(); + let bad = context.new_module::("bad", None); + let mut attrs = JoltProtocolParams::fixture().attrs(); + for (name, value) in &mut attrs { + if name == "num_committed" { + *value = "40 : i64".to_owned(); + } + } + context + .append_op_with_owned_attrs(&bad, "protocol.params", Some("jolt.params"), &attrs) + .expect("append params"); + context + .append_op( + &bad, + "piop.oracle_family", + Some("jolt.main_witness_polys"), + &[ + ( + "ordered_oracles", + "[@RdInc, @RamInc, @InstructionRa_0, @RamRa_0, @BytecodeRa_0]", + ), + ("count", "40 : i64"), + ("domain", "@jolt.trace_domain"), + ("visibility", r#""committed""#), + ], + ) + .expect("append family"); + + let error = verify_jolt_protocol_schema(&bad).expect_err("bad derived param rejected"); + assert!(error.to_string().contains("num_committed must be 42")); +} + +#[test] +fn concrete_verifier_rejects_unthreaded_transcript_absorb() { + let context = MeliorContext::new(); + let concrete = context.new_module::("bad", None); + context + .append_op( + &concrete, + "transcript.absorb", + Some("bad_absorb"), + &[ + ("label", r#""commitment""#), + ("source", "@jolt.main_witness_commitments"), + ], + ) + .expect("append bad absorb"); + + let error = verify_concrete_transcript(&concrete).expect_err("missing transcript state"); + assert!(error + .to_string() + .contains("requires a prior transcript.state result")); +} + +#[test] +fn protocol_schema_accepts_explicit_sumcheck_and_opening_flow() { + let context = MeliorContext::new(); + let protocol = context + .parse_module::(explicit_sumcheck_protocol()) + .expect("parse explicit sumcheck protocol"); + + verify_protocol_schema(&protocol).expect("explicit sumcheck protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower protocol copy to concrete"); + verify_concrete_transcript(&concrete).expect("sumcheck/opening ops thread transcript state"); + + let text = concrete.to_text_mlir(); + assert!(text.contains("\"piop.sumcheck_batch\"(%")); + assert!(text.contains("round_schedule = [2, 1, 1]")); + assert!(text.contains("\"pcs.opening_claim\"(%")); + assert!(text.contains("\"pcs.opening_batch\"(%")); + assert!(text.contains("\"pcs.batch_open\"(%")); +} + +#[test] +fn opening_batch_schema_rejects_hidden_or_reordered_claims() { + let context = MeliorContext::new(); + let protocol = context + .parse_module::(&explicit_sumcheck_protocol().replace( + "ordered_claims = [@stage1.outer.opening]", + "ordered_claims = [@wrong.opening]", + )) + .expect("parse explicit sumcheck protocol"); + + let error = verify_protocol_schema(&protocol).expect_err("opening batch order mismatch"); + assert!(error + .to_string() + .contains("expected @wrong.opening, got @stage1.outer.opening")); +} + +#[test] +fn opening_claim_equal_lowers_through_ssa_pipeline() { + let context = MeliorContext::new(); + let protocol = context + .parse_module::(&opening_claim_equal_protocol( + "LeftInstructionInput", + "LeftInstructionInput", + "point_and_eval", + )) + .expect("parse opening equality protocol"); + verify_protocol_schema(&protocol).expect("opening equality protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower protocol to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let compute = lower_stage2_to_compute(&context, &prover).expect("lower equality to compute"); + verify_compute_schema(&compute).expect("compute equality schema is valid"); + let kernelized = + resolve_compute_kernels(&context, &compute).expect("preserve equality through kernels"); + let cpu = lower_compute_to_cpu(&context, &kernelized).expect("lower equality to CPU"); + verify_cpu_schema(&cpu).expect("CPU equality schema is valid"); + + let compute_text = kernelized.to_text_mlir(); + let cpu_text = cpu.to_text_mlir(); + assert!(compute_text.contains("\"compute.opening_claim_equal\"(%")); + assert!(compute_text.contains("mode = \"point_and_eval\"")); + assert!(cpu_text.contains("\"cpu.opening_claim_equal\"(%")); + assert!(cpu_text.contains("mode = \"point_and_eval\"")); +} + +#[test] +fn opening_claim_equal_rejects_incompatible_claim_metadata() { + let context = MeliorContext::new(); + let protocol = context + .parse_module::(&opening_claim_equal_protocol( + "LeftInstructionInput", + "RightInstructionInput", + "point_and_eval", + )) + .expect("parse bad opening equality protocol"); + + let error = verify_protocol_schema(&protocol).expect_err("mismatched claims are rejected"); + assert!(error.to_string().contains("compares incompatible claims")); +} + +#[test] +fn opening_claim_equal_rejects_unsupported_mode() { + let context = MeliorContext::new(); + let protocol = context + .parse_module::(&opening_claim_equal_protocol( + "LeftInstructionInput", + "LeftInstructionInput", + "eval_only", + )) + .expect("parse bad opening equality mode"); + + let error = verify_protocol_schema(&protocol).expect_err("unsupported equality mode rejected"); + assert!(error.to_string().contains("expected \"point_and_eval\"")); +} + +#[test] +fn sumcheck_compute_lowers_to_cpu_kernel_ir() { + let context = MeliorContext::new(); + let compute = context + .parse_module::(explicit_sumcheck_compute()) + .expect("parse explicit sumcheck compute"); + + verify_compute_schema(&compute).expect("compute sumcheck schema is valid"); + let kernelized = + resolve_compute_kernels(&context, &compute).expect("resolve sumcheck compute kernels"); + verify_compute_schema(&kernelized).expect("kernelized sumcheck schema is valid"); + let cpu = lower_compute_to_cpu(&context, &kernelized).expect("lower sumcheck compute to CPU"); + verify_cpu_schema(&cpu).expect("CPU sumcheck schema is valid"); + + let text = cpu.to_text_mlir(); + assert!(text.contains("\"cpu.transcript_squeeze\"(%")); + assert!(text.contains("\"cpu.sumcheck_batch\"(%")); + assert!(text.contains("\"cpu.sumcheck_driver\"(%")); + assert!(text.contains("\"cpu.sumcheck_eval\"(%")); + assert!(text.contains("\"cpu.pcs_opening_claim\"(%")); + assert!(text.contains("\"cpu.pcs_batch_open\"(%")); + assert!(text.contains("!cpu.sumcheck_claim_type")); +} + +#[test] +fn jolt_stage1_outer_protocol_defines_virtual_claim_flow() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = + build_stage1_outer_protocol(&context, ¶ms).expect("build stage1 outer protocol"); + verify_protocol_schema(&protocol).expect("stage1 protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage1 to concrete"); + verify_concrete_transcript(&concrete).expect("stage1 transcript is threaded"); + + let text = protocol.to_text_mlir(); + assert!(text.contains("sym_name = \"stage1.uniskip.sumcheck\"")); + assert!(text.contains("sym_name = \"stage1.outer_remaining.sumcheck\"")); + assert!(text.contains("relation = @jolt.stage1.outer.uniskip")); + assert!(!text.contains("kernel = @")); + assert!(text.contains("\"piop.sumcheck_claim\"(%")); + assert!(text.contains("\"piop.sumcheck_eval\"(%")); + assert!(text.contains("\"piop.opening_claim\"(%")); + assert!(text.contains("\"piop.opening_batch\"(%")); + assert!(text.contains("count = 35 : i64")); + assert!(text.contains("ordered_claims = [@stage1.outer_remaining.opening.LeftInstructionInput")); + assert!(text.contains("oracle = @OpFlagIsLastInSequence")); + assert!(!text.contains("\"pcs.opening_claim\"")); + assert_or_update_fixture("tests/fixtures/stage1_outer_protocol.mlir", &text); +} + +#[test] +fn jolt_stage2_protocol_defines_product_ram_claim_flow() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage2_protocol(&context, ¶ms).expect("build stage2 protocol"); + verify_protocol_schema(&protocol).expect("stage2 protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage2 to concrete"); + verify_concrete_transcript(&concrete).expect("stage2 transcript is threaded"); + + let text = protocol.to_text_mlir(); + assert!(text.contains("sym_name = \"stage2.product_virtual.uniskip.sumcheck\"")); + assert!(text.contains("sym_name = \"stage2.sumcheck\"")); + assert!(text.contains("relation = @jolt.stage2.product_virtual.uniskip")); + assert!(text.contains("relation = @jolt.stage2.batched")); + assert!(text.contains("\"piop.opening_input\"()")); + assert!(text.contains("\"field.add\"(%")); + assert!(text.contains("\"poly.lagrange_basis_eval\"(%")); + assert!(text.contains("sym_name = \"stage2.ram_read_write.claim_expr\"")); + assert!(text.contains("\"piop.sumcheck_instance_result\"(%")); + assert!(text.contains("round_offset = 14 : i64")); + assert!(text.contains("\"poly.point_slice\"(%")); + assert!(text.contains("\"poly.point_concat\"(%")); + assert!(text.contains( + "ordered_claims = [@stage2.ram_read_write.input, @stage2.product_virtual.remainder.input" + )); + assert!(text.contains("ordered_claims = [@stage2.ram_read_write.opening.RamVal, @stage2.ram_read_write.opening.RamRa, @stage2.ram_read_write.opening.RamInc")); + assert!(text.contains("claim_kind = \"committed\"")); + assert!(text.contains("source_claim = @stage1.outer_remaining.opening.RamAddress")); + assert!(!text.contains("kernel = @")); + assert!(!text.contains("\"compute.")); + assert_or_update_fixture("tests/fixtures/stage2_protocol.mlir", &text); +} + +#[test] +fn jolt_stage2_lowers_to_compute_and_cpu_role_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage2_protocol(&context, ¶ms).expect("build stage2 protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage2 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage2_to_compute(&context, &prover).expect("lower prover stage2"); + let verifier_compute = + lower_stage2_to_compute(&context, &verifier).expect("lower verifier stage2"); + verify_compute_schema(&prover_compute).expect("prover stage2 compute schema is valid"); + verify_compute_schema(&verifier_compute).expect("verifier stage2 compute schema is valid"); + + let prover_compute_text = prover_compute.to_text_mlir(); + let verifier_compute_text = verifier_compute.to_text_mlir(); + assert!(prover_compute_text.contains("\"compute.opening_input\"()")); + assert!(prover_compute_text.contains("\"compute.field_add\"(%")); + assert!(prover_compute_text.contains("\"compute.poly_lagrange_basis_eval\"(%")); + assert!(prover_compute_text.contains("\"compute.point_slice\"(%")); + assert!(prover_compute_text.contains("\"compute.point_concat\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_claim\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_driver\"(%")); + assert!(!prover_compute_text.contains("kernel = @")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify_claim\"")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify\"")); + assert!(!verifier_compute_text.contains("\"compute.kernel\"")); + assert!(!verifier_compute_text.contains("kernel = @")); + + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + verify_compute_schema(&prover_kernel_compute) + .expect("prover kernelized stage2 compute schema is valid"); + verify_compute_schema(&verifier_kernel_compute) + .expect("verifier kernelized stage2 compute schema is valid"); + + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + verify_cpu_schema(&prover_cpu).expect("prover stage2 CPU schema is valid"); + verify_cpu_schema(&verifier_cpu).expect("verifier stage2 CPU schema is valid"); + + let prover_cpu_text = prover_cpu.to_text_mlir(); + let verifier_cpu_text = verifier_cpu.to_text_mlir(); + assert!(prover_cpu_text.contains("\"cpu.opening_input\"()")); + assert!(prover_cpu_text.contains("\"cpu.field_add\"(%")); + assert!(prover_cpu_text.contains("\"cpu.poly_lagrange_basis_eval\"(%")); + assert!(prover_cpu_text.contains("\"cpu.point_slice\"(%")); + assert!(prover_cpu_text.contains("\"cpu.point_concat\"(%")); + assert!(prover_cpu_text.contains("\"cpu.kernel\"()")); + assert!(prover_cpu_text.contains("kernel = @jolt.cpu.stage2.batched")); + assert!(verifier_cpu_text.contains("\"cpu.opening_input\"()")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify_claim\"")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify\"")); + assert!(!verifier_cpu_text.contains("\"cpu.kernel\"")); + assert!(!verifier_cpu_text.contains("kernel = @")); + + assert_or_update_fixture( + "tests/fixtures/stage2_prover_compute.mlir", + &prover_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/stage2_verifier_compute.mlir", + &verifier_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/stage2_prover_kernel_compute.mlir", + &prover_kernel_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/stage2_verifier_kernel_compute.mlir", + &verifier_kernel_compute.to_text_mlir(), + ); + assert_or_update_fixture("tests/fixtures/stage2_prover_cpu.mlir", &prover_cpu_text); + assert_or_update_fixture( + "tests/fixtures/stage2_verifier_cpu.mlir", + &verifier_cpu_text, + ); +} + +#[test] +fn jolt_stage3_protocol_defines_shift_instruction_register_flow() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage3_protocol(&context, ¶ms).expect("build stage3 protocol"); + verify_protocol_schema(&protocol).expect("stage3 protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage3 to concrete"); + verify_concrete_transcript(&concrete).expect("stage3 transcript is threaded"); + + let text = protocol.to_text_mlir(); + assert!(text.contains("sym_name = \"stage3.spartan_shift.input\"")); + assert!(text.contains("sym_name = \"stage3.instruction_input.input\"")); + assert!(text.contains("sym_name = \"stage3.registers_claim_reduction.input\"")); + assert!(text.contains("\"piop.opening_claim_equal\"(%")); + assert!(text.contains("\"field.add\"(%")); + assert!(text.contains("\"field.mul\"(%")); + assert!(text.contains("\"field.sub\"(%")); + assert!(text.contains("policy = \"jolt_core_stage3_aligned\"")); + assert!(text.contains("point_order = \"reverse\"")); + assert!(text.contains("ordered_claims = [@stage3.spartan_shift.input, @stage3.instruction_input.input, @stage3.registers_claim_reduction.input]")); + assert!(text.contains("ordered_claims = [@stage3.spartan_shift.opening.UnexpandedPC, @stage3.spartan_shift.opening.PC")); + assert!(!text.contains("kernel = @")); + assert!(!text.contains("\"compute.")); + assert_or_update_fixture("tests/fixtures/stage3_protocol.mlir", &text); +} + +#[test] +fn jolt_stage3_lowers_to_compute_and_cpu_role_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage3_protocol(&context, ¶ms).expect("build stage3 protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage3 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage3_to_compute(&context, &prover).expect("lower prover stage3"); + let verifier_compute = + lower_stage3_to_compute(&context, &verifier).expect("lower verifier stage3"); + verify_compute_schema(&prover_compute).expect("prover stage3 compute schema is valid"); + verify_compute_schema(&verifier_compute).expect("verifier stage3 compute schema is valid"); + + let prover_compute_text = prover_compute.to_text_mlir(); + let verifier_compute_text = verifier_compute.to_text_mlir(); + assert!(prover_compute_text.contains("\"compute.opening_input\"()")); + assert!(prover_compute_text.contains("\"compute.opening_claim_equal\"(%")); + assert!(prover_compute_text.contains("\"compute.field_add\"(%")); + assert!(prover_compute_text.contains("\"compute.field_mul\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_claim\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_driver\"(%")); + assert!(!prover_compute_text.contains("kernel = @")); + assert!(verifier_compute_text.contains("\"compute.opening_claim_equal\"(%")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify_claim\"")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify\"")); + assert!(!verifier_compute_text.contains("\"compute.kernel\"")); + assert!(!verifier_compute_text.contains("kernel = @")); + + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + verify_compute_schema(&prover_kernel_compute) + .expect("prover kernelized stage3 compute schema is valid"); + verify_compute_schema(&verifier_kernel_compute) + .expect("verifier kernelized stage3 compute schema is valid"); + + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + verify_cpu_schema(&prover_cpu).expect("prover stage3 CPU schema is valid"); + verify_cpu_schema(&verifier_cpu).expect("verifier stage3 CPU schema is valid"); + + let prover_kernel_text = prover_kernel_compute.to_text_mlir(); + let verifier_kernel_text = verifier_kernel_compute.to_text_mlir(); + let prover_cpu_text = prover_cpu.to_text_mlir(); + let verifier_cpu_text = verifier_cpu.to_text_mlir(); + assert!(prover_kernel_text.contains("kernel = @jolt.cpu.stage3.batched")); + assert!(!verifier_kernel_text.contains("kernel = @")); + assert!(prover_cpu_text.contains("\"cpu.opening_claim_equal\"(%")); + assert!(prover_cpu_text.contains("\"cpu.kernel\"()")); + assert!(prover_cpu_text.contains("kernel = @jolt.cpu.stage3.batched")); + assert!(verifier_cpu_text.contains("\"cpu.opening_claim_equal\"(%")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify_claim\"")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify\"")); + assert!(!verifier_cpu_text.contains("\"cpu.kernel\"")); + assert!(!verifier_cpu_text.contains("kernel = @")); + + assert_or_update_fixture( + "tests/fixtures/stage3_prover_compute.mlir", + &prover_compute_text, + ); + assert_or_update_fixture( + "tests/fixtures/stage3_verifier_compute.mlir", + &verifier_compute_text, + ); + assert_or_update_fixture( + "tests/fixtures/stage3_prover_kernel_compute.mlir", + &prover_kernel_text, + ); + assert_or_update_fixture( + "tests/fixtures/stage3_verifier_kernel_compute.mlir", + &verifier_kernel_text, + ); + assert_or_update_fixture("tests/fixtures/stage3_prover_cpu.mlir", &prover_cpu_text); + assert_or_update_fixture( + "tests/fixtures/stage3_verifier_cpu.mlir", + &verifier_cpu_text, + ); +} + +#[test] +fn jolt_stage4_protocol_defines_registers_and_ram_val_flow() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage4_protocol(&context, ¶ms).expect("build stage4 protocol"); + verify_protocol_schema(&protocol).expect("stage4 protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage4 to concrete"); + verify_concrete_transcript(&concrete).expect("stage4 transcript is threaded"); + + let text = protocol.to_text_mlir(); + assert!(text.contains("sym_name = \"stage4.registers_read_write.input\"")); + assert!(text.contains("sym_name = \"stage4.ram_val_check.input\"")); + assert!(text.contains("\"transcript.absorb_bytes\"(%")); + assert!(text.contains("label = \"ram_val_check_gamma\"")); + assert!(text.contains("payload = \"\"")); + assert!(text.contains("sym_name = \"stage4.input.initial_ram.RamValInit\"")); + assert!(text.contains("sym_name = \"stage4.registers.rs1_claim_consistency\"")); + assert!(text.contains("sym_name = \"stage4.registers.rs2_claim_consistency\"")); + assert!(text.contains( + "ordered_claims = [@stage4.registers_read_write.input, @stage4.ram_val_check.input]" + )); + assert!(text.contains("ordered_claims = [@stage4.registers_read_write.opening.RegistersVal")); + assert!(text.contains("@stage4.ram_val_check.opening.RamRa")); + assert!(text.contains("@stage4.ram_val_check.opening.RamInc")); + assert!(!text.contains("kernel = @")); + assert!(!text.contains("\"compute.")); + assert_or_update_fixture("tests/fixtures/stage4_protocol.mlir", &text); +} + +#[test] +fn jolt_stage4_lowers_to_compute_and_cpu_role_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage4_protocol(&context, ¶ms).expect("build stage4 protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage4 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage4_to_compute(&context, &prover).expect("lower prover stage4"); + let verifier_compute = + lower_stage4_to_compute(&context, &verifier).expect("lower verifier stage4"); + verify_compute_schema(&prover_compute).expect("prover stage4 compute schema is valid"); + verify_compute_schema(&verifier_compute).expect("verifier stage4 compute schema is valid"); + + let prover_compute_text = prover_compute.to_text_mlir(); + let verifier_compute_text = verifier_compute.to_text_mlir(); + assert!(prover_compute_text.contains("\"compute.transcript_absorb_bytes\"(%")); + assert!(prover_compute_text.contains("\"compute.opening_claim_equal\"(%")); + assert!(prover_compute_text.contains("\"compute.field_sub\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_claim\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_driver\"(%")); + assert!(verifier_compute_text.contains("\"compute.transcript_absorb_bytes\"(%")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify_claim\"")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify\"")); + assert!(!verifier_compute_text.contains("kernel = @")); + + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + verify_compute_schema(&prover_kernel_compute) + .expect("prover kernelized stage4 compute schema is valid"); + verify_compute_schema(&verifier_kernel_compute) + .expect("verifier kernelized stage4 compute schema is valid"); + + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + verify_cpu_schema(&prover_cpu).expect("prover stage4 CPU schema is valid"); + verify_cpu_schema(&verifier_cpu).expect("verifier stage4 CPU schema is valid"); + + let prover_kernel_text = prover_kernel_compute.to_text_mlir(); + let verifier_kernel_text = verifier_kernel_compute.to_text_mlir(); + let prover_cpu_text = prover_cpu.to_text_mlir(); + let verifier_cpu_text = verifier_cpu.to_text_mlir(); + assert!(prover_kernel_text.contains("kernel = @jolt.cpu.stage4.batched")); + assert!(prover_cpu_text.contains("\"cpu.transcript_absorb_bytes\"(%")); + assert!(prover_cpu_text.contains("kernel = @jolt.cpu.stage4.batched")); + assert!(verifier_cpu_text.contains("\"cpu.transcript_absorb_bytes\"(%")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify_claim\"")); + assert!(!verifier_kernel_text.contains("kernel = @")); + assert!(!verifier_cpu_text.contains("kernel = @")); + + assert_or_update_fixture( + "tests/fixtures/stage4_prover_compute.mlir", + &prover_compute_text, + ); + assert_or_update_fixture( + "tests/fixtures/stage4_verifier_compute.mlir", + &verifier_compute_text, + ); + assert_or_update_fixture( + "tests/fixtures/stage4_prover_kernel_compute.mlir", + &prover_kernel_text, + ); + assert_or_update_fixture( + "tests/fixtures/stage4_verifier_kernel_compute.mlir", + &verifier_kernel_text, + ); + assert_or_update_fixture("tests/fixtures/stage4_prover_cpu.mlir", &prover_cpu_text); + assert_or_update_fixture( + "tests/fixtures/stage4_verifier_cpu.mlir", + &verifier_cpu_text, + ); +} + +#[test] +fn jolt_stage5_protocol_defines_value_lookup_reduction_flow() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage5_protocol(&context, ¶ms).expect("build stage5 protocol"); + verify_protocol_schema(&protocol).expect("stage5 protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage5 to concrete"); + verify_concrete_transcript(&concrete).expect("stage5 transcript is threaded"); + + let text = protocol.to_text_mlir(); + assert!(text.contains("sym_name = \"stage5.instruction_read_raf.input\"")); + assert!(text.contains("sym_name = \"stage5.ram_ra_claim_reduction.input\"")); + assert!(text.contains("sym_name = \"stage5.registers_val_evaluation.input\"")); + assert!(text.contains("sym_name = \"stage5.instruction_read_raf.gamma\"")); + assert!(text.contains("sym_name = \"stage5.ram_ra_claim_reduction.gamma\"")); + assert!(text.contains("sym_name = \"stage5.instruction.lookup_output_claim_consistency\"")); + assert!(text.contains("round_schedule = [128, 18]")); + assert!(text.contains("ordered_claims = [@stage5.instruction_read_raf.input, @stage5.ram_ra_claim_reduction.input, @stage5.registers_val_evaluation.input]")); + assert!(text.contains("@stage5.instruction_read_raf.opening.LookupTableFlag_0")); + assert!(text.contains("@stage5.instruction_read_raf.opening.InstructionRa_0")); + assert!(text.contains("@stage5.instruction_read_raf.opening.InstructionRafFlag")); + assert!(text.contains("@stage5.ram_ra_claim_reduction.opening.RamRa")); + assert!(text.contains("@stage5.registers_val_evaluation.opening.RdInc")); + assert!(text.contains("@stage5.registers_val_evaluation.opening.RdWa")); + assert!(!text.contains("kernel = @")); + assert!(!text.contains("\"compute.")); +} + +#[test] +fn jolt_stage5_lowers_to_compute_and_cpu_role_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage5_protocol(&context, ¶ms).expect("build stage5 protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage5 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage5_to_compute(&context, &prover).expect("lower prover stage5"); + let verifier_compute = + lower_stage5_to_compute(&context, &verifier).expect("lower verifier stage5"); + verify_compute_schema(&prover_compute).expect("prover stage5 compute schema is valid"); + verify_compute_schema(&verifier_compute).expect("verifier stage5 compute schema is valid"); + + let prover_compute_text = prover_compute.to_text_mlir(); + let verifier_compute_text = verifier_compute.to_text_mlir(); + assert!(prover_compute_text.contains("\"compute.opening_claim_equal\"(%")); + assert!(prover_compute_text.contains("\"compute.field_pow\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_claim\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_driver\"(%")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify_claim\"")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify\"")); + assert!(!verifier_compute_text.contains("kernel = @")); + + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + verify_compute_schema(&prover_kernel_compute) + .expect("prover kernelized stage5 compute schema is valid"); + verify_compute_schema(&verifier_kernel_compute) + .expect("verifier kernelized stage5 compute schema is valid"); + + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + verify_cpu_schema(&prover_cpu).expect("prover stage5 CPU schema is valid"); + verify_cpu_schema(&verifier_cpu).expect("verifier stage5 CPU schema is valid"); + + let prover_kernel_text = prover_kernel_compute.to_text_mlir(); + let verifier_kernel_text = verifier_kernel_compute.to_text_mlir(); + let prover_cpu_text = prover_cpu.to_text_mlir(); + let verifier_cpu_text = verifier_cpu.to_text_mlir(); + assert!(prover_kernel_text.contains("kernel = @jolt.cpu.stage5.batched")); + assert!(prover_cpu_text.contains("kernel = @jolt.cpu.stage5.batched")); + assert!(prover_cpu_text.contains("point_order = \"instruction_read_raf\"")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify_claim\"")); + assert!(!verifier_kernel_text.contains("kernel = @")); + assert!(!verifier_cpu_text.contains("kernel = @")); +} + +#[test] +fn jolt_stage6_protocol_defines_bytecode_booleanity_and_virtualization_flow() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage6_protocol(&context, ¶ms).expect("build stage6 protocol"); + verify_protocol_schema(&protocol).expect("stage6 protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage6 to concrete"); + verify_concrete_transcript(&concrete).expect("stage6 transcript is threaded"); + + let text = protocol.to_text_mlir(); + assert!(text.contains("sym_name = \"stage6.bytecode_read_raf.input\"")); + assert!(text.contains("sym_name = \"stage6.booleanity.input\"")); + assert!(text.contains("sym_name = \"stage6.hamming_booleanity.input\"")); + assert!(text.contains("sym_name = \"stage6.ram_ra_virtual.input\"")); + assert!(text.contains("sym_name = \"stage6.instruction_ra_virtual.input\"")); + assert!(text.contains("sym_name = \"stage6.inc_claim_reduction.input\"")); + assert!(text.contains("sym_name = \"stage6.bytecode_read_raf.gamma\"")); + assert!(text.contains("sym_name = \"stage6.bytecode_read_raf.stage5_gamma\"")); + assert!(text.contains("sym_name = \"stage6.booleanity.gamma\"")); + assert!(text.contains("sym_name = \"stage6.instruction_ra_virtual.gamma\"")); + assert!(text.contains("sym_name = \"stage6.inc_claim_reduction.gamma\"")); + assert!(text.contains("sym_name = \"stage6.booleanity.gamma_sq_0\"")); + assert!(text.contains("source_claim = @stage2.ram_read_write.opening.RamInc")); + assert!(text.contains("source_claim = @stage4.registers_read_write.opening.RdInc")); + assert!(text.contains("source_claim = @stage5.registers_val_evaluation.opening.RdInc")); + assert!(text.contains("round_schedule = [14, 18]")); + assert!(text.contains("ordered_claims = [@stage6.bytecode_read_raf.input, @stage6.booleanity.input, @stage6.hamming_booleanity.input, @stage6.ram_ra_virtual.input, @stage6.instruction_ra_virtual.input, @stage6.inc_claim_reduction.input]")); + assert!(text.contains("@stage6.bytecode_read_raf.opening.BytecodeRa_0")); + assert!(text.contains("@stage6.booleanity.opening.InstructionRa_0")); + assert!(text.contains("@stage6.hamming_booleanity.opening.HammingWeight")); + assert!(text.contains("@stage6.ram_ra_virtual.opening.RamRa_0")); + assert!(text.contains("@stage6.instruction_ra_virtual.opening.InstructionRa_0")); + assert!(text.contains("@stage6.inc_claim_reduction.opening.RamInc")); + assert!(text.contains("@stage6.inc_claim_reduction.opening.RdInc")); + assert!(!text.contains("kernel = @")); + assert!(!text.contains("\"compute.")); +} + +#[test] +fn jolt_stage6_lowers_to_compute_and_cpu_role_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage6_protocol(&context, ¶ms).expect("build stage6 protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage6 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage6_to_compute(&context, &prover).expect("lower prover stage6"); + let verifier_compute = + lower_stage6_to_compute(&context, &verifier).expect("lower verifier stage6"); + verify_compute_schema(&prover_compute).expect("prover stage6 compute schema is valid"); + verify_compute_schema(&verifier_compute).expect("verifier stage6 compute schema is valid"); + + let prover_compute_text = prover_compute.to_text_mlir(); + let verifier_compute_text = verifier_compute.to_text_mlir(); + assert!(prover_compute_text.contains("\"compute.field_pow\"(%")); + assert!(prover_compute_text.contains("\"compute.field_zero\"()")); + assert!(prover_compute_text.contains("\"compute.sumcheck_claim\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_driver\"(%")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify_claim\"")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify\"")); + assert!(!verifier_compute_text.contains("kernel = @")); + + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + verify_compute_schema(&prover_kernel_compute) + .expect("prover kernelized stage6 compute schema is valid"); + verify_compute_schema(&verifier_kernel_compute) + .expect("verifier kernelized stage6 compute schema is valid"); + + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + verify_cpu_schema(&prover_cpu).expect("prover stage6 CPU schema is valid"); + verify_cpu_schema(&verifier_cpu).expect("verifier stage6 CPU schema is valid"); + + let prover_kernel_text = prover_kernel_compute.to_text_mlir(); + let verifier_kernel_text = verifier_kernel_compute.to_text_mlir(); + let prover_cpu_text = prover_cpu.to_text_mlir(); + let verifier_cpu_text = verifier_cpu.to_text_mlir(); + assert!(prover_kernel_text.contains("kernel = @jolt.cpu.stage6.batched")); + assert!(prover_cpu_text.contains("kernel = @jolt.cpu.stage6.batched")); + assert!(prover_cpu_text.contains("point_order = \"bytecode_read_raf\"")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify_claim\"")); + assert!(!verifier_kernel_text.contains("kernel = @")); + assert!(!verifier_cpu_text.contains("kernel = @")); +} + +#[test] +fn jolt_stage7_protocol_defines_hamming_weight_claim_reduction_flow() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage7_protocol(&context, ¶ms).expect("build stage7 protocol"); + verify_protocol_schema(&protocol).expect("stage7 protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage7 to concrete"); + verify_concrete_transcript(&concrete).expect("stage7 transcript is threaded"); + + let text = protocol.to_text_mlir(); + assert!(text.contains("sym_name = \"jolt.stage7_hamming_weight_claim_reduction_domain\"")); + assert!(text.contains("sym_name = \"jolt.stage7.hamming_weight_claim_reduction\"")); + assert!(text.contains("sym_name = \"jolt.stage7.batched\"")); + assert!(text.contains("sym_name = \"stage7.hamming_weight_claim_reduction.gamma\"")); + assert!(text.contains("sym_name = \"stage7.field.one\"")); + assert!(text.contains("sym_name = \"stage7.hamming_weight_claim_reduction.input\"")); + assert!(text.contains("round_schedule = [4]")); + assert!(text.contains("ordered_claims = [@stage7.hamming_weight_claim_reduction.input]")); + assert!(text.contains("source_claim = @stage6.booleanity.opening.InstructionRa_0")); + assert!(text.contains("source_claim = @stage6.instruction_ra_virtual.opening.InstructionRa_0")); + assert!(text.contains("source_claim = @stage6.bytecode_read_raf.opening.BytecodeRa_0")); + assert!(text.contains("source_claim = @stage6.ram_ra_virtual.opening.RamRa_0")); + assert!(text.contains("source_claim = @stage6.hamming_booleanity.opening.HammingWeight")); + assert!(text.contains("sym_name = \"stage7.hamming_weight_claim_reduction.point.cycle\"")); + assert!(text.contains("sym_name = \"stage7.hamming_weight_claim_reduction.point\"")); + assert!(text.contains("@stage7.hamming_weight_claim_reduction.opening.InstructionRa_0")); + assert!(text.contains("@stage7.hamming_weight_claim_reduction.opening.BytecodeRa_0")); + assert!(text.contains("@stage7.hamming_weight_claim_reduction.opening.RamRa_0")); + assert!(!text.contains("kernel = @")); + assert!(!text.contains("\"compute.")); +} + +#[test] +fn jolt_stage7_lowers_to_compute_and_cpu_role_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage7_protocol(&context, ¶ms).expect("build stage7 protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage7 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage7_to_compute(&context, &prover).expect("lower prover stage7"); + let verifier_compute = + lower_stage7_to_compute(&context, &verifier).expect("lower verifier stage7"); + verify_compute_schema(&prover_compute).expect("prover stage7 compute schema is valid"); + verify_compute_schema(&verifier_compute).expect("verifier stage7 compute schema is valid"); + + let prover_compute_text = prover_compute.to_text_mlir(); + let verifier_compute_text = verifier_compute.to_text_mlir(); + assert!(prover_compute_text.contains("\"compute.field_one\"()")); + assert!(prover_compute_text.contains("\"compute.field_pow\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_claim\"(%")); + assert!(prover_compute_text.contains("\"compute.sumcheck_driver\"(%")); + assert!(prover_compute_text.contains("\"compute.point_concat\"(%")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify_claim\"")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify\"")); + assert!(!verifier_compute_text.contains("kernel = @")); + + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + verify_compute_schema(&prover_kernel_compute) + .expect("prover kernelized stage7 compute schema is valid"); + verify_compute_schema(&verifier_kernel_compute) + .expect("verifier kernelized stage7 compute schema is valid"); + + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + verify_cpu_schema(&prover_cpu).expect("prover stage7 CPU schema is valid"); + verify_cpu_schema(&verifier_cpu).expect("verifier stage7 CPU schema is valid"); + + let prover_kernel_text = prover_kernel_compute.to_text_mlir(); + let verifier_kernel_text = verifier_kernel_compute.to_text_mlir(); + let prover_cpu_text = prover_cpu.to_text_mlir(); + let verifier_cpu_text = verifier_cpu.to_text_mlir(); + assert!(prover_kernel_text.contains("kernel = @jolt.cpu.stage7.batched")); + assert!(prover_cpu_text.contains("kernel = @jolt.cpu.stage7.batched")); + assert!(prover_cpu_text.contains("point_order = \"reverse\"")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify_claim\"")); + assert!(!verifier_kernel_text.contains("kernel = @")); + assert!(!verifier_cpu_text.contains("kernel = @")); +} + +#[test] +fn jolt_stage8_protocol_defines_evaluation_proof_flow() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage8_protocol(&context, ¶ms).expect("build stage8 protocol"); + verify_protocol_schema(&protocol).expect("stage8 protocol schema is valid"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage8 to concrete"); + verify_concrete_transcript(&concrete).expect("stage8 transcript is threaded"); + + let text = protocol.to_text_mlir(); + assert!(text.contains("sym_name = \"jolt.stage8\"")); + assert!(text.contains("name = \"evaluation_proof\"")); + assert!(text.contains("sym_name = \"stage8.evaluation.point_source\"")); + assert!(text.contains("source_claim = @stage7.input.stage6.booleanity.InstructionRa_0")); + assert!(text.contains("sym_name = \"stage8.evaluation.opening.RamInc\"")); + assert!(text.contains("source_claim = @stage6.inc_claim_reduction.eval.RamInc")); + assert!(text.contains("sym_name = \"stage8.evaluation.opening.InstructionRa_0\"")); + assert!( + text.contains("source_claim = @stage7.hamming_weight_claim_reduction.eval.InstructionRa_0") + ); + assert!(text.contains("\"pcs.opening_batch\"(%")); + assert!(text.contains("policy = \"jolt_stage8_joint_rlc\"")); + assert!(text.contains("transcript_label = \"rlc_claims\"")); + assert!(text.contains("ordered_claims = [@stage8.evaluation.opening.RamInc, @stage8.evaluation.opening.RdInc, @stage8.evaluation.opening.InstructionRa_0")); +} + +#[test] +fn jolt_stage8_lowers_to_compute_and_cpu_role_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_stage8_protocol(&context, ¶ms).expect("build stage8 protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage8 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage8_to_compute(&context, &prover).expect("lower prover stage8"); + let verifier_compute = + lower_stage8_to_compute(&context, &verifier).expect("lower verifier stage8"); + verify_compute_schema(&prover_compute).expect("prover stage8 compute schema is valid"); + verify_compute_schema(&verifier_compute).expect("verifier stage8 compute schema is valid"); + + let prover_compute_text = prover_compute.to_text_mlir(); + let verifier_compute_text = verifier_compute.to_text_mlir(); + assert!(prover_compute_text.contains("\"compute.pcs_opening_claim\"(%")); + assert!(prover_compute_text.contains("\"compute.pcs_opening_batch\"(%")); + assert!(prover_compute_text.contains("\"compute.pcs_batch_open\"(%")); + assert!(!prover_compute_text.contains("\"compute.pcs_batch_verify\"(%")); + assert!(verifier_compute_text.contains("\"compute.pcs_batch_verify\"(%")); + assert!(!verifier_compute_text.contains("\"compute.pcs_batch_open\"(%")); + + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + verify_compute_schema(&prover_kernel_compute) + .expect("prover kernelized stage8 compute schema is valid"); + verify_compute_schema(&verifier_kernel_compute) + .expect("verifier kernelized stage8 compute schema is valid"); + + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + verify_cpu_schema(&prover_cpu).expect("prover stage8 CPU schema is valid"); + verify_cpu_schema(&verifier_cpu).expect("verifier stage8 CPU schema is valid"); + + let prover_cpu_text = prover_cpu.to_text_mlir(); + let verifier_cpu_text = verifier_cpu.to_text_mlir(); + assert!(prover_cpu_text.contains("\"cpu.pcs_batch_open\"(%")); + assert!(verifier_cpu_text.contains("\"cpu.pcs_batch_verify\"(%")); + assert!(!prover_cpu_text.contains("kernel = @")); + assert!(!verifier_cpu_text.contains("kernel = @")); +} + +#[test] +fn stage2_rust_targets_extract_and_compile() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let (prover_cpu, verifier_cpu) = build_stage2_pipeline_cpu(&context, ¶ms); + let prover_program = stage2_cpu_program(&prover_cpu).expect("extract prover stage2 program"); + let verifier_program = + stage2_cpu_program(&verifier_cpu).expect("extract verifier stage2 program"); + + assert_eq!(prover_program.role, Role::Prover); + assert_eq!(verifier_program.role, Role::Verifier); + assert_eq!(prover_program.kernels.len(), 7); + assert!(verifier_program.kernels.is_empty()); + assert_eq!(prover_program.opening_inputs.len(), 11); + assert_eq!(prover_program.field_exprs.len(), 21); + assert_eq!(prover_program.field_constants.len(), 1); + assert_eq!(prover_program.claims.len(), 6); + assert_eq!(prover_program.drivers.len(), 2); + assert_eq!(prover_program.point_slices.len(), 1); + assert_eq!(prover_program.point_concats.len(), 1); + assert!(prover_program + .claims + .iter() + .any(|claim| claim.claim_value == "stage2.ram_read_write.claim_expr")); + assert!(prover_program + .drivers + .iter() + .any(|driver| driver.kernel.as_deref() == Some("jolt.cpu.stage2.batched"))); + assert!(verifier_program + .claims + .iter() + .all(|claim| claim.kernel.is_none() && claim.relation.is_some())); + assert!(verifier_program + .drivers + .iter() + .all(|driver| driver.kernel.is_none() && driver.relation.is_some())); + + let prover_source = emit_stage2_rust(&prover_cpu).expect("emit stage2 prover rust"); + let verifier_source = emit_stage2_rust(&verifier_cpu).expect("emit stage2 verifier rust"); + assert_eq!(prover_source.filename, "prove_stage2.rs"); + assert_eq!(verifier_source.filename, "verify_stage2.rs"); + assert!(prover_source.source.contains("jolt_stage2_ram_read_write")); + assert!(prover_source.source.contains("Stage2KernelExecutor")); + assert!(!verifier_source.source.contains("jolt_kernels")); + assert!(verifier_source.source.contains("Stage2VerifierProgramPlan")); + assert!(verifier_source.source.contains("pub fn verify_stage2")); + assert!(verifier_source.source.contains("SumcheckVerifier::verify")); + assert_or_update_fixture("tests/fixtures/prove_stage2.rs", &prover_source.source); + assert_or_update_fixture("tests/fixtures/verify_stage2.rs", &verifier_source.source); + assert_rust_source_compiles(&prover_source.filename, &prover_source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn stage3_rust_targets_extract_and_compile() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let (prover_cpu, verifier_cpu) = build_stage3_pipeline_cpu(&context, ¶ms); + let prover_program = stage3_cpu_program(&prover_cpu).expect("extract prover stage3 program"); + let verifier_program = + stage3_cpu_program(&verifier_cpu).expect("extract verifier stage3 program"); + + assert_eq!(prover_program.role, Role::Prover); + assert_eq!(verifier_program.role, Role::Verifier); + assert_eq!(prover_program.kernels.len(), 4); + assert!(verifier_program.kernels.is_empty()); + assert_eq!(prover_program.opening_inputs.len(), 12); + assert_eq!(prover_program.field_exprs.len(), 19); + assert_eq!(prover_program.field_constants.len(), 1); + assert_eq!(prover_program.opening_equalities.len(), 2); + assert_eq!(prover_program.claims.len(), 3); + assert_eq!(prover_program.drivers.len(), 1); + assert_eq!(prover_program.opening_claims.len(), 16); + assert!(prover_program + .drivers + .iter() + .any(|driver| driver.kernel.as_deref() == Some("jolt.cpu.stage3.batched"))); + assert!(verifier_program + .claims + .iter() + .all(|claim| claim.kernel.is_none() && claim.relation.is_some())); + assert!(verifier_program + .drivers + .iter() + .all(|driver| driver.kernel.is_none() && driver.relation.is_some())); + + let prover_source = emit_stage3_rust(&prover_cpu).expect("emit stage3 prover rust"); + let verifier_source = emit_stage3_rust(&verifier_cpu).expect("emit stage3 verifier rust"); + assert_eq!(prover_source.filename, "prove_stage3.rs"); + assert_eq!(verifier_source.filename, "verify_stage3.rs"); + assert!(prover_source.source.contains("jolt_stage3_spartan_shift")); + assert!(prover_source.source.contains("Stage3KernelExecutor")); + assert!(prover_source + .source + .contains("Stage3OpeningClaimEqualityPlan")); + assert!(!verifier_source.source.contains("jolt_kernels")); + assert!(verifier_source.source.contains("Stage3VerifierProgramPlan")); + assert!(verifier_source.source.contains("pub fn verify_stage3")); + assert!(verifier_source + .source + .contains("super::common::verify_batched_sumcheck")); + assert!(verifier_source + .source + .contains("Stage3OpeningClaimEqualityPlan")); + assert_or_update_fixture("tests/fixtures/prove_stage3.rs", &prover_source.source); + assert_or_update_fixture("tests/fixtures/verify_stage3.rs", &verifier_source.source); + assert_rust_source_compiles(&prover_source.filename, &prover_source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn stage4_rust_targets_extract_and_compile() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let (prover_cpu, verifier_cpu) = build_stage4_pipeline_cpu(&context, ¶ms); + let prover_program = stage4_cpu_program(&prover_cpu).expect("extract prover stage4 program"); + let verifier_program = + stage4_cpu_program(&verifier_cpu).expect("extract verifier stage4 program"); + + assert_eq!(prover_program.role, Role::Prover); + assert_eq!(verifier_program.role, Role::Verifier); + assert_eq!(prover_program.kernels.len(), 3); + assert!(verifier_program.kernels.is_empty()); + assert_eq!(prover_program.steps.len(), 4); + assert_eq!(prover_program.transcript_squeezes.len(), 2); + assert_eq!(prover_program.transcript_absorb_bytes.len(), 1); + assert_eq!(prover_program.opening_inputs.len(), 8); + assert_eq!(prover_program.field_exprs.len(), 9); + assert!(prover_program.field_constants.is_empty()); + assert_eq!(prover_program.opening_equalities.len(), 2); + assert_eq!(prover_program.claims.len(), 2); + assert_eq!(prover_program.drivers.len(), 1); + assert_eq!(prover_program.instance_results.len(), 2); + assert_eq!(prover_program.evals.len(), 7); + assert_eq!(prover_program.point_slices.len(), 2); + assert_eq!(prover_program.point_concats.len(), 1); + assert_eq!(prover_program.opening_claims.len(), 7); + assert_eq!(prover_program.opening_batches.len(), 1); + assert!(prover_program + .transcript_absorb_bytes + .iter() + .any( + |absorb| absorb.symbol == "stage4.ram_val_check.domain_separator" + && absorb.label == "ram_val_check_gamma" + && absorb.payload.is_empty() + )); + assert!(prover_program + .drivers + .iter() + .any(|driver| driver.kernel.as_deref() == Some("jolt.cpu.stage4.batched"))); + assert!(verifier_program + .claims + .iter() + .all(|claim| claim.kernel.is_none() && claim.relation.is_some())); + assert!(verifier_program + .drivers + .iter() + .all(|driver| driver.kernel.is_none() && driver.relation.is_some())); + + let prover_source = emit_stage4_rust(&prover_cpu).expect("emit stage4 prover rust"); + let verifier_source = emit_stage4_rust(&verifier_cpu).expect("emit stage4 verifier rust"); + assert_eq!(prover_source.filename, "prove_stage4.rs"); + assert_eq!(verifier_source.filename, "verify_stage4.rs"); + assert!(prover_source.source.contains("jolt_stage4_ram_val_check")); + assert!(prover_source + .source + .contains("Stage4TranscriptAbsorbBytesPlan")); + assert!(prover_source + .source + .contains("STAGE4_TRANSCRIPT_ABSORB_BYTES")); + assert!(prover_source.source.contains("Stage4KernelExecutor")); + assert!(prover_source.source.contains("execute_stage4_program")); + assert!(prover_source.source.contains("execute_stage4_prover")); + assert!(!verifier_source.source.contains("jolt_kernels")); + assert!(verifier_source + .source + .contains("Stage4TranscriptAbsorbBytesPlan")); + assert!(verifier_source + .source + .contains("relation: Some(\"jolt.stage4.batched\")")); + assert!(verifier_source.source.contains("Stage4VerifierProgramPlan")); + assert!(verifier_source.source.contains("pub fn verify_stage4")); + assert!(verifier_source.source.contains("LabelWithCount")); + assert!(verifier_source + .source + .contains("super::common::verify_batched_sumcheck")); + assert!(verifier_source.source.contains("stage4_verifier_program")); + assert_or_update_fixture("tests/fixtures/prove_stage4.rs", &prover_source.source); + assert_or_update_fixture("tests/fixtures/verify_stage4.rs", &verifier_source.source); + assert_rust_source_compiles(&prover_source.filename, &prover_source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn stage5_rust_targets_extract_and_compile() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let (prover_cpu, verifier_cpu) = build_stage5_pipeline_cpu(&context, ¶ms); + let prover_program = stage5_cpu_program(&prover_cpu).expect("extract prover stage5 program"); + let verifier_program = + stage5_cpu_program(&verifier_cpu).expect("extract verifier stage5 program"); + + assert_eq!(prover_program.role, Role::Prover); + assert_eq!(verifier_program.role, Role::Verifier); + assert_eq!(prover_program.kernels.len(), 4); + assert!(verifier_program.kernels.is_empty()); + assert_eq!(prover_program.steps.len(), 3); + assert_eq!(prover_program.transcript_squeezes.len(), 2); + assert!(prover_program.transcript_absorb_bytes.is_empty()); + assert_eq!(prover_program.opening_inputs.len(), 8); + assert_eq!(prover_program.field_exprs.len(), 10); + assert!(prover_program.field_constants.is_empty()); + assert_eq!(prover_program.opening_equalities.len(), 1); + assert_eq!(prover_program.claims.len(), 3); + assert_eq!(prover_program.drivers.len(), 1); + assert_eq!(prover_program.instance_results.len(), 3); + assert_eq!( + prover_program.evals.len(), + params.lookup_table_count + params.instruction_ra_virtual_d + 4 + ); + assert_eq!( + prover_program.point_slices.len(), + params.instruction_ra_virtual_d + 3 + ); + assert_eq!( + prover_program.point_concats.len(), + params.instruction_ra_virtual_d + 2 + ); + assert_eq!( + prover_program.opening_claims.len(), + params.lookup_table_count + params.instruction_ra_virtual_d + 4 + ); + assert_eq!(prover_program.opening_batches.len(), 1); + assert!(prover_program + .drivers + .iter() + .any(|driver| driver.kernel.as_deref() == Some("jolt.cpu.stage5.batched"))); + assert!(prover_program.instance_results.iter().any(|instance| { + instance.symbol == "stage5.instruction_read_raf.instance" + && instance.point_order == "instruction_read_raf" + })); + assert!(verifier_program + .claims + .iter() + .all(|claim| claim.kernel.is_none() && claim.relation.is_some())); + assert!(verifier_program + .drivers + .iter() + .all(|driver| driver.kernel.is_none() && driver.relation.is_some())); + + let prover_source = emit_stage5_rust(&prover_cpu).expect("emit stage5 prover rust"); + let verifier_source = emit_stage5_rust(&verifier_cpu).expect("emit stage5 verifier rust"); + assert_eq!(prover_source.filename, "prove_stage5.rs"); + assert_eq!(verifier_source.filename, "verify_stage5.rs"); + assert!(prover_source + .source + .contains("jolt_stage5_instruction_read_raf")); + assert!(prover_source.source.contains("Stage5KernelExecutor")); + assert!(prover_source.source.contains("execute_stage5_program")); + assert!(prover_source.source.contains("execute_stage5_prover")); + assert!(!verifier_source.source.contains("jolt_kernels")); + assert!(verifier_source.source.contains("Stage5VerifierProgramPlan")); + assert!(verifier_source.source.contains("pub fn verify_stage5")); + assert!(verifier_source + .source + .contains("relation: Some(\"jolt.stage5.batched\")")); + assert!(verifier_source + .source + .contains("expected_instruction_read_raf")); + assert!(verifier_source + .source + .contains("jolt.stage5.instruction_read_raf")); + assert!(verifier_source + .source + .contains("LookupTableKind::::all")); + assert!(verifier_source + .source + .contains("use jolt_lookup_tables::LookupTableKind")); + assert!(verifier_source + .source + .contains("expected_ram_ra_claim_reduction")); + assert!(verifier_source + .source + .contains("expected_registers_val_evaluation")); + assert!(verifier_source + .source + .contains("jolt.stage5.ram_ra_claim_reduction")); + assert!(verifier_source + .source + .contains("jolt.stage5.registers_val_evaluation")); + assert!(verifier_source.source.contains("LookupTableFlag_39")); + assert!(!verifier_source.source.contains("LookupTableFlag_40")); + assert!(verifier_source + .source + .contains("stage5.instruction_read_raf.eval.InstructionRa_7")); + assert!(!verifier_source + .source + .contains("stage5.instruction_read_raf.eval.InstructionRa_8")); + assert!(!verifier_source + .source + .contains("jolt.stage5.registers_read_write")); + assert!(!verifier_source.source.contains("jolt.stage5.ram_val_check")); + assert!(verifier_source + .source + .contains("super::common::verify_batched_sumcheck")); + assert!(verifier_source.source.contains("stage5_verifier_program")); + assert_rust_source_compiles(&prover_source.filename, &prover_source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn stage6_rust_targets_extract_and_compile() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let (prover_cpu, verifier_cpu) = build_stage6_pipeline_cpu(&context, ¶ms); + let prover_program = stage6_cpu_program(&prover_cpu).expect("extract prover stage6 program"); + let verifier_program = + stage6_cpu_program(&verifier_cpu).expect("extract verifier stage6 program"); + + assert_eq!(prover_program.role, Role::Prover); + assert_eq!(verifier_program.role, Role::Verifier); + assert_eq!(prover_program.kernels.len(), 7); + assert!(verifier_program.kernels.is_empty()); + assert_eq!(prover_program.steps.len(), 10); + assert_eq!(prover_program.transcript_squeezes.len(), 9); + assert!(prover_program.transcript_absorb_bytes.is_empty()); + assert_eq!(prover_program.opening_inputs.len(), 90); + assert!(prover_program.field_exprs.len() > 150); + assert_eq!(prover_program.field_constants.len(), 1); + assert!(prover_program.opening_equalities.is_empty()); + assert_eq!(prover_program.claims.len(), 6); + assert_eq!(prover_program.drivers.len(), 1); + assert_eq!(prover_program.instance_results.len(), 6); + assert_eq!( + prover_program.evals.len(), + params.bytecode_d + + params.instruction_d + + params.bytecode_d + + params.ram_d + + 1 + + params.ram_d + + params.instruction_d + + 2 + ); + assert_eq!(prover_program.point_zeros.len(), 2); + assert_eq!( + prover_program.point_slices.len(), + params.bytecode_d + 1 + params.ram_d + params.instruction_d + ); + assert_eq!( + prover_program.point_concats.len(), + params.bytecode_d + 1 + params.ram_d + params.instruction_d + 1 + ); + assert_eq!( + prover_program.opening_claims.len(), + prover_program.evals.len() + ); + assert_eq!(prover_program.opening_batches.len(), 1); + assert!(prover_program + .drivers + .iter() + .any(|driver| driver.kernel.as_deref() == Some("jolt.cpu.stage6.batched"))); + assert!(prover_program.instance_results.iter().any(|instance| { + instance.symbol == "stage6.bytecode_read_raf.instance" + && instance.point_order == "bytecode_read_raf" + })); + assert!(prover_program.instance_results.iter().any(|instance| { + instance.symbol == "stage6.booleanity.instance" + && instance.point_order == "stage6_booleanity" + })); + assert!(verifier_program.opening_inputs.iter().any(|input| { + input.symbol == "stage6.input.stage1.LookupOutput" + && input.source_stage == "stage1" + && input.source_claim == "stage1.outer_remaining.opening.LookupOutput" + })); + assert!(verifier_program.claims.iter().any(|claim| { + claim.symbol == "stage6.hamming_booleanity.input" + && claim + .input_openings + .contains(&"stage6.input.stage1.LookupOutput".to_owned()) + })); + assert!(verifier_program.claims.iter().any(|claim| { + claim.symbol == "stage6.booleanity.input" && claim.input_openings.is_empty() + })); + assert!(verifier_program + .claims + .iter() + .all(|claim| claim.kernel.is_none() && claim.relation.is_some())); + assert!(verifier_program + .drivers + .iter() + .all(|driver| driver.kernel.is_none() && driver.relation.is_some())); + + let prover_source = emit_stage6_rust(&prover_cpu).expect("emit stage6 prover rust"); + let verifier_source = emit_stage6_rust(&verifier_cpu).expect("emit stage6 verifier rust"); + assert_eq!(prover_source.filename, "prove_stage6.rs"); + assert_eq!(verifier_source.filename, "verify_stage6.rs"); + assert!(prover_source + .source + .contains("jolt_stage6_bytecode_read_raf")); + assert!(prover_source.source.contains("Stage6KernelExecutor")); + assert!(prover_source.source.contains("execute_stage6_program")); + assert!(prover_source.source.contains("execute_stage6_prover")); + assert!(!verifier_source.source.contains("jolt_kernels")); + assert!(verifier_source.source.contains("Stage6VerifierProgramPlan")); + assert!(verifier_source.source.contains("pub fn verify_stage6")); + assert!(verifier_source + .source + .contains("relation: Some(\"jolt.stage6.batched\")")); + assert!(verifier_source + .source + .contains("jolt.stage6.bytecode_read_raf")); + assert!(verifier_source.source.contains("Stage6VerifierData")); + assert!(verifier_source.source.contains("Stage6BytecodeReadRafData")); + assert!(verifier_source.source.contains("Stage6BytecodeEntry")); + assert!(verifier_source + .source + .contains("expected_bytecode_read_raf")); + assert!(verifier_source + .source + .contains("stage6.bytecode_read_raf.data")); + assert!(verifier_source.source.contains("expected_booleanity")); + assert!(verifier_source + .source + .contains("expected_hamming_booleanity")); + assert!(verifier_source + .source + .contains("jolt.stage6.inc_claim_reduction")); + assert!(verifier_source + .source + .contains("stage6.input.stage1.LookupOutput")); + assert!(verifier_source.source.contains("expected_ram_ra_virtual")); + assert!(verifier_source + .source + .contains("expected_instruction_ra_virtual")); + assert!(verifier_source + .source + .contains("expected_inc_claim_reduction")); + assert!(verifier_source + .source + .contains("stage6.bytecode_read_raf.eval.BytecodeRa_0")); + assert!(verifier_source + .source + .contains("stage6.booleanity.eval.InstructionRa_31")); + assert!(verifier_source + .source + .contains("stage6.inc_claim_reduction.eval.RdInc")); + assert!(verifier_source + .source + .contains("super::common::verify_batched_sumcheck")); + assert!(verifier_source.source.contains("stage6_verifier_program")); + assert_rust_source_compiles(&prover_source.filename, &prover_source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn stage7_rust_targets_extract_and_compile() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let total_ra = params.instruction_d + params.bytecode_d + params.ram_d; + let (prover_cpu, verifier_cpu) = build_stage7_pipeline_cpu(&context, ¶ms); + let prover_program = stage7_cpu_program(&prover_cpu).expect("extract prover stage7 program"); + let verifier_program = + stage7_cpu_program(&verifier_cpu).expect("extract verifier stage7 program"); + + assert_eq!(prover_program.role, Role::Prover); + assert_eq!(verifier_program.role, Role::Verifier); + assert_eq!(prover_program.kernels.len(), 2); + assert!(verifier_program.kernels.is_empty()); + assert_eq!(prover_program.steps.len(), 2); + assert_eq!(prover_program.transcript_squeezes.len(), 1); + assert!(prover_program.transcript_absorb_bytes.is_empty()); + assert_eq!(prover_program.opening_inputs.len(), 1 + 2 * total_ra); + assert_eq!(prover_program.field_constants.len(), 1); + assert!(prover_program.field_exprs.len() >= 3 * total_ra); + assert_eq!(prover_program.claims.len(), 1); + assert_eq!(prover_program.batches.len(), 1); + assert_eq!(prover_program.drivers.len(), 1); + assert_eq!(prover_program.instance_results.len(), 1); + assert_eq!(prover_program.evals.len(), total_ra); + assert!(prover_program.point_zeros.is_empty()); + assert_eq!(prover_program.point_slices.len(), 1); + assert_eq!(prover_program.point_concats.len(), 1); + assert_eq!(prover_program.opening_claims.len(), total_ra); + assert_eq!(prover_program.opening_batches.len(), 1); + assert!(prover_program + .drivers + .iter() + .any(|driver| driver.kernel.as_deref() == Some("jolt.cpu.stage7.batched"))); + assert!(prover_program.claims.iter().any(|claim| { + claim.symbol == "stage7.hamming_weight_claim_reduction.input" + && claim.kernel.as_deref() == Some("jolt.cpu.stage7.hamming_weight_claim_reduction") + })); + assert!(prover_program.opening_claims.iter().any(|claim| { + claim.symbol == "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0" + && claim.point_source == "stage7.hamming_weight_claim_reduction.point" + })); + assert!(verifier_program + .claims + .iter() + .all(|claim| claim.kernel.is_none() && claim.relation.is_some())); + assert!(verifier_program + .drivers + .iter() + .all(|driver| driver.kernel.is_none() && driver.relation.is_some())); + + let prover_source = emit_stage7_rust(&prover_cpu).expect("emit stage7 prover rust"); + let verifier_source = emit_stage7_rust(&verifier_cpu).expect("emit stage7 verifier rust"); + assert_eq!(prover_source.filename, "prove_stage7.rs"); + assert_eq!(verifier_source.filename, "verify_stage7.rs"); + assert!(prover_source + .source + .contains("jolt_stage7_hamming_weight_claim_reduction")); + assert!(prover_source.source.contains("Stage7KernelExecutor")); + assert!(prover_source.source.contains("execute_stage7_program")); + assert!(prover_source.source.contains("execute_stage7_prover")); + assert!(!verifier_source.source.contains("jolt_kernels")); + assert!(verifier_source.source.contains("Stage7VerifierProgramPlan")); + assert!(verifier_source.source.contains("pub fn verify_stage7")); + assert!(verifier_source + .source + .contains("relation: Some(\"jolt.stage7.batched\")")); + assert!(verifier_source + .source + .contains("jolt.stage7.hamming_weight_claim_reduction")); + assert!(verifier_source + .source + .contains("expected_hamming_weight_claim_reduction")); + assert!(verifier_source + .source + .contains("stage7.input.stage6.booleanity.InstructionRa_0")); + assert!(verifier_source + .source + .contains("stage7.hamming_weight_claim_reduction.eval.InstructionRa_0")); + assert!(verifier_source + .source + .contains("super::common::verify_batched_sumcheck")); + assert!(verifier_source.source.contains("stage7_verifier_program")); + assert_rust_source_compiles(&prover_source.filename, &prover_source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn stage8_rust_targets_extract_and_compile() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let expected_claims = params.num_committed; + let (prover_cpu, verifier_cpu) = build_stage8_pipeline_cpu(&context, ¶ms); + let prover_program = stage8_cpu_program(&prover_cpu).expect("extract prover stage8 program"); + let verifier_program = + stage8_cpu_program(&verifier_cpu).expect("extract verifier stage8 program"); + + assert_eq!(prover_program.role, Role::Prover); + assert_eq!(verifier_program.role, Role::Verifier); + assert_eq!(prover_program.opening_inputs.len(), expected_claims + 1); + assert_eq!(prover_program.opening_claims.len(), expected_claims); + assert_eq!(prover_program.opening_batches.len(), 1); + assert_eq!(prover_program.pcs_proofs.len(), 1); + assert_eq!(prover_program.pcs_proofs[0].mode, "open"); + assert_eq!(verifier_program.pcs_proofs[0].mode, "verify"); + assert_eq!( + prover_program.opening_batches[0].ordered_claims, + prover_program.opening_batches[0].claim_operands + ); + assert!(prover_program.opening_claims.iter().any(|claim| { + claim.symbol == "stage8.evaluation.opening.RamInc" + && claim.source_claim == "stage6.inc_claim_reduction.eval.RamInc" + })); + assert!(prover_program.opening_claims.iter().any(|claim| { + claim.symbol == "stage8.evaluation.opening.InstructionRa_0" + && claim.source_claim == "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0" + })); + + let prover_source = emit_stage8_rust(&prover_cpu).expect("emit stage8 prover rust"); + let verifier_source = emit_stage8_rust(&verifier_cpu).expect("emit stage8 verifier rust"); + assert_eq!(prover_source.filename, "prove_stage8.rs"); + assert_eq!(verifier_source.filename, "verify_stage8.rs"); + assert!(prover_source.source.contains("pub const STAGE8_PROGRAM")); + assert!(prover_source + .source + .contains("stage8.evaluation.point_source")); + assert!(prover_source.source.contains("jolt_stage8_joint_rlc")); + assert!(prover_source + .source + .contains("stage6.inc_claim_reduction.eval.RamInc")); + assert!(prover_source + .source + .contains("stage7.hamming_weight_claim_reduction.eval.InstructionRa_0")); + assert!(verifier_source.source.contains("mode: \"verify\"")); + assert_rust_source_compiles(&prover_source.filename, &prover_source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn stage4_generated_artifact_crates_compile_in_isolation() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let (prover_cpu, verifier_cpu) = build_stage4_pipeline_cpu(&context, ¶ms); + let stage = ProtocolStage::new("stage4", "stage4", 4, ProtocolStageKind::Proof); + let config = jolt_artifact_config(); + let artifacts = vec![ + protocol_rust_artifact( + &config, + stage.clone(), + Role::Prover, + emit_stage4_rust(&prover_cpu).expect("emit stage4 prover"), + ), + protocol_rust_artifact( + &config, + stage, + Role::Verifier, + emit_stage4_rust(&verifier_cpu).expect("emit stage4 verifier"), + ), + ]; + for artifact in &artifacts { + validate_jolt_rust_artifact_imports(artifact).expect("stage4 import policy"); + } + + let output_root = new_temp_dir("bolt_stage4_generated_crates"); + let dependency_root = workspace_root().join("crates"); + let generated_crates = + assemble_jolt_generated_crates(artifacts, &dependency_root.display().to_string()) + .expect("assemble stage4 crates"); + write_jolt_generated_crates(&generated_crates, &output_root) + .expect("write stage4 generated crates"); + redirect_generated_prover_to_generated_verifier(&output_root, &dependency_root); + for generated in &generated_crates { + assert_generated_crate_manifest_compiles(&output_root, &generated.crate_name); + } + let _ = std::fs::remove_dir_all(output_root); +} + +#[test] +fn generic_artifact_assembly_supports_non_jolt_protocol_config() { + let config = non_jolt_artifact_config(); + let stage = ProtocolStage::new("alpha", "alpha", 1, ProtocolStageKind::Proof); + let artifacts = vec![ + protocol_rust_artifact( + &config, + stage.clone(), + Role::Prover, + non_jolt_alpha_prover_source(), + ), + protocol_rust_artifact( + &config, + stage, + Role::Verifier, + non_jolt_alpha_verifier_source(), + ), + ]; + assert_eq!( + artifacts[0].path, "acme-prover/src/stages/alpha.rs", + "generic artifact path should derive from config and stage module" + ); + assert_eq!( + artifacts[1].path, "acme-verifier/src/stages/alpha.rs", + "generic artifact path should derive from config and stage module" + ); + for artifact in &artifacts { + validate_rust_artifact_imports(&config, artifact).expect("generic import policy"); + } + + let generated = + assemble_generated_crates(&config, artifacts, "../deps").expect("assemble generic crates"); + let prover = generated + .iter() + .find(|generated| generated.crate_name == "acme-prover") + .expect("generated prover crate"); + let verifier = generated + .iter() + .find(|generated| generated.crate_name == "acme-verifier") + .expect("generated verifier crate"); + + let prover_manifest = prover + .files + .iter() + .find(|file| file.path == "Cargo.toml") + .expect("prover manifest") + .source + .as_str(); + assert!(prover_manifest.contains("name = \"acme-prover\"")); + assert!(prover_manifest.contains("acme-verifier = { path = \"../deps/acme-verifier\" }")); + assert!(prover_manifest.contains("serde = { version = \"1\", default-features = false }")); + assert!(!prover_manifest.contains("serde = { path = ")); + assert!(prover.files.iter().any(|file| file.path == "src/prover.rs")); + assert!(prover + .files + .iter() + .any(|file| file.path == "src/stages/alpha.rs")); + + let verifier_stages = verifier + .files + .iter() + .find(|file| file.path == "src/stages/mod.rs") + .expect("verifier stages module") + .source + .as_str(); + assert!(verifier_stages.contains("pub mod shared;")); + assert!(verifier_stages.contains("pub mod alpha;")); + assert!(verifier + .files + .iter() + .any(|file| file.path == "src/verifier.rs")); + assert!(verifier + .files + .iter() + .any(|file| file.path == "src/stages/shared.rs")); + + let generated_surface = generated + .iter() + .flat_map(|generated| generated.files.iter()) + .map(|file| file.source.as_str()) + .collect::>() + .join("\n"); + assert!( + !generated_surface.contains("jolt") && !generated_surface.contains("Jolt"), + "generic artifact assembly leaked Jolt names into a non-Jolt protocol fixture" + ); + assert!( + !generated_surface.contains("ark-bn254") && !generated_surface.contains("arkworks-algebra"), + "generic artifact assembly leaked Jolt/arkworks standalone manifest patches into a non-Jolt protocol fixture" + ); + assert!(generated_surface.contains("pub const TRANSCRIPT_LABEL: &[u8] = b\"acme transcript\";")); + assert!(generated_surface.contains("crate::stages::shared::StageProof")); + assert!(generated_surface.contains("pub fn prove_acme")); + assert!(generated_surface.contains("pub fn verify_acme")); +} + +#[test] +fn generated_jolt_artifacts_have_uniform_crate_layout_and_import_rules() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let (commitment_prover_cpu, commitment_verifier_cpu) = + build_commitment_pipeline_cpu(&context, ¶ms); + let (stage1_prover_cpu, stage1_verifier_cpu) = build_stage1_pipeline_cpu(&context, ¶ms); + let (stage2_prover_cpu, stage2_verifier_cpu) = build_stage2_pipeline_cpu(&context, ¶ms); + let (stage3_prover_cpu, stage3_verifier_cpu) = build_stage3_pipeline_cpu(&context, ¶ms); + let (stage4_prover_cpu, stage4_verifier_cpu) = build_stage4_pipeline_cpu(&context, ¶ms); + let (stage5_prover_cpu, stage5_verifier_cpu) = build_stage5_pipeline_cpu(&context, ¶ms); + let (stage6_prover_cpu, stage6_verifier_cpu) = build_stage6_pipeline_cpu(&context, ¶ms); + let (stage7_prover_cpu, stage7_verifier_cpu) = build_stage7_pipeline_cpu(&context, ¶ms); + let (stage8_prover_cpu, stage8_verifier_cpu) = build_stage8_pipeline_cpu(&context, ¶ms); + + let emitted = [ + ( + JoltProtocolStage::Commitment, + Role::Prover, + emit_commitment_rust(&commitment_prover_cpu).expect("emit commitment prover"), + ), + ( + JoltProtocolStage::Commitment, + Role::Verifier, + emit_commitment_rust(&commitment_verifier_cpu).expect("emit commitment verifier"), + ), + ( + JoltProtocolStage::Stage1Outer, + Role::Prover, + emit_stage1_rust(&stage1_prover_cpu).expect("emit stage1 prover"), + ), + ( + JoltProtocolStage::Stage1Outer, + Role::Verifier, + emit_stage1_rust(&stage1_verifier_cpu).expect("emit stage1 verifier"), + ), + ( + JoltProtocolStage::Stage2, + Role::Prover, + emit_stage2_rust(&stage2_prover_cpu).expect("emit stage2 prover"), + ), + ( + JoltProtocolStage::Stage2, + Role::Verifier, + emit_stage2_rust(&stage2_verifier_cpu).expect("emit stage2 verifier"), + ), + ( + JoltProtocolStage::Stage3, + Role::Prover, + emit_stage3_rust(&stage3_prover_cpu).expect("emit stage3 prover"), + ), + ( + JoltProtocolStage::Stage3, + Role::Verifier, + emit_stage3_rust(&stage3_verifier_cpu).expect("emit stage3 verifier"), + ), + ( + JoltProtocolStage::Stage4, + Role::Prover, + emit_stage4_rust(&stage4_prover_cpu).expect("emit stage4 prover"), + ), + ( + JoltProtocolStage::Stage4, + Role::Verifier, + emit_stage4_rust(&stage4_verifier_cpu).expect("emit stage4 verifier"), + ), + ( + JoltProtocolStage::Stage5, + Role::Prover, + emit_stage5_rust(&stage5_prover_cpu).expect("emit stage5 prover"), + ), + ( + JoltProtocolStage::Stage5, + Role::Verifier, + emit_stage5_rust(&stage5_verifier_cpu).expect("emit stage5 verifier"), + ), + ( + JoltProtocolStage::Stage6, + Role::Prover, + emit_stage6_rust(&stage6_prover_cpu).expect("emit stage6 prover"), + ), + ( + JoltProtocolStage::Stage6, + Role::Verifier, + emit_stage6_rust(&stage6_verifier_cpu).expect("emit stage6 verifier"), + ), + ( + JoltProtocolStage::Stage7, + Role::Prover, + emit_stage7_rust(&stage7_prover_cpu).expect("emit stage7 prover"), + ), + ( + JoltProtocolStage::Stage7, + Role::Verifier, + emit_stage7_rust(&stage7_verifier_cpu).expect("emit stage7 verifier"), + ), + ( + JoltProtocolStage::Stage8, + Role::Prover, + emit_stage8_rust(&stage8_prover_cpu).expect("emit stage8 prover"), + ), + ( + JoltProtocolStage::Stage8, + Role::Verifier, + emit_stage8_rust(&stage8_verifier_cpu).expect("emit stage8 verifier"), + ), + ]; + let artifacts = emitted + .into_iter() + .map(|(stage, role, source)| { + let artifact = jolt_rust_artifact(stage, role, source).expect("canonical artifact"); + validate_jolt_rust_artifact_imports(&artifact).expect("artifact import policy"); + artifact + }) + .collect::>(); + + let paths = artifacts + .iter() + .map(|artifact| artifact.path.as_str()) + .collect::>(); + assert_eq!( + paths, + vec![ + "jolt-prover/src/stages/commitment.rs", + "jolt-verifier/src/stages/commitment.rs", + "jolt-prover/src/stages/stage1_outer.rs", + "jolt-verifier/src/stages/stage1_outer.rs", + "jolt-prover/src/stages/stage2.rs", + "jolt-verifier/src/stages/stage2.rs", + "jolt-prover/src/stages/stage3.rs", + "jolt-verifier/src/stages/stage3.rs", + "jolt-prover/src/stages/stage4.rs", + "jolt-verifier/src/stages/stage4.rs", + "jolt-prover/src/stages/stage5.rs", + "jolt-verifier/src/stages/stage5.rs", + "jolt-prover/src/stages/stage6.rs", + "jolt-verifier/src/stages/stage6.rs", + "jolt-prover/src/stages/stage7.rs", + "jolt-verifier/src/stages/stage7.rs", + "jolt-prover/src/stages/stage8.rs", + "jolt-verifier/src/stages/stage8.rs", + ] + ); + assert!(artifacts + .iter() + .filter(|artifact| artifact.crate_name == "jolt-verifier") + .all(|artifact| !artifact.source.source.contains("jolt_kernels"))); + assert!(artifacts + .iter() + .filter(|artifact| { artifact.crate_name == "jolt-prover" && artifact.stage.is_proof() }) + .all(|artifact| artifact.source.source.contains("jolt_kernels"))); + let workspace_generated_crates = assemble_jolt_workspace_generated_crates(artifacts.clone()) + .expect("assemble workspace generated role crates"); + if std::env::var_os("JOLT_UPDATE_GOLDENS").is_some() { + write_jolt_generated_crates(&workspace_generated_crates, workspace_root().join("crates")) + .expect("update checked-in generated role crates"); + } + assert_checked_in_generated_role_crate_sources_match(&workspace_generated_crates); + let dependency_root = workspace_root().join("crates").display().to_string(); + let generated_crates = assemble_jolt_generated_crates(artifacts, &dependency_root) + .expect("assemble generated role crates"); + assert_eq!( + generated_crates + .iter() + .map(|generated| generated.crate_name.as_str()) + .collect::>(), + vec!["jolt-prover", "jolt-verifier"] + ); + for generated in &generated_crates { + assert_generated_role_crate_compiles(generated); + } + let output_root = new_temp_dir("bolt_generated_crates"); + write_jolt_generated_crates(&workspace_generated_crates, &output_root) + .expect("write generated role crates"); + for generated in &workspace_generated_crates { + for file in &generated.files { + assert!(output_root + .join(&generated.crate_name) + .join(&file.path) + .exists()); + } + } + let _ = std::fs::remove_dir_all(output_root); +} + +#[test] +fn jolt_stage1_outer_lowers_to_compute_and_cpu_kernel_ir() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = + build_stage1_outer_protocol(&context, ¶ms).expect("build stage1 outer protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage1 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage1_to_compute(&context, &prover).expect("lower prover stage1"); + let verifier_compute = + lower_stage1_to_compute(&context, &verifier).expect("lower verifier stage1"); + verify_compute_schema(&prover_compute).expect("prover stage1 compute schema is valid"); + verify_compute_schema(&verifier_compute).expect("verifier stage1 compute schema is valid"); + assert!(prover_compute + .to_text_mlir() + .contains("relation = @jolt.stage1.outer.uniskip")); + assert!(!prover_compute.to_text_mlir().contains("kernel = @")); + let verifier_compute_text = verifier_compute.to_text_mlir(); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify_claim\"")); + assert!(verifier_compute_text.contains("\"compute.sumcheck_verify\"")); + assert!(!verifier_compute_text.contains("\"compute.kernel\"")); + assert!(!verifier_compute_text.contains("kernel = @")); + + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + verify_compute_schema(&prover_kernel_compute) + .expect("prover kernelized stage1 compute schema is valid"); + verify_compute_schema(&verifier_kernel_compute) + .expect("verifier kernelized stage1 compute schema is valid"); + + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + verify_cpu_schema(&prover_cpu).expect("prover stage1 CPU schema is valid"); + verify_cpu_schema(&verifier_cpu).expect("verifier stage1 CPU schema is valid"); + let program = stage1_cpu_program(&prover_cpu).expect("extract prover stage1 CPU program"); + + let cpu_text = prover_cpu.to_text_mlir(); + let verifier_cpu_text = verifier_cpu.to_text_mlir(); + assert!(cpu_text.contains("\"cpu.kernel\"()")); + assert!(cpu_text.contains("kernel = @jolt.cpu.stage1.outer.uniskip")); + assert!(cpu_text.contains("kernel = @jolt.cpu.stage1.outer.remaining")); + assert!(cpu_text.contains("\"cpu.sumcheck_driver\"(%")); + assert!(cpu_text.contains("\"cpu.sumcheck_eval\"(%")); + assert!(cpu_text.contains("\"cpu.opening_claim\"(%")); + assert!(cpu_text.contains("\"cpu.opening_batch\"(%")); + assert!(cpu_text.contains("\"cpu.sumcheck_claim\"(%")); + assert!(cpu_text.contains("count = 35 : i64")); + assert!(!cpu_text.contains("\"cpu.pcs_opening_claim\"")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify_claim\"")); + assert!(verifier_cpu_text.contains("\"cpu.sumcheck_verify\"")); + assert!(!verifier_cpu_text.contains("\"cpu.kernel\"")); + assert!(!verifier_cpu_text.contains("kernel = @")); + assert_eq!(program.role, Role::Prover); + assert_eq!(program.kernels.len(), 2); + assert!(program.kernels.iter().any(|kernel| { + kernel.symbol == "jolt.cpu.stage1.outer.uniskip" + && kernel.relation == "jolt.stage1.outer.uniskip" + && kernel.abi == "jolt_stage1_outer_uniskip" + })); + assert!(program.kernels.iter().any(|kernel| { + kernel.symbol == "jolt.cpu.stage1.outer.remaining" + && kernel.relation == "jolt.stage1.outer.remaining" + && kernel.abi == "jolt_stage1_outer_remaining" + })); + assert_eq!(program.claims.len(), 2); + assert_eq!(program.batches.len(), 2); + assert_eq!(program.drivers.len(), 2); + assert_eq!(program.opening_claims.len(), 36); + assert_eq!(program.opening_batches.len(), 1); + let uniskip = program + .drivers + .iter() + .find(|driver| driver.symbol == "stage1.uniskip.sumcheck") + .expect("uniskip driver"); + assert_eq!( + uniskip.kernel.as_deref(), + Some("jolt.cpu.stage1.outer.uniskip") + ); + assert_eq!(uniskip.round_schedule, vec![1]); + assert_eq!(uniskip.num_rounds, 1); + assert_eq!(uniskip.degree, 27); + let remaining = program + .drivers + .iter() + .find(|driver| driver.symbol == "stage1.outer_remaining.sumcheck") + .expect("remaining driver"); + assert_eq!( + remaining.kernel.as_deref(), + Some("jolt.cpu.stage1.outer.remaining") + ); + assert_eq!(remaining.round_schedule, vec![params.log_t + 1]); + assert_eq!(remaining.num_rounds, params.log_t + 1); + assert_eq!(remaining.degree, 3); + assert_eq!( + program + .evals + .iter() + .filter(|eval| eval.source == "stage1.outer_remaining.sumcheck") + .count(), + 35 + ); + assert_eq!(program.opening_batches[0].count, 35); + assert_eq!( + program.opening_batches[0].ordered_claims, + program.opening_batches[0].claim_operands + ); + + assert_or_update_fixture( + "tests/fixtures/stage1_outer_prover_compute.mlir", + &prover_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/stage1_outer_verifier_compute.mlir", + &verifier_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/stage1_outer_prover_kernel_compute.mlir", + &prover_kernel_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/stage1_outer_verifier_kernel_compute.mlir", + &verifier_kernel_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/stage1_outer_prover_cpu.mlir", + &prover_cpu.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/stage1_outer_verifier_cpu.mlir", + &verifier_cpu.to_text_mlir(), + ); +} + +#[test] +fn stage1_rust_emission_matches_golden_and_compiles() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = + build_stage1_outer_protocol(&context, ¶ms).expect("build stage1 outer protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower stage1 to concrete"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage1_to_compute(&context, &prover).expect("lower prover stage1"); + let verifier_compute = + lower_stage1_to_compute(&context, &verifier).expect("lower verifier stage1"); + let prover_kernel_compute = + resolve_compute_kernels(&context, &prover_compute).expect("resolve prover kernels"); + let verifier_kernel_compute = + resolve_compute_kernels(&context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = + lower_compute_to_cpu(&context, &prover_kernel_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_kernel_compute).expect("lower verifier CPU"); + let source = emit_stage1_rust(&prover_cpu).expect("emit prover stage1 rust"); + let verifier_source = emit_stage1_rust(&verifier_cpu).expect("emit verifier stage1 rust"); + + assert_eq!(source.filename, "prove_stage1_outer.rs"); + assert_eq!(verifier_source.filename, "verify_stage1_outer.rs"); + assert!(source.source.contains("pub fn prove_stage1_outer")); + assert!(verifier_source + .source + .contains("pub fn verify_stage1_outer")); + assert!(source.source.contains("jolt_stage1_outer_uniskip")); + assert!(source.source.contains("jolt_stage1_outer_remaining")); + assert!(!verifier_source.source.contains("jolt_kernels")); + assert!(verifier_source.source.contains("jolt_sumcheck")); + assert_or_update_fixture("tests/fixtures/prove_stage1_outer.rs", &source.source); + assert_or_update_fixture( + "tests/fixtures/verify_stage1_outer.rs", + &verifier_source.source, + ); + assert_rust_source_compiles(&source.filename, &source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn generated_stage1_prover_shape_proof_verifier_accepts() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::new(2, 2, 2); + let (prover_cpu, verifier_cpu) = build_stage1_pipeline_cpu(&context, ¶ms); + let prover_source = emit_stage1_rust(&prover_cpu).expect("emit stage1 prover rust"); + let verifier_source = emit_stage1_rust(&verifier_cpu).expect("emit stage1 verifier rust"); + + assert_eq!(prover_source.filename, "prove_stage1_outer.rs"); + assert_eq!(verifier_source.filename, "verify_stage1_outer.rs"); + assert_generated_stage1_self_parity_runs( + &prover_source, + &verifier_source, + &generated_stage1_shape_self_parity_main(), + ); +} + +#[test] +fn generated_stage1_real_executor_reaches_kernel_dispatch() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::new(2, 2, 2); + let (prover_cpu, verifier_cpu) = build_stage1_pipeline_cpu(&context, ¶ms); + let prover_source = emit_stage1_rust(&prover_cpu).expect("emit stage1 prover rust"); + let verifier_source = emit_stage1_rust(&verifier_cpu).expect("emit stage1 verifier rust"); + + assert_generated_stage1_self_parity_runs( + &prover_source, + &verifier_source, + generated_stage1_real_dispatch_main(), + ); +} + +#[test] +fn generated_stage1_real_executor_self_verifies_synthetic_remaining() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::new(2, 2, 2); + let (prover_cpu, verifier_cpu) = build_stage1_pipeline_cpu(&context, ¶ms); + let prover_source = emit_stage1_rust(&prover_cpu).expect("emit stage1 prover rust"); + let verifier_source = emit_stage1_rust(&verifier_cpu).expect("emit stage1 verifier rust"); + + assert_generated_stage1_self_parity_runs( + &prover_source, + &verifier_source, + &generated_stage1_synthetic_remaining_main(), + ); +} + +#[test] +fn generated_stage1_real_executor_self_verifies_r1cs_data() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::new(2, 2, 2); + let (prover_cpu, verifier_cpu) = build_stage1_pipeline_cpu(&context, ¶ms); + let prover_source = emit_stage1_rust(&prover_cpu).expect("emit stage1 prover rust"); + let verifier_source = emit_stage1_rust(&verifier_cpu).expect("emit stage1 verifier rust"); + + assert_generated_stage1_self_parity_runs( + &prover_source, + &verifier_source, + &generated_stage1_r1cs_data_main(), + ); +} + +#[test] +fn jolt_protocol_chain_commitment_stage1_fixture_tracks_phase_order() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let chain = jolt_protocol_chain_commitment_stage1_fixture(&context, ¶ms); + + assert_or_update_fixture( + "tests/fixtures/jolt_protocol_chain_commitment_stage1.yaml", + &chain, + ); +} + +#[test] +fn generated_jolt_chain_commitment_then_stage1_self_parity_runs() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::new(2, 2, 2); + let (commitment_prover_cpu, commitment_verifier_cpu) = + build_commitment_pipeline_cpu(&context, ¶ms); + let (stage1_prover_cpu, stage1_verifier_cpu) = build_stage1_pipeline_cpu(&context, ¶ms); + let commitment_prover = + emit_commitment_rust(&commitment_prover_cpu).expect("emit commitment prover rust"); + let commitment_verifier = + emit_commitment_rust(&commitment_verifier_cpu).expect("emit commitment verifier rust"); + let stage1_prover = emit_stage1_rust(&stage1_prover_cpu).expect("emit stage1 prover rust"); + let stage1_verifier = + emit_stage1_rust(&stage1_verifier_cpu).expect("emit stage1 verifier rust"); + + assert_generated_jolt_chain_self_parity_runs( + &[ + &commitment_prover, + &commitment_verifier, + &stage1_prover, + &stage1_verifier, + ], + &generated_commitment_stage1_chain_main(), + ); +} + +#[test] +fn commitment_pipeline_matches_golden_mlir_fixtures() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_commitment_protocol(&context, ¶ms).expect("build protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower Fiat-Shamir state"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_commitment_to_compute(&context, &prover).expect("lower compute"); + let verifier_compute = + lower_commitment_to_compute(&context, &verifier).expect("lower verifier compute"); + let prover_cpu = lower_compute_to_cpu(&context, &prover_compute).expect("lower to CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_compute).expect("lower verifier to CPU"); + + assert_or_update_fixture( + "tests/fixtures/commitment_protocol.mlir", + &protocol.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/commitment_concrete.mlir", + &concrete.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/commitment_prover_party.mlir", + &prover.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/commitment_verifier_party.mlir", + &verifier.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/commitment_prover_compute.mlir", + &prover_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/commitment_verifier_compute.mlir", + &verifier_compute.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/commitment_prover_cpu.mlir", + &prover_cpu.to_text_mlir(), + ); + assert_or_update_fixture( + "tests/fixtures/commitment_verifier_cpu.mlir", + &verifier_cpu.to_text_mlir(), + ); +} + +#[test] +fn commitment_rust_emission_matches_golden_and_compiles() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::fixture(); + let protocol = build_commitment_protocol(&context, ¶ms).expect("build protocol"); + let concrete = + lower_piop_and_fiat_shamir(&context, &protocol).expect("lower Fiat-Shamir state"); + let prover = project_prover_party(&context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(&context, &concrete).expect("project verifier party"); + let prover_compute = lower_commitment_to_compute(&context, &prover).expect("lower compute"); + let verifier_compute = + lower_commitment_to_compute(&context, &verifier).expect("lower verifier compute"); + let prover_cpu = lower_compute_to_cpu(&context, &prover_compute).expect("lower to CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_compute).expect("lower verifier to CPU"); + let source = emit_commitment_rust(&prover_cpu).expect("emit prover commitment rust"); + let verifier_source = + emit_commitment_rust(&verifier_cpu).expect("emit verifier commitment rust"); + + assert_eq!(source.filename, "prove_commitment_phase.rs"); + assert_eq!(verifier_source.filename, "verify_commitment_phase.rs"); + assert_or_update_fixture("tests/fixtures/prove_commitment_phase.rs", &source.source); + assert_or_update_fixture( + "tests/fixtures/verify_commitment_phase.rs", + &verifier_source.source, + ); + assert_rust_source_compiles(&source.filename, &source.source); + assert_rust_source_compiles(&verifier_source.filename, &verifier_source.source); +} + +#[test] +fn generated_commitment_prover_verifier_self_parity_runs() { + let context = MeliorContext::new(); + let prover_cpu = build_small_commitment_cpu(&context, Role::Prover); + let verifier_cpu = build_small_commitment_cpu(&context, Role::Verifier); + let prover_source = + emit_commitment_rust(&prover_cpu).expect("emit small prover commitment rust"); + let verifier_source = + emit_commitment_rust(&verifier_cpu).expect("emit small verifier commitment rust"); + + assert_eq!(prover_source.filename, "prove_commitment_phase.rs"); + assert_eq!(verifier_source.filename, "verify_commitment_phase.rs"); + assert_generated_commitment_self_parity_runs( + &prover_source, + &verifier_source, + &generated_small_self_parity_main(), + ); +} + +#[test] +fn pipeline_generated_commitment_prover_verifier_self_parity_runs() { + let context = MeliorContext::new(); + let params = JoltProtocolParams::new(0, 0, 0); + let (prover_cpu, verifier_cpu) = build_commitment_pipeline_cpu(&context, ¶ms); + let prover_source = + emit_commitment_rust(&prover_cpu).expect("emit pipeline prover commitment rust"); + let verifier_source = + emit_commitment_rust(&verifier_cpu).expect("emit pipeline verifier commitment rust"); + + assert_eq!(prover_source.filename, "prove_commitment_phase.rs"); + assert_eq!(verifier_source.filename, "verify_commitment_phase.rs"); + assert_generated_commitment_self_parity_runs( + &prover_source, + &verifier_source, + &generated_pipeline_self_parity_main(), + ); +} + +#[test] +fn commitment_rust_emission_requires_cpu_target_params() { + let context = MeliorContext::new(); + let cpu = context + .parse_module::( + r#" +module @bad attributes {bolt.phase = "cpu", bolt.role = "prover"} { + %0 = "cpu.oracle_family_init"() {count = 1 : i64, family = @bad.family, sym_name = "bad.family"} : () -> !cpu.oracle_family + %1 = "cpu.oracle_ref"() {domain = @bad.domain, num_vars = 1 : i64, oracle = @A, sym_name = "bad.A"} : () -> !cpu.oracle_buffer + %2 = "cpu.oracle_family_append"(%0, %1) {family = @bad.family, index = 0 : i64, oracle = @A, sym_name = "bad.family.append0"} : (!cpu.oracle_family, !cpu.oracle_buffer) -> !cpu.oracle_family + %3 = "cpu.pcs_commit_batch"(%2) {artifact = @bad.artifact, count = 1 : i64, domain = @bad.domain, label = "bad", num_vars = 1 : i64, oracle_family = @bad.family, ordered_oracles = [@A], pcs = @dory, sym_name = "bad.batch"} : (!cpu.oracle_family) -> !cpu.commitment_artifact +} +"#, + ) + .expect("parse bad CPU module"); + + let error = emit_commitment_rust(&cpu).expect_err("missing params rejected"); + assert!(error.to_string().contains("missing cpu.params")); +} + +fn build_small_commitment_cpu(context: &MeliorContext, role: Role) -> bolt::BoltModule<'_, Cpu> { + let (batch_op, optional_op) = match role { + Role::Prover => ("cpu.pcs_commit_batch", "cpu.pcs_commit_optional"), + Role::Verifier => ("cpu.pcs_receive_batch", "cpu.pcs_receive_optional"), + }; + context + .parse_module::(&format!( + r#" +module @small.commitment_phase attributes {{bolt.phase = "cpu", bolt.role = "{}"}} {{ + "cpu.params"() {{field = @bn254_fr, pcs = @dory, sym_name = "small.params", transcript = @blake2b_transcript}} : () -> () + "cpu.function"() {{source = @small.commitment_phase, sym_name = "small.commitment_phase"}} : () -> () + %0 = "cpu.transcript_init"() {{scheme = @blake2b_transcript, sym_name = "fs0"}} : () -> !cpu.transcript_state + %1 = "cpu.oracle_family_init"() {{count = 2 : i64, family = @small.main_polys, sym_name = "small.main_polys"}} : () -> !cpu.oracle_family + %2 = "cpu.oracle_ref"() {{domain = @small.domain, num_vars = 2 : i64, oracle = @A, sym_name = "small.A"}} : () -> !cpu.oracle_buffer + %3 = "cpu.oracle_family_append"(%1, %2) {{family = @small.main_polys, index = 0 : i64, oracle = @A, sym_name = "small.main_polys.append0"}} : (!cpu.oracle_family, !cpu.oracle_buffer) -> !cpu.oracle_family + %4 = "cpu.oracle_ref"() {{domain = @small.domain, num_vars = 2 : i64, oracle = @B, sym_name = "small.B"}} : () -> !cpu.oracle_buffer + %5 = "cpu.oracle_family_append"(%3, %4) {{family = @small.main_polys, index = 1 : i64, oracle = @B, sym_name = "small.main_polys.append1"}} : (!cpu.oracle_family, !cpu.oracle_buffer) -> !cpu.oracle_family + %6 = "{batch_op}"(%5) {{artifact = @small.main, count = 2 : i64, domain = @small.domain, label = "commitment", num_vars = 2 : i64, oracle_family = @small.main_polys, ordered_oracles = [@A, @B], pcs = @dory, sym_name = "small.main"}} : (!cpu.oracle_family) -> !cpu.commitment_artifact + %7 = "cpu.oracle_ref"() {{domain = @small.domain, num_vars = 2 : i64, oracle = @Advice, sym_name = "small.Advice"}} : () -> !cpu.oracle_buffer + %8 = "{optional_op}"(%7) {{artifact = @small.advice, domain = @small.domain, label = "advice", num_vars = 2 : i64, oracle = @Advice, pcs = @dory, skip_policy = "missing_or_zero", sym_name = "small.advice"}} : (!cpu.oracle_buffer) -> !cpu.commitment_artifact + %9 = "cpu.transcript_absorb"(%0, %6) {{label = "commitment", optional = false, sym_name = "small.absorb_main"}} : (!cpu.transcript_state, !cpu.commitment_artifact) -> !cpu.transcript_state + %10 = "cpu.transcript_absorb"(%9, %8) {{label = "advice", optional = true, sym_name = "small.absorb_advice"}} : (!cpu.transcript_state, !cpu.commitment_artifact) -> !cpu.transcript_state +}} +"#, + role.as_str() + )) + .expect("parse small CPU module") +} + +fn build_commitment_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_commitment_protocol(context, params).expect("build protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower Fiat-Shamir state"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = + lower_commitment_to_compute(context, &prover).expect("lower prover compute"); + let verifier_compute = + lower_commitment_to_compute(context, &verifier).expect("lower verifier compute"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn build_stage1_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_stage1_outer_protocol(context, params).expect("build stage1 protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower stage1 protocol"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage1_to_compute(context, &prover).expect("lower prover stage1"); + let verifier_compute = + lower_stage1_to_compute(context, &verifier).expect("lower verifier stage1"); + let prover_compute = + resolve_compute_kernels(context, &prover_compute).expect("resolve prover kernels"); + let verifier_compute = + resolve_compute_kernels(context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn build_stage2_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_stage2_protocol(context, params).expect("build stage2 protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower stage2 protocol"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage2_to_compute(context, &prover).expect("lower prover stage2"); + let verifier_compute = + lower_stage2_to_compute(context, &verifier).expect("lower verifier stage2"); + let prover_compute = + resolve_compute_kernels(context, &prover_compute).expect("resolve prover kernels"); + let verifier_compute = + resolve_compute_kernels(context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn build_stage3_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_stage3_protocol(context, params).expect("build stage3 protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower stage3 protocol"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage3_to_compute(context, &prover).expect("lower prover stage3"); + let verifier_compute = + lower_stage3_to_compute(context, &verifier).expect("lower verifier stage3"); + let prover_compute = + resolve_compute_kernels(context, &prover_compute).expect("resolve prover kernels"); + let verifier_compute = + resolve_compute_kernels(context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn build_stage4_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_stage4_protocol(context, params).expect("build stage4 protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower stage4 protocol"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage4_to_compute(context, &prover).expect("lower prover stage4"); + let verifier_compute = + lower_stage4_to_compute(context, &verifier).expect("lower verifier stage4"); + let prover_compute = + resolve_compute_kernels(context, &prover_compute).expect("resolve prover kernels"); + let verifier_compute = + resolve_compute_kernels(context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn build_stage5_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_stage5_protocol(context, params).expect("build stage5 protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower stage5 protocol"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage5_to_compute(context, &prover).expect("lower prover stage5"); + let verifier_compute = + lower_stage5_to_compute(context, &verifier).expect("lower verifier stage5"); + let prover_compute = + resolve_compute_kernels(context, &prover_compute).expect("resolve prover kernels"); + let verifier_compute = + resolve_compute_kernels(context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn build_stage6_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_stage6_protocol(context, params).expect("build stage6 protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower stage6 protocol"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage6_to_compute(context, &prover).expect("lower prover stage6"); + let verifier_compute = + lower_stage6_to_compute(context, &verifier).expect("lower verifier stage6"); + let prover_compute = + resolve_compute_kernels(context, &prover_compute).expect("resolve prover kernels"); + let verifier_compute = + resolve_compute_kernels(context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn build_stage7_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_stage7_protocol(context, params).expect("build stage7 protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower stage7 protocol"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage7_to_compute(context, &prover).expect("lower prover stage7"); + let verifier_compute = + lower_stage7_to_compute(context, &verifier).expect("lower verifier stage7"); + let prover_compute = + resolve_compute_kernels(context, &prover_compute).expect("resolve prover kernels"); + let verifier_compute = + resolve_compute_kernels(context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn build_stage8_pipeline_cpu<'c>( + context: &'c MeliorContext, + params: &JoltProtocolParams, +) -> (bolt::BoltModule<'c, Cpu>, bolt::BoltModule<'c, Cpu>) { + let protocol = build_stage8_protocol(context, params).expect("build stage8 protocol"); + let concrete = lower_piop_and_fiat_shamir(context, &protocol).expect("lower stage8 protocol"); + let prover = project_prover_party(context, &concrete).expect("project prover party"); + let verifier = project_verifier_party(context, &concrete).expect("project verifier party"); + let prover_compute = lower_stage8_to_compute(context, &prover).expect("lower prover stage8"); + let verifier_compute = + lower_stage8_to_compute(context, &verifier).expect("lower verifier stage8"); + let prover_compute = + resolve_compute_kernels(context, &prover_compute).expect("resolve prover kernels"); + let verifier_compute = + resolve_compute_kernels(context, &verifier_compute).expect("resolve verifier kernels"); + let prover_cpu = lower_compute_to_cpu(context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(context, &verifier_compute).expect("lower verifier CPU"); + (prover_cpu, verifier_cpu) +} + +fn non_jolt_artifact_config() -> ProtocolArtifactConfig { + ProtocolArtifactConfig { + protocol_name: "Acme".to_owned(), + type_prefix: "Acme".to_owned(), + transcript_label: "acme transcript".to_owned(), + repository: None, + prover_crate_name: "acme-prover".to_owned(), + verifier_crate_name: "acme-verifier".to_owned(), + crates_io_patches: Vec::new(), + standalone_dependency_overrides: vec![ProtocolStandaloneDependency::new( + "serde", + "serde = { version = \"1\", default-features = false }", + )], + common_dependencies: vec!["serde".to_owned()], + prover_dependencies: Vec::new(), + verifier_dependencies: Vec::new(), + instrumentation_prefix: None, + prover_forbidden_imports: vec!["forbidden_prover".to_owned()], + verifier_forbidden_imports: vec!["forbidden_verifier".to_owned()], + kernel_crate: None, + field_type: RustTypeRef::new("std::primitive::u64"), + default_transcript_type: RustTypeRef::new("crate::stages::alpha::DefaultTranscript"), + transcript_trait: RustTypeRef::new("crate::stages::alpha::Transcript"), + commitment_type: RustTypeRef::new("crate::stages::shared::Commitment"), + prover_setup_type: RustTypeRef::new("crate::stages::alpha::ProverSetup"), + role_api_extension: None, + verifier_runtime_modules: vec![ProtocolRuntimeModule { + module_name: "shared".to_owned(), + file: GeneratedFile { + path: "src/stages/shared.rs".to_owned(), + source: non_jolt_verifier_common_source(), + }, + }], + verifier_named_eval_type: RustTypeRef::new("crate::stages::shared::StageNamedEval"), + verifier_sumcheck_output_type: RustTypeRef::new( + "crate::stages::shared::StageSumcheckOutput", + ), + verifier_stage_proof_type: RustTypeRef::new("crate::stages::shared::StageProof"), + } +} + +fn non_jolt_alpha_prover_source() -> RustSourceFile { + RustSourceFile { + filename: "prove_alpha.rs".to_owned(), + source: r" +pub struct DefaultTranscript(core::marker::PhantomData); + +pub trait Transcript { + type Challenge; +} + +pub struct ProverSetup; + +#[derive(Clone, Debug)] +pub struct AlphaExecutionArtifacts { + pub sumchecks: Vec>, +} + +#[derive(Clone, Debug)] +pub struct AlphaSumcheckOutput { + pub driver: &'static str, + pub point: Vec, + pub evals: Vec>, + pub proof: (), +} + +#[derive(Clone, Debug)] +pub struct AlphaNamedEval { + pub name: &'static str, + pub oracle: &'static str, + pub value: F, +} + +#[derive(Debug)] +pub struct AlphaKernelError; + +pub trait AlphaKernelExecutor {} + +pub fn execute_alpha( + _executor: &mut E, + _transcript: &mut T, +) -> Result, AlphaKernelError> +where + E: AlphaKernelExecutor, +{ + Ok(AlphaExecutionArtifacts { + sumchecks: Vec::new(), + }) +} +" + .trim_start() + .to_owned(), + } +} + +fn non_jolt_alpha_verifier_source() -> RustSourceFile { + RustSourceFile { + filename: "verify_alpha.rs".to_owned(), + source: r" +pub struct DefaultTranscript(core::marker::PhantomData); + +pub trait Transcript { + type Challenge; +} + +pub type AlphaNamedEval = super::shared::StageNamedEval; +pub type AlphaSumcheckOutput = super::shared::StageSumcheckOutput; +pub type AlphaProof = super::shared::StageProof; + +#[derive(Clone, Debug)] +pub struct AlphaExecutionArtifacts { + pub sumchecks: Vec>, +} + +#[derive(Debug)] +pub enum VerifyAlphaError {} + +pub fn verify_alpha( + _proof: &AlphaProof, + _transcript: &mut T, +) -> Result, VerifyAlphaError> { + Ok(AlphaExecutionArtifacts { + sumchecks: Vec::new(), + }) +} +" + .trim_start() + .to_owned(), + } +} + +fn non_jolt_verifier_common_source() -> String { + r" +#[derive(Clone, Debug)] +pub struct Commitment; + +#[derive(Clone, Debug)] +pub struct StageNamedEval { + pub name: &'static str, + pub oracle: &'static str, + pub value: F, +} + +#[derive(Clone, Debug)] +pub struct StageSumcheckOutput { + pub driver: &'static str, + pub point: Vec, + pub evals: Vec>, + pub proof: (), +} + +#[derive(Clone, Debug)] +pub struct StageProof { + pub sumchecks: Vec>, +} +" + .trim_start() + .to_owned() +} + +fn jolt_protocol_chain_commitment_stage1_fixture( + context: &MeliorContext, + params: &JoltProtocolParams, +) -> String { + let (commitment_prover_cpu, commitment_verifier_cpu) = + build_commitment_pipeline_cpu(context, params); + let (stage1_prover_cpu, stage1_verifier_cpu) = build_stage1_pipeline_cpu(context, params); + let commitment_prover = + commitment_cpu_program(&commitment_prover_cpu).expect("extract commitment prover program"); + let commitment_verifier = commitment_cpu_program(&commitment_verifier_cpu) + .expect("extract commitment verifier program"); + let stage1_prover = stage1_cpu_program(&stage1_prover_cpu).expect("extract stage1 prover"); + let stage1_verifier = + stage1_cpu_program(&stage1_verifier_cpu).expect("extract stage1 verifier"); + let commitment_prover_source = + emit_commitment_rust(&commitment_prover_cpu).expect("emit commitment prover"); + let commitment_verifier_source = + emit_commitment_rust(&commitment_verifier_cpu).expect("emit commitment verifier"); + let stage1_prover_source = emit_stage1_rust(&stage1_prover_cpu).expect("emit stage1 prover"); + let stage1_verifier_source = + emit_stage1_rust(&stage1_verifier_cpu).expect("emit stage1 verifier"); + + let mut text = String::new(); + writeln!(&mut text, "# Jolt protocol chain fixture").unwrap(); + writeln!(&mut text, "params:").unwrap(); + writeln!(&mut text, " log_t: {}", params.log_t).unwrap(); + writeln!(&mut text, " log_k_bytecode: {}", params.log_k_bytecode).unwrap(); + writeln!(&mut text, " log_k_ram: {}", params.log_k_ram).unwrap(); + writeln!(&mut text, " trace_length: {}", params.trace_length).unwrap(); + writeln!(&mut text, "phases:").unwrap(); + writeln!(&mut text, " - name: commitment").unwrap(); + writeln!( + &mut text, + " protocol_fixture: tests/fixtures/commitment_protocol.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " concrete_fixture: tests/fixtures/commitment_concrete.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " prover_cpu_fixture: tests/fixtures/commitment_prover_cpu.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " verifier_cpu_fixture: tests/fixtures/commitment_verifier_cpu.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " prover_rust_fixture: tests/fixtures/{}", + commitment_prover_source.filename + ) + .unwrap(); + writeln!( + &mut text, + " verifier_rust_fixture: tests/fixtures/{}", + commitment_verifier_source.filename + ) + .unwrap(); + writeln!( + &mut text, + " prover_batches: {}", + commitment_prover.batch_plans.len() + ) + .unwrap(); + writeln!( + &mut text, + " verifier_batches: {}", + commitment_verifier.batch_plans.len() + ) + .unwrap(); + writeln!( + &mut text, + " optional_commitments: {}", + commitment_prover.optional_plans.len() + ) + .unwrap(); + writeln!( + &mut text, + " transcript_steps: {}", + commitment_prover.transcript_steps.len() + ) + .unwrap(); + writeln!(&mut text, " - name: stage1_outer").unwrap(); + writeln!(&mut text, " consumes_transcript_from: commitment").unwrap(); + writeln!( + &mut text, + " protocol_fixture: tests/fixtures/stage1_outer_protocol.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " prover_compute_fixture: tests/fixtures/stage1_outer_prover_compute.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " verifier_compute_fixture: tests/fixtures/stage1_outer_verifier_compute.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " prover_kernel_compute_fixture: tests/fixtures/stage1_outer_prover_kernel_compute.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " verifier_kernel_compute_fixture: tests/fixtures/stage1_outer_verifier_kernel_compute.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " prover_cpu_fixture: tests/fixtures/stage1_outer_prover_cpu.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " verifier_cpu_fixture: tests/fixtures/stage1_outer_verifier_cpu.mlir" + ) + .unwrap(); + writeln!( + &mut text, + " prover_rust_fixture: tests/fixtures/{}", + stage1_prover_source.filename + ) + .unwrap(); + writeln!( + &mut text, + " verifier_rust_fixture: tests/fixtures/{}", + stage1_verifier_source.filename + ) + .unwrap(); + writeln!( + &mut text, + " transcript_squeezes: {}", + stage1_prover.transcript_squeezes.len() + ) + .unwrap(); + writeln!( + &mut text, + " prover_sumcheck_drivers: {}", + stage1_prover.drivers.len() + ) + .unwrap(); + writeln!( + &mut text, + " verifier_sumcheck_drivers: {}", + stage1_verifier.drivers.len() + ) + .unwrap(); + writeln!( + &mut text, + " opening_claims: {}", + stage1_prover.opening_claims.len() + ) + .unwrap(); + writeln!( + &mut text, + " opening_batches: {}", + stage1_prover.opening_batches.len() + ) + .unwrap(); + writeln!(&mut text, " drivers:").unwrap(); + for driver in &stage1_prover.drivers { + writeln!( + &mut text, + " - {}: kernel={} rounds={} degree={} proof_slot={}", + driver.symbol, + driver.kernel.as_deref().unwrap_or(""), + driver.num_rounds, + driver.degree, + driver.proof_slot + ) + .unwrap(); + } + writeln!(&mut text, "parity_gates:").unwrap(); + writeln!( + &mut text, + " - pipeline_generated_commitment_prover_verifier_self_parity_runs" + ) + .unwrap(); + writeln!( + &mut text, + " - generated_stage1_real_executor_self_verifies_synthetic_remaining" + ) + .unwrap(); + writeln!( + &mut text, + " - generated_jolt_chain_commitment_then_stage1_self_parity_runs" + ) + .unwrap(); + text +} + +fn opening_claim_equal_protocol(left_oracle: &str, right_oracle: &str, mode: &str) -> String { + let right_oracle_def = if left_oracle == right_oracle { + String::new() + } else { + format!( + r#" "piop.oracle"() {{commit_domain = @trace, domain = @trace, field = @bn254_fr, layout = "virtual", sym_name = "{right_oracle}", visibility = "virtual"}} : () -> () +"# + ) + }; + format!( + r#" +module @opening.claim.equal attributes {{bolt.phase = "protocol"}} {{ + "field.define"() {{modulus_bits = 254 : i64, role = "scalar", sym_name = "bn254_fr"}} : () -> () + "hash.function"() {{algorithm = "blake2b", sym_name = "blake2b"}} : () -> () + "transcript.scheme"() {{hash = @blake2b, sym_name = "blake2b_transcript"}} : () -> () + "pcs.scheme"() {{field = @bn254_fr, sym_name = "dory"}} : () -> () + "poly.domain"() {{field = @bn254_fr, log_size = 16 : i64, sym_name = "trace"}} : () -> () + "protocol.params"() {{field = @bn254_fr, pcs = @dory, sym_name = "params", transcript = @blake2b_transcript}} : () -> () + "protocol.boundary"() {{roles = ["prover", "verifier"], sym_name = "opening.claim.equal"}} : () -> () + "piop.oracle"() {{commit_domain = @trace, domain = @trace, field = @bn254_fr, layout = "virtual", sym_name = "{left_oracle}", visibility = "virtual"}} : () -> () +{right_oracle_def} + %left:3 = "piop.opening_input"() {{claim_kind = "virtual", domain = @trace, oracle = @{left_oracle}, point_arity = 16 : i64, source_claim = @stage2.product_virtual.remainder.opening.{left_oracle}, source_stage = @stage2, sym_name = "stage3.input.stage2_left.{left_oracle}"}} : () -> (!poly.point, !field.scalar, !piop.opening_claim_type) + %right:3 = "piop.opening_input"() {{claim_kind = "virtual", domain = @trace, oracle = @{right_oracle}, point_arity = 16 : i64, source_claim = @stage2.instruction_lookup.claim_reduction.opening.{right_oracle}, source_stage = @stage2, sym_name = "stage3.input.stage2_right.{right_oracle}"}} : () -> (!poly.point, !field.scalar, !piop.opening_claim_type) + "piop.opening_claim_equal"(%left#2, %right#2) {{mode = "{mode}", sym_name = "stage3.instruction_input.left_claim_consistency"}} : (!piop.opening_claim_type, !piop.opening_claim_type) -> () +}} +"# + ) +} + +fn transcript_absorb_bytes_protocol(params: &JoltProtocolParams) -> String { + format!( + r#" +module @transcript.absorb.bytes attributes {{bolt.phase = "protocol"}} {{ + "field.define"() {{modulus_bits = 254 : i64, role = "scalar", sym_name = "bn254_fr"}} : () -> () + "hash.function"() {{algorithm = "blake2b", sym_name = "blake2b"}} : () -> () + "transcript.scheme"() {{hash = @blake2b, sym_name = "blake2b_transcript"}} : () -> () + "pcs.scheme"() {{field = @bn254_fr, sym_name = "dory"}} : () -> () + "protocol.params"() {{{params_attrs}, sym_name = "jolt.params"}} : () -> () + "protocol.boundary"() {{roles = ["prover", "verifier"], sym_name = "transcript.absorb.bytes"}} : () -> () + %0 = "transcript.state"() {{scheme = @blake2b_transcript, sym_name = "fs_after_stage3"}} : () -> !transcript.state_type + %1 = "transcript.absorb_bytes"(%0) {{label = "ram_val_check_gamma", payload = "", sym_name = "stage4.ram_val_check.domain_separator"}} : (!transcript.state_type) -> !transcript.state_type + %2:2 = "transcript.squeeze"(%1) {{count = 1 : i64, kind = "challenge_scalar", label = "ram_val_check_gamma", sym_name = "stage4.ram_val_check.gamma"}} : (!transcript.state_type) -> (!transcript.state_type, !field.scalar) +}} +"#, + params_attrs = jolt_params_attrs_source(params) + ) +} + +fn jolt_params_attrs_source(params: &JoltProtocolParams) -> String { + params + .attrs() + .into_iter() + .map(|(name, value)| format!("{name} = {value}")) + .collect::>() + .join(", ") +} + +fn explicit_sumcheck_protocol() -> &'static str { + r#" +module @explicit.sumcheck attributes {bolt.phase = "protocol"} { + "field.define"() {modulus_bits = 254 : i64, role = "scalar", sym_name = "bn254_fr"} : () -> () + "hash.function"() {algorithm = "blake2b", sym_name = "blake2b"} : () -> () + "transcript.scheme"() {hash = @blake2b, sym_name = "blake2b_transcript"} : () -> () + "pcs.scheme"() {field = @bn254_fr, sym_name = "dory"} : () -> () + "poly.domain"() {field = @bn254_fr, log_size = 16 : i64, sym_name = "trace"} : () -> () + "piop.relation"() {degree = 3 : i64, domain = @trace, kind = "sumcheck", num_rounds = 4 : i64, output_count = 1 : i64, sym_name = "jolt.stage1.outer.remaining"} : () -> () + %0 = "transcript.state"() {scheme = @blake2b_transcript, sym_name = "fs0"} : () -> !transcript.state_type + %1, %alpha = "transcript.squeeze"(%0) {count = 1 : i64, kind = "scalar", label = "sumcheck_claim", sym_name = "stage1.alpha"} : (!transcript.state_type) -> (!transcript.state_type, !field.scalar) + %stage = "piop.stage"() {name = "stage1", order = 1 : i64, roles = ["prover", "verifier"], sym_name = "stage1"} : () -> !piop.stage_type + %claim_value = "field.const"() {field = @bn254_fr, value = 0 : i64, sym_name = "stage1.outer.claim_value"} : () -> !field.scalar + %claim = "piop.sumcheck_claim"(%claim_value) {claim = @stage1.outer.claim, degree = 3 : i64, domain = @trace, num_rounds = 4 : i64, relation = @jolt.stage1.outer.remaining, stage = @stage1, sym_name = "stage1.outer.claim"} : (!field.scalar) -> !piop.sumcheck_claim_type + %batch = "piop.sumcheck_batch"(%stage, %claim) {claim_label = "sumcheck_claim", count = 1 : i64, ordered_claims = [@stage1.outer.claim], policy = "jolt_core_front_loaded", proof_slot = @stage1.sumcheck, round_label = "sumcheck_poly", round_schedule = [2, 1, 1], stage = @stage1, sym_name = "stage1.outer.batch"} : (!piop.stage_type, !piop.sumcheck_claim_type) -> !piop.sumcheck_batch_type + %2, %point, %result, %proof = "piop.sumcheck"(%1, %batch) {claim_label = "sumcheck_claim", degree = 3 : i64, num_rounds = 4 : i64, policy = "jolt_core_front_loaded", proof_slot = @stage1.sumcheck, relation = @jolt.stage1.outer.remaining, round_label = "sumcheck_poly", round_schedule = [2, 1, 1], stage = @stage1, sym_name = "stage1.outer.sumcheck"} : (!transcript.state_type, !piop.sumcheck_batch_type) -> (!transcript.state_type, !poly.point, !piop.sumcheck_result_type, !piop.sumcheck_proof_type) + %eval = "piop.sumcheck_eval"(%result) {index = 0 : i64, name = @stage1.outer.eval, oracle = @RdInc, source = @stage1.outer.sumcheck, sym_name = "stage1.outer.eval"} : (!piop.sumcheck_result_type) -> !field.scalar + %opening = "pcs.opening_claim"(%point, %eval) {domain = @trace, family = @jolt.main_witness_polys, oracle = @RdInc, point_arity = 4 : i64, sym_name = "stage1.outer.opening"} : (!poly.point, !field.scalar) -> !pcs.opening_claim_type + %openings = "pcs.opening_batch"(%opening) {count = 1 : i64, ordered_claims = [@stage1.outer.opening], policy = "jolt_core_order", proof_slot = @stage1.openings, sym_name = "stage1.opening_batch"} : (!pcs.opening_claim_type) -> !pcs.opening_batch_type + %3, %opening_proof = "pcs.batch_open"(%2, %openings) {pcs = @dory, proof_slot = @stage1.openings, sym_name = "stage1.open", transcript_label = "opening_proof"} : (!transcript.state_type, !pcs.opening_batch_type) -> (!transcript.state_type, !pcs.opening_proof_type) +} +"# +} + +fn explicit_sumcheck_compute() -> &'static str { + r#" +module @explicit.sumcheck attributes {bolt.phase = "compute", bolt.role = "prover"} { + "compute.params"() {field = @bn254_fr, pcs = @dory, sym_name = "params", transcript = @blake2b_transcript} : () -> () + "compute.function"() {source = @explicit.sumcheck, sym_name = "explicit.sumcheck"} : () -> () + "compute.relation"() {degree = 3 : i64, domain = @trace, kind = "sumcheck", num_rounds = 4 : i64, output_count = 1 : i64, sym_name = "jolt.stage1.outer.remaining"} : () -> () + %0 = "compute.transcript_init"() {scheme = @blake2b_transcript, sym_name = "fs0"} : () -> !compute.transcript_state + %1, %alpha = "compute.transcript_squeeze"(%0) {count = 1 : i64, kind = "scalar", label = "sumcheck_claim", sym_name = "stage1.alpha"} : (!compute.transcript_state) -> (!compute.transcript_state, !compute.field_value) + %claim_value = "compute.field_const"() {field = @bn254_fr, value = 0 : i64, sym_name = "stage1.outer.claim_value"} : () -> !compute.field_value + %claim = "compute.sumcheck_claim"(%claim_value) {claim = @stage1.outer.claim, degree = 3 : i64, domain = @trace, num_rounds = 4 : i64, relation = @jolt.stage1.outer.remaining, stage = @stage1, sym_name = "stage1.outer.claim"} : (!compute.field_value) -> !compute.sumcheck_claim_type + %batch = "compute.sumcheck_batch"(%claim) {claim_label = "sumcheck_claim", count = 1 : i64, ordered_claims = [@stage1.outer.claim], policy = "jolt_core_front_loaded", proof_slot = @stage1.sumcheck, round_label = "sumcheck_poly", round_schedule = [2, 1, 1], stage = @stage1, sym_name = "stage1.outer.batch"} : (!compute.sumcheck_claim_type) -> !compute.sumcheck_batch_type + %2, %point, %result, %proof = "compute.sumcheck_driver"(%1, %batch) {claim_label = "sumcheck_claim", degree = 3 : i64, num_rounds = 4 : i64, policy = "jolt_core_front_loaded", proof_slot = @stage1.sumcheck, relation = @jolt.stage1.outer.remaining, round_label = "sumcheck_poly", round_schedule = [2, 1, 1], stage = @stage1, sym_name = "stage1.outer.sumcheck"} : (!compute.transcript_state, !compute.sumcheck_batch_type) -> (!compute.transcript_state, !compute.point, !compute.sumcheck_result_type, !compute.sumcheck_proof_type) + %eval = "compute.sumcheck_eval"(%result) {index = 0 : i64, name = @stage1.outer.eval, oracle = @RdInc, source = @stage1.outer.sumcheck, sym_name = "stage1.outer.eval"} : (!compute.sumcheck_result_type) -> !compute.field_value + %opening = "compute.pcs_opening_claim"(%point, %eval) {domain = @trace, family = @jolt.main_witness_polys, oracle = @RdInc, point_arity = 4 : i64, sym_name = "stage1.outer.opening"} : (!compute.point, !compute.field_value) -> !compute.opening_claim_type + %openings = "compute.pcs_opening_batch"(%opening) {count = 1 : i64, ordered_claims = [@stage1.outer.opening], policy = "jolt_core_order", proof_slot = @stage1.openings, sym_name = "stage1.opening_batch"} : (!compute.opening_claim_type) -> !compute.opening_batch_type + %3, %opening_proof = "compute.pcs_batch_open"(%2, %openings) {pcs = @dory, proof_slot = @stage1.openings, sym_name = "stage1.open", transcript_label = "opening_proof"} : (!compute.transcript_state, !compute.opening_batch_type) -> (!compute.transcript_state, !compute.opening_proof_type) +} +"# +} + +fn assert_or_update_fixture(path: &str, actual: &str) { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(path); + if std::env::var_os("JOLT_UPDATE_GOLDENS").is_some() { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).expect("create golden fixture directory"); + } + std::fs::write(&path, actual).expect("write golden fixture"); + return; + } + if !path.exists() { + return; + } + let expected = std::fs::read_to_string(&path).expect("read golden fixture"); + assert_eq!(expected, actual); +} + +fn assert_rust_source_compiles(_filename: &str, source: &str) { + let dir = new_temp_dir("bolt_emit"); + let workspace_root = workspace_root(); + std::fs::write( + dir.join("Cargo.toml"), + generated_crate_manifest(&workspace_root), + ) + .expect("write generated cargo manifest"); + std::fs::create_dir_all(dir.join("src")).expect("create generated src dir"); + if source.contains("super::common") { + let common = std::fs::read_to_string( + workspace_root.join("crates/jolt-verifier/src/stages/common.rs"), + ) + .expect("read generated verifier common stage source"); + std::fs::write(dir.join("src/common.rs"), common).expect("write generated common source"); + std::fs::write(dir.join("src/generated.rs"), source).expect("write generated source"); + std::fs::write( + dir.join("src/lib.rs"), + "pub mod common;\n#[rustfmt::skip]\npub mod generated;\n", + ) + .expect("write generated lib wrapper"); + } else { + std::fs::write(dir.join("src/lib.rs"), source).expect("write generated source"); + } + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let output = Command::new(cargo) + .arg("check") + .arg("--manifest-path") + .arg(dir.join("Cargo.toml")) + .arg("-q") + .env("CARGO_TARGET_DIR", dir.join("target")) + .output() + .expect("run cargo check"); + assert!( + output.status.success(), + "generated rust did not compile\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let _ = std::fs::remove_dir_all(dir); +} + +fn assert_generated_role_crate_compiles(generated: &JoltGeneratedCrate) { + let dir = new_temp_dir(&generated.crate_name); + for file in &generated.files { + let path = dir.join(&file.path); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).expect("create generated crate dir"); + } + std::fs::write(path, &file.source).expect("write generated crate file"); + } + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let output = Command::new(cargo) + .arg("check") + .arg("--manifest-path") + .arg(dir.join("Cargo.toml")) + .arg("-q") + .env("CARGO_TARGET_DIR", dir.join("target")) + .output() + .expect("run generated role crate check"); + assert!( + output.status.success(), + "generated role crate `{}` did not compile\nstdout:\n{}\nstderr:\n{}", + generated.crate_name, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let _ = std::fs::remove_dir_all(dir); +} + +fn assert_generated_crate_manifest_compiles(output_root: &Path, crate_name: &str) { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let output = Command::new(cargo) + .arg("check") + .arg("--manifest-path") + .arg(output_root.join(crate_name).join("Cargo.toml")) + .arg("-q") + .env( + "CARGO_TARGET_DIR", + output_root.join("target").join(crate_name), + ) + .output() + .expect("run generated crate check"); + assert!( + output.status.success(), + "generated crate `{crate_name}` did not compile\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +fn redirect_generated_prover_to_generated_verifier(output_root: &Path, dependency_root: &Path) { + let manifest_path = output_root.join("jolt-prover").join("Cargo.toml"); + let workspace_verifier = format!( + "jolt-verifier = {{ path = \"{}/jolt-verifier\" }}", + dependency_root.display() + ); + let generated_verifier = format!( + "jolt-verifier = {{ path = \"{}\" }}", + output_root.join("jolt-verifier").display() + ); + let manifest = std::fs::read_to_string(&manifest_path).expect("read generated prover manifest"); + let manifest = manifest.replace(&workspace_verifier, &generated_verifier); + std::fs::write(&manifest_path, manifest).expect("rewrite generated prover manifest"); +} + +fn assert_checked_in_generated_role_crate_sources_match(generated_crates: &[JoltGeneratedCrate]) { + let crates_root = workspace_root().join("crates"); + for generated in generated_crates { + for file in &generated.files { + let checked_in_path = crates_root.join(&generated.crate_name).join(&file.path); + let checked_in = + std::fs::read_to_string(&checked_in_path).expect("read checked-in generated file"); + assert_eq!( + checked_in, + file.source, + "checked-in generated crate file `{}` is stale; regenerate with the Bolt artifact writer", + checked_in_path.display() + ); + if generated.crate_name == "jolt-verifier" { + assert!( + !checked_in.contains("use jolt_prover") + && !checked_in.contains("jolt_prover::") + && !checked_in.contains("use jolt_kernels") + && !checked_in.contains("jolt_kernels::") + && !checked_in.contains("use jolt_core") + && !checked_in.contains("jolt_core::"), + "generated verifier file `{}` imports non-audit role/runtime code", + checked_in_path.display() + ); + } + if generated.crate_name == "jolt-prover" { + assert!( + !checked_in.contains("jolt_verifier::stages"), + "generated prover file `{}` imports verifier stage internals instead of only verifier-owned proof types", + checked_in_path.display() + ); + } + } + } +} + +fn assert_generated_commitment_self_parity_runs( + prover_source: &RustSourceFile, + verifier_source: &RustSourceFile, + main_source: &str, +) { + let dir = new_temp_dir("bolt_self_parity"); + let workspace_root = workspace_root(); + std::fs::write( + dir.join("Cargo.toml"), + generated_crate_manifest(&workspace_root), + ) + .expect("write generated cargo manifest"); + let src_dir = dir.join("src"); + std::fs::create_dir_all(&src_dir).expect("create generated src dir"); + std::fs::write(src_dir.join(&prover_source.filename), &prover_source.source) + .expect("write generated prover source"); + std::fs::write( + src_dir.join(&verifier_source.filename), + &verifier_source.source, + ) + .expect("write generated verifier source"); + std::fs::write(src_dir.join("main.rs"), main_source) + .expect("write generated self-parity harness"); + + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let output = Command::new(cargo) + .arg("run") + .arg("--manifest-path") + .arg(dir.join("Cargo.toml")) + .arg("-q") + .env("CARGO_TARGET_DIR", dir.join("target")) + .output() + .expect("run generated self-parity crate"); + assert!( + output.status.success(), + "generated commitment self-parity failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let _ = std::fs::remove_dir_all(dir); +} + +fn assert_generated_stage1_self_parity_runs( + prover_source: &RustSourceFile, + verifier_source: &RustSourceFile, + main_source: &str, +) { + let dir = new_temp_dir("bolt_stage1_self_parity"); + let workspace_root = workspace_root(); + std::fs::write( + dir.join("Cargo.toml"), + generated_crate_manifest(&workspace_root), + ) + .expect("write generated cargo manifest"); + let src_dir = dir.join("src"); + std::fs::create_dir_all(&src_dir).expect("create generated src dir"); + let main_source = if verifier_source.source.contains("super::common") { + write_verifier_common_module(&src_dir, &workspace_root); + format!("mod common;\n{main_source}") + } else { + main_source.to_owned() + }; + std::fs::write(src_dir.join(&prover_source.filename), &prover_source.source) + .expect("write generated stage1 prover source"); + std::fs::write( + src_dir.join(&verifier_source.filename), + &verifier_source.source, + ) + .expect("write generated stage1 verifier source"); + std::fs::write(src_dir.join("main.rs"), main_source) + .expect("write generated stage1 self-parity harness"); + + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let output = Command::new(cargo) + .arg("run") + .arg("--manifest-path") + .arg(dir.join("Cargo.toml")) + .arg("-q") + .env("CARGO_TARGET_DIR", dir.join("target")) + .output() + .expect("run generated stage1 self-parity crate"); + assert!( + output.status.success(), + "generated stage1 self-parity failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let _ = std::fs::remove_dir_all(dir); +} + +fn assert_generated_jolt_chain_self_parity_runs(files: &[&RustSourceFile], main_source: &str) { + let dir = new_temp_dir("bolt_chain_self_parity"); + let workspace_root = workspace_root(); + std::fs::write( + dir.join("Cargo.toml"), + generated_crate_manifest(&workspace_root), + ) + .expect("write generated cargo manifest"); + let src_dir = dir.join("src"); + std::fs::create_dir_all(&src_dir).expect("create generated src dir"); + let main_source = if files + .iter() + .any(|file| file.source.contains("super::common")) + { + write_verifier_common_module(&src_dir, &workspace_root); + format!("mod common;\n{main_source}") + } else { + main_source.to_owned() + }; + for file in files { + std::fs::write(src_dir.join(&file.filename), &file.source) + .expect("write generated chain source"); + } + std::fs::write(src_dir.join("main.rs"), main_source) + .expect("write generated chain self-parity harness"); + + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let output = Command::new(cargo) + .arg("run") + .arg("--manifest-path") + .arg(dir.join("Cargo.toml")) + .arg("-q") + .env("CARGO_TARGET_DIR", dir.join("target")) + .output() + .expect("run generated chain self-parity crate"); + assert!( + output.status.success(), + "generated commitment+stage1 self-parity failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let _ = std::fs::remove_dir_all(dir); +} + +fn write_verifier_common_module(src_dir: &Path, workspace_root: &Path) { + let common = + std::fs::read_to_string(workspace_root.join("crates/jolt-verifier/src/stages/common.rs")) + .expect("read generated verifier common stage source"); + std::fs::write(src_dir.join("common.rs"), common).expect("write generated common source"); +} + +fn workspace_root() -> std::path::PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("workspace root") + .to_path_buf() +} + +fn generated_crate_manifest(workspace_root: &Path) -> String { + format!( + r#"[package] +name = "generated-commitment-phase-check" +version = "0.0.0" +edition = "2021" + +[patch.crates-io] +ark-bn254 = {{ git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout" }} +ark-ec = {{ git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout" }} +ark-ff = {{ git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout" }} +ark-serialize = {{ git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout" }} + +[dependencies] +jolt-dory = {{ path = "{}" }} +jolt-field = {{ path = "{}" }} +jolt-kernels = {{ path = "{}" }} +jolt-lookup-tables = {{ path = "{}" }} +jolt-openings = {{ path = "{}" }} +jolt-poly = {{ path = "{}" }} +jolt-r1cs = {{ path = "{}" }} +jolt-sumcheck = {{ path = "{}" }} +jolt-transcript = {{ path = "{}" }} +jolt-witness = {{ path = "{}" }} +rayon = "1.12.0" +serde = {{ version = "1.0", default-features = false, features = ["derive"] }} +tracing = {{ version = "0.1.37", default-features = false, features = ["attributes"] }} +"#, + workspace_root.join("crates/jolt-dory").display(), + workspace_root.join("crates/jolt-field").display(), + workspace_root.join("crates/jolt-kernels").display(), + workspace_root.join("crates/jolt-lookup-tables").display(), + workspace_root.join("crates/jolt-openings").display(), + workspace_root.join("crates/jolt-poly").display(), + workspace_root.join("crates/jolt-r1cs").display(), + workspace_root.join("crates/jolt-sumcheck").display(), + workspace_root.join("crates/jolt-transcript").display(), + workspace_root.join("crates/jolt-witness").display(), + ) +} + +fn new_temp_dir(prefix: &str) -> std::path::PathBuf { + let nonce = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock after unix epoch") + .as_nanos(); + let dir = std::env::temp_dir().join(format!("{}_{}_{}", prefix, std::process::id(), nonce)); + std::fs::create_dir_all(&dir).expect("create generated crate temp dir"); + dir +} + +fn generated_small_self_parity_main() -> String { + let mut source = r#"mod prove_commitment_phase; +mod verify_commitment_phase; + +use std::borrow::Cow; + +use jolt_dory::DoryScheme; +use jolt_field::{Field, Fr}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +struct Inputs; + +impl prove_commitment_phase::CommitmentInputProvider for Inputs { + fn materialize(&mut self, oracle: &'static str) -> Option> { + match oracle { + "A" => Some(Cow::Owned(vec![Fr::from_u64(1), Fr::from_u64(2)])), + "B" => Some(Cow::Owned(vec![ + Fr::from_u64(3), + Fr::from_u64(4), + Fr::from_u64(5), + Fr::from_u64(6), + ])), + "Advice" => Some(Cow::Owned(vec![Fr::from_u64(0), Fr::from_u64(0)])), + _ => None, + } + } +} + +"# + .to_owned(); + source.push_str(tracing_transcript_support()); + source.push_str( + r#" +fn main() { + let prover_setup = + DoryScheme::setup_prover(prove_commitment_phase::COMMITMENT_BATCH_PLANS[0].num_vars); + let mut inputs = Inputs; + let mut prover_transcript = TracingTranscript::new(b"self"); + let prover = prove_commitment_phase::prove_commitment_phase( + &mut inputs, + &prover_setup, + &mut prover_transcript, + ) + .expect("prover commitment phase"); + + assert_eq!(prover.commitments.len(), 3); + assert!(prover.commitments[2].is_none()); + + let mut verifier_transcript = TracingTranscript::new(b"self"); + let verifier = verify_commitment_phase::verify_commitment_phase( + &prover.commitments, + &mut verifier_transcript, + ) + .expect("verifier commitment phase"); + + assert_eq!(prover.commitments, verifier.commitments); + assert_eq!(prover.records.len(), verifier.records.len()); + assert_transcript_step_parity(&prover_transcript, &verifier_transcript); +} +"#, + ); + source +} + +fn generated_pipeline_self_parity_main() -> String { + let mut source = "mod prove_commitment_phase; +mod verify_commitment_phase; + +use jolt_dory::DoryScheme; +use jolt_field::{Field, Fr}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +" + .to_owned(); + source.push_str(tracing_transcript_support()); + source.push_str( + r#" +fn main() { + let prover_setup = + DoryScheme::setup_prover(prove_commitment_phase::COMMITMENT_BATCH_PLANS[0].num_vars); + let inputs = prove_commitment_phase::CommitmentOracleInputs { + rd_inc: &[1], + ram_inc: &[2], + instruction_keys: &[Some(0x1234_5678_9abc_def0_0123_4567_89ab_cdefu128)], + ram_addresses: &[], + bytecode_indices: &[], + untrusted_advice: None, + trusted_advice: None, + }; + let mut oracles = prove_commitment_phase::build_commitment_oracles(&inputs) + .expect("build commitment oracles"); + let mut prover_transcript = TracingTranscript::new(b"pipeline"); + let prover = prove_commitment_phase::prove_commitment_phase( + &mut oracles, + &prover_setup, + &mut prover_transcript, + ) + .expect("prover commitment phase"); + + let expected_slots = prove_commitment_phase::COMMITMENT_BATCH_PLANS + .iter() + .map(|plan| plan.oracles.len()) + .sum::() + + prove_commitment_phase::OPTIONAL_COMMITMENT_PLANS.len(); + assert_eq!(prover.commitments.len(), expected_slots); + + let mut verifier_transcript = TracingTranscript::new(b"pipeline"); + let verifier = verify_commitment_phase::verify_commitment_phase( + &prover.commitments, + &mut verifier_transcript, + ) + .expect("verifier commitment phase"); + + assert_eq!(prover.commitments, verifier.commitments); + assert_eq!(prover.records.len(), verifier.records.len()); + for (prover_record, verifier_record) in prover.records.iter().zip(&verifier.records) { + assert_eq!(prover_record.artifact, verifier_record.artifact); + assert_eq!(prover_record.oracle, verifier_record.oracle); + assert_eq!(prover_record.label, verifier_record.label); + assert_eq!(prover_record.num_vars, verifier_record.num_vars); + } + assert_transcript_step_parity(&prover_transcript, &verifier_transcript); +} +"#, + ); + source +} + +fn generated_stage1_shape_self_parity_main() -> String { + let mut source = r"mod prove_stage1_outer; +mod verify_stage1_outer; + +use jolt_field::{Field, Fr}; +use jolt_kernels::stage1::Stage1ShapeKernelExecutor; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +" + .to_owned(); + source.push_str(tracing_transcript_support()); + source.push_str(&stage1_verifier_proof_adapter(true)); + source.push_str( + r#" +fn main() { + let mut prover_executor = Stage1ShapeKernelExecutor; + let mut prover_transcript = TracingTranscript::new(b"stage1"); + let prover = prove_stage1_outer::prove_stage1_outer( + &mut prover_executor, + &mut prover_transcript, + ) + .expect("generated prover runs shape kernels"); + + let proof = verifier_proof_from_prover_artifacts(&prover); + let mut verifier_transcript = TracingTranscript::new(b"stage1"); + let verifier = verify_stage1_outer::verify_stage1_outer( + &proof, + &mut verifier_transcript, + ) + .expect("generated verifier accepts shape proof"); + + assert_eq!( + prover.sumchecks.len(), + prove_stage1_outer::STAGE1_SUMCHECK_DRIVERS.len() + ); + assert_eq!(prover.sumchecks.len(), verifier.sumchecks.len()); + assert_eq!(prover.opening_batches.len(), verifier.opening_batches.len()); + for (prover_batch, verifier_batch) in prover.opening_batches.iter().zip(&verifier.opening_batches) { + assert_eq!(prover_batch.symbol, verifier_batch.symbol); + assert_eq!(prover_batch.count, verifier_batch.count); + } + for (prover_sumcheck, verifier_sumcheck) in prover.sumchecks.iter().zip(&verifier.sumchecks) { + assert_eq!(prover_sumcheck.driver, verifier_sumcheck.driver); + assert_eq!(prover_sumcheck.evals.len(), verifier_sumcheck.evals.len()); + for (prover_eval, verifier_eval) in prover_sumcheck.evals.iter().zip(&verifier_sumcheck.evals) { + assert_eq!(prover_eval.name, verifier_eval.name); + assert_eq!(prover_eval.oracle, verifier_eval.oracle); + assert_eq!(prover_eval.value, verifier_eval.value); + } + assert_eq!( + prover_sumcheck.proof.round_polynomials.len(), + verifier_sumcheck.proof.round_polynomials.len() + ); + for (prover_round, verifier_round) in prover_sumcheck + .proof + .round_polynomials + .iter() + .zip(&verifier_sumcheck.proof.round_polynomials) + { + assert_eq!(prover_round.coefficients(), verifier_round.coefficients()); + } + } + assert_ne!(prover_transcript.state(), verifier_transcript.state()); +} +"#, + ); + source +} + +fn stage1_verifier_proof_adapter(clear_points: bool) -> String { + let point_expr = if clear_points { + "Vec::new()" + } else { + "sumcheck.point.clone()" + }; + r" +fn verifier_proof_from_prover_artifacts( + artifacts: &jolt_kernels::stage1::Stage1ExecutionArtifacts, +) -> verify_stage1_outer::Stage1Proof { + verify_stage1_outer::Stage1Proof { + sumchecks: artifacts + .sumchecks + .iter() + .map(|sumcheck| verify_stage1_outer::Stage1SumcheckOutput { + driver: sumcheck.driver, + point: $POINT_EXPR, + evals: sumcheck + .evals + .iter() + .map(|eval| verify_stage1_outer::Stage1NamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + }) + .collect(), + proof: sumcheck.proof.clone(), + }) + .collect(), + } +} + +" + .replace("$POINT_EXPR", point_expr) +} + +fn generated_stage1_real_dispatch_main() -> &'static str { + r#"mod prove_stage1_outer; +mod verify_stage1_outer; + +use jolt_field::{Field, Fr}; +use jolt_kernels::stage1::{ + Stage1KernelError, Stage1ProverInputs, Stage1ProverKernelExecutor, +}; +use jolt_sumcheck::SumcheckError; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +fn main() { + let inputs = Stage1ProverInputs::::empty(2); + let mut prover_executor = Stage1ProverKernelExecutor::new(inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage1"); + let prover_error = prove_stage1_outer::prove_stage1_outer( + &mut prover_executor, + &mut prover_transcript, + ) + .expect_err("real prover requires uniskip extended evaluations"); + assert_eq!( + prover_error, + Stage1KernelError::MissingKernelInput { + kernel: "jolt_stage1_outer_uniskip", + input: "uniskip_extended_evals", + } + ); + + let proof = verify_stage1_outer::Stage1Proof { + sumchecks: vec![ + verify_stage1_outer::Stage1SumcheckOutput { + driver: "stage1.uniskip.sumcheck", + point: Vec::new(), + evals: Vec::new(), + proof: Default::default(), + }, + verify_stage1_outer::Stage1SumcheckOutput { + driver: "stage1.outer_remaining.sumcheck", + point: Vec::new(), + evals: Vec::new(), + proof: Default::default(), + }, + ], + }; + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + let verifier_error = verify_stage1_outer::verify_stage1_outer( + &proof, + &mut verifier_transcript, + ) + .expect_err("real verifier rejects empty uniskip proof"); + assert!(matches!( + verifier_error, + verify_stage1_outer::VerifyStage1Error::Sumcheck { + driver: "stage1.uniskip.sumcheck", + error: SumcheckError::WrongNumberOfRounds { expected: 1, got: 0 }, + } + )); +} +"# +} + +fn generated_stage1_synthetic_remaining_main() -> String { + let mut source = r"mod prove_stage1_outer; +mod verify_stage1_outer; + +use jolt_field::{Field, Fr}; +use jolt_kernels::stage1::{ + Stage1OuterRemainingContext, Stage1OuterRemainingEvaluator, Stage1ProverInputs, + Stage1ProverKernelExecutor, +}; +use jolt_poly::UnivariatePoly; +use jolt_sumcheck::SumcheckError; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +struct SumZeroRemainingEvaluator; + +impl Stage1OuterRemainingEvaluator for SumZeroRemainingEvaluator { + fn evaluate(&self, _context: Stage1OuterRemainingContext<'_, Fr>, point: &[Fr]) -> Fr { + point[0] + point[0] - Fr::from_u64(1) + } + + fn evaluate_virtual_oracle( + &self, + _context: Stage1OuterRemainingContext<'_, Fr>, + _oracle: &str, + point: &[Fr], + ) -> Option { + Some(point.iter().copied().sum()) + } +} + +" + .to_owned(); + source.push_str(&stage1_verifier_proof_adapter(false)); + source.push_str( + r#" +fn main() { + let extended_evals = vec![Fr::from_u64(0); 9]; + let evaluator = SumZeroRemainingEvaluator; + let inputs = Stage1ProverInputs::::empty(2) + .with_uniskip_extended_evals(&extended_evals) + .with_outer_remaining_evaluator(&evaluator); + let mut prover_executor = Stage1ProverKernelExecutor::new(inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage1"); + let prover_artifacts = prove_stage1_outer::prove_stage1_outer( + &mut prover_executor, + &mut prover_transcript, + ) + .expect("generated real stage1 prover succeeds"); + + let proof = verifier_proof_from_prover_artifacts(&prover_artifacts); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + let verifier_artifacts = verify_stage1_outer::verify_stage1_outer( + &proof, + &mut verifier_transcript, + ) + .expect("generated real stage1 verifier accepts prover proof"); + + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!(prover_artifacts.sumchecks.len(), 2); + assert_eq!(verifier_artifacts.sumchecks.len(), 2); + assert_eq!( + prover_artifacts.sumchecks[1].point, + verifier_artifacts.sumchecks[1].point + ); + + let mut extra_proof = proof.clone(); + extra_proof.sumchecks.push(proof.sumchecks[0].clone()); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + assert!(matches!( + verify_stage1_outer::verify_stage1_outer(&extra_proof, &mut verifier_transcript), + Err(verify_stage1_outer::VerifyStage1Error::UnexpectedProofCount { + expected: 2, + got: 3, + }) + )); + + let mut wrong_driver = proof.clone(); + wrong_driver.sumchecks.swap(0, 1); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + assert!(matches!( + verify_stage1_outer::verify_stage1_outer(&wrong_driver, &mut verifier_transcript), + Err(verify_stage1_outer::VerifyStage1Error::InvalidProof { + driver: "stage1.uniskip.sumcheck", + reason: "driver symbol mismatch", + }) + )); + + let mut wrong_round = proof.clone(); + let mut coefficients = wrong_round.sumchecks[0].proof.round_polynomials[0] + .coefficients() + .to_vec(); + coefficients[0] += Fr::from_u64(1); + wrong_round.sumchecks[0].proof.round_polynomials[0] = UnivariatePoly::new(coefficients); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + assert!(matches!( + verify_stage1_outer::verify_stage1_outer(&wrong_round, &mut verifier_transcript), + Err(verify_stage1_outer::VerifyStage1Error::Sumcheck { + driver: "stage1.uniskip.sumcheck", + error: SumcheckError::RoundCheckFailed { .. }, + }) + )); + + let mut wrong_uniskip_eval = proof.clone(); + wrong_uniskip_eval.sumchecks[0].evals[0].value += Fr::from_u64(1); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + assert!(matches!( + verify_stage1_outer::verify_stage1_outer(&wrong_uniskip_eval, &mut verifier_transcript), + Err(verify_stage1_outer::VerifyStage1Error::InvalidProof { + driver: "stage1.uniskip.sumcheck", + reason: "eval value mismatch", + }) + )); + + let mut wrong_remaining_eval = proof.clone(); + wrong_remaining_eval.sumchecks[1].evals.swap(0, 1); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + assert!(matches!( + verify_stage1_outer::verify_stage1_outer(&wrong_remaining_eval, &mut verifier_transcript), + Err(verify_stage1_outer::VerifyStage1Error::InvalidProof { + driver: "stage1.outer_remaining.sumcheck", + reason: "eval name mismatch", + }) + )); + + let mut wrong_point = proof.clone(); + wrong_point.sumchecks[1].point[0] += Fr::from_u64(1); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + assert!(matches!( + verify_stage1_outer::verify_stage1_outer(&wrong_point, &mut verifier_transcript), + Err(verify_stage1_outer::VerifyStage1Error::InvalidProof { + driver: "stage1.outer_remaining.sumcheck", + reason: "outer remaining point mismatch", + }) + )); +} +"#, + ); + source +} + +fn generated_stage1_r1cs_data_main() -> String { + let mut source = r"mod prove_stage1_outer; +mod verify_stage1_outer; + +use jolt_field::{Field, Fr}; +use jolt_kernels::stage1::{ + Stage1OuterR1csData, Stage1ProverInputs, Stage1ProverKernelExecutor, +}; +use jolt_r1cs::{constraints::rv64, R1csKey}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +" + .to_owned(); + source.push_str(&stage1_verifier_proof_adapter(false)); + source.push_str( + r#" +fn main() { + let key = R1csKey::new(rv64::rv64_constraints::(), 4); + let mut witness = vec![Fr::from_u64(0); key.num_cycles * key.num_vars_padded]; + for cycle in 0..key.num_cycles { + let base = cycle * key.num_vars_padded; + witness[base + rv64::V_CONST] = Fr::from_u64(1); + witness[base + rv64::V_FLAG_DO_NOT_UPDATE_UNEXPANDED_PC] = Fr::from_u64(1); + key.matrices + .check_witness(&witness[base..base + rv64::NUM_VARS_PER_CYCLE]) + .expect("noop cycle satisfies RV64 constraints"); + } + let data = Stage1OuterR1csData::new(&key, &witness).expect("valid R1CS witness shape"); + let inputs = Stage1ProverInputs::::empty(key.num_cycle_vars()) + .with_outer_remaining_evaluator(&data); + let mut prover_executor = Stage1ProverKernelExecutor::new(inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage1"); + let prover_artifacts = prove_stage1_outer::prove_stage1_outer( + &mut prover_executor, + &mut prover_transcript, + ) + .expect("generated real stage1 prover succeeds with R1CS data"); + + let proof = verifier_proof_from_prover_artifacts(&prover_artifacts); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage1"); + let verifier_artifacts = verify_stage1_outer::verify_stage1_outer( + &proof, + &mut verifier_transcript, + ) + .expect("generated real stage1 verifier accepts R1CS-backed proof"); + + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!(prover_artifacts.sumchecks.len(), 2); + assert_eq!(verifier_artifacts.sumchecks.len(), 2); + for (prover_sumcheck, verifier_sumcheck) in prover_artifacts + .sumchecks + .iter() + .zip(verifier_artifacts.sumchecks.iter()) + { + assert_eq!(prover_sumcheck.point, verifier_sumcheck.point); + assert_eq!(prover_sumcheck.evals.len(), verifier_sumcheck.evals.len()); + for (prover_eval, verifier_eval) in prover_sumcheck.evals.iter().zip(&verifier_sumcheck.evals) { + assert_eq!(prover_eval.oracle, verifier_eval.oracle); + assert_eq!(prover_eval.value, verifier_eval.value); + } + } +} +"#, + ); + source +} + +fn generated_commitment_stage1_chain_main() -> String { + let mut source = r"mod prove_commitment_phase; +mod prove_stage1_outer; +mod verify_commitment_phase; +mod verify_stage1_outer; + +use jolt_dory::DoryScheme; +use jolt_field::{Field, Fr}; +use jolt_kernels::stage1::{ + Stage1OuterRemainingContext, Stage1OuterRemainingEvaluator, Stage1ProverInputs, + Stage1ProverKernelExecutor, +}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +struct SumZeroRemainingEvaluator; + +impl Stage1OuterRemainingEvaluator for SumZeroRemainingEvaluator { + fn evaluate(&self, _context: Stage1OuterRemainingContext<'_, Fr>, point: &[Fr]) -> Fr { + point[0] + point[0] - Fr::from_u64(1) + } + + fn evaluate_virtual_oracle( + &self, + _context: Stage1OuterRemainingContext<'_, Fr>, + _oracle: &str, + point: &[Fr], + ) -> Option { + Some(point.iter().copied().sum()) + } +} + +" + .to_owned(); + source.push_str(tracing_transcript_support()); + source.push_str(&stage1_verifier_proof_adapter(false)); + source.push_str( + r#" +fn main() { + let prover_setup = + DoryScheme::setup_prover(prove_commitment_phase::COMMITMENT_BATCH_PLANS[0].num_vars); + let commitment_inputs = prove_commitment_phase::CommitmentOracleInputs { + rd_inc: &[1, 0, 0, 0], + ram_inc: &[2, 0, 0, 0], + instruction_keys: &[ + Some(0x1234_5678_9abc_def0_0123_4567_89ab_cdefu128), + Some(0), + Some(0), + Some(0), + ], + ram_addresses: &[Some(0), Some(1), Some(2), Some(3)], + bytecode_indices: &[Some(0), Some(1), Some(2), Some(3)], + untrusted_advice: None, + trusted_advice: None, + }; + let mut commitment_oracles = prove_commitment_phase::build_commitment_oracles( + &commitment_inputs, + ) + .expect("build commitment oracles"); + let mut prover_transcript = TracingTranscript::new(b"jolt-chain"); + let commitment = prove_commitment_phase::prove_commitment_phase( + &mut commitment_oracles, + &prover_setup, + &mut prover_transcript, + ) + .expect("prover commitment phase"); + + let extended_evals = vec![Fr::from_u64(0); 9]; + let evaluator = SumZeroRemainingEvaluator; + let stage1_inputs = Stage1ProverInputs::::empty(2) + .with_uniskip_extended_evals(&extended_evals) + .with_outer_remaining_evaluator(&evaluator); + let mut stage1_prover_executor = Stage1ProverKernelExecutor::new(stage1_inputs); + let stage1 = prove_stage1_outer::prove_stage1_outer( + &mut stage1_prover_executor, + &mut prover_transcript, + ) + .expect("stage1 prover phase"); + + let mut verifier_transcript = TracingTranscript::new(b"jolt-chain"); + let verified_commitment = verify_commitment_phase::verify_commitment_phase( + &commitment.commitments, + &mut verifier_transcript, + ) + .expect("verifier commitment phase"); + let stage1_proof = verifier_proof_from_prover_artifacts(&stage1); + let verified_stage1 = verify_stage1_outer::verify_stage1_outer( + &stage1_proof, + &mut verifier_transcript, + ) + .expect("stage1 verifier phase"); + + assert_eq!(commitment.commitments, verified_commitment.commitments); + assert_eq!(stage1.sumchecks.len(), 2); + assert_eq!(verified_stage1.sumchecks.len(), 2); + assert_eq!(stage1.sumchecks[1].point, verified_stage1.sumchecks[1].point); + assert_transcript_step_parity(&prover_transcript, &verifier_transcript); +} +"#, + ); + source +} + +fn tracing_transcript_support() -> &'static str { + r"#[derive(Clone, Debug, PartialEq, Eq)] +enum TranscriptEvent { + Init([u8; 32]), + Append { bytes: Vec, state: [u8; 32] }, + Challenge { state: [u8; 32] }, +} + +#[derive(Clone, Default)] +struct TracingTranscript { + inner: Blake2bTranscript, + events: Vec, +} + +impl Transcript for TracingTranscript { + type Challenge = Fr; + + fn new(label: &'static [u8]) -> Self { + let inner = Blake2bTranscript::::new(label); + let events = vec![TranscriptEvent::Init(*inner.state())]; + Self { inner, events } + } + + fn append_bytes(&mut self, bytes: &[u8]) { + self.inner.append_bytes(bytes); + self.events.push(TranscriptEvent::Append { + bytes: bytes.to_vec(), + state: *self.inner.state(), + }); + } + + fn challenge(&mut self) -> Fr { + let challenge = self.inner.challenge(); + self.events.push(TranscriptEvent::Challenge { + state: *self.inner.state(), + }); + challenge + } + + fn state(&self) -> &[u8; 32] { + self.inner.state() + } +} + +fn assert_transcript_step_parity(prover: &TracingTranscript, verifier: &TracingTranscript) { + assert_eq!(prover.events, verifier.events); + assert_eq!(prover.state(), verifier.state()); +} + +" +} diff --git a/crates/bolt/tests/verifier_cleanup.rs b/crates/bolt/tests/verifier_cleanup.rs new file mode 100644 index 0000000000..55231001df --- /dev/null +++ b/crates/bolt/tests/verifier_cleanup.rs @@ -0,0 +1,597 @@ +#![expect( + clippy::expect_used, + clippy::print_stderr, + reason = "verifier cleanup tests use explicit panic messages and print metrics for CI logs" +)] + +use std::path::{Path, PathBuf}; + +const GENERATED_VERIFIER_TARGET_LOC: usize = 6_000; +const GENERATED_VERIFIER_STRETCH_LOC: usize = 3_000; +const VERIFIER_RS_TARGET_LOC: usize = 500; +const VERIFIER_RS_STRETCH_LOC: usize = 350; +const STAGE6_STAGE7_TARGET_LOC: usize = 3_000; + +const GENERATED_VERIFIER_BASELINE_LOC_CEILING: usize = 9_185; +const SHARED_RUNTIME_BASELINE_LOC_CEILING: usize = 1_900; +const VERIFIER_RS_BASELINE_LOC_CEILING: usize = VERIFIER_RS_TARGET_LOC; +const STAGE6_STAGE7_BASELINE_LOC_CEILING: usize = STAGE6_STAGE7_TARGET_LOC; +const STAGE_LOCAL_PLAN_STRUCT_BASELINE_CEILING: usize = 18; +const FIELD_EXPR_OPERAND_CONSTANT_BASELINE_CEILING: usize = 0; +const STAGE_HELPER_FUNCTION_BASELINE_CEILING: usize = 38; +const RELATION_STRING_SITE_BASELINE_CEILING: usize = 72; + +const ALLOWED_JOLT_PROTOCOL_SYMBOLS: &[&str] = &[ + "jolt.commitment_phase", + "jolt.main_witness_commit_domain", + "jolt.main_witness_commitments", + "jolt.main_witness_polys", + "jolt.ram_address_domain", + "jolt.stage1.outer.remaining", + "jolt.stage1.outer.uniskip", + "jolt.stage1_outer", + "jolt.stage1_uniskip_domain", + "jolt.stage2", + "jolt.stage2.batched", + "jolt.stage2.instruction_lookup.claim_reduction", + "jolt.stage2.product_virtual.remainder", + "jolt.stage2.product_virtual.uniskip", + "jolt.stage2.ram.output_check", + "jolt.stage2.ram.output_check.layout", + "jolt.stage2.ram.raf_evaluation", + "jolt.stage2.ram.read_write", + "jolt.stage2_ram_rw_domain", + "jolt.stage2_uniskip_domain", + "jolt.stage3", + "jolt.stage3.batched", + "jolt.stage3.instruction_input", + "jolt.stage3.registers_claim_reduction", + "jolt.stage3.spartan_shift", + "jolt.stage4", + "jolt.stage4.batched", + "jolt.stage4.ram_val_check", + "jolt.stage4.registers_read_write", + "jolt.stage4_registers_rw_domain", + "jolt.stage5.batched", + "jolt.stage5.instruction_read_raf", + "jolt.stage5.ram_ra_claim_reduction", + "jolt.stage5.registers_val_evaluation", + "jolt.stage5_instruction_ra_chunk_domain", + "jolt.stage5_instruction_read_raf_domain", + "jolt.stage6.batched", + "jolt.stage6.booleanity", + "jolt.stage6.bytecode_read_raf", + "jolt.stage6.hamming_booleanity", + "jolt.stage6.inc_claim_reduction", + "jolt.stage6.instruction_ra_virtual", + "jolt.stage6.ram_ra_virtual", + "jolt.stage6_booleanity_domain", + "jolt.stage6_bytecode_read_raf_domain", + "jolt.stage7.batched", + "jolt.stage7.hamming_booleanity", + "jolt.stage7.hamming_weight_claim_reduction", + "jolt.stage7_hamming_weight_claim_reduction_domain", + "jolt.stage8", + "jolt.trace_domain", + "jolt.trusted_advice_commitment", + "jolt.untrusted_advice_commitment", +]; + +const GENERIC_COMPILER_JOLT_PATTERNS: &[&str] = &[ + "jolt.", + "Jolt", + "jolt_", + "jolt-", + "jolt_core", + "stage1_outer", + "stage1", + "stage2", + "stage3", + "stage4", + "stage5", + "stage6", + "stage7", + "stage8", + "uniskip", + "spartan", + "bytecode", + "hamming", + "instruction_read", + "ram_val", + "ram_ra", + "registers_read", + "lookup", + "dory", + "bn254", +]; + +#[derive(Debug, Default)] +struct VerifierCleanupMetrics { + total_loc: usize, + generated_surface_loc: usize, + shared_runtime_loc: usize, + verifier_rs_loc: usize, + stage6_stage7_loc: usize, + stage_local_generic_plan_structs: usize, + field_expr_operand_constants: usize, + stage_local_helper_functions: usize, + relation_string_sites: usize, +} + +#[test] +fn checked_in_generated_verifier_metrics_are_recorded_and_bounded() { + let verifier_src = workspace_root().join("crates/jolt-verifier/src"); + let metrics = verifier_cleanup_metrics(&verifier_src); + + eprintln!( + "\nGenerated verifier cleanup metrics\n\ + generated_surface_loc: {generated_surface_loc} (target <= {target_loc}, stretch <= {stretch_loc})\n\ + shared_runtime_loc: {shared_runtime_loc} (baseline ceiling <= {shared_runtime_baseline})\n\ + total_loc: {total_loc} (baseline ceiling <= {baseline_loc})\n\ + verifier_rs_loc: {verifier_rs_loc} (target <= {verifier_target}, stretch <= {verifier_stretch}, baseline ceiling <= {verifier_baseline})\n\ + stage6_stage7_loc: {stage6_stage7_loc} (target <= {stage67_target}, baseline ceiling <= {stage67_baseline})\n\ + stage_local_generic_plan_structs: {plan_structs} (baseline ceiling <= {plan_baseline})\n\ + field_expr_operand_constants: {operand_constants} (baseline ceiling <= {operand_baseline})\n\ + stage_local_helper_functions: {helper_functions} (baseline ceiling <= {helper_baseline})\n\ + relation_string_sites: {relation_sites} (baseline ceiling <= {relation_baseline})", + generated_surface_loc = metrics.generated_surface_loc, + shared_runtime_loc = metrics.shared_runtime_loc, + shared_runtime_baseline = SHARED_RUNTIME_BASELINE_LOC_CEILING, + total_loc = metrics.total_loc, + target_loc = GENERATED_VERIFIER_TARGET_LOC, + stretch_loc = GENERATED_VERIFIER_STRETCH_LOC, + baseline_loc = GENERATED_VERIFIER_BASELINE_LOC_CEILING, + verifier_rs_loc = metrics.verifier_rs_loc, + verifier_target = VERIFIER_RS_TARGET_LOC, + verifier_stretch = VERIFIER_RS_STRETCH_LOC, + verifier_baseline = VERIFIER_RS_BASELINE_LOC_CEILING, + stage6_stage7_loc = metrics.stage6_stage7_loc, + stage67_target = STAGE6_STAGE7_TARGET_LOC, + stage67_baseline = STAGE6_STAGE7_BASELINE_LOC_CEILING, + plan_structs = metrics.stage_local_generic_plan_structs, + plan_baseline = STAGE_LOCAL_PLAN_STRUCT_BASELINE_CEILING, + operand_constants = metrics.field_expr_operand_constants, + operand_baseline = FIELD_EXPR_OPERAND_CONSTANT_BASELINE_CEILING, + helper_functions = metrics.stage_local_helper_functions, + helper_baseline = STAGE_HELPER_FUNCTION_BASELINE_CEILING, + relation_sites = metrics.relation_string_sites, + relation_baseline = RELATION_STRING_SITE_BASELINE_CEILING, + ); + + assert!( + metrics.generated_surface_loc <= GENERATED_VERIFIER_TARGET_LOC, + "generated verifier surface is {} LOC; keep reducing generated stage/orchestration code or intentionally update the cleanup target", + metrics.generated_surface_loc + ); + assert!( + metrics.generated_surface_loc > GENERATED_VERIFIER_STRETCH_LOC, + "cleanup metric reached the stretch target; tighten the generated verifier surface gate" + ); + assert!( + metrics.shared_runtime_loc <= SHARED_RUNTIME_BASELINE_LOC_CEILING, + "shared verifier runtime grew to {} LOC; keep generic runtime small and audited", + metrics.shared_runtime_loc + ); + assert!( + metrics.total_loc <= GENERATED_VERIFIER_BASELINE_LOC_CEILING, + "checked-in verifier grew to {} LOC; lower generated/runtime surface, or intentionally update the cleanup baseline", + metrics.total_loc + ); + assert!( + metrics.verifier_rs_loc <= VERIFIER_RS_BASELINE_LOC_CEILING, + "top-level verifier grew to {} LOC; keep orchestration small and readable", + metrics.verifier_rs_loc + ); + assert!( + metrics.stage6_stage7_loc <= STAGE6_STAGE7_BASELINE_LOC_CEILING, + "Stage 6/7 generated verifier surface grew to {} LOC; compact plan data before adding more generated code", + metrics.stage6_stage7_loc + ); + assert!( + metrics.stage_local_generic_plan_structs <= STAGE_LOCAL_PLAN_STRUCT_BASELINE_CEILING, + "stage-local generic plan struct count grew to {}; move shared plan types into common verifier runtime", + metrics.stage_local_generic_plan_structs + ); + assert!( + metrics.field_expr_operand_constants == FIELD_EXPR_OPERAND_CONSTANT_BASELINE_CEILING, + "field-expression operand constants grew to {}; compact field expression encoding", + metrics.field_expr_operand_constants + ); + assert!( + metrics.stage_local_helper_functions <= STAGE_HELPER_FUNCTION_BASELINE_CEILING, + "stage-local helper function count grew to {}; factor verifier mechanics into shared runtime", + metrics.stage_local_helper_functions + ); + assert!( + metrics.relation_string_sites <= RELATION_STRING_SITE_BASELINE_CEILING, + "relation string sites grew to {}; prefer typed relation plan data or explicit allowlists", + metrics.relation_string_sites + ); +} + +#[test] +fn checked_in_generated_verifier_respects_boundary_hygiene() { + let verifier_root = workspace_root().join("crates/jolt-verifier"); + let manifest = + std::fs::read_to_string(verifier_root.join("Cargo.toml")).expect("read verifier manifest"); + for package in [ + "jolt-prover", + "jolt-kernels", + "jolt-core", + "jolt-equivalence", + "jolt-profiling", + "tracer", + ] { + assert!( + !manifest.contains(package), + "generated verifier manifest depends on forbidden package `{package}`" + ); + } + + for path in rust_files(&verifier_root.join("src")) { + let source = std::fs::read_to_string(&path).expect("read verifier source"); + for pattern in [ + "use jolt_prover", + "jolt_prover::", + "use jolt_kernels", + "jolt_kernels::", + "use jolt_core", + "jolt_core::", + "use jolt_equivalence", + "jolt_equivalence::", + "use jolt_profiling", + "jolt_profiling::", + "use tracer", + "tracer::", + ] { + assert!( + !source.contains(pattern), + "generated verifier source `{}` contains forbidden import/reference `{pattern}`", + path.display() + ); + } + assert!( + !source.contains("JoltField::Challenge") + && !source.contains("Transcript") + && !source.contains("Challenge = <"), + "generated verifier source `{}` drifted away from the full-field transcript path", + path.display() + ); + } +} + +#[test] +fn verifier_cpu_fixtures_are_kernel_free() { + let fixtures = workspace_root().join("crates/bolt/tests/fixtures"); + if !fixtures.exists() { + eprintln!("skipping optional verifier MLIR scratch fixture check; run commitment_ir with JOLT_UPDATE_GOLDENS=1 to materialize fixtures"); + return; + } + let mut checked = 0usize; + for path in files_with_extension(&fixtures, "mlir") { + let file_name = path + .file_name() + .and_then(|name| name.to_str()) + .expect("fixture file name"); + if !file_name.contains("verifier") { + continue; + } + checked += 1; + let source = std::fs::read_to_string(&path).expect("read verifier MLIR fixture"); + for pattern in ["kernel = @", "\"cpu.kernel\"", "\"compute.kernel\""] { + assert!( + !source.contains(pattern), + "verifier MLIR fixture `{}` contains forbidden kernel marker `{pattern}`", + path.display() + ); + } + } + assert!(checked > 0, "no verifier MLIR fixtures were checked"); +} + +#[test] +fn checked_in_generated_verifier_protocol_symbols_are_allowlisted() { + let verifier_root = workspace_root().join("crates/jolt-verifier/src"); + let mut checked = 0usize; + for path in rust_files(&verifier_root) { + let source = std::fs::read_to_string(&path).expect("read verifier source"); + for symbol in quoted_jolt_protocol_symbols(&source) { + checked += 1; + assert_allowed_jolt_protocol_symbol(&path, symbol); + } + } + assert!( + checked > 0, + "no generated verifier Jolt symbols were checked" + ); +} + +#[test] +fn verifier_mlir_fixtures_protocol_symbols_are_allowlisted() { + let fixtures = workspace_root().join("crates/bolt/tests/fixtures"); + if !fixtures.exists() { + eprintln!("skipping optional verifier MLIR scratch symbol check; run commitment_ir with JOLT_UPDATE_GOLDENS=1 to materialize fixtures"); + return; + } + let mut checked = 0usize; + for path in files_with_extension(&fixtures, "mlir") { + let file_name = path + .file_name() + .and_then(|name| name.to_str()) + .expect("fixture file name"); + if !file_name.contains("verifier") { + continue; + } + let source = std::fs::read_to_string(&path).expect("read verifier MLIR fixture"); + for symbol in mlir_jolt_protocol_symbols(&source) { + checked += 1; + assert_allowed_jolt_protocol_symbol(&path, symbol); + } + } + assert!(checked > 0, "no verifier MLIR Jolt symbols were checked"); +} + +#[test] +fn generic_compiler_rejects_jolt_protocol_strings() { + let root = workspace_root(); + let mut offenders = Vec::new(); + for path in generic_compiler_source_files(&root) { + let source = std::fs::read_to_string(&path).expect("read generic compiler source"); + let hits = count_generic_compiler_jolt_hits(&source); + if hits == 0 { + continue; + } + + let relative = relative_workspace_path(&root, &path); + offenders.push(format!("{relative}: {hits} hit(s)")); + } + assert!( + offenders.is_empty(), + "generic compiler source contains quarantined Jolt protocol strings:\n{}", + offenders.join("\n") + ); +} + +#[test] +fn jolt_artifact_apis_are_quarantined_out_of_generic_exports() { + let root = workspace_root(); + let artifact_source = + std::fs::read_to_string(root.join("crates/bolt/src/emit/rust/artifacts.rs")) + .expect("read generic artifact assembly"); + for pattern in [ + "JoltProtocolStage", + "JoltArtifactCrate", + "JoltRustArtifact", + "JoltGeneratedCrate", + "JoltGeneratedFile", + "jolt_artifact_config", + "jolt_rust_artifact", + "assemble_jolt_generated_crates", + "assemble_jolt_workspace_generated_crates", + "write_jolt_generated_crates", + "validate_jolt_rust_artifact_imports", + ] { + assert!( + !artifact_source.contains(pattern), + "generic artifact assembly still exposes quarantined Jolt API `{pattern}`" + ); + } + + let rust_mod_source = std::fs::read_to_string(root.join("crates/bolt/src/emit/rust/mod.rs")) + .expect("read Rust emitter exports"); + assert!( + !rust_mod_source.contains("assemble_jolt_") + && !rust_mod_source.contains("JoltProtocolStage") + && !rust_mod_source.contains("jolt_artifact_config"), + "generic Rust emitter exports still re-export Jolt artifact APIs" + ); + + let lib_source = + std::fs::read_to_string(root.join("crates/bolt/src/lib.rs")).expect("read bolt lib"); + assert!( + !lib_source.contains("pub use protocols::jolt"), + "root bolt exports must keep Jolt APIs under bolt::protocols::jolt" + ); +} + +fn verifier_cleanup_metrics(verifier_src: &Path) -> VerifierCleanupMetrics { + let mut metrics = VerifierCleanupMetrics::default(); + for path in rust_files(verifier_src) { + let source = std::fs::read_to_string(&path).expect("read verifier source"); + let relative = path + .strip_prefix(verifier_src) + .expect("relative verifier path"); + let line_count = source.lines().count(); + metrics.total_loc += line_count; + if relative == Path::new("stages/common.rs") { + metrics.shared_runtime_loc += line_count; + } else { + metrics.generated_surface_loc += line_count; + } + if relative == Path::new("verifier.rs") { + metrics.verifier_rs_loc = line_count; + } + if relative == Path::new("stages/stage6.rs") || relative == Path::new("stages/stage7.rs") { + metrics.stage6_stage7_loc += line_count; + } + if relative.starts_with("stages") { + metrics.stage_local_generic_plan_structs += + count_stage_local_generic_plan_structs(&source); + metrics.field_expr_operand_constants += count_field_expr_operand_constants(&source); + metrics.stage_local_helper_functions += count_stage_local_helper_functions(&source); + metrics.relation_string_sites += count_relation_string_sites(&source); + } + } + metrics +} + +fn count_stage_local_generic_plan_structs(source: &str) -> usize { + const PLAN_SUFFIXES: &[&str] = &[ + "FieldExprPlan", + "OpeningClaimPlan", + "OpeningClaimEqualityPlan", + "SumcheckClaimPlan", + "SumcheckDriverPlan", + "SumcheckEvalPlan", + "SumcheckInstanceResultPlan", + "PointSlicePlan", + "PointConcatPlan", + "ProgramStepPlan", + "TranscriptSqueezePlan", + "TranscriptAbsorbBytesPlan", + "CpuProgramPlan", + "VerifierProgramPlan", + "NamedEval", + ]; + source + .lines() + .filter(|line| { + let line = line.trim_start(); + (line.starts_with("pub struct Stage") || line.starts_with("pub type Stage")) + && PLAN_SUFFIXES.iter().any(|suffix| line.contains(suffix)) + }) + .count() +} + +fn count_field_expr_operand_constants(source: &str) -> usize { + source + .lines() + .filter(|line| line.contains("FIELD_EXPR_") && line.contains("OPERAND")) + .count() +} + +fn count_stage_local_helper_functions(source: &str) -> usize { + const HELPER_PREFIXES: &[&str] = &[ + "fn evaluate_stage", + "fn verify_opening_equalities", + "fn append_opening_claims", + "fn find_", + "fn expected_", + "fn pow_field", + "fn single_operand", + "fn require_operand_count", + ]; + source + .lines() + .filter(|line| { + let line = line.trim_start(); + HELPER_PREFIXES + .iter() + .any(|prefix| line.starts_with(prefix)) + }) + .count() +} + +fn count_relation_string_sites(source: &str) -> usize { + source + .lines() + .filter(|line| { + line.contains("match instance.relation") + || line.contains("match claim.relation") + || line.contains("match driver.relation") + || line.contains("relation: Some(\"jolt.") + || line.contains("relation: \"jolt.") + }) + .count() +} + +fn assert_allowed_jolt_protocol_symbol(path: &Path, symbol: &str) { + assert!( + ALLOWED_JOLT_PROTOCOL_SYMBOLS.contains(&symbol), + "`{}` contains unreviewed Jolt protocol symbol `{symbol}`", + path.display() + ); +} + +fn quoted_jolt_protocol_symbols(source: &str) -> Vec<&str> { + let mut symbols = Vec::new(); + let mut rest = source; + while let Some(offset) = rest.find("\"jolt.") { + let after_quote = &rest[offset + 1..]; + if let Some(end) = after_quote.find('"') { + symbols.push(&after_quote[..end]); + rest = &after_quote[end + 1..]; + } else { + break; + } + } + symbols +} + +fn mlir_jolt_protocol_symbols(source: &str) -> Vec<&str> { + let mut symbols = Vec::new(); + let mut rest = source; + while let Some(offset) = rest.find("@jolt") { + let after_at = &rest[offset + 1..]; + let end = after_at + .char_indices() + .find_map(|(index, ch)| { + (!matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '.')).then_some(index) + }) + .unwrap_or(after_at.len()); + symbols.push(&after_at[..end]); + rest = &after_at[end..]; + } + symbols +} + +fn generic_compiler_source_files(root: &Path) -> Vec { + let source_root = root.join("crates/bolt/src"); + let mut files = rust_files(&source_root) + .into_iter() + .filter(|path| { + !relative_workspace_path(root, path).starts_with("crates/bolt/src/protocols/") + }) + .collect::>(); + files.sort(); + files +} + +fn count_generic_compiler_jolt_hits(source: &str) -> usize { + source + .lines() + .filter(|line| { + GENERIC_COMPILER_JOLT_PATTERNS + .iter() + .any(|pattern| line.contains(pattern)) + }) + .count() +} + +fn relative_workspace_path(root: &Path, path: &Path) -> String { + path.strip_prefix(root) + .expect("workspace-relative path") + .to_string_lossy() + .replace('\\', "/") +} + +fn rust_files(root: &Path) -> Vec { + files_with_extension(root, "rs") +} + +fn files_with_extension(root: &Path, extension: &str) -> Vec { + let mut files = Vec::new(); + collect_files_with_extension(root, extension, &mut files); + files.sort(); + files +} + +fn collect_files_with_extension(root: &Path, extension: &str, files: &mut Vec) { + for entry in std::fs::read_dir(root).expect("read directory") { + let entry = entry.expect("read directory entry"); + let path = entry.path(); + if path.is_dir() { + collect_files_with_extension(&path, extension, files); + } else if path.extension().and_then(|ext| ext.to_str()) == Some(extension) { + files.push(path); + } + } +} + +fn workspace_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("workspace root") + .to_path_buf() +} diff --git a/crates/jolt-crypto/benches/crypto.rs b/crates/jolt-crypto/benches/crypto.rs index d41511ccb3..42d5d6fd4e 100644 --- a/crates/jolt-crypto/benches/crypto.rs +++ b/crates/jolt-crypto/benches/crypto.rs @@ -5,7 +5,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use jolt_crypto::{ Bn254, Bn254G1, Bn254G2, JoltGroup, PairingGroup, Pedersen, PedersenSetup, VectorCommitment, }; -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; +use jolt_field::{Field, Fr}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-crypto/fuzz/fuzz_targets/group_arith.rs b/crates/jolt-crypto/fuzz/fuzz_targets/group_arith.rs index cfe9b9062a..16318d8985 100644 --- a/crates/jolt-crypto/fuzz/fuzz_targets/group_arith.rs +++ b/crates/jolt-crypto/fuzz/fuzz_targets/group_arith.rs @@ -1,14 +1,14 @@ #![no_main] use jolt_crypto::{Bn254, Bn254G1, JoltGroup}; -use jolt_field::{Fr, ReducingBytes}; +use jolt_field::{Field, Fr}; use libfuzzer_sys::fuzz_target; fn parse_input(data: &[u8]) -> Option<(Fr, Fr, Bn254G1)> { if data.len() < 64 { return None; } - let s1 = ::from_le_bytes_mod_order(&data[..32]); - let s2 = ::from_le_bytes_mod_order(&data[32..64]); + let s1 = Fr::from_bytes(&data[..32]); + let s2 = Fr::from_bytes(&data[32..64]); let g = Bn254::g1_generator(); let p = g.scalar_mul(&s1); Some((s1, s2, p)) diff --git a/crates/jolt-crypto/fuzz/fuzz_targets/pedersen_commit.rs b/crates/jolt-crypto/fuzz/fuzz_targets/pedersen_commit.rs index aaa1d14a62..d6e6719857 100644 --- a/crates/jolt-crypto/fuzz/fuzz_targets/pedersen_commit.rs +++ b/crates/jolt-crypto/fuzz/fuzz_targets/pedersen_commit.rs @@ -1,6 +1,6 @@ #![no_main] use jolt_crypto::{Bn254, Bn254G1, VectorCommitment, JoltGroup, Pedersen, PedersenSetup}; -use jolt_field::{Fr, FromPrimitiveInt, ReducingBytes}; +use jolt_field::{Field, Fr}; use libfuzzer_sys::fuzz_target; /// Fixed small setup (4 generators) — deterministic so we don't waste fuzzer @@ -23,9 +23,9 @@ fuzz_target!(|data: &[u8]| { let setup = fixed_setup(); let values: Vec = (0..4) - .map(|i| ::from_le_bytes_mod_order(&data[i * 32..(i + 1) * 32])) + .map(|i| Fr::from_bytes(&data[i * 32..(i + 1) * 32])) .collect(); - let blinding = ::from_le_bytes_mod_order(&data[128..160]); + let blinding = Fr::from_bytes(&data[128..160]); // Commit-verify round-trip let c = Pedersen::::commit(&setup, &values, &blinding); diff --git a/crates/jolt-crypto/src/ec/bn254/gt.rs b/crates/jolt-crypto/src/ec/bn254/gt.rs index befa42df4c..4237a4d113 100644 --- a/crates/jolt-crypto/src/ec/bn254/gt.rs +++ b/crates/jolt-crypto/src/ec/bn254/gt.rs @@ -139,6 +139,11 @@ impl AppendToTranscript for Bn254GT { buf.reverse(); transcript.append_bytes(&buf); } + + fn serialized_len(&self) -> u64 { + use ark_serialize::CanonicalSerialize; + self.0.uncompressed_size() as u64 + } } impl JoltGroup for Bn254GT { diff --git a/crates/jolt-crypto/src/ec/bn254/mod.rs b/crates/jolt-crypto/src/ec/bn254/mod.rs index 29ccc507ab..cba4b71d8a 100644 --- a/crates/jolt-crypto/src/ec/bn254/mod.rs +++ b/crates/jolt-crypto/src/ec/bn254/mod.rs @@ -139,6 +139,11 @@ macro_rules! impl_jolt_group_wrapper { buf.reverse(); transcript.append_bytes(&buf); } + + fn serialized_len(&self) -> u64 { + use ::ark_serialize::CanonicalSerialize; + self.0.uncompressed_size() as u64 + } } impl $crate::JoltGroup for $wrapper { @@ -259,8 +264,7 @@ impl PairingGroup for Bn254 { /// catches silent modular reduction when `F` has a larger modulus than BN254 Fr. #[inline] pub(crate) fn field_to_fr(f: &F) -> ark_bn254::Fr { - let mut bytes = vec![0u8; F::NUM_BYTES]; - f.to_bytes_le(&mut bytes); + let bytes = f.to_bytes(); #[cfg(debug_assertions)] { use ark_ff::{BigInteger, PrimeField as _}; diff --git a/crates/jolt-crypto/tests/coverage.rs b/crates/jolt-crypto/tests/coverage.rs index 3dca4b1230..ab34960ffc 100644 --- a/crates/jolt-crypto/tests/coverage.rs +++ b/crates/jolt-crypto/tests/coverage.rs @@ -7,7 +7,7 @@ use jolt_crypto::ec::bn254::glv; use jolt_crypto::{ Bn254, Bn254G1, Bn254G2, Bn254GT, HomomorphicCommitment, JoltGroup, PairingGroup, }; -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; +use jolt_field::{Field, Fr}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-crypto/tests/group_laws.rs b/crates/jolt-crypto/tests/group_laws.rs index 4477227487..740dd4cbdc 100644 --- a/crates/jolt-crypto/tests/group_laws.rs +++ b/crates/jolt-crypto/tests/group_laws.rs @@ -1,7 +1,7 @@ //! Algebraic group law tests for BN254 G1 and G2. use jolt_crypto::{Bn254, Bn254G1, Bn254G2, JoltGroup}; -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; +use jolt_field::{Field, Fr}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-crypto/tests/pairing.rs b/crates/jolt-crypto/tests/pairing.rs index a50c75dd4d..1daefca73c 100644 --- a/crates/jolt-crypto/tests/pairing.rs +++ b/crates/jolt-crypto/tests/pairing.rs @@ -1,7 +1,7 @@ //! Pairing bilinearity and consistency tests for BN254. use jolt_crypto::{Bn254, Bn254G2, Bn254GT, JoltGroup, PairingGroup}; -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; +use jolt_field::{Field, Fr}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-crypto/tests/pedersen.rs b/crates/jolt-crypto/tests/pedersen.rs index b1d3201b43..a640c4ed55 100644 --- a/crates/jolt-crypto/tests/pedersen.rs +++ b/crates/jolt-crypto/tests/pedersen.rs @@ -1,7 +1,7 @@ //! Pedersen commitment scheme tests over BN254 G1. use jolt_crypto::{Bn254, Bn254G1, JoltGroup, Pedersen, PedersenSetup, VectorCommitment}; -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; +use jolt_field::{Field, Fr}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-crypto/tests/serialization.rs b/crates/jolt-crypto/tests/serialization.rs index d543789d24..216193e51b 100644 --- a/crates/jolt-crypto/tests/serialization.rs +++ b/crates/jolt-crypto/tests/serialization.rs @@ -2,7 +2,7 @@ //! Serialization round-trip tests for all BN254 types. use jolt_crypto::{Bn254, Bn254G1, Bn254G2, Bn254GT, JoltGroup, PairingGroup, PedersenSetup}; -use jolt_field::{Fr, FromPrimitiveInt}; +use jolt_field::{Field, Fr}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-dory/benches/dory.rs b/crates/jolt-dory/benches/dory.rs index 76b8a6efda..3d220bc5fd 100644 --- a/crates/jolt-dory/benches/dory.rs +++ b/crates/jolt-dory/benches/dory.rs @@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use jolt_dory::{DoryScheme, DoryVerifierSetup}; -use jolt_field::{Fr, RandomSampling}; +use jolt_field::{Field, Fr}; use jolt_openings::{CommitmentScheme, StreamingCommitment, ZkOpeningScheme}; use jolt_poly::{OneHotPolynomial, Polynomial}; use jolt_transcript::Transcript; @@ -58,9 +58,7 @@ fn bench_open(c: &mut Criterion) { || { let mut rng = ChaCha20Rng::seed_from_u64(0); let poly = Polynomial::::random(nv, &mut rng); - let point: Vec = (0..nv) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); (poly, point, eval) }, @@ -89,9 +87,7 @@ fn bench_verify(c: &mut Criterion) { || { let mut rng = ChaCha20Rng::seed_from_u64(0); let poly = Polynomial::::random(nv, &mut rng); - let point: Vec = (0..nv) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, _) = DoryScheme::commit(poly.evaluations(), &setup); let mut transcript = @@ -160,8 +156,8 @@ fn bench_combine(c: &mut Criterion) { let poly_b = Polynomial::::random(num_vars, &mut rng); let (commit_a, _) = DoryScheme::commit(poly_a.evaluations(), &setup); let (commit_b, _) = DoryScheme::commit(poly_b.evaluations(), &setup); - let s_a = ::random(&mut rng); - let s_b = ::random(&mut rng); + let s_a = Fr::random(&mut rng); + let s_b = Fr::random(&mut rng); group.bench_with_input(BenchmarkId::from_parameter(num_vars), &num_vars, |b, _| { b.iter(|| { @@ -186,7 +182,7 @@ fn bench_combine_hints(c: &mut Criterion) { .map(|_| { let poly = Polynomial::::random(num_vars, &mut rng); let (_, hint) = DoryScheme::commit(poly.evaluations(), &setup); - (hint, ::random(&mut rng)) + (hint, Fr::random(&mut rng)) }) .collect(); let hints: Vec<_> = hints_and_scalars.iter().map(|(h, _)| h.clone()).collect(); @@ -223,9 +219,7 @@ fn bench_open_zk(c: &mut Criterion) { || { let mut rng = ChaCha20Rng::seed_from_u64(0); let poly = Polynomial::::random(nv, &mut rng); - let point: Vec = (0..nv) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); (poly, point, eval) }, @@ -255,9 +249,7 @@ fn bench_verify_zk(c: &mut Criterion) { || { let mut rng = ChaCha20Rng::seed_from_u64(0); let poly = Polynomial::::random(nv, &mut rng); - let point: Vec = (0..nv) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, _) = DoryScheme::commit(poly.evaluations(), &setup); let mut transcript = diff --git a/crates/jolt-dory/src/scheme.rs b/crates/jolt-dory/src/scheme.rs index 9b40b34891..86c16a1013 100644 --- a/crates/jolt-dory/src/scheme.rs +++ b/crates/jolt-dory/src/scheme.rs @@ -14,7 +14,7 @@ use dory::primitives::arithmetic::{ }; use dory::primitives::poly::{MultilinearLagrange, Polynomial as DoryPolynomial}; use jolt_crypto::{Bn254G1, Bn254GT, Commitment, DeriveSetup, JoltGroup, PedersenSetup}; -use jolt_field::Fr; +use jolt_field::{Field, Fr}; use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, OpeningsError, ZkOpeningScheme}; use jolt_poly::MultilinearPoly; use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript}; @@ -65,6 +65,12 @@ pub(crate) fn jolt_g1_vec_to_ark(v: Vec) -> Vec { unsafe { std::mem::transmute(v) } } +#[inline] +pub(crate) fn jolt_g1_to_ark(jolt: Bn254G1) -> ArkG1 { + // SAFETY: same layout as jolt_g1_vec_to_ark. + unsafe { std::mem::transmute_copy(&jolt) } +} + #[inline] pub(crate) fn ark_to_jolt_g1_vec(v: Vec) -> Vec { // SAFETY: same layout as jolt_g1_vec_to_ark. @@ -141,8 +147,8 @@ impl CommitmentScheme for DoryScheme { let num_cols = 1usize << sigma; let num_rows = 1usize << (num_vars - sigma); - let row_commitments = if poly.is_one_hot() { - commit_rows_one_hot(poly, num_rows, num_cols, &setup.0) + let row_commitments = if poly.is_sparse() { + commit_rows_sparse(poly, num_rows, num_cols, &setup.0) } else { commit_rows_dense(poly, sigma, &setup.0) }; @@ -174,7 +180,7 @@ impl CommitmentScheme for DoryScheme { let row_commitments = match hint { Some(h) => jolt_g1_vec_to_ark(h.0), - None if poly.is_one_hot() => commit_rows_one_hot(poly, num_rows, num_cols, &setup.0), + None if poly.is_sparse() => commit_rows_sparse(poly, num_rows, num_cols, &setup.0), None => commit_rows_dense(poly, sigma, &setup.0), }; @@ -298,7 +304,7 @@ impl ZkOpeningScheme for DoryScheme { let row_commitments = match hint { Some(h) => jolt_g1_vec_to_ark(h.0), - None if poly.is_one_hot() => commit_rows_one_hot(poly, num_rows, num_cols, &setup.0), + None if poly.is_sparse() => commit_rows_sparse(poly, num_rows, num_cols, &setup.0), None => commit_rows_dense(poly, sigma, &setup.0), }; @@ -371,8 +377,9 @@ fn commit_rows_dense + ?Sized>( .collect() } -/// One-hot commit: O(T) group additions for unit-valued one-hot polynomials. -fn commit_rows_one_hot + ?Sized>( +/// Sparse commit: O(nnz) group operations, with the one-hot case kept as pure +/// group addition. +fn commit_rows_sparse + ?Sized>( poly: &P, num_rows: usize, num_cols: usize, @@ -380,24 +387,32 @@ fn commit_rows_one_hot + ?Sized>( ) -> Vec { let g1_bases = &setup.g1_vec[..num_cols]; - let mut cols_per_row: Vec> = vec![Vec::new(); num_rows]; - poly.for_each_one(&mut |flat_idx| { + let mut entries_per_row: Vec> = vec![Vec::new(); num_rows]; + poly.for_each_nonzero(&mut |flat_idx, value| { let row = flat_idx / num_cols; let col = flat_idx % num_cols; debug_assert!( row < num_rows && col < num_cols, - "for_each_one out-of-bounds flat_idx: row={row} num_rows={num_rows} col={col} num_cols={num_cols}", + "for_each_nonzero out-of-bounds flat_idx: row={row} num_rows={num_rows} col={col} num_cols={num_cols}", ); - cols_per_row[row].push(col); + entries_per_row[row].push((col, value)); }); - cols_per_row + entries_per_row .par_iter() - .map(|cols| { - cols.iter() - .fold(::G1::identity(), |acc, &col| { - ::G1::add(&acc, &g1_bases[col]) - }) + .map(|entries| { + entries.iter().fold( + ::G1::identity(), + |acc, &(col, value)| { + let term = if value == Fr::from_u64(1) { + g1_bases[col] + } else { + let base = ark_to_jolt_g1(g1_bases[col]); + jolt_g1_to_ark(base.scalar_mul(&value)) + }; + ::G1::add(&acc, &term) + }, + ) }) .collect() } @@ -455,7 +470,7 @@ impl> MultilinearLagrange for DorySourceAdapter<'_ mod tests { use super::*; use jolt_crypto::{Pedersen, VectorCommitment}; - use jolt_field::{FromPrimitiveInt, RandomSampling}; + use jolt_field::Field; use jolt_poly::Polynomial; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -469,9 +484,7 @@ mod tests { let verifier_setup = DoryVerifierSetup(prover_setup.0.to_verifier_setup()); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -519,13 +532,8 @@ mod tests { .collect(); let (commit_sum_direct, _) = DoryScheme::commit(&sum_evals, &prover_setup); - let combined = DoryScheme::combine( - &[commit_a, commit_b], - &[ - ::from_u64(1), - ::from_u64(1), - ], - ); + let combined = + DoryScheme::combine(&[commit_a, commit_b], &[Fr::from_u64(1), Fr::from_u64(1)]); assert_eq!( commit_sum_direct, combined, @@ -542,9 +550,7 @@ mod tests { let verifier_setup = DoryVerifierSetup(prover_setup.0.to_verifier_setup()); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -583,12 +589,8 @@ mod tests { capacity, ); - let values = vec![ - ::from_u64(1), - ::from_u64(2), - ::from_u64(3), - ]; - let blinding = ::from_u64(42); + let values = vec![Fr::from_u64(1), Fr::from_u64(2), Fr::from_u64(3)]; + let blinding = Fr::from_u64(42); let commitment = as VectorCommitment>::commit(&vc_setup, &values, &blinding); assert!( as VectorCommitment>::verify( diff --git a/crates/jolt-dory/src/streaming.rs b/crates/jolt-dory/src/streaming.rs index 9645312143..a8770cbd40 100644 --- a/crates/jolt-dory/src/streaming.rs +++ b/crates/jolt-dory/src/streaming.rs @@ -4,12 +4,42 @@ use dory::backends::arkworks::G1Routines; use dory::primitives::arithmetic::{DoryRoutines, PairingCurve}; use jolt_field::Fr; use jolt_openings::StreamingCommitment; +use rayon::prelude::*; -use crate::scheme::{ark_to_jolt_g1, ark_to_jolt_gt, jolt_fr_to_ark, jolt_g1_vec_to_ark, ArkFr}; -use crate::types::{DoryCommitment, DoryPartialCommitment}; +use crate::scheme::{ + ark_to_jolt_g1, ark_to_jolt_g1_vec, ark_to_jolt_gt, jolt_fr_to_ark, jolt_g1_vec_to_ark, ArkFr, +}; +use crate::types::{DoryCommitment, DoryHint, DoryPartialCommitment, DoryProverSetup}; type InnerBN254 = dory::backends::arkworks::BN254; +impl crate::DoryScheme { + #[tracing::instrument(skip_all, name = "DoryScheme::commit_evaluations_with_row_len")] + pub fn commit_evaluations_with_row_len( + data: &[Fr], + row_len: usize, + setup: &DoryProverSetup, + ) -> (DoryCommitment, DoryHint) { + assert!(row_len > 0, "Dory row length must be nonzero"); + + let g1_bases = &setup.0.g1_vec[..row_len]; + let row_commitments: Vec<_> = data + .par_chunks(row_len) + .map(|chunk| { + let scalars: Vec = chunk.iter().map(jolt_fr_to_ark).collect(); + G1Routines::msm(&g1_bases[..chunk.len()], &scalars) + }) + .collect(); + + let g2_bases = &setup.0.g2_vec[..row_commitments.len()]; + let tier_2 = ::multi_pair_g2_setup(&row_commitments, g2_bases); + ( + DoryCommitment(ark_to_jolt_gt(&tier_2)), + DoryHint(ark_to_jolt_g1_vec(row_commitments)), + ) + } +} + impl StreamingCommitment for crate::DoryScheme { type PartialCommitment = DoryPartialCommitment; @@ -19,49 +49,18 @@ impl StreamingCommitment for crate::DoryScheme { } } - /// Commits one full row of the polynomial as `MSM(g1_bases[..chunk.len()], chunk)`, - /// matching the per-row work in [`DoryScheme::commit`](crate::DoryScheme::commit)'s - /// dense path. Caller must feed every row at the same chunk width. #[tracing::instrument(skip_all, name = "DoryScheme::stream_feed")] fn feed(partial: &mut Self::PartialCommitment, chunk: &[Fr], setup: &Self::ProverSetup) { - assert!( - chunk.len().is_power_of_two(), - "streaming: chunk length ({}) must be a power of two", - chunk.len(), - ); - assert!( - chunk.len() <= setup.0.g1_vec.len(), - "streaming: chunk length ({}) exceeds Dory SRS size ({})", - chunk.len(), - setup.0.g1_vec.len(), - ); - let g1_bases = &setup.0.g1_vec[..chunk.len()]; let scalars: Vec = chunk.iter().map(jolt_fr_to_ark).collect(); let row_commitment = G1Routines::msm(g1_bases, &scalars); partial.row_commitments.push(ark_to_jolt_g1(row_commitment)); } - /// Aggregates row commitments into the final tier-2 commitment, matching - /// [`DoryScheme::commit`](crate::DoryScheme::commit). Asserts that the - /// streamed row count is a power of two (the layout `DoryScheme::commit` - /// produces). #[tracing::instrument(skip_all, name = "DoryScheme::stream_finish")] fn finish(partial: Self::PartialCommitment, setup: &Self::ProverSetup) -> Self::Output { - let num_rows = partial.row_commitments.len(); - assert!( - num_rows.is_power_of_two(), - "streaming: row count ({num_rows}) must be a power of two", - ); - assert!( - num_rows <= setup.0.g2_vec.len(), - "streaming: row count ({}) exceeds Dory SRS size ({})", - num_rows, - setup.0.g2_vec.len(), - ); - let ark_rows = jolt_g1_vec_to_ark(partial.row_commitments); - let g2_bases = &setup.0.g2_vec[..num_rows]; + let g2_bases = &setup.0.g2_vec[..ark_rows.len()]; let tier_2 = ::multi_pair_g2_setup(&ark_rows, g2_bases); DoryCommitment(ark_to_jolt_gt(&tier_2)) } @@ -69,7 +68,7 @@ impl StreamingCommitment for crate::DoryScheme { #[cfg(test)] mod tests { - use jolt_field::RandomSampling; + use jolt_field::Field; use jolt_openings::{CommitmentScheme, StreamingCommitment}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -88,7 +87,7 @@ mod tests { let prover_setup = DoryScheme::setup_prover(num_vars); let evals: Vec = (0..num_rows * num_cols) - .map(|_| ::random(&mut rng)) + .map(|_| ::random(&mut rng)) .collect(); let poly = jolt_poly::Polynomial::new(evals.clone()); @@ -105,4 +104,56 @@ mod tests { "streaming and direct commitments must match" ); } + + #[test] + fn batched_rows_match_streaming_and_direct() { + let num_vars: usize = 6; + let row_len = 1usize << num_vars.div_ceil(2); + let mut rng = ChaCha20Rng::seed_from_u64(101); + + let prover_setup = DoryScheme::setup_prover(num_vars); + let evals: Vec = (0..(1usize << num_vars)) + .map(|_| ::random(&mut rng)) + .collect(); + let poly = jolt_poly::Polynomial::new(evals.clone()); + + let (direct, direct_hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); + let (batched, batched_hint) = + DoryScheme::commit_evaluations_with_row_len(&evals, row_len, &prover_setup); + + let mut partial = DoryScheme::begin(&prover_setup); + for row in evals.chunks(row_len) { + DoryScheme::feed(&mut partial, row, &prover_setup); + } + let streamed = DoryScheme::finish(partial, &prover_setup); + + assert_eq!(batched, direct); + assert_eq!(batched, streamed); + assert_eq!(batched_hint.0, direct_hint.0); + } + + #[test] + fn batched_rows_allow_short_final_row() { + let num_vars: usize = 5; + let row_len = 1usize << num_vars.div_ceil(2); + let short_len = (1usize << num_vars) - 1; + let mut rng = ChaCha20Rng::seed_from_u64(102); + + let prover_setup = DoryScheme::setup_prover(num_vars); + let evals: Vec = (0..short_len) + .map(|_| ::random(&mut rng)) + .collect(); + + let (batched, batched_hint) = + DoryScheme::commit_evaluations_with_row_len(&evals, row_len, &prover_setup); + let mut partial = DoryScheme::begin(&prover_setup); + for row in evals.chunks(row_len) { + DoryScheme::feed(&mut partial, row, &prover_setup); + } + let streaming_hint = partial.row_commitments.clone(); + let streamed = DoryScheme::finish(partial, &prover_setup); + + assert_eq!(batched, streamed); + assert_eq!(batched_hint.0, streaming_hint); + } } diff --git a/crates/jolt-dory/src/types.rs b/crates/jolt-dory/src/types.rs index 97c5f8fd4f..76d05a92e9 100644 --- a/crates/jolt-dory/src/types.rs +++ b/crates/jolt-dory/src/types.rs @@ -38,6 +38,10 @@ impl AppendToTranscript for DoryCommitment { fn append_to_transcript(&self, transcript: &mut T) { self.0.append_to_transcript(transcript); } + + fn serialized_len(&self) -> u64 { + self.0.serialized_len() + } } impl HomomorphicCommitment for DoryCommitment { @@ -135,7 +139,7 @@ fn validate_proof_round_count(buf: &[u8]) -> Result<(), String> { #[expect(clippy::expect_used, reason = "tests may panic on assertion failures")] mod tests { use super::*; - use jolt_field::RandomSampling; + use jolt_field::Field; use jolt_openings::CommitmentScheme; use jolt_poly::Polynomial; use jolt_transcript::Transcript; @@ -173,9 +177,7 @@ mod tests { let prover_setup = crate::DoryScheme::setup_prover(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = crate::DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -212,9 +214,7 @@ mod tests { let prover_setup = crate::DoryScheme::setup_prover(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let mut transcript = jolt_transcript::Blake2bTranscript::new(b"serde-bp"); @@ -247,9 +247,7 @@ mod tests { let prover_setup = crate::DoryScheme::setup_prover(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let mut transcript = jolt_transcript::Blake2bTranscript::new(b"serde-oversized"); diff --git a/crates/jolt-dory/tests/commit_open_verify.rs b/crates/jolt-dory/tests/commit_open_verify.rs index 378ac74436..a373bbbd9a 100644 --- a/crates/jolt-dory/tests/commit_open_verify.rs +++ b/crates/jolt-dory/tests/commit_open_verify.rs @@ -7,7 +7,7 @@ use dory::backends::arkworks::ArkG1; use jolt_dory::DoryScheme; -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; +use jolt_field::{Field, Fr}; use jolt_openings::{ AdditivelyHomomorphic, CommitmentScheme, StreamingCommitment, ZkOpeningScheme, }; @@ -21,9 +21,7 @@ fn round_trip>(num_vars: usize, seed: u64, label: let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -123,9 +121,7 @@ fn wrong_eval_rejected() { let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -153,9 +149,7 @@ fn wrong_point_rejected() { let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -189,8 +183,8 @@ fn combine_linear_combination() { let (commit_a, _) = DoryScheme::commit(poly_a.evaluations(), &prover_setup); let (commit_b, _) = DoryScheme::commit(poly_b.evaluations(), &prover_setup); - let c1 = ::random(&mut rng); - let c2 = ::random(&mut rng); + let c1 = Fr::random(&mut rng); + let c2 = Fr::random(&mut rng); let combined = DoryScheme::combine(&[commit_a, commit_b], &[c1, c2]); @@ -231,9 +225,7 @@ fn wrong_commitment_rejected() { let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -265,9 +257,7 @@ fn wrong_transcript_domain_rejected() { let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -292,9 +282,7 @@ fn zk_round_trip>(num_vars: usize, seed: u64, labe let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -330,9 +318,7 @@ fn zk_wrong_commitment_rejected() { let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -357,9 +343,7 @@ fn wrong_eval_commitment_rejected_zk() { let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); @@ -385,9 +369,7 @@ fn zk_wrong_transcript_domain_rejected() { let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); let poly = Polynomial::::random(num_vars, &mut rng); - let point: Vec = (0..num_vars) - .map(|_| ::random(&mut rng)) - .collect(); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); diff --git a/crates/jolt-equivalence/Cargo.toml b/crates/jolt-equivalence/Cargo.toml new file mode 100644 index 0000000000..f34c8597fc --- /dev/null +++ b/crates/jolt-equivalence/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "jolt-equivalence" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Cross-system equivalence testing between jolt-core and Bolt-generated Jolt artifacts" + +[lints] +workspace = true + +[dependencies] +# jolt-core reference prover/verifier. Equivalence tests compare against the +# generated full-field Fiat-Shamir path. +jolt-core = { path = "../../jolt-core", features = ["host", "challenge-254-bit"] } +ark-bn254.workspace = true +ark-ff.workspace = true +ark-serialize.workspace = true +ark-std.workspace = true +postcard.workspace = true + +# Bolt-generated Jolt path and shared modular primitives. +common = { path = "../../common" } +jolt-field = { path = "../jolt-field", features = ["bn254"] } +jolt-transcript = { path = "../jolt-transcript" } +bolt = { path = "../bolt" } +jolt-prover = { path = "../jolt-prover" } +jolt-verifier = { path = "../jolt-verifier" } +jolt-trace = { path = "../jolt-trace" } +jolt-dory = { path = "../jolt-dory" } +jolt-inlines-sha2.workspace = true +jolt-kernels = { path = "../jolt-kernels" } +jolt-lookup-tables = { path = "../jolt-lookup-tables" } +jolt-openings = { path = "../jolt-openings", features = ["test-utils"] } +jolt-poly = { path = "../jolt-poly" } +jolt-profiling = { path = "../jolt-profiling" } +jolt-r1cs = { path = "../jolt-r1cs" } +jolt-sumcheck = { path = "../jolt-sumcheck" } +jolt-witness = { path = "../jolt-witness" } +tracer = { path = "../../tracer" } +num-traits.workspace = true +blake2.workspace = true +rayon.workspace = true +serde.workspace = true +strum.workspace = true +tracing.workspace = true diff --git a/crates/jolt-equivalence/GOAL.md b/crates/jolt-equivalence/GOAL.md new file mode 100644 index 0000000000..3ff20c9d6f --- /dev/null +++ b/crates/jolt-equivalence/GOAL.md @@ -0,0 +1,1418 @@ +# Jolt Equivalence Goal + +`jolt-equivalence` is the bootstrap oracle and gate suite for Jolt-on-Bolt. +It exists to prove that the Bolt-generated prover/verifier is equivalent to +`jolt-core` while Bolt is maturing. It must stay a thin logical comparison +crate: run both implementations, translate public artifacts into common shapes, +compare them, mutate proofs, and enforce perf gates. + +It must not become a shadow implementation of Jolt. + +## Objective + +Shrink `jolt-equivalence` into a strict oracle/gating crate while moving all +Jolt semantic construction required for equivalence into Jolt-on-Bolt itself. + +The target state is: + +```text +core temporary oracle -> canonical public artifacts +Bolt generated prover -> canonical public artifacts +Bolt generated verifier -> accepts valid Bolt proof and rejects mutated proofs +jolt-equivalence -> compares artifacts, drives fuzz/tamper/perf gates +``` + +The harness should answer: + +```text +Did Bolt produce the same public protocol artifacts as core? +Did Bolt verifier accept the honest Bolt proof? +Did Bolt verifier reject malformed or tampered proofs? +Did Bolt stay within configured perf bounds versus core? +``` + +The harness should not answer: + +```text +How are Stage 6/7 opening points normalized? +How is hamming-weight claim reduction constructed? +How are witness polynomials evaluated? +How do private core verifier stages compute intermediate state? +``` + +Those are Bolt/codegen/kernel responsibilities. + +## Non-Negotiables + +- `jolt-equivalence` must not encode major Jolt stage semantics. +- New semantic mismatches must be fixed in Bolt IR/codegen/kernels/generated + prover/verifier, not by adding reconstruction logic to the harness. +- Core access must use public APIs or explicit public oracle/export APIs added + for equivalence. Do not depend on private verifier stage methods. +- Generated Bolt artifacts must expose enough canonical data for the harness to + compare without rebuilding hidden state. +- Checked-in `jolt-prover` and `jolt-verifier` are generated artifacts, not + hand-maintained source. Any behavioral change visible there must be made in + Bolt IR/codegen/kernels or shared runtime/witness crates, then kept in sync + through the generated-artifact rail. +- The full-field non-zk path is the active equivalence target: + `Transcript` / `challenge-254-bit`. +- Later-stage prover-side witness data, claim inputs, and opening inputs must + come from generated Bolt prover artifacts or shared `jolt-witness` + primitives, not from harness reconstruction. +- The eventual replacement path is Bolt over core. Core is only the temporary + oracle until Bolt is mature enough to deprecate it. + +## Allowed Responsibilities + +`jolt-equivalence` may contain: + +- test fixtures that run core and Bolt on the same guest/input +- adapters from public core artifacts into canonical comparison structs +- adapters from generated Bolt artifacts into the same structs +- transcript checkpoint comparison +- commitment and opening-claim comparison +- proof acceptance checks +- proof mutation and fuzz/tamper helpers +- perf metric collection and threshold checks +- small, generic conversion helpers between core/Bolt field/proof types + +Adapters may normalize representation, but only when the normalization is a +thin public-format translation. If the code needs to know a stage-specific +formula, construct a witness polynomial, rebuild a claim reduction, or infer +hidden verifier state, it belongs outside `jolt-equivalence`. + +## Forbidden Responsibilities + +Do not add or preserve harness code that: + +- reconstructs Stage 6/7 opening points from sumcheck internals +- rebuilds hamming-weight claim reduction inputs +- evaluates committed witness polynomials to compensate for missing artifacts +- maintains a local value store mirroring generated verifier internals +- uses private `jolt-core` stage verifier methods as the main parity path +- hard-codes stage-specific point-order semantics in tests +- fixes equivalence failures by changing expected values only in the harness + +Existing code in this category is migration debt and should shrink each slice. + +## Current Baseline + +The important current baseline is approximately: + +```text +jolt-core full-field challenge patch: 4 files, 60 insertions, 19 deletions +jolt-equivalence Rust LOC: 6,064 total +tests/bolt_commitment.rs: 317 LOC +tests/bolt_perf.rs: 20 LOC +tests/generated_role_crates.rs: 142 LOC +adapters.rs: 62 LOC +artifacts.rs: 144 LOC +bolt_programs.rs: 170 LOC +checkpoint.rs: 273 LOC +plan_adapters.rs: 755 LOC +plan_adapters/generated_*.rs: 271 LOC +plan_adapters/stage{1..7}.rs: 187 LOC +bolt_oracle.rs: 842 LOC +commitment_oracle.rs: 419 LOC +core_conversion.rs: 374 LOC +tamper.rs: 897 LOC +checks.rs: 566 LOC +core_oracle.rs: 520 LOC +perf.rs: 77 LOC +jolt-kernels/src/trace.rs: 391 LOC +jolt-witness/src/lib.rs: 1,133 LOC +``` + +The core diff is acceptable in principle. The equivalence harness is too large +and currently contains semantic reconstruction that should move into Bolt. + +Known active blocker: + +```text +None for the focused muldiv equivalence oracle. Stage 6 Booleanity, Stage 7 +hamming-weight claim reduction, generated verification, Dory opening proof +bytes, and core acceptance all pass the focused real-trace parity gate. +``` + +Current progress on this branch: + +- Stage 6 execution artifacts now carry public opening-claim values, so Stage 7 + inputs can be adapted from Stage 6 artifacts instead of replaying a local + Stage 6 verifier store inside `jolt-equivalence`. +- Stage 6 core acceptance now includes a strict generated-eval to + `jolt_core::poly::opening_proof::OpeningId` comparison for public opening + claims. The check maps generated eval names to core opening IDs and compares + values only; it does not synthesize opening points or rebuild claims. +- Stage 1 kernel/generated plan adapters now use the shared adapter macro path + instead of bespoke per-role copies, reducing duplicated adapter code while + preserving the generated/kernel plan contract. +- Generated commitment prover/verifier plan adapters now share one local + lowering macro, preserving the same generated plan contract while cutting the + generated adapter subtree by another 60 LOC. +- Core/Bolt uni-skip proof conversion and Stage 1/2 uni-skip coefficient + checks now share generic helpers; Stage 4/5/6/7 artifact match wrappers now + share one macro. These are representation-only harness cleanups and do not + alter generated prover/verifier semantics. +- Stage 6 Booleanity now has an indexed kernel path that ports core's sparse + Booleanity semantics into `jolt-kernels`, with sparse RA index data supplied + by the caller. The dense fallback remains internally verifier-consistent, but + the core-equivalent path is sparse/indexed. +- Generated Stage 6 verifier output-claim reconstruction now uses core's + Booleanity segment-wise point convention via the Bolt verifier-common + template; `jolt-verifier` is regenerated from that source. +- Generic one-hot index/materialization helpers moved to `jolt-witness`: + sparse chunk indices, address-major one-hot materialization, point evals from + sparse indices, and MSB-first chunk/point splitting. +- Stage 6 witness materialization moved out of `jolt-equivalence` into + `jolt-witness`: sparse RA indices, Booleanity oracles, read-RAF chunks, + virtual RA evals, hamming-weight inputs, and `rd_inc`/`ram_inc` columns are + now produced by shared `Stage6WitnessInputs`/`Stage6WitnessParams` helpers. + The harness still invokes this shared primitive until generated prover + artifacts expose the same data directly. +- Stage 6/7 ordered witness-slice views moved into `jolt-witness` via + `Stage6WitnessSlices`; `bolt_oracle.rs` no longer assembles Booleanity, + read-RAF, virtual RA, or hamming-weight index chunk ordering by hand. +- Stage 6 witness construction no longer materializes dense RA Booleanity + one-hot polynomials for the normal sparse/indexed Booleanity path. The + generated prover now supplies sparse index chunks through `jolt-witness`, and + `jolt-kernels` derives the Booleanity domain from the sparse path when + indices are present. On SHA2-chain `2^20`, this moved + `bolt.prove.inputs.stage6_witness` from roughly `5.46s` to roughly `0.24s` + and reduced Bolt prove time from roughly `28.9s` to `22.6-22.8s`. +- Stage 6 bytecode read-RAF now consumes the sparse bytecode RA index chunks + already produced by `jolt-witness` when initializing its cycle phase, while + retaining the dense one-hot fallback for synthetic callers. On SHA2-chain + `2^20`, this reduced the bytecode read-RAF Stage 6 bucket from roughly + `4.3s` to roughly `1.6s` per Stage 6 execution and moved Bolt prove time to + roughly `19.4-19.9s` (`~1.97-2.00x` core). The SHA2-chain `2^16` prove ratio + is now `1.174x`, inside the 20% target. +- Stage 6 bytecode read-RAF witness construction no longer materializes dense + address-major bytecode RA chunks in the generated prover path. `jolt-witness` + now carries bytecode RA chunk lengths alongside sparse bytecode RA indices, + and `jolt-kernels` can run bytecode read-RAF from sparse indices plus chunk + lengths with no dense chunk input. The dense chunk fallback remains covered + by synthetic kernel tests. +- Stage 6/7 witness-slice-to-kernel input wiring moved onto `jolt-kernels` + public builder methods: `Stage6ProverInputs::with_stage6_witness` and + `Stage7ProverInputs::with_stage6_witness_indices`. The equivalence harness + now invokes kernel-owned routing instead of spelling out each witness field. +- Stage 6 witness construction from kernel opening inputs moved onto + `jolt-kernels::stage6_witness_from_opening_inputs`; `bolt_oracle.rs` no + longer builds `Stage6OpeningInputRef` lists or `Stage6WitnessInputs` locally. +- Generated `jolt-prover` now exposes `stage6_witness_from_opening_inputs` + from the Bolt artifact generator, so the full oracle calls the generated + prover API for Stage 6 witness materialization instead of invoking + `jolt-kernels` directly. +- Generated `jolt-prover` now also exposes `stage6_prover_inputs` and + `stage7_prover_inputs` from the Bolt artifact generator. The full oracle no + longer constructs Stage 6/7 prover input builders directly from + `jolt-kernels`; it asks the generated prover to route shared `jolt-witness` + data into the kernel executor shape. +- Generated `jolt-prover` now exposes Stage 1/2/3/4/5 prover-input helpers + from the Bolt artifact generator. The full oracle no longer spells out the + Stage 1 outer evaluator builder, Stage 2 product/instruction/RAM builder + chain, Stage 3 cycle builder, or Stage 4/5 sparse-trace builder chains in + either staged or monolithic prover setup. +- Generated `jolt-prover` now exposes Stage 5/6 opening-input derivation from + prior public artifacts. The full oracle no longer derives later-stage prover + opening inputs in `jolt-equivalence`; it only adapts the generated prover's + kernel-shaped opening inputs into generated-verifier-shaped inputs for + verifier calls. +- Generated `jolt-prover` now also exposes Stage 2/3/4 opening-input + derivation, including the Stage 4 initial-RAM precomputed opening through + `jolt-witness`. The generic source-claim opening-input derivation loop and + Stage 1/2/3/4 claim lookup helpers were deleted from + `jolt-equivalence::adapters`; remaining call sites delegate to the generated + prover API. +- Generated `jolt-prover` now exposes its stage artifact-to-proof conversion + helpers. `jolt-equivalence::adapters` no longer re-implements the generated + proof construction for Stage 2/3/4/5/6/7 or the monolithic Jolt proof stage + wrappers; it delegates to generated prover APIs and only keeps the narrow + Stage 1 tamper proof bridge and kernel replay bridges. +- Repetitive Bolt program lowering helpers in `bolt_programs.rs` were collapsed + into one local lowering macro. This is not a semantic move, but it removes + duplicated harness scaffolding around the Bolt compiler pipeline. +- Stage 2/3 full plan adapters now share the same static-plan conversion + macros as the later-stage adapter layer. +- The bespoke Stage 2 product-uniskip prefix adapter was deleted. The focused + product gate now uses the full Stage 2 generated/kernel plans while still + comparing and tampering the product sumcheck directly, so the harness no + longer owns a special partial-plan construction path. +- Stage 2/3 core opening-claim parity checks now share one comparison helper. +- Transcript state-history assertions now accept checkpoint logs directly, + removing repeated `transcript_states(...)` wrapping from the oracle and + tamper call sites without changing the gate semantics. +- Generated commitment prover/verifier artifact-to-trace conversion now shares + one adapter macro, keeping the commitment oracle focused on public + representation conversion. +- Commitment-oracle raw prover/verifier helpers are private and the one-use + prover-with-cycles wrapper is gone; tests use the focused pair runners. +- The harness-side Stage 1 core R1CS-row to RV64-row replay was deleted. + Stage 1 now gates typed RV64 and generic R1CS evaluators directly against + public core uni-skip proof coefficients, instead of maintaining a second + local core-row translation path. +- Monolithic Jolt tamper rejection assertions now share one local error-shape + assertion macro, preserving each negative case while removing repeated + `matches!(Err(...))` scaffolding. +- Removed a stale `core_conversion.rs` comment from the incremental staging + period that no longer described the proof-conversion adapter. +- Core proof-conversion helpers that are only used inside the crate are now + `fn`/`pub(crate)` instead of public API; the public surface keeps the + commitment transcript adapters used by tests. +- Commitment-oracle helpers that only wire generated artifacts internally are + now `fn`/`pub(crate)`, leaving the public surface focused on test-facing + commitment traces and transcript construction. +- Adapter canonicalizers, later-stage tamper inputs, and internal core/check + helpers are crate-private; an unused Stage 4 artifact match wrapper was + deleted after clippy exposed it as stale public surface. +- Core-oracle entry points now expose only the fixture builders and focused + Stage 1/2 acceptance checks used by integration tests; deeper full-oracle + acceptance helpers and generic guest fixture construction are crate-private. +- Unused `CheckpointTranscript` convenience methods and their stale usage + example were removed; live call sites use the borrowed checkpoint log. +- Obvious artifact/core-conversion comments that restated item names were + deleted while retaining representation and invariant notes. +- Redundant `bolt_oracle.rs` generated verifier-program rebundling, a + borrowed Stage 5 opening clone, and a duplicate transcript-prefix assertion + were deleted; the existing generated verifier bundle now drives all Stage + 5/6/7/full verifier gates. +- Plan-adapter re-exports now keep only the Stage 1/2 adapters used by the + focused integration tests public; commitment, later-stage, and Stage 8 plan + adapters are crate-private full-oracle plumbing. +- Bolt program builders now use the same public boundary: commitment and Stage + 1/2 builders remain public for focused tests, while Stage 3-8 builders are + crate-private full-oracle plumbing. +- `adapters` and `artifacts` are no longer public modules; canonical artifact + types remain re-exported from the crate root, while representation adapter + internals stay crate-private. +- Monolithic wrong-stage-slot tamper rejection now uses the shared local + verifier-error assertion macro instead of hand-rolled `matches!` scaffolding. +- `CoreMuldivCommitmentFixture` now exposes only the fields required by the + focused tests as public; later-stage witness/core-verifier data and Stage 6 + witness params are crate-private full-oracle inputs. +- Stage 2/3 core opening-claim parity now fails if Bolt omits any eval for a + mapped opening that `jolt-core` exposes publicly, and it fails if a stage's + mapping matches no public core claims. Candidate mappings that are not public + core opening claims remain ignored by this specific public-oracle gate. +- The full Bolt oracle now uses generated `jolt-prover` Stage 6/7 proof + conversion helpers instead of hand-building generated proof structs. +- The full Bolt oracle now calls generated prover stage execution wrappers for + Stage 1/2/3/4/5/6/7 prover paths and Stage 5/6/7 proof-carrying replay paths + instead of invoking `jolt-kernels` execution functions directly. Verifier-mode + kernel prefix checks still use direct kernel execution as public parity gates. +- Generated `jolt-prover` now also exposes Stage 5/6/7 proof-carrying replay + helpers. The full oracle no longer constructs later-stage proof-carrying + kernel executors directly; it asks the generated prover API to replay the + public proof artifacts with the generated prover plans. +- Generated `jolt-prover` also exposes Stage 1/2/3/4 verifier-mode replay + helpers for the public prefix parity gates. The full oracle no longer builds + early-stage verifier kernel executors or selects verifier execution mode + locally. +- Real-trace commitment proving now uses generated `jolt-prover` commitment + phase APIs with `jolt-witness::CommitmentTraceSources` / + `SparseCommitmentInputs`. The harness no longer interprets + `OracleGeneration` or materializes dense/one-hot witness oracles for the + muldiv commitment path. +- The synthetic commitment transcript-ordering gate also runs through the + generated commitment prover/verifier with a tiny synthetic input provider. + The old harness-local commitment commit/replay loop was deleted. +- Commitment transcript reconstruction now replays the generated commitment + phase's recorded append bytes after the Jolt preamble. Later-stage tests, + tamper gates, and the full Bolt oracle no longer pass Bolt commitment CPU + programs around only to recover transcript step ordering. +- Commitment oracle helpers now own generated commitment plan adaptation for + test-facing prover/verifier trace construction. The focused tests no longer + leak generated commitment plans at each call site. +- Core-vs-Bolt perf thresholds are now a named constant in + `jolt-equivalence::perf`, so the perf oracle configuration lives with the + perf helpers instead of inside the full Bolt oracle driver. +- Generated commitment trace-source storage now lives behind + `GeneratedCommitmentInputStorage` in `jolt-equivalence::commitment_oracle`. + The full Bolt oracle no longer imports generated commitment input types or + selects trace-source columns at the monolithic prover call site. +- The SHA2-chain `2^20` perf oracle job now runs on pull requests as a standard + CI gate. Manual workflow dispatch can still skip it with `include_large = + false`. +- Bolt equivalence and perf CI gates no longer use path filters, so required + PR gates cannot be skipped by changes in shared crates, guests, inlines, or + root `jolt-core`. +- The Bolt equivalence workflow now also has an optional full + `jolt-equivalence` sweep: it runs on the nightly schedule and can be triggered + manually with `include_full_sweep=true`, while the smaller generated-role and + real-data parity/tamper jobs remain the standard PR gates. +- `crates/bolt/TESTING.md` documents both SHA2-chain perf oracle PR gates, + the required nested commitment/evaluation/verification spans, and exact + local `cargo nextest` commands for the ignored perf tests. +- Perf span gating now uses span names observed by `jolt-profiling` rather + than a harness-populated allowlist, and `bolt.prove` is part of the required + top-level Bolt span contract. +- Stage 1 univariate-skip target ordering and proof-polynomial recovery now + live in `jolt-kernels`. `jolt-equivalence` still compares the resulting + values, but no longer owns that Stage 1 formula. +- The canonical artifact model now uses `jolt_profiling::PerfMetrics` + directly. The unused local `PerfSnapshot`/`PerfSpan` vocabulary was deleted + so perf gates have one metric shape. +- Non-generated `jolt-equivalence`/`jolt-kernels` code no longer uses local + `#[allow(...)]` lint suppressions. Intentional fail-fast oracle/test helper + panics are documented with `#[expect(...)]`, and + `cargo clippy -p jolt-equivalence --tests --offline -- -D warnings` + passes through the full local dependency stack. +- Bolt emitter/template sources now carry the lint expectations and mechanical + cleanups needed by generated `jolt-prover`/`jolt-verifier`, so the checked-in + generated artifacts stay aligned with their generator rail instead of being + hand-maintained. +- Generated commitment and evaluation proof code now emits nested perf spans + for Dory commitment work, Stage 8 joint-opening construction, and generated + verifier evaluation-proof checking. The core-vs-Bolt perf gate requires + those observed spans, giving CI visibility into commit/opening costs instead + of only coarse stage totals. +- Generated verifier common runtime now validates opening-input count, expected + symbols, and point arity before seeding verifier state. This is implemented + in the Bolt verifier-common template and reflected in checked-in + `jolt-verifier`, so extra/missing malformed opening inputs are rejected by + generated code rather than tolerated by the harness. +- Generated `jolt-prover` now owns construction of kernel prover executors from + generated stage-input structs, including a `JoltProverStageInputs` wrapper for + monolithic proving. The full oracle still supplies witness/input data, but no + longer instantiates prover executors or wires `JoltProverInputs` directly. +- Generated `jolt-prover` now exposes Stage 5 kernel-proof and Stage 6 bytecode + read-RAF data storage helpers. The full oracle no longer imports Stage 5/6 + kernel modules just to build those representation values. +- Generated `jolt-prover` now owns generated-to-kernel Stage 6/7 proof-shape + conversion for proof-carrying replay. The local + `define_kernel_proof_adapter!` macro was deleted from `adapters.rs`. +- Generated `jolt-verifier` now exposes public stage challenge/execution + artifact aliases, and generated `jolt-prover` now exposes Stage 6/7 generated + execution-artifact wrapping plus partial `JoltProof` assembly helpers. The + oracle and tamper gates call those generated APIs directly instead of + maintaining local Stage 6/7 artifact wrappers or partial proof constructors in + `jolt-equivalence`. +- Early-stage tamper gates now replay Stage 1/2/3 proof prefixes through + generated `jolt-prover` replay helpers instead of instantiating verifier + kernel executors in `jolt-equivalence`. +- Generated `jolt-prover` now exposes Stage 1 kernel-proof to generated-proof + conversion, and `jolt-equivalence::adapters` no longer maps Stage 1 + sumcheck/eval records by hand. +- The remaining pass-through generated proof adapters for Stages 2/3/4/5 were + deleted; oracle and tamper checks now call the generated `jolt-prover` + `stage*_proof` helpers directly. +- Generated `jolt-verifier` now exposes top-level Stage 2 RAM data aliases and + generated `jolt-prover` owns kernel-to-verifier Stage 2 RAM data storage. + The local `GeneratedStage2RamData` adapter was deleted from + `jolt-equivalence`. +- Generated `jolt-verifier` now exposes a top-level verifier opening-input + alias, and generated `jolt-prover` owns generic kernel-to-verifier + opening-input conversion. The six local generated opening-input adapter + functions were deleted from `jolt-equivalence`. +- Generated `jolt-verifier` now reexports standalone stage verifier entry + points and verifier program plan aliases at the crate root. The oracle and + tamper gates no longer invoke generated verifier stage functions through + `jolt_verifier::stages::*`; stage modules are only used where typed generated + proof/input aliases are still the public data shape. +- Generated `jolt-verifier` also reexports Stage 6 verifier-data aliases at + the crate root, and generated `jolt-prover` constructs them through that + boundary. The tamper module no longer imports generated verifier stage + modules for proof/opening/verifier-data type aliases. +- Common generated proof, opening-input, and execution-artifact shapes are now + named through `jolt_verifier` crate-root aliases in the oracle, conversion, + adapter, check, and tamper layers. Remaining `jolt_verifier::stages::*` + imports in `jolt-equivalence` are limited to real stage constants/plans such + as Stage 6 opening-claim plans and generated program adapters. +- Stale equivalence documentation was brought back in sync with the current + full standard non-zk prefix: `crates/jolt-equivalence/README.md` no longer + describes Stage 1/2 failures, and `crates/bolt/TESTING.md` now points at the + `jolt-equivalence` goal and nextest-based gates. +- Generated `jolt-prover` now owns Stage 6 witness-entry to generated verifier + bytecode data conversion, using public aliases emitted in generated + `jolt-verifier`. The local Stage 6 bytecode verifier-data adapter was deleted + from `jolt-equivalence`. +- The generated/kernel static-plan adapter macros are now mode-aware instead + of maintaining separate Stage 2/3 legacy and Stage 4/5/6/7 kernel/generated + mapping bodies. This keeps the remaining plan bridge as thin format + translation; the per-stage adapter submodules now carry much less repeated + field-mapping code. +- Stage 2/3 generated/kernel sumcheck plans now use the shared optional + `kernel`/`relation` shape. Generated Stage 2/3 verifier code aliases the + same shared plan structs as the later stages, and the equivalence adapter + uses one no-absorb generic lowering path instead of the stale Stage 2/3 + legacy adapter macro. +- Stage 1 generated/kernel sumcheck plans now also use the shared optional + `kernel`/`relation` shape. Generated Stage 1 verifier code aliases the same + shared plan structs, and the last `legacy_kernel`/`legacy_generated` + conversion arms were deleted from the equivalence plan adapter layer. +- The stale verifier-only sumcheck claim/driver plan structs were deleted from + the Bolt verifier-common template and checked-in `jolt-verifier` runtime. + All generated verifier stages now use the same shared sumcheck plan structs. +- Generated stage plus monolithic-prefix proof tamper rejection now shares one + local helper in `tamper.rs`, and stale one-use prefix replay wrappers were + deleted. The covered coefficient, eval, point, and opening-input mutations + are unchanged. +- The Stage 2 product-uniskip oracle now runs through the full Stage 2 + generated/kernel plans and passes Stage 2 RAM data through the existing + verifier input path. This deleted the focused product-only plan adapter and + reduced `plan_adapters.rs` to 767 LOC and the crate's Rust total to 6,572 + LOC without dropping the product proof parity or tamper gate. +- The full Bolt oracle now reuses the already-verified Stage 2 boundary + transcript and opening inputs when seeding the standalone generated Stage 3 + verifier path. This deletes a redundant Stage 1/2 prefix replay from + `bolt_oracle.rs`; the oracle still compares the same staged, generated, and + monolithic transcript histories. +- Stage 2/3 public core opening-claim expectation tables in `checks.rs` now + use a compact row macro with explicit one-row-per-claim mappings. This keeps + the public-oracle map visible while removing repeated wrapper boilerplate, + reducing `checks.rs` to 585 LOC and the crate's Rust total to 6,424 LOC. +- The shared Stage 1/2/3/4/5/6/7 plan adapter macros no longer carry stale + kernel/generated dispatch arms for already-unified claim and driver shapes. + The generated and kernel plan contracts are unchanged, but + `plan_adapters.rs` is down to 755 LOC and the crate's Rust total is 6,412 + LOC. +- Full core-proof conversion now reuses the staged Stage 1-7 core-proof patch + helper chain before patching commitments and the evaluation proof. This + removes a second hand-written list of Stage 1-7 sumcheck-field assignments + from `core_conversion.rs`. +- The one-use core opening-claims clone wrapper was deleted from + `core_conversion.rs`; the proof clone now spells the public `Claims` clone + directly at the field assignment site. +- Generated-role crate tests now share one small driver-presence assertion + macro, preserving the same Stage 1-7 prover/verifier surface checks while + trimming repeated test scaffolding. +- Stale commitment oracle wrappers were deleted: the unused direct + trace-source prover runner and the one-use transcript-trace method. The live + commitment path still goes through `GeneratedCommitmentInputStorage` and the + generated prover/verifier commitment APIs. +- Generated Stage 8 prover/verifier plan adapters now share the same typed + conversion macro, cutting another 67 LOC from `plan_adapters.rs` without + changing the public adapter functions. +- Stage 1/2/3/4 trace-row materialization moved into + `jolt-kernels::trace`, next to the kernel ABI structs that consume those + rows. The core oracle now calls shared kernel-owned helpers for RV64 rows, + product-virtual rows, instruction-lookup rows, RAM accesses, Stage 3 cycles, + Stage 4 register accesses, and Stage 5 lookup trace columns instead of + constructing them locally. +- Stage 6 bytecode-entry materialization also moved into + `jolt-kernels::trace`, using public `jolt-trace` instruction flag helpers. + The core oracle now supplies only the core lookup-table index callback, + keeping bytecode witness row construction out of the harness. +- Generated `jolt-prover` now exposes `JoltProverWitnessInputs` plus + `prove_jolt_with_witness_inputs`, emitted from the Bolt artifact generator. + The full Bolt oracle no longer constructs each monolithic kernel stage input + struct itself; it passes the public witness/opening views into the generated + prover API and lets that crate assemble the stage inputs. +- Generated `jolt-prover` now also exposes standalone Stage 1/2/3/4/5/6/7 + witness-input prove wrappers emitted from the Bolt artifact generator. The + staged full oracle now passes public witness/opening views to those generated + APIs instead of constructing each stage input struct locally. +- Generated `jolt-prover` crate-root exports now include those standalone + witness prove wrappers plus Stage 6 verifier-data conversion. The harness no + longer reaches into `jolt_prover::prover` for equivalence paths; it consumes + the generated crate's public API boundary directly. +- Focused Stage 1/2 tests and the full Bolt oracle no longer instantiate kernel + executors directly. They call generated prover/replay APIs and keep direct + kernel execution plumbing out of the harness path. +- The Stage 1 plan adapter no longer synthesizes missing verifier kernels in + the harness. Kernel replay uses the prover plan, while generated-verifier + checks use the generated verifier plan. +- Deleted the unused `CoreScaffold` bridge from `core_conversion.rs`; core-proof + conversion now keeps only live proof translation helpers. +- Stage 3/4/5 tamper checks now consume generated-verifier start transcripts + and opening inputs produced by the full oracle instead of replaying Stage 1/2 + prefixes and reconstructing Stage 3 openings internally for each mutation. +- Stage 2 batched tamper checks now consume the focused test's Stage 2 start + transcript and opening inputs instead of replaying Stage 1 inside every + mutation check. +- Stage 2 product-uniskip tamper checks reuse the verified Stage 2 start + transcript and generated opening inputs from their positive chain check, + avoiding repeated Stage 1 prefix replay per mutation. +- Stage 1 tamper checks now receive the already-created Stage 1 start + transcript from the focused test instead of rebuilding the commitment + transcript per mutation. +- Generated `jolt-prover` now owns Stage 6 witness construction inside + monolithic proving and exposes Stage 6/7 trace-witness prove wrappers. The + full oracle passes `Stage6WitnessParams` plus trace inputs instead of creating + `Stage6WitnessPolynomials` or witness slices locally. +- Generated `jolt-prover` also owns Stage 4/5 sparse trace witness + construction inside monolithic proving and exposes Stage 4/5 trace-witness + prove wrappers. The full oracle passes register/RAM accesses instead of + creating `Stage45SparseTraceWitness` locally, and the core fixture no longer + exposes that witness builder. +- Fixture-derived Stage 2 RAM data and Stage 6 witness parameters are now + exposed as named public views on `CoreMuldivCommitmentFixture`; `bolt_oracle.rs` + no longer spells out the fixture field mapping for those kernel inputs. +- Fixture-derived Stage 1 R1CS key/data views are now exposed on + `CoreMuldivCommitmentFixture`; the focused tests and Bolt oracle no longer + construct `Stage1OuterRv64Data`/`Stage1OuterR1csData` directly from fixture + fields. +- Stage 4/5 sparse trace witness routing moved onto `jolt-kernels` public + builder methods: `Stage4ProverInputs::with_sparse_trace_witness` and + `Stage5ProverInputs::with_sparse_trace_witness`. The equivalence harness no + longer constructs those kernel witness structs by hand in both standalone and + monolithic prover paths. +- Stage 4/5 sparse trace witness bundle routing moved further into + `jolt-kernels` via `with_stage45_sparse_trace_witness`; `bolt_oracle.rs` + now passes the shared `Stage45SparseTraceWitness` bundle instead of selecting + the per-stage sparse columns itself. +- Stage 4/5 sparse trace witness columns moved into + `jolt-witness::stage4_5_sparse_trace_witness`; `bolt_oracle.rs` no longer + computes `rd_inc`, `ram_inc`, RAM address, or RD write-address columns by + hand. +- Additional primitive prover-side helpers moved into `jolt-witness`: generated + dense/one-hot `CycleInput` source selection, one-hot padding policy mapping, + optional oracle fixture data, `u64` increment columns, optional address + columns, and generic `u64` MLE evaluation. The harness now calls shared + helpers instead of defining local `stage4_rd_inc`, `stage2_ram_inc`, + `dense_source`, `one_hot_source`, `padding_value`, `optional_oracle_data`, or + `mle_eval_u64` functions. +- Generated commitment trace-source grouping moved into + `jolt-witness::CommitmentTraceSources`, and the Bolt commitment emitter now + generates `CommitmentOracleInputs::from_trace_sources`. The equivalence + oracle no longer selects generated commitment columns by source-string at the + monolithic prover call site. +- The commitment oracle now exposes canonical `CommitmentTrace` and + `TranscriptTrace` snapshots. The commitment tests compare these + representation-only artifacts through `EquivalenceRun` for Bolt + prover/verifier parity and core commitment parity, starting to wire the + canonical model into live gates. +- Monolithic commitment parity now also compares canonical `CommitmentTrace` + snapshots, and the old raw optional-commitment checker was deleted from + `checks.rs`. +- Stage 4/5/6/7 artifact parity now compares canonical `StageArtifacts` + snapshots instead of comparing generated/kernel proof structs directly. +- The full Bolt oracle now compares staged-vs-monolithic generated prover + output as canonical `EquivalenceRun` commitments plus Stage 4/5/6/7 snapshots. +- Generated `jolt-verifier` now exposes explicit + `verify_jolt_through_stage{5,6,7}_with_programs` entrypoints emitted from + Bolt's artifact generator. The equivalence oracle no longer fabricates empty + Stage 6/7 verifier programs to scope prefix gates. +- Bolt perf metrics now include generated proof byte counts, and the perf + oracle enables the proof-size ratio gate instead of leaving proof size + unreported. +- Generated proof-size accounting now lives in `jolt-equivalence::perf` + instead of the full Bolt oracle, keeping measurement details with the perf + gate helpers. +- The full generated prover/verifier parity check now compares canonical + `EquivalenceRun` commitment and Stage 4/5/6/7 snapshots for both sides + instead of only checking late-stage sumcheck counts. +- Generated `jolt-prover` now exposes + `stage7_opening_inputs_from_stage6_artifacts{,_with_program}` from the Bolt + artifact generator. The full oracle no longer looks up Stage 6 opening + claims by Stage 7 source strings locally; `jolt-equivalence` only converts + the generated prover's kernel input shape into the generated verifier input + shape. +- The generated standalone verifier crate now has an explicit `serde` + dependency override, so the generated-artifact crate-layout gate compiles it + without relying on workspace path dependency resolution. +- Stage 6 bytecode read-RAF source data now uses a neutral + `jolt-witness::Stage6BytecodeEntry` fixture shape. Generated verifier data is + rendered from that shared witness shape, while kernel prover bytecode input is + rendered by `jolt-kernels::stage6::Stage6BytecodeReadRafDataStorage`. This + deletes the generated-verifier-to-kernel bytecode conversion from + `jolt-equivalence::adapters` and preserves the generated prover/verifier + import boundary enforced by the artifact gate. +- Stage 2 product-virtual witness construction moved into + `Stage2ProverInputs::with_product_virtual_witness`; the kernel now derives + the product uniskip extended evaluations from the Product opening point + instead of requiring `jolt-equivalence` to find `tau_low` and compute them. + The focused Stage 2 tests now use this builder too, so + `tests/bolt_commitment.rs` no longer imports or calls + `product_virtual_uniskip_extended_evals` directly. +- Stage 4/5 sparse trace witness construction over kernel access types moved + into `jolt-kernels::stage4::stage4_5_sparse_trace_witness_from_accesses`; + the equivalence oracle no longer maps `Stage4RegisterAccess` and + `Stage2RamAccess` into generic tuple streams itself. +- The core fixture now exposes the Stage 4/5 sparse trace witness as a named + public view; `bolt_oracle.rs` no longer pairs fixture register/RAM access + fields to build that witness at the call site. +- Stage 4 initial-RAM opening construction moved into + `jolt-witness::stage4_ram_val_init_opening`; `jolt-equivalence` no longer + performs the `RamValInit` MLE evaluation directly. +- Point-order helpers moved into `jolt-witness`: `reverse_point`, + `reversed_suffix`, `normalized_stage4_registers_rw_point`, + `stage5_instruction_cycle_point`, `stage5_instruction_ra_point`, + `stage5_ram_ra_point`, and `stage5_registers_val_point`. The harness still + wires opening inputs from generated/core artifacts, but no longer owns those + point-normalization primitives. +- Transcript state-history checker helpers moved from `tests/bolt_commitment.rs` + into `jolt-equivalence::checkpoint`, starting the Slice 4 split of checker + utilities out of the monolithic integration test. +- Commitment/proof checker helpers moved into `jolt-equivalence::checks`, and + the malformed unrelated Dory proof fixture moved into + `jolt-equivalence::tamper`. +- Stage 1 tamper rejection moved into `jolt-equivalence::tamper`; the + integration test invokes the tamper gate instead of owning the mutation and + verifier-rejection loop. +- Stage 2 product uni-skip chain verifier/tamper rejection moved into + `jolt-equivalence::tamper`; `tests/bolt_commitment.rs` no longer owns the + Stage 2 product mutation loop. +- Stage 2 batched verifier tamper rejection moved into + `jolt-equivalence::tamper`; the integration test now only supplies the + public Stage 1/2 artifacts, RAM data, and verifier plans. +- Repeated tamper mutation construction in `jolt-equivalence::tamper` now uses + small shared helpers for coefficient/eval/point edits, preserving the same + rejection coverage while shrinking stage-local boilerplate. +- Repeated tamper transcript replay, Stage 1/2 prefix replay, and generated + monolithic verifier rejection setup now use small shared helpers in + `jolt-equivalence::tamper`; the mutation set and verifier coverage are + unchanged. +- Bolt transcript construction with the Jolt preamble and commitment transcript + steps is now centralized in `jolt-equivalence::commitment_oracle`; the test + driver, `bolt_oracle.rs`, and `tamper.rs` no longer each own local + `CheckpointTranscript` bootstrap helpers. +- Repeated batched sumcheck tamper triplets now use one local macro in + `jolt-equivalence::tamper`; the same coefficient/eval/point mutations are + still applied to every covered stage. +- Stage 6/7 generated verifier and monolithic-prefix tamper rejection moved + into `jolt-equivalence::tamper`; `bolt_oracle.rs` now passes public + artifacts into those gates instead of owning the mutation loops. +- Stage 6/7 tamper gates now also mutate public opening-claim input + evaluations and require both the standalone generated verifier and generated + monolithic prefix verifier to reject them. The generated proof shape has no + separate opening-claim field, so this gates the public claim contract the + verifier actually consumes. +- Stage 6/7 tamper gates now also remove one opening-claim input and append a + duplicate extra opening-claim input, and shorten an opening point, requiring + both the standalone generated verifier and generated monolithic prefix + verifier to reject malformed public input lists and invalid point arity. +- Generated verifier opening-input validation now lives in the Bolt verifier + common template and checked-in generated verifier: malformed input count, + duplicate/missing symbols, and invalid point arity are rejected before a + stage accepts public opening inputs. +- The generated verifier cleanup gate is back under its strict targets after a + generator-level top-level API compaction and regeneration: + generated surface 5,966 LOC, shared runtime 1,789 LOC, total verifier 7,755 + LOC, and `verifier.rs` 487 LOC. +- Monolithic full-proof tamper gates now mutate the Stage 8 opening-point + suffix consumed from the declared Stage 7 opening input, and require the + generated verifier/Dory check to reject the proof. Prefix point coordinates + are intentionally not gated here because Stage 8 reconstructs them from the + Stage 7 sumcheck point rather than trusting the opening input. +- Stage 3/4/5 generated verifier and monolithic-prefix tamper rejection moved + into `jolt-equivalence::tamper`; the full-trace Bolt oracle no longer owns + per-stage proof mutation loops. +- Monolithic full-proof verifier rejection gates moved into + `jolt-equivalence::tamper`: missing verifier setup, missing evaluation proof, + tampered evaluation proof, missing required commitment, missing Stage 1-7 + proofs, and swapped Stage 6/7 proof slots. + `bolt_oracle.rs` now passes the honest monolithic proof plus public verifier + inputs into one tamper gate. +- Repeated generated monolithic `JoltVerifierInputs` literals in + `bolt_oracle.rs` now use one borrowed verifier-input view with explicit + stage-prefix constructors, reducing verifier wiring duplication without + changing any stage semantics. +- Generated `jolt-verifier` now emits target-scoping methods on + `JoltVerifierInputs` (`through_stage5`, `through_stage6`, `through_stage7`, + and `full`). The equivalence harness consumes that generated API directly, + so `adapters.rs` no longer owns monolithic Jolt verifier input construction. +- Stage 2/3 core opening-claim parity tables moved into + `jolt-equivalence::checks`; `tests/bolt_commitment.rs` no longer imports core + opening IDs or witness polynomial enums for those comparisons. +- Stage 1/2/3/6 core proof coefficient parity helpers moved into + `jolt-equivalence::checks`, further narrowing `tests/bolt_commitment.rs` to + orchestration plus core acceptance wiring. +- Stage 1 uniskip extended-eval parity moved into + `jolt-equivalence::checks`, removing another checker helper from the + monolithic integration driver. +- Stage 4/5/6/7 artifact equality assertions moved into + `jolt-equivalence::checks`, continuing the split of checker code out of the + monolithic `bolt_commitment.rs` test. +- Repeated Stage 4/5/6/7 artifact equality loops and Stage 2/3/6 compressed + sumcheck coefficient checks now use local checker macros; the assertions are + still thin public-artifact comparisons rather than semantic reconstruction. +- Repeated canonical Stage 4/5/6/7 artifact wrapper functions in + `jolt-equivalence::adapters` now share one typed adapter macro, preserving + public function names while cutting another 25 LOC of representation-only + boilerplate. +- The perf tracing setup helper moved into `jolt-equivalence::perf`; the perf + oracle tests now live in the dedicated `tests/bolt_perf.rs` target. +- Thin generated/kernel representation adapters for opening inputs, Stage 2 + RAM data, and Stage 6 bytecode entries moved into + `jolt-equivalence::adapters`. +- Repeated generated/kernel opening-input representation adapters now share one + typed adapter macro in `jolt-equivalence::adapters`. +- Stage 2/3/4/5/6 opening-input derivation now shares one generic + `source_stage`/`source_claim` adapter loop; the stage-specific source map is + explicit, but the harness no longer repeats the same map/assert/panic + scaffolding in every stage adapter. +- Generated Stage 2 RAM conversion now uses + `jolt-equivalence::adapters::GeneratedStage2RamData`; `bolt_oracle.rs` and + Stage 2 tamper checks no longer rebuild generated RAM access/layout structs + locally. +- Generated-to-kernel Stage 6 bytecode read-RAF conversion now uses + `jolt-equivalence::adapters::KernelStage6BytecodeData`; the Bolt oracle no + longer unwraps generated verifier data and rebuilds kernel bytecode data + locally. +- Bolt oracle transcript initialization now uses small preamble/commitment + helpers, keeping transcript setup explicit while reducing repeated harness + scaffolding. +- Generated Stage 6 verifier-data construction from bytecode preprocessing + moved into `jolt-equivalence::adapters`; `tests/bolt_commitment.rs` now + treats it as a public-format adapter instead of owning the conversion. +- Kernel/generated/Jolt proof-shape converters moved into + `jolt-equivalence::adapters`; `bolt_commitment.rs` now imports + `to_generated_*`, `to_kernel_*`, `to_jolt_*`, and generated execution + artifact adapters instead of defining them locally. +- Core proof-shape converters moved into `jolt-equivalence::core_conversion`; + `bolt_commitment.rs` now imports `clone_core_proof`, + `to_core_uniskip_proof`, and `to_core_stage2_uniskip_proof` instead of + defining local core proof constructors. +- Core proof patch builders for staged Bolt acceptance and full/evaluation + proofs moved into `jolt-equivalence::core_conversion`; the integration test + now only calls public core verifier acceptance on those converted proofs. +- Core oracle fixture construction, trace-to-kernel public oracle data, RV64 + row parity checks, and public core acceptance gates moved into + `jolt-equivalence::core_oracle`; the integration test no longer owns the + jolt-core runner or staged core verifier wrappers. +- Core commitment transcript replay moved into + `jolt-equivalence::core_conversion`; the monolithic test now supplies only + public commitment records and transcript steps to the oracle helper. +- Bolt commitment-phase replay moved into + `jolt-equivalence::commitment_oracle`; the test no longer owns commitment + materialization, optional-commit skip policy handling, Dory layout commits, + or commitment transcript-log construction. +- Bolt preamble transcript append logic moved into + `jolt-equivalence::commitment_oracle` behind a small source trait, removing + local label/u64/bytes transcript encoding helpers from the integration test. +- Commitment oracle data materialization for real traces moved into + `jolt-equivalence::commitment_oracle`; the integration test now asks the + Bolt adapter for oracle inputs instead of owning `OracleGeneration` handling. +- Bolt protocol-program construction moved into + `jolt-equivalence::bolt_programs`; `tests/bolt_commitment.rs` no longer owns + MLIR lowering/projecting/extraction glue for each stage. +- Generated/kernel static plan adapters moved into + `jolt-equivalence::plan_adapters`; the integration test now asks for + translated plans instead of owning the `leak_*` conversion block. +- Stage 1/2/3/4/5/6/7 kernel plan adapters were split into focused + `plan_adapters/stage*.rs` submodules, reducing the monolithic + `plan_adapters.rs` while preserving the same public adapter functions. +- Generated commitment and Stage 1/2/3/4/5/6/7 verifier plan adapters were + split into focused `plan_adapters/generated_*.rs` submodules. +- Stage 4/5/6/7 generated-verifier and kernel plan adapters now use typed + macro-generated field mapping instead of repeated stage-local boilerplate, + reducing the adapter subtree by about 1.3k LOC without changing the public + adapter functions. +- Kernel/generated/Jolt proof-shape converters now use typed adapter macros + instead of repeated stage-local conversion bodies, preserving the same public + conversion functions while cutting more boilerplate from `adapters.rs`. +- Full real-trace Bolt prover/verifier orchestration moved into + `jolt-equivalence::bolt_oracle`; `tests/bolt_commitment.rs` is now focused + on commitment/Stage 1/Stage 2 orchestration and public gate calls. +- Stage 7 opening-input adaptation from public Stage 6 opening claims moved + into `jolt-equivalence::adapters`. +- Stage 2/3/4/5/6/7 kernel execution artifacts now record public opening-claim + values produced by the stage plans, including logical aliases that are + transcript-deduped. Stage 3/4/5/6 opening inputs are now derived by thin + `source_claim` lookups over generated/kernel `opening_inputs` instead of + local stage point/eval formulas in `bolt_commitment.rs`. +- Stage 2 opening inputs are also derived by thin `source_claim` lookups over + the generated/kernel `opening_inputs` plan. The local + `stage2_product_opening_inputs` and `stage2_opening_inputs` builders were + deleted from `tests/bolt_commitment.rs`. +- Stage 2 RAM read-write instance point order is fixed in Bolt codegen and the + checked-in generated Stage 2 prover/verifier constants so committed `RamInc` + opening points match core-facing trace-domain semantics. This removed the + harness-only Stage 6 `RamInc` normalization workaround. +- Dead `#[cfg(any())]` private-core replay blocks were deleted from + `tests/bolt_commitment.rs`. +- The remaining live private-core preamble replay path was deleted: + `run_core_preamble`, `core_challenge_to_fr`, and + `assert_core_stage1_tau_matches_bolt` no longer exist. Stage 1 parity now + relies on public verifier acceptance plus direct proof-coefficient parity. +- Checked-in generated `jolt-prover` changes are regenerated from Bolt emitter + changes; `jolt-prover`/`jolt-verifier` remain generated artifacts. +- The focused `bolt_stage3_batched_real_muldiv_self_parity` oracle now hard-gates + Stage 5/6/7 core acceptance, generated verifier acceptance, full core + acceptance, and Dory joint-opening proof byte equality. +- Added a dedicated `Bolt equivalence` CI workflow with explicit macOS/LLVM + nextest jobs for generated role full-field parity and real-data + parity/tamper gates. +- Moved the ignored SHA2-chain perf oracle tests into dedicated + `tests/bolt_perf.rs` and pointed the perf-oracle workflow at that nextest + target. +- `scripts/setup-bolt-dev.sh` now installs `cargo-nextest` along with the + local LLVM/MLIR environment, matching the branch's documented gates. +- Deleted local generated-verifier replay debt: `Stage6LocalValueStore`, + `stage6_value_store`, `stage6_instance_point`, and + `stage6_opening_as_stage7_input`. +- Deleted stale `tests/hash_debug.rs`, a one-off Blake2b op-112 divergence + scanner with no assertions and no role in the equivalence or CI gates. +- Deleted the unused legacy `StageTrace`/`ProtocolTrace` API from `lib.rs`; + the crate now exposes the canonical `artifacts.rs` model instead of two + parallel comparison vocabularies. + +## Initial Helper Classification + +This classification is the Slice 1 migration ledger for +`tests/bolt_commitment.rs`. It is intentionally conservative: anything that +constructs stage-specific protocol meaning is semantic debt until it is moved +into Bolt, generated crates, or `jolt-witness`. + +Adapter/oracle helpers allowed to remain, but should move out of the monolithic +test file: + +- fixture runners: `core_muldiv_commitment_fixture`, + `core_sha2_chain_commitment_fixture`, `core_guest_commitment_fixture` +- Bolt protocol builders: `bolt_commitment_programs_with_params`, + `bolt_stage{1,2,3,4,5,6,7,8}_programs_with_params` +- generated-plan adapters: `leak_*`, `role_name`, `synthetic_stage1_*`, + `leak_stage*_program`, `leak_generated_*_program` +- proof/artifact representation adapters: `to_generated_*`, `to_jolt_*`, + `to_kernel_*`, `generated_stage*_execution_artifacts`, + `kernel_stage*_opening_inputs`, `generated_stage*_opening_inputs` +- commitment/transcript adapters: generated commitment phase runners, + `append_bolt_preamble`, `append_bolt_commitment_trace`, + `core_commitment_log`, `core_commitments_transcript_log` +- thin field/serialization conversions: `core_challenge_to_fr`, + `core_append_serializable_bytes`, `commit_with_layout`, + `into_padded_oracle` + +Checker helpers allowed to remain, but should move to focused check modules: + +- transcript/checkpoint checks: `transcript_states`, + `assert_state_history_match`, `assert_state_history_prefix_match` +- artifact equality checks: `assert_stage*_artifacts_match`, + `assert_commitments_match`, `assert_dory_proofs_match` +- core/Bolt acceptance gates that use public verifier APIs: + `assert_core_verifies_proof`, `assert_core_accepts_bolt_stage*`, + `assert_core_accepts_bolt_evaluation_proof`, + `assert_core_accepts_full_bolt_proof` +- early-stage parity checks that do not reconstruct hidden later-stage + semantics: `assert_core_stage*_sumcheck_proof_matches_bolt`, + `assert_stage1_uniskip_extended_evals_match_core` + +Tamper/fuzz helpers are allowed, but should move to `tamper.rs`: + +- `assert_bolt_stage1_tamper_rejected` +- stage-specific proof mutation helpers now live in `tamper.rs` +- malformed proof helpers such as `unrelated_dory_proof` + +Perf helpers are allowed, but should move to `perf.rs`: + +- `maybe_setup_perf_trace` +- dedicated ignored perf gates in `tests/bolt_perf.rs` +- core/Bolt timing, RSS, proof-size, and tracing metric collection + +Semantic debt that must leave `jolt-equivalence`: + +- private-core or accumulator-derived stage reconstruction: + `ProofOpeningClaims`, `core_stage{4,5,6,7}_artifacts`, + `core_stage8_transcript_states`, + `core_stage{4,5,6,7}_round_polynomials`, + `core_stage{4,5,6,7}_opening_inputs`, + `core_stage{4,5,6,7}_evals`, + `core_stage*_virtual_*`, `core_stage*_committed_*` +- core transcript state replay that depends on private verifier internals: + deleted from the live harness; keep it out. + +The expected trend is that adapter/checker/tamper/perf helpers move into small +modules, while semantic-debt helpers disappear because Bolt/generated artifacts +or `jolt-witness` expose the required public data directly. + +## Target Architecture + +Use a small canonical model inside `jolt-equivalence`: + +```rust +struct EquivalenceRun { + commitments: CommitmentTrace, + transcript: TranscriptTrace, + stages: Vec, + opening_claims: OpeningClaims, + verifier_result: VerifierResult, + perf: PerfMetrics, +} +``` + +Core and Bolt should each have narrow adapters: + +```text +core_oracle.rs + run_core(...) + public artifacts -> EquivalenceRun + +bolt_oracle.rs + run_generated_bolt(...) + generated artifacts -> EquivalenceRun + +checks.rs + compare_commitments + compare_transcripts + compare_opening_claims + compare_stage_outputs + compare_perf + +tamper.rs + mutate proof artifacts + assert generated verifier rejects +``` + +The heavy semantic logic must live in: + +```text +crates/bolt IR, typed plans, lowering, codegen +crates/jolt-witness prover-side witness/data materialization primitives +crates/jolt-kernels prover/runtime relation execution +crates/jolt-prover generated prover artifact production +crates/jolt-verifier generated verifier artifact checking +``` + +For later-stage prover inputs, Bolt should use or adapt `jolt-witness` rather +than rebuilding witness and claim materialization inside `jolt-equivalence`. +If Stage 6/7 needs reusable primitives for RA chunks, hamming-weight data, +opening-input materialization, claim inputs, or committed oracle layouts, add +those primitives to `jolt-witness` and have generated prover code consume them. +The equivalence crate may inspect the resulting public artifacts, but it must +not be the place where those prover-side semantics are constructed. + +`jolt-witness` is the canonical home for reusable prover-side construction +that is not inherently codegen-specific. That includes witness columns, +committed-oracle layouts, RA chunk/index materialization, hamming-weight inputs, +and claim/opening input data needed by later stages. The generated prover may +wrap or specialize those primitives, but `jolt-equivalence` must only compare +the public artifacts emitted by core and Bolt. + +## Focused Goal-Mode Slices + +### Slice 1: Define The Boundary + +Objective: + +```text +Introduce a canonical equivalence artifact model and classify existing +bolt_commitment.rs helpers as adapter, checker, tamper, perf, or semantic debt. +``` + +Acceptance gates: + +- No new semantic reconstruction is added. +- Documented list of debt helpers exists in code comments or this file. +- New canonical structs are representation-only. +- Existing tests are not weakened or deleted unless replaced by equivalent + stricter gates. + +### Slice 2: Move Stage 6 Opening Semantics Into Bolt + +Objective: + +```text +Make generated Bolt Stage 6 expose canonical opening claims and normalized +points/evals directly, matching core public semantics. +``` + +Required work: + +- Represent point order/normalization as typed protocol facts, not loose + stage-local string recovery in the harness. +- Use or extend `jolt-witness` for Stage 6 prover-side witness/data + materialization, including committed RA oracle layouts and claim inputs. +- Generated Stage 6 prover artifacts must carry the exact opening claims + Stage 7 consumes. +- Generated Stage 6 verifier must check those claims through the same public + artifact contract. +- Remove matching Stage 6 reconstruction from `bolt_commitment.rs`. + +Acceptance gates: + +- Stage 6 proof produced by Bolt self-verifies. +- Stage 6 canonical opening claims compare against core oracle. +- `jolt-equivalence` no longer synthesizes Stage 6 output opening points. +- Harness LOC decreases or any increase is limited to generic adapters/checks. + +### Slice 3: Move Stage 7 Hamming-Weight Semantics Into Bolt + +Objective: + +```text +Make generated Bolt Stage 7 consume Stage 6 canonical openings and construct +the hamming-weight claim reduction with core-equivalent semantics. +``` + +Required work: + +- Stage 7 input claim is derived from generated Stage 6 canonical openings. +- Use or extend `jolt-witness` for Stage 7 prover-side RA chunk/index and + hamming-weight input materialization. +- Hamming-weight reduction point construction matches core semantics in Bolt + kernels/codegen. +- Generated Stage 7 prover and verifier agree without harness-side witness MLE + evaluation. +- Stage 7 output opening claims are public generated artifacts. + +Acceptance gates: + +- Current failing Stage 7 relation output mismatch is resolved in Bolt code. +- `bolt_stage3_batched_real_muldiv_self_parity` passes without semantic + reconstruction in `jolt-equivalence`. +- Stage 7 tamper checks reject coefficient, eval, point, and opening-claim + mutations. +- Harness LOC decreases. + +### Slice 4: Thin The Harness + +Objective: + +```text +Refactor bolt_commitment.rs into focused oracle/check/tamper/perf modules and +delete semantic debt. +``` + +Target shape: + +```text +tests/bolt_commitment.rs small integration driver +src/core_oracle.rs core public oracle adapter +src/bolt_oracle.rs generated Bolt adapter +src/checks.rs parity assertions +src/tamper.rs proof mutations +src/perf.rs perf gates +``` + +Acceptance gates: + +- No local value-store replay in tests. +- No witness polynomial MLE evaluation in tests except generic checker helpers + operating on already-public artifacts. +- No private core verifier stage methods. +- `bolt_commitment.rs` is reduced to orchestration and assertions. + +### Slice 5: CI Gates + +Objective: + +```text +Make equivalence checks standard PR gates with small always-on tests and +explicit perf/fuzz lanes. +``` + +Required gates: + +- small real guest parity gate +- generated role crate full-field challenge parity gate +- Bolt proof tamper rejection gate +- SHA2-chain `2^16` core-vs-Bolt perf oracle +- SHA2-chain `2^20` core-vs-Bolt perf oracle +- optional longer fuzz/tamper lane + +Acceptance gates: + +- CI names are explicit and stable. +- Perf thresholds are configured in one place. +- Perf failures report stage-level timing, heavy computation spans, proof + bytes, and peak RSS. +- Ignored/local perf tests can be run manually with one documented command. + +### Slice 6: SHA2-Chain Perf Closure + +Objective: + +```text +Bring Jolt-on-Bolt SHA2-chain proving overhead within 20% of jolt-core while +keeping the equivalence crate a thin oracle/gate and keeping all semantic +changes in Bolt codegen, generated artifacts, jolt-kernels, or jolt-witness. +``` + +Current measured baseline: + +```text +sha2-chain 2^16: + prove_ms: core=1987.685, bolt=2523.469, ratio=1.270x + verify_ms: core=89.220, bolt=118.322, ratio=1.326x + proof_bytes: core=80209, bolt=111198, ratio=1.386x + peak_rss_mb: core=209, bolt=1248, ratio=5.971x + +sha2-chain 2^20: + prove_ms: core ~= 9.9s, bolt ~= 22.6-22.8s, ratio ~= 2.29-2.30x + verify_ms: core ~= 0.11s, bolt ~= 0.13s, ratio ~= 1.18-1.26x + proof_bytes: core=89041, bolt=121398, ratio=1.363x + peak_rss_mb: core ~= 1.84GB, bolt ~= 6.1-6.3GB, ratio ~= 3.3-3.4x +``` + +Perfetto/tracing overhead is not the cause of the 2^20 regression. Traced and +untraced runs landed in the same ratio band. + +Current post-witness-fix 2^20 hotspot shape: + +```text +bolt.stage6: ~10.6s + bytecode_read_raf: ~4.3s + instruction_ra_virtual: ~2.9s + booleanity: ~2.6s +bolt.commitment: ~5.3s +bolt.stage8: ~3.8s + joint_opening_hint: ~1.4s + dory_open: ~1.9s +``` + +Current post-bytecode-sparse 2^20 shape: + +```text +sha2-chain 2^16: + prove_ms: core=2020.866, bolt=2373.067, ratio=1.174x + verify_ms: core=90.403, bolt=118.888, ratio=1.315x + proof_bytes: core=80209, bolt=111198, ratio=1.386x + peak_rss_mb: core=207, bolt=1109, ratio=5.357x + +sha2-chain 2^20: + prove_ms: core=9825.685, bolt=19381.338, ratio=1.973x + verify_ms: core=104.379, bolt=132.872, ratio=1.273x + proof_bytes: core=89041, bolt=121398, ratio=1.363x + peak_rss_mb: core=1839, bolt=6094, ratio=3.314x + +Stage 6 per execution: + bytecode_read_raf: ~1.58s + instruction_ra_virtual: ~2.75-3.32s + booleanity: ~2.31-2.44s +``` + +The final dense bytecode read-RAF witness allocation removal was verified by +`jolt-witness`, focused Stage 6 kernel tests, kernel/witness clippy, and full +`jolt-equivalence`. Its 2^20 perf rerun was intentionally stopped during +laptop handoff, so the latest measured perf numbers above are from the sparse +kernel path before that allocation-only cleanup. Re-run the 2^20 perf gate +before judging any memory/setup movement from the allocation cleanup. + +#### Handoff Plan Of Attack + +Retained optimization: + +- Bytecode read-RAF now uses sparse bytecode RA index chunks plus explicit + chunk lengths. This is a core-equivalent direction and belongs in + `jolt-witness`/`jolt-kernels`, not `jolt-equivalence`. +- Dense bytecode RA read-RAF materialization is removed from normal + `stage6_witness_polynomials` output; callers that still provide dense chunks + continue through the dense fallback. + +Rejected experiment: + +- Parallelizing indexed Booleanity cycle-state construction and dense binds + made the 2^20 run slower (`prove_ms` about `25.5s`, ratio `2.13x`) and was + reverted. Do not reapply that shape without a more targeted profile. + +Next targets: + +1. Port `instruction_ra_virtual` from generic dense product terms toward the + core `RaPolynomial`/split-eq sum-of-products algorithm. This is now the + largest Stage 6 bucket, roughly `2.7-3.3s` per Stage 6 execution. +2. Improve indexed Booleanity with an algorithmic change rather than broad + parallel binding. Current bucket is roughly `2.3-2.4s` per Stage 6 + execution. +3. Re-run the post-allocation-cleanup 2^20 perf gate and record setup/prove/RSS + movement. The interrupted command was stopped deliberately before commit. +4. After Stage 6 is no longer dominant, attack `bolt.commitment` (`~5.3s`) and + Stage 8/Dory opening (`~3.8s`) with generated spans intact. +5. Tighten the perf thresholds from disabled diagnostic ratios to the actual + target gates once 2^20 prove is within `1.20x`. + +Primary target: + +```text +sha2-chain 2^16 prove_ms ratio <= 1.20x +sha2-chain 2^20 prove_ms ratio <= 1.20x +``` + +Secondary targets: + +```text +sha2-chain 2^16 verify_ms ratio <= 1.35x +sha2-chain 2^20 verify_ms ratio <= 1.35x +sha2-chain 2^16 proof_bytes ratio <= 1.40x +sha2-chain 2^20 proof_bytes ratio <= 1.40x +sha2-chain 2^16 peak_rss_mb <= 1.25GB +sha2-chain 2^20 peak_rss_mb <= 6.30GB, and should continue decreasing unless +a documented protocol change explains the tradeoff. +``` + +Required work: + +- Port the remaining Stage 6 RA-heavy prover paths toward core-equivalent + sparse/specialized algorithms instead of dense generic `DenseStage6State` + products. The first targets are bytecode read-RAF, instruction RA virtual, + and Booleanity. +- Keep semantic parity in `jolt-kernels`, `jolt-witness`, Bolt IR/codegen, or + checked-in generated artifacts. Do not add perf-specific reconstruction or + shortcuts to `jolt-equivalence`. +- Keep instrumentation code-generated when it concerns generated + `jolt-prover`/`jolt-verifier` code. Direct runtime/kernel spans belong in + the owning shared crate. +- Preserve the existing `bolt.prove`, `bolt.commitment`, `bolt.stage*`, + `bolt.evaluate.*`, and verifier perf span contract so regressions remain + attributable in CI. +- Treat `setup_ms`, commitment, and Stage 8/Dory opening work as follow-on + targets after Stage 6 is no longer the dominant gap. + +Acceptance gates: + +- Both ignored SHA2-chain perf oracle tests pass with `prove_ms <= 1.20x`. +- The 2^20 perf report identifies no single uninstrumented Bolt prover bucket + larger than `500ms`. +- `jolt-equivalence` contains no new stage-semantic reconstruction for the perf + fix. +- Focused Stage 6 kernel tests pass. +- The full `jolt-equivalence` suite passes. +- Generated artifacts are regenerated from Bolt when generated code changes. + +#### Active Goal Contract + +Goal: + +```text +Close the SHA2-chain Jolt-on-Bolt prover regression to <= 20% overhead versus +jolt-core at both 2^16 and 2^20, without moving Jolt semantics into the +equivalence harness. +``` + +Why this matters: + +```text +The equivalence crate is supposed to be an oracle/gate. If Bolt is slower or +semantically different, the fix must land in Bolt-generated code, +jolt-kernels, jolt-witness, or shared runtime code. The harness may measure and +compare; it must not compensate for missing prover semantics. +``` + +Non-negotiable scope: + +- Do not hand-edit generated `jolt-prover` or `jolt-verifier` behavior. Change + Bolt/codegen/templates/shared crates and regenerate. +- Do not add perf-specific witness reconstruction, opening reconstruction, or + stage-specific semantic shortcuts to `jolt-equivalence`. +- Do not weaken correctness, tamper, proof-size, verifier, or perf gates to + make the goal pass. +- Keep generated prover/verifier spans code-generated when the work being + measured is generated code. +- Keep runtime/kernel spans in the crate that owns the runtime/kernel work. +- Treat Perfetto overhead as negligible unless a repeat measurement proves + otherwise; the current traced and untraced runs agree on the 2^20 gap. + +Current blocking regression: + +```text +2^16 prove: core 2.02s, Bolt 2.37s, 1.174x +2^20 prove: core about 9.8s, Bolt about 19.4s, 1.97x +``` + +The current evidence points at Stage 6 first, then commitment and Stage 8: + +```text +bolt.stage6: still dominant, with instruction_ra_virtual and booleanity largest +bolt.commitment: about 5.3s +bolt.stage8: about 3.8s +``` + +Stage 6 subtargets, in priority order: + +```text +instruction_ra_virtual about 2.7-3.3s per Stage 6 execution +booleanity about 2.3-2.4s per Stage 6 execution +bytecode_read_raf about 1.6s per Stage 6 execution after sparse-index port +``` + +Implementation direction: + +- Prefer core-equivalent sparse/indexed paths over dense generic bind loops for + RA-heavy Stage 6 work. +- Use `jolt-witness` for reusable prover-side witness/index/chunk material + that is not inherently Bolt-codegen-specific. +- Use `jolt-kernels` for core-equivalent relation execution and specialized + prover algorithms. +- Use Bolt artifact generation for generated prover/verifier APIs and spans. +- Keep `jolt-equivalence` changes limited to metric collection, reporting, + thresholds, and thin public-artifact comparison. + +Completion evidence required: + +- Run the 2^16 and 2^20 SHA2-chain perf gates and report: + `setup_ms`, `prove_ms`, `verify_ms`, `proof_bytes`, peak RSS, and top spans. +- Re-run any surprising perf result at least once before treating it as real. +- Show no uninstrumented Bolt prover bucket larger than `500ms` at 2^20. +- Run focused Stage 6 kernel tests. +- Run the full `jolt-equivalence` suite. +- Run `cargo fmt --check` and `git diff --check`. +- If generated artifacts changed, show the generator command used to regenerate + them. + +## Strict Per-Slice Gates + +Every goal-mode slice must satisfy: + +```text +cargo fmt --check +git diff --check +cargo check -p jolt-core --no-default-features --features minimal --offline +cargo check -p jolt-core --no-default-features --features minimal,challenge-254-bit --offline +``` + +Use the local Bolt MLIR/LLVM environment for equivalence tests: + +```sh +env MLIR_SYS_220_PREFIX=/opt/homebrew/opt/llvm \ + PATH=/opt/homebrew/opt/llvm/bin:/Users/mgeorghiades/.cargo/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin \ + SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + BINDGEN_EXTRA_CLANG_ARGS=-isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + cargo nextest run -p jolt-equivalence --test generated_role_crates \ + --cargo-quiet --offline + +env MLIR_SYS_220_PREFIX=/opt/homebrew/opt/llvm \ + PATH=/opt/homebrew/opt/llvm/bin:/Users/mgeorghiades/.cargo/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin \ + SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + BINDGEN_EXTRA_CLANG_ARGS=-isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + cargo nextest run -p jolt-equivalence --test bolt_commitment \ + --cargo-quiet --offline +``` + +Before a slice is considered complete, also run the broadest feasible +equivalence gate: + +```sh +env MLIR_SYS_220_PREFIX=/opt/homebrew/opt/llvm \ + PATH=/opt/homebrew/opt/llvm/bin:/Users/mgeorghiades/.cargo/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin \ + SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + BINDGEN_EXTRA_CLANG_ARGS=-isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + cargo nextest run -p jolt-equivalence --cargo-quiet --offline +``` + +Perf oracle gates are required before landing perf-sensitive slices: + +```sh +env MLIR_SYS_220_PREFIX=/opt/homebrew/opt/llvm \ + PATH=/opt/homebrew/opt/llvm/bin:/Users/mgeorghiades/.cargo/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin \ + SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + BINDGEN_EXTRA_CLANG_ARGS=-isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + cargo nextest run -p jolt-equivalence --test bolt_perf --release \ + --cargo-quiet --offline --run-ignored only --no-capture \ + bolt_sha2_chain_2_16_core_vs_bolt_perf_oracle + +env MLIR_SYS_220_PREFIX=/opt/homebrew/opt/llvm \ + PATH=/opt/homebrew/opt/llvm/bin:/Users/mgeorghiades/.cargo/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin \ + SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + BINDGEN_EXTRA_CLANG_ARGS=-isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \ + cargo nextest run -p jolt-equivalence --test bolt_perf --release \ + --cargo-quiet --offline --run-ignored only --no-capture \ + bolt_sha2_chain_2_20_core_vs_bolt_perf_oracle +``` + +## Regression Rules + +A change fails review if it: + +- increases `jolt-equivalence` semantic reconstruction +- hides a Bolt/core mismatch behind harness normalization +- adds prover witness or claim materialization to `jolt-equivalence` instead of + `jolt-witness`/generated prover code +- weakens tamper rejection coverage +- removes a parity gate without replacing it with a stricter one +- adds new private-core dependencies +- makes generated verifier depend on prover-only crates or core internals +- makes perf gates less observable + +## Definition Of Done + +This goal is complete when: + +- Jolt-on-Bolt passes real-data completeness parity against core. +- Generated Bolt verifier rejects representative mutated proofs. +- Stage 6/7 semantics live in Bolt/codegen/kernels, not in the harness. +- `jolt-equivalence` is mostly adapters, checks, tamper/fuzz, and perf gates. +- Small correctness gates are practical for PR CI. +- Larger SHA2-chain perf gates are documented, stable, and actionable. +- The harness is small enough that future compiler work uses it as a gate + instead of treating it as a place to repair semantics. diff --git a/crates/jolt-equivalence/README.md b/crates/jolt-equivalence/README.md new file mode 100644 index 0000000000..de23e83b78 --- /dev/null +++ b/crates/jolt-equivalence/README.md @@ -0,0 +1,50 @@ +# jolt-equivalence + +`jolt-equivalence` is the real-data oracle for the Bolt implementation of +Jolt. Synthetic fixtures belong in unit tests, but a Bolt protocol stage is not +accepted until this crate proves the generated/Bolt artifacts agree with +`jolt-core` on real trace data. + +## Stage Gate Shape + +Each stage test should execute the complete implemented prefix and then check: + +1. **Bolt self-acceptance**: Bolt prover artifacts are accepted by the Bolt + verifier on the same trace. +2. **Bolt transcript parity**: Bolt prover and verifier transcript states match + step-for-step through the stage boundary. +3. **Core acceptance**: Bolt-produced artifacts are spliced into the matching + `jolt-core` proof fields, and `jolt-core` accepts through that stage. +4. **Core transcript/artifact parity**: Bolt transcript states and observable + proof components match `jolt-core` through the stage boundary. +5. **Tamper rejection**: the generated/Bolt verifier rejects representative + mutations for every new soundness obligation introduced by the stage. + +Stage tests should be named by stage, use real traced data, and avoid depending +on generated verifier internals that are not part of the canonical +`jolt-verifier` artifact contract. + +Equivalence tests should import the checked-in generated role crates under +`crates/jolt-prover` and `crates/jolt-verifier`, not temp compiler outputs. +Those crates are regenerated by Bolt's Rust artifact rail and expose the +implemented prefix through their generated stage registries plus top-level +prover/verifier APIs. + +## Current Prefix + +The active Bolt prefix covers the full standard non-zk path: commitment, +Stages 1-7, and Stage 8 evaluation/opening proof construction. The focused +real-trace oracle runs generated prover and verifier artifacts against +`jolt-core`, checks staged and monolithic verifier agreement, compares public +proof artifacts and transcript checkpoints, and requires representative +tampering to be rejected. + +The always-on equivalence gates are: + +```bash +cargo nextest run -p jolt-equivalence --test generated_role_crates --cargo-quiet +cargo nextest run -p jolt-equivalence --test bolt_commitment --cargo-quiet +``` + +The larger SHA2-chain perf oracles live in `tests/bolt_perf.rs` and are run by +the dedicated Bolt perf workflow. diff --git a/crates/jolt-equivalence/src/adapters.rs b/crates/jolt-equivalence/src/adapters.rs new file mode 100644 index 0000000000..8d53c6d00f --- /dev/null +++ b/crates/jolt-equivalence/src/adapters.rs @@ -0,0 +1,59 @@ +//! Thin representation adapters between kernel and generated stage types. + +use jolt_field::Fr; +use jolt_kernels::stage4::Stage4ExecutionArtifacts; +use jolt_verifier::{JoltStageExecutionArtifacts, JoltStageProof}; + +use crate::artifacts::{NamedScalar, StageArtifacts, SumcheckArtifacts}; + +macro_rules! canonical_stage_artifacts { + ($stage:literal, $artifacts:expr) => {{ + StageArtifacts { + stage: $stage.to_owned(), + sumchecks: $artifacts + .sumchecks + .iter() + .map(|output| SumcheckArtifacts { + driver: output.driver.to_owned(), + point: output.point.clone(), + round_polynomials: output + .proof + .round_polynomials + .iter() + .map(|poly| poly.clone().into_coefficients()) + .collect(), + evals: output + .evals + .iter() + .map(|eval| NamedScalar { + name: eval.name.to_owned(), + value: eval.value, + }) + .collect(), + }) + .collect(), + opening_batches: Vec::new(), + } + }}; +} + +macro_rules! define_canonical_stage_adapters { + ($($function:ident, $stage:literal, $input:ty;)*) => { + $( + pub(crate) fn $function(artifacts: &$input) -> StageArtifacts { + canonical_stage_artifacts!($stage, artifacts) + } + )* + }; +} + +define_canonical_stage_adapters! { + canonical_stage4_artifacts, "Stage 4", Stage4ExecutionArtifacts; + canonical_generated_stage4_execution_artifacts, "Stage 4", JoltStageExecutionArtifacts; + canonical_generated_stage5_proof, "Stage 5", JoltStageProof; + canonical_generated_stage5_execution_artifacts, "Stage 5", JoltStageExecutionArtifacts; + canonical_generated_stage6_proof, "Stage 6", JoltStageProof; + canonical_generated_stage6_execution_artifacts, "Stage 6", JoltStageExecutionArtifacts; + canonical_generated_stage7_proof, "Stage 7", JoltStageProof; + canonical_generated_stage7_execution_artifacts, "Stage 7", JoltStageExecutionArtifacts; +} diff --git a/crates/jolt-equivalence/src/artifacts.rs b/crates/jolt-equivalence/src/artifacts.rs new file mode 100644 index 0000000000..4c0da86fbd --- /dev/null +++ b/crates/jolt-equivalence/src/artifacts.rs @@ -0,0 +1,144 @@ +//! Canonical comparison artifacts for Jolt-on-Bolt equivalence gates. +//! +//! These types are intentionally representation-only. They give core and +//! generated Bolt adapters a common shape for public artifacts, but they do not +//! encode Jolt stage semantics, point normalization rules, witness +//! materialization, or claim-reduction formulas. + +use crate::TranscriptEvent; +use jolt_profiling::PerfMetrics; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ArtifactSource { + Core, + Bolt, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct EquivalenceRun { + pub source: ArtifactSource, + pub commitments: CommitmentTrace, + pub transcript: TranscriptTrace, + pub stages: Vec>, + pub opening_claims: OpeningClaims, + pub verifier_result: VerifierResult, + pub perf: Option, +} + +impl EquivalenceRun { + pub fn new(source: ArtifactSource) -> Self { + Self { + source, + commitments: CommitmentTrace::default(), + transcript: TranscriptTrace::default(), + stages: Vec::new(), + opening_claims: OpeningClaims::default(), + verifier_result: VerifierResult::not_run(), + perf: None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CommitmentTrace { + pub commitments: Vec, +} + +/// One public commitment slot. +/// +/// `bytes = None` represents an intentionally skipped optional commitment. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CommitmentArtifact { + pub label: String, + pub artifact: String, + pub bytes: Option>, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct TranscriptTrace { + pub events: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StageArtifacts { + pub stage: String, + pub sumchecks: Vec>, + pub opening_batches: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SumcheckArtifacts { + pub driver: String, + pub point: Vec, + pub round_polynomials: Vec>, + pub evals: Vec>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NamedScalar { + pub name: String, + pub value: F, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OpeningClaims { + pub claims: Vec>, +} + +impl Default for OpeningClaims { + fn default() -> Self { + Self { claims: Vec::new() } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OpeningClaim { + pub symbol: String, + pub oracle: String, + pub domain: String, + pub kind: OpeningClaimKind, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OpeningClaimKind { + Committed, + Virtual, + Advice, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OpeningBatchArtifacts { + pub symbol: String, + pub ordered_claims: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VerifierResult { + pub accepted: bool, + pub error: Option, +} + +impl VerifierResult { + pub fn accepted() -> Self { + Self { + accepted: true, + error: None, + } + } + + pub fn rejected(error: impl Into) -> Self { + Self { + accepted: false, + error: Some(error.into()), + } + } + + pub fn not_run() -> Self { + Self { + accepted: false, + error: Some("verifier not run".to_owned()), + } + } +} diff --git a/crates/jolt-equivalence/src/bolt_oracle.rs b/crates/jolt-equivalence/src/bolt_oracle.rs new file mode 100644 index 0000000000..3bca6cffef --- /dev/null +++ b/crates/jolt-equivalence/src/bolt_oracle.rs @@ -0,0 +1,841 @@ +//! Bolt-side oracle driver for full real-trace equivalence gates. +//! +//! This module orchestrates the generated/kernel Bolt prover and verifier path +//! and compares its public artifacts against the core oracle. Stage semantics +//! should continue to move into Bolt, kernels, generated crates, or +//! `jolt-witness`, not into this module. + +#![expect( + clippy::expect_used, + reason = "equivalence oracle should fail fast on invalid generated artifacts" +)] + +use std::time::Instant; + +use jolt_dory::{DoryProof, DoryScheme}; +use jolt_openings::CommitmentScheme as _; +use jolt_profiling::{check_core_vs_bolt_gate, time_it, PeakRssSampler}; + +use crate::adapters::{ + canonical_generated_stage4_execution_artifacts, canonical_generated_stage5_execution_artifacts, + canonical_generated_stage5_proof, canonical_generated_stage6_execution_artifacts, + canonical_generated_stage6_proof, canonical_generated_stage7_execution_artifacts, + canonical_generated_stage7_proof, canonical_stage4_artifacts, +}; +use crate::artifacts::{ArtifactSource, EquivalenceRun, TranscriptTrace, VerifierResult}; +use crate::bolt_programs::{ + bolt_commitment_programs_with_params, bolt_stage1_programs_with_params, + bolt_stage2_programs_with_params, bolt_stage3_programs_with_params, + bolt_stage4_programs_with_params, bolt_stage5_programs_with_params, + bolt_stage6_programs_with_params, bolt_stage7_programs_with_params, + bolt_stage8_programs_with_params, +}; +use crate::checkpoint::{assert_state_history_match, assert_state_history_prefix_match}; +use crate::checks::{ + assert_canonical_stage_artifacts_match, assert_dory_proofs_match, + assert_equivalence_run_artifacts_match, assert_stage5_artifacts_match, + assert_stage6_artifacts_match, assert_stage7_artifacts_match, +}; +use crate::commitment_oracle::{ + generated_commitment_trace, generated_verifier_commitment_trace, + run_generated_bolt_commitment_pair_with_cycles, transcript_with_bolt_commitment_trace, + transcript_with_bolt_preamble, GeneratedCommitmentInputStorage, +}; +use crate::core_oracle::{ + assert_core_accepts_bolt_evaluation_proof, assert_core_accepts_bolt_stage3, + assert_core_accepts_bolt_stage4, assert_core_accepts_bolt_stage5, + assert_core_accepts_bolt_stage6, assert_core_accepts_bolt_stage7, + assert_core_accepts_full_bolt_proof, CoreMuldivCommitmentFixture, +}; +use crate::perf::{ + generated_bolt_perf_metrics, print_core_vs_bolt_perf_summary, CORE_VS_BOLT_PERF_THRESHOLDS, +}; +use crate::plan_adapters::{ + leak_generated_commitment_prover_program, leak_generated_commitment_verifier_program, + leak_generated_stage1_verifier_program, leak_generated_stage2_verifier_program, + leak_generated_stage3_verifier_program, leak_generated_stage4_verifier_program, + leak_generated_stage5_verifier_program, leak_generated_stage6_verifier_program, + leak_generated_stage7_verifier_program, leak_generated_stage8_prover_program, + leak_generated_stage8_verifier_program, leak_stage1_program, leak_stage2_program, + leak_stage3_program, leak_stage4_program, leak_stage5_program, leak_stage6_program, + leak_stage7_program, +}; +use crate::tamper::{ + assert_bolt_stage3_4_5_tamper_rejected, assert_bolt_stage6_tamper_rejected, + assert_bolt_stage7_tamper_rejected, assert_monolithic_jolt_tamper_rejected, + MonolithicJoltTamperInput, Stage345TamperInput, Stage6TamperInput, Stage7TamperInput, +}; + +pub fn assert_bolt_full_real_trace_self_parity( + fixture: CoreMuldivCommitmentFixture, + enforce_perf_gate: bool, +) { + let bolt_setup_start = Instant::now(); + let _bolt_setup_span = tracing::info_span!("bolt.setup").entered(); + let (commitment_prover_program, commitment_verifier_program) = + bolt_commitment_programs_with_params(&fixture.params); + let (stage1_prover_program, stage1_verifier_program) = + bolt_stage1_programs_with_params(&fixture.params); + let (stage2_prover_program, stage2_verifier_program) = + bolt_stage2_programs_with_params(&fixture.params); + let (stage3_prover_program, stage3_verifier_program) = + bolt_stage3_programs_with_params(&fixture.params); + let (stage4_prover_program, stage4_verifier_program) = + bolt_stage4_programs_with_params(&fixture.params); + let (stage5_prover_program, stage5_verifier_program) = + bolt_stage5_programs_with_params(&fixture.params); + let (stage6_prover_program, stage6_verifier_program) = + bolt_stage6_programs_with_params(&fixture.params); + let (stage7_prover_program, stage7_verifier_program) = + bolt_stage7_programs_with_params(&fixture.params); + let (stage8_prover_program, stage8_verifier_program) = + bolt_stage8_programs_with_params(&fixture.params); + let generated_commitment_prover_plan = + leak_generated_commitment_prover_program(&commitment_prover_program); + let generated_commitment_verifier_plan = + leak_generated_commitment_verifier_program(&commitment_verifier_program); + + let (commitment_prover_trace, commitment_verifier_trace) = + run_generated_bolt_commitment_pair_with_cycles( + generated_commitment_prover_plan, + generated_commitment_verifier_plan, + &fixture.pcs_setup, + &fixture.cycle_inputs, + ); + + let stage1_prover_plan = leak_stage1_program(&stage1_prover_program); + let stage2_prover_plan = leak_stage2_program(&stage2_prover_program); + let stage3_prover_plan = leak_stage3_program(&stage3_prover_program); + let stage4_prover_plan = leak_stage4_program(&stage4_prover_program); + let stage5_prover_plan = leak_stage5_program(&stage5_prover_program); + let stage6_prover_plan = leak_stage6_program(&stage6_prover_program); + let stage7_prover_plan = leak_stage7_program(&stage7_prover_program); + let stage8_prover_plan = leak_generated_stage8_prover_program(&stage8_prover_program); + let generated_stage1_verifier_plan = + leak_generated_stage1_verifier_program(&stage1_verifier_program); + let generated_stage2_verifier_plan = + leak_generated_stage2_verifier_program(&stage2_verifier_program); + let generated_stage3_verifier_plan = + leak_generated_stage3_verifier_program(&stage3_verifier_program); + let generated_stage4_verifier_plan = + leak_generated_stage4_verifier_program(&stage4_verifier_program); + let generated_stage5_verifier_plan = + leak_generated_stage5_verifier_program(&stage5_verifier_program); + let generated_stage6_verifier_plan = + leak_generated_stage6_verifier_program(&stage6_verifier_program); + let generated_stage7_verifier_plan = + leak_generated_stage7_verifier_program(&stage7_verifier_program); + let generated_stage8_verifier_plan = + leak_generated_stage8_verifier_program(&stage8_verifier_program); + let generated_programs = jolt_verifier::JoltVerifierPrograms { + commitment: generated_commitment_verifier_plan, + stage1_outer: generated_stage1_verifier_plan, + stage2: generated_stage2_verifier_plan, + stage3: generated_stage3_verifier_plan, + stage4: generated_stage4_verifier_plan, + stage5: generated_stage5_verifier_plan, + stage6: generated_stage6_verifier_plan, + stage7: generated_stage7_verifier_plan, + stage8: generated_stage8_verifier_plan, + }; + let r1cs_key = fixture.r1cs_key(); + let data = fixture.stage1_outer_rv64_data(&r1cs_key); + let bolt_setup_ms = bolt_setup_start.elapsed().as_secs_f64() * 1_000.0; + drop(_bolt_setup_span); + + let mut prover_transcript = + transcript_with_bolt_commitment_trace(&fixture, &commitment_prover_trace); + let stage1_artifacts = jolt_prover::prove_stage1_outer_with_witness_inputs( + stage1_prover_plan, + r1cs_key.num_cycle_vars(), + &data, + &mut prover_transcript, + ) + .expect("Bolt Stage 1 prover succeeds"); + + let stage2_openings = + jolt_prover::stage2_opening_inputs_from_artifacts(stage2_prover_plan, &stage1_artifacts) + .expect("generated prover derives Stage 2 opening inputs from artifacts"); + let ram_data = fixture.stage2_ram_data(); + let stage2_artifacts = jolt_prover::prove_stage2_with_witness_inputs( + stage2_prover_plan, + &stage2_openings, + &fixture.product_virtual_cycles, + &fixture.instruction_lookup_cycles, + &ram_data, + &mut prover_transcript, + ) + .expect("Bolt Stage 2 prover succeeds"); + + let stage3_openings = jolt_prover::stage3_opening_inputs_from_artifacts( + stage3_prover_plan, + &stage1_artifacts, + &stage2_artifacts, + ) + .expect("generated prover derives Stage 3 opening inputs from artifacts"); + let stage3_artifacts = jolt_prover::prove_stage3_with_witness_inputs( + stage3_prover_plan, + &stage3_openings, + &fixture.stage3_cycles, + &mut prover_transcript, + ) + .expect("Bolt Stage 3 prover succeeds"); + + let stage4_openings = jolt_prover::stage4_opening_inputs_from_artifacts( + stage4_prover_plan, + &fixture.initial_ram_state, + &stage2_artifacts, + &stage3_artifacts, + ) + .expect("generated prover derives Stage 4 opening inputs from artifacts"); + let stage4_artifacts = jolt_prover::prove_stage4_with_trace_witness_inputs( + stage4_prover_plan, + &stage4_openings, + 1 << fixture.params.register_log_k, + fixture.proof.trace_length, + fixture.proof.ram_K, + &fixture.stage4_register_accesses, + &fixture.ram_accesses, + &mut prover_transcript, + ) + .expect("Bolt Stage 4 prover succeeds"); + + let mut verifier_transcript = + transcript_with_bolt_commitment_trace(&fixture, &commitment_verifier_trace); + let stage1_proof = stage1_artifacts.clone().into(); + let verified_stage1 = jolt_prover::replay_stage1_outer_proof_with_program( + stage1_prover_plan, + &stage1_proof, + &mut verifier_transcript, + ) + .expect("Bolt Stage 1 verifier accepts"); + let stage2_verifier_openings = + jolt_prover::stage2_opening_inputs_from_artifacts(stage2_prover_plan, &verified_stage1) + .expect("generated prover derives Stage 2 verifier opening inputs from artifacts"); + let stage2_proof = stage2_artifacts.clone().into(); + let verified_stage2 = jolt_prover::replay_stage2_proof_with_program( + stage2_prover_plan, + &stage2_proof, + &stage2_verifier_openings, + Some(&ram_data), + &mut verifier_transcript, + ) + .expect("Bolt Stage 2 verifier accepts"); + let generated_stage3_prefix_transcript = verifier_transcript.clone(); + let stage3_verifier_openings = jolt_prover::stage3_opening_inputs_from_artifacts( + stage3_prover_plan, + &verified_stage1, + &verified_stage2, + ) + .expect("generated prover derives Stage 3 verifier opening inputs from artifacts"); + let stage3_proof = stage3_artifacts.clone().into(); + let verified_stage3 = jolt_prover::replay_stage3_proof_with_program( + stage3_prover_plan, + &stage3_proof, + &stage3_verifier_openings, + &mut verifier_transcript, + ) + .expect("Bolt Stage 3 verifier accepts"); + let stage4_verifier_openings = jolt_prover::stage4_opening_inputs_from_artifacts( + stage4_prover_plan, + &fixture.initial_ram_state, + &verified_stage2, + &verified_stage3, + ) + .expect("generated prover derives Stage 4 verifier opening inputs from artifacts"); + let stage4_proof = stage4_artifacts.clone().into(); + let verified_stage4 = jolt_prover::replay_stage4_proof_with_program( + stage4_prover_plan, + &stage4_proof, + &stage4_verifier_openings, + &mut verifier_transcript, + ) + .expect("Bolt Stage 4 verifier accepts"); + + assert_eq!( + stage3_artifacts.sumchecks.len(), + verified_stage3.sumchecks.len() + ); + assert_eq!( + stage4_artifacts.sumchecks.len(), + verified_stage4.sumchecks.len() + ); + assert_state_history_match(prover_transcript.log(), verifier_transcript.log()); + assert_core_accepts_bolt_stage3( + &fixture, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + ); + assert_core_accepts_bolt_stage4( + &fixture, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + ); + + let mut generated_verifier_transcript = generated_stage3_prefix_transcript; + let generated_stage3_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&stage3_verifier_openings); + let generated_stage3_proof = jolt_prover::stage3_proof(&stage3_artifacts); + let generated_stage3_start_transcript = generated_verifier_transcript.clone(); + let generated_verified_stage3 = jolt_verifier::verify_stage3_with_program( + generated_stage3_verifier_plan, + &generated_stage3_proof, + &generated_stage3_openings, + &mut generated_verifier_transcript, + ) + .expect("generated Stage 3 verifier accepts real muldiv proof"); + assert_eq!( + stage3_artifacts.sumchecks.len(), + generated_verified_stage3.sumchecks.len() + ); + let generated_stage4_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&stage4_verifier_openings); + let generated_stage4_proof = jolt_prover::stage4_proof(&stage4_artifacts); + let generated_stage4_start_transcript = generated_verifier_transcript.clone(); + let generated_verified_stage4 = jolt_verifier::verify_stage4_with_program( + generated_stage4_verifier_plan, + &generated_stage4_proof, + &generated_stage4_openings, + &mut generated_verifier_transcript, + ) + .expect("generated Stage 4 verifier accepts real muldiv proof"); + assert_eq!( + stage4_artifacts.sumchecks.len(), + generated_verified_stage4.sumchecks.len() + ); + assert_state_history_match( + verifier_transcript.log(), + generated_verifier_transcript.log(), + ); + let kernel_stage5_openings = jolt_prover::stage5_opening_inputs_from_artifacts( + stage5_prover_plan, + &stage2_artifacts, + &stage4_artifacts, + ) + .expect("generated prover derives Stage 5 opening inputs from artifacts"); + let generated_stage5_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&kernel_stage5_openings); + let mut stage5_prover_transcript = prover_transcript.clone(); + let stage5_artifacts = jolt_prover::prove_stage5_with_trace_witness_inputs( + stage5_prover_plan, + &kernel_stage5_openings, + fixture.proof.trace_length, + fixture.proof.ram_K, + 1 << fixture.params.register_log_k, + &fixture.stage5_lookup_indices, + &fixture.stage5_lookup_table_indices, + &fixture.stage5_is_interleaved_operands, + fixture.params.lookups_ra_virtual_log_k_chunk, + &fixture.stage4_register_accesses, + &fixture.ram_accesses, + &mut stage5_prover_transcript, + ) + .expect("Bolt Stage 5 prover succeeds"); + assert_eq!(stage5_artifacts.sumchecks.len(), 1); + let stage5_proof = jolt_prover::stage5_kernel_proof(&stage5_artifacts); + let mut kernel_stage5_transcript = verifier_transcript.clone(); + let kernel_verified_stage5 = jolt_prover::replay_stage5_proof_with_program( + stage5_prover_plan, + &stage5_proof, + &kernel_stage5_openings, + &mut kernel_stage5_transcript, + ) + .expect("kernel Stage 5 replay accepts Bolt real muldiv proof"); + assert_eq!(kernel_verified_stage5.sumchecks.len(), 1); + assert_state_history_match( + stage5_prover_transcript.log(), + kernel_stage5_transcript.log(), + ); + let generated_stage5_proof = jolt_prover::stage5_proof(&stage5_artifacts); + let generated_stage5_start_transcript = generated_verifier_transcript.clone(); + let generated_verified_stage5 = jolt_verifier::verify_stage5_with_program( + generated_stage5_verifier_plan, + &generated_stage5_proof, + &generated_stage5_openings, + &mut generated_verifier_transcript, + ) + .expect("generated Stage 5 verifier accepts Bolt real muldiv proof"); + assert_eq!(generated_verified_stage5.sumchecks.len(), 1); + let generated_verified_stage5_proof = jolt_verifier::JoltStageProof { + sumchecks: generated_verified_stage5.sumchecks.clone(), + }; + assert_stage5_artifacts_match(&generated_stage5_proof, &generated_verified_stage5_proof); + assert_state_history_match( + stage5_prover_transcript.log(), + generated_verifier_transcript.log(), + ); + assert_core_accepts_bolt_stage5( + &fixture, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &generated_stage5_proof, + ); + let kernel_stage6_openings = jolt_prover::stage6_opening_inputs_from_artifacts( + stage6_prover_plan, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &stage5_artifacts, + ) + .expect("generated prover derives Stage 6 opening inputs from artifacts"); + let generated_stage6_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&kernel_stage6_openings); + let generated_stage6_data = jolt_prover::stage6_verifier_data_from_witness_entries( + &fixture.stage6_bytecode_entries, + fixture.stage6_entry_bytecode_index, + fixture.stage6_num_lookup_tables, + ); + let kernel_stage6_bytecode_data = + jolt_prover::stage6_bytecode_read_raf_data_from_witness_entries( + &fixture.stage6_bytecode_entries, + fixture.stage6_entry_bytecode_index, + fixture.stage6_num_lookup_tables, + ); + let mut stage6_prover_transcript = generated_verifier_transcript.clone(); + let stage6_artifacts = jolt_prover::prove_stage6_with_trace_witness_inputs( + stage6_prover_plan, + &kernel_stage6_openings, + kernel_stage6_bytecode_data.as_input(), + fixture.stage6_witness_params(), + &fixture.cycle_inputs, + fixture.params.instruction_ra_virtual_d, + &mut stage6_prover_transcript, + ) + .expect("Bolt Stage 6 prover succeeds"); + assert_eq!(stage6_artifacts.sumchecks.len(), 1); + let generated_stage6_proof = jolt_prover::stage6_proof(&stage6_artifacts); + let generated_stage6_artifacts = jolt_prover::stage6_execution_artifacts(&stage6_artifacts); + assert_stage6_artifacts_match(&generated_stage6_proof, &generated_stage6_artifacts); + let mut kernel_stage6_transcript = generated_verifier_transcript.clone(); + let kernel_stage6_proof = jolt_prover::stage6_kernel_proof(&generated_stage6_proof); + let kernel_verified_stage6 = jolt_prover::replay_stage6_proof_with_program( + stage6_prover_plan, + &kernel_stage6_proof, + &kernel_stage6_openings, + Some(kernel_stage6_bytecode_data.as_input()), + &mut kernel_stage6_transcript, + ) + .expect("kernel Stage 6 replay accepts Bolt real muldiv proof"); + assert_eq!(kernel_verified_stage6.sumchecks.len(), 1); + assert_stage6_artifacts_match( + &generated_stage6_proof, + &jolt_prover::stage6_execution_artifacts(&kernel_verified_stage6), + ); + assert_state_history_match( + stage6_prover_transcript.log(), + kernel_stage6_transcript.log(), + ); + let mut generated_stage6_transcript = generated_verifier_transcript.clone(); + let generated_verified_stage6 = jolt_verifier::verify_stage6_with_program( + generated_stage6_verifier_plan, + &generated_stage6_proof, + &generated_stage6_openings, + Some(&generated_stage6_data), + &mut generated_stage6_transcript, + ) + .expect("generated Stage 6 verifier accepts Bolt real muldiv proof"); + assert_stage6_artifacts_match(&generated_stage6_proof, &generated_verified_stage6); + assert_state_history_match( + stage6_prover_transcript.log(), + generated_stage6_transcript.log(), + ); + assert_core_accepts_bolt_stage6( + &fixture, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &generated_stage5_proof, + &generated_stage6_proof, + ); + + let kernel_stage7_openings = + jolt_prover::stage7_opening_inputs_from_stage6_artifacts_with_program( + stage7_prover_plan, + &stage6_artifacts, + ) + .expect("generated prover derives Stage 7 opening inputs from Stage 6 artifacts"); + let generated_stage7_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&kernel_stage7_openings); + let mut stage7_prover_transcript = stage6_prover_transcript.clone(); + let stage7_artifacts = jolt_prover::prove_stage7_with_trace_witness_inputs( + stage7_prover_plan, + &kernel_stage7_openings, + fixture.stage6_witness_params(), + &fixture.cycle_inputs, + &kernel_stage6_openings, + &mut stage7_prover_transcript, + ) + .expect("Bolt Stage 7 prover succeeds"); + assert_eq!(stage7_artifacts.sumchecks.len(), 1); + let generated_stage7_proof = jolt_prover::stage7_proof(&stage7_artifacts); + let generated_stage7_artifacts = jolt_prover::stage7_execution_artifacts(&stage7_artifacts); + assert_stage7_artifacts_match(&generated_stage7_proof, &generated_stage7_artifacts); + let mut kernel_stage7_transcript = generated_stage6_transcript.clone(); + let kernel_stage7_proof = jolt_prover::stage7_kernel_proof(&generated_stage7_proof); + let kernel_verified_stage7 = jolt_prover::replay_stage7_proof_with_program( + stage7_prover_plan, + &kernel_stage7_proof, + &kernel_stage7_openings, + &mut kernel_stage7_transcript, + ) + .expect("kernel Stage 7 replay accepts Bolt real muldiv proof"); + assert_eq!(kernel_verified_stage7.sumchecks.len(), 1); + assert_stage7_artifacts_match( + &generated_stage7_proof, + &jolt_prover::stage7_execution_artifacts(&kernel_verified_stage7), + ); + assert_state_history_match( + stage7_prover_transcript.log(), + kernel_stage7_transcript.log(), + ); + let mut generated_stage7_transcript = generated_stage6_transcript.clone(); + let generated_verified_stage7 = jolt_verifier::verify_stage7_with_program( + generated_stage7_verifier_plan, + &generated_stage7_proof, + &generated_stage7_openings, + &mut generated_stage7_transcript, + ) + .expect("generated Stage 7 verifier accepts Bolt real muldiv proof"); + assert_stage7_artifacts_match(&generated_stage7_proof, &generated_verified_stage7); + assert_state_history_match( + stage7_prover_transcript.log(), + generated_stage7_transcript.log(), + ); + assert_core_accepts_bolt_stage7( + &fixture, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &generated_stage5_proof, + &generated_stage6_proof, + &generated_stage7_proof, + ); + + let generated_jolt_stage2_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&stage2_verifier_openings); + let generated_jolt_stage3_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&stage3_verifier_openings); + let generated_jolt_stage4_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&stage4_verifier_openings); + let generated_ram_data_storage = jolt_prover::stage2_verifier_ram_data(&ram_data); + let generated_ram_data = generated_ram_data_storage.as_input(); + let generated_jolt_inputs = jolt_verifier::JoltVerifierInputs { + stage2_openings: &generated_jolt_stage2_openings, + stage2_ram: Some(&generated_ram_data), + stage3_openings: &generated_jolt_stage3_openings, + stage4_openings: &generated_jolt_stage4_openings, + stage5_openings: &generated_stage5_openings, + stage6_openings: &generated_stage6_openings, + stage6_data: Some(&generated_stage6_data), + stage7_openings: &generated_stage7_openings, + evaluation_setup: None, + }; + let generated_jolt_proof = jolt_prover::jolt_proof_through_stage5( + &commitment_verifier_trace.commitments, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &generated_stage5_proof, + ); + let mut generated_jolt_transcript = transcript_with_bolt_preamble(&fixture); + let generated_jolt_artifacts = jolt_verifier::verify_jolt_through_stage5_with_programs( + &generated_jolt_proof, + generated_jolt_inputs.through_stage5(), + generated_programs, + &mut generated_jolt_transcript, + ) + .expect("generated monolithic verifier accepts real muldiv proof"); + assert_eq!( + stage3_artifacts.sumchecks.len(), + generated_jolt_artifacts.stage3.sumchecks.len() + ); + assert_canonical_stage_artifacts_match( + "generated monolithic through-stage5 Stage 4", + canonical_stage4_artifacts(&stage4_artifacts), + canonical_generated_stage4_execution_artifacts(&generated_jolt_artifacts.stage4), + ); + assert_canonical_stage_artifacts_match( + "generated monolithic through-stage5 Stage 5", + canonical_generated_stage5_proof(&generated_stage5_proof), + canonical_generated_stage5_execution_artifacts(&generated_jolt_artifacts.stage5), + ); + assert_state_history_match( + generated_verifier_transcript.log(), + generated_jolt_transcript.log(), + ); + let generated_jolt_proof_with_stage6 = jolt_prover::jolt_proof_through_stage6( + &commitment_verifier_trace.commitments, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &generated_stage5_proof, + &generated_stage6_proof, + ); + let mut generated_jolt_stage6_transcript = transcript_with_bolt_preamble(&fixture); + let generated_jolt_stage6_artifacts = jolt_verifier::verify_jolt_through_stage6_with_programs( + &generated_jolt_proof_with_stage6, + generated_jolt_inputs.through_stage6(), + generated_programs, + &mut generated_jolt_stage6_transcript, + ) + .expect("generated monolithic verifier accepts Bolt Stage 6 proof"); + assert_stage6_artifacts_match( + &generated_stage6_proof, + &generated_jolt_stage6_artifacts.stage6, + ); + assert_state_history_match( + generated_stage6_transcript.log(), + generated_jolt_stage6_transcript.log(), + ); + let generated_jolt_proof_with_stage7 = jolt_prover::jolt_proof_through_stage7( + &commitment_verifier_trace.commitments, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &generated_stage5_proof, + &generated_stage6_proof, + &generated_stage7_proof, + ); + let mut generated_jolt_stage7_transcript = transcript_with_bolt_preamble(&fixture); + let generated_jolt_stage7_artifacts = jolt_verifier::verify_jolt_through_stage7_with_programs( + &generated_jolt_proof_with_stage7, + generated_jolt_inputs.through_stage7(), + generated_programs, + &mut generated_jolt_stage7_transcript, + ) + .expect("generated monolithic verifier accepts Bolt Stage 7 proof"); + assert_stage7_artifacts_match( + &generated_stage7_proof, + &generated_jolt_stage7_artifacts.stage7, + ); + assert_state_history_match( + generated_stage7_transcript.log(), + generated_jolt_stage7_transcript.log(), + ); + + let monolithic_prover_programs = jolt_prover::JoltProverPrograms { + commitment: generated_commitment_prover_plan, + stage1_outer: stage1_prover_plan, + stage2: stage2_prover_plan, + stage3: stage3_prover_plan, + stage4: stage4_prover_plan, + stage5: stage5_prover_plan, + stage6: stage6_prover_plan, + stage7: stage7_prover_plan, + stage8: stage8_prover_plan, + }; + let monolithic_commitment_storage = + GeneratedCommitmentInputStorage::from_cycles(&fixture.cycle_inputs); + let mut monolithic_commitment_inputs = monolithic_commitment_storage.sparse_inputs(); + let mut monolithic_prover_transcript = transcript_with_bolt_preamble(&fixture); + let bolt_rss_sampler = PeakRssSampler::start().expect("start Bolt RSS sampler"); + let (bolt_prove_ms, monolithic_prove_result) = time_it(|| { + jolt_prover::prove_jolt_with_witness_inputs( + jolt_prover::JoltProverWitnessInputs { + commitment_inputs: &mut monolithic_commitment_inputs, + prover_setup: &fixture.pcs_setup, + stage1_trace_num_vars: r1cs_key.num_cycle_vars(), + stage1_outer_evaluator: &data, + stage2_openings: &stage2_openings, + product_virtual_cycles: &fixture.product_virtual_cycles, + instruction_lookup_cycles: &fixture.instruction_lookup_cycles, + ram: &ram_data, + stage3_openings: &stage3_openings, + stage3_cycles: &fixture.stage3_cycles, + stage4_openings: &stage4_openings, + register_count: 1 << fixture.params.register_log_k, + trace_len: fixture.proof.trace_length, + ram_k: fixture.proof.ram_K, + register_accesses: &fixture.stage4_register_accesses, + stage5_openings: &kernel_stage5_openings, + lookup_indices: &fixture.stage5_lookup_indices, + lookup_table_indices: &fixture.stage5_lookup_table_indices, + is_interleaved_operands: &fixture.stage5_is_interleaved_operands, + ra_virtual_log_k_chunk: fixture.params.lookups_ra_virtual_log_k_chunk, + stage6_openings: &kernel_stage6_openings, + stage6_bytecode_data: kernel_stage6_bytecode_data.as_input(), + stage6_witness_params: fixture.stage6_witness_params(), + cycle_inputs: &fixture.cycle_inputs, + instruction_ra_virtual_d: fixture.params.instruction_ra_virtual_d, + stage7_openings: &kernel_stage7_openings, + evaluation_openings: Some(&kernel_stage7_openings), + }, + monolithic_prover_programs, + &mut monolithic_prover_transcript, + ) + }); + let bolt_peak_rss_mb = bolt_rss_sampler.finish(); + let (monolithic_proof, monolithic_artifacts) = + monolithic_prove_result.expect("generated monolithic prover produces real trace proof"); + let monolithic_evaluation = monolithic_proof + .evaluation + .as_ref() + .expect("generated monolithic prover emits evaluation proof"); + assert_state_history_prefix_match( + generated_stage7_transcript.log(), + monolithic_prover_transcript.log(), + ); + assert_dory_proofs_match( + &DoryProof(fixture.proof.joint_opening_proof.clone()), + &monolithic_evaluation.joint_opening_proof, + ); + assert_core_accepts_full_bolt_proof(&fixture, &monolithic_proof, &monolithic_artifacts); + assert_core_accepts_bolt_evaluation_proof(&fixture, monolithic_evaluation); + + let mut staged_bolt_run = EquivalenceRun::new(ArtifactSource::Bolt); + staged_bolt_run.commitments = commitment_prover_trace.commitment_trace(); + staged_bolt_run.stages = vec![ + canonical_stage4_artifacts(&stage4_artifacts), + canonical_generated_stage5_proof(&generated_stage5_proof), + canonical_generated_stage6_proof(&generated_stage6_proof), + canonical_generated_stage7_proof(&generated_stage7_proof), + ]; + staged_bolt_run.verifier_result = VerifierResult::accepted(); + let monolithic_stage5_proof = jolt_prover::stage5_proof(&monolithic_artifacts.stage5); + let monolithic_stage6_artifacts = + jolt_prover::stage6_execution_artifacts(&monolithic_artifacts.stage6); + let monolithic_stage7_artifacts = + jolt_prover::stage7_execution_artifacts(&monolithic_artifacts.stage7); + let mut monolithic_bolt_run = EquivalenceRun::new(ArtifactSource::Bolt); + monolithic_bolt_run.commitments = generated_commitment_trace(&monolithic_artifacts.commitment); + monolithic_bolt_run.transcript = TranscriptTrace { + events: monolithic_prover_transcript.log().to_vec(), + }; + monolithic_bolt_run.stages = vec![ + canonical_stage4_artifacts(&monolithic_artifacts.stage4), + canonical_generated_stage5_proof(&monolithic_stage5_proof), + canonical_generated_stage6_execution_artifacts(&monolithic_stage6_artifacts), + canonical_generated_stage7_execution_artifacts(&monolithic_stage7_artifacts), + ]; + monolithic_bolt_run.verifier_result = VerifierResult::accepted(); + assert_equivalence_run_artifacts_match( + "staged-vs-monolithic generated prover", + &staged_bolt_run, + &monolithic_bolt_run, + ); + + let mut monolithic_verify_transcript = transcript_with_bolt_preamble(&fixture); + let evaluation_setup = DoryScheme::verifier_setup(&fixture.pcs_setup); + let (bolt_verify_ms, monolithic_verify_result) = time_it(|| { + jolt_verifier::verify_jolt_with_programs( + &monolithic_proof, + generated_jolt_inputs.full(&evaluation_setup), + generated_programs, + &mut monolithic_verify_transcript, + ) + }); + let monolithic_verified_artifacts = monolithic_verify_result + .expect("generated monolithic verifier accepts generated monolithic prover proof"); + let mut monolithic_verifier_run = EquivalenceRun::new(ArtifactSource::Bolt); + monolithic_verifier_run.commitments = + generated_verifier_commitment_trace(&monolithic_verified_artifacts.commitment); + monolithic_verifier_run.transcript = TranscriptTrace { + events: monolithic_verify_transcript.log().to_vec(), + }; + monolithic_verifier_run.stages = vec![ + canonical_generated_stage4_execution_artifacts(&monolithic_verified_artifacts.stage4), + canonical_generated_stage5_execution_artifacts(&monolithic_verified_artifacts.stage5), + canonical_generated_stage6_execution_artifacts(&monolithic_verified_artifacts.stage6), + canonical_generated_stage7_execution_artifacts(&monolithic_verified_artifacts.stage7), + ]; + monolithic_verifier_run.verifier_result = VerifierResult::accepted(); + assert_equivalence_run_artifacts_match( + "generated monolithic prover-vs-verifier", + &monolithic_bolt_run, + &monolithic_verifier_run, + ); + + let bolt_metrics = generated_bolt_perf_metrics( + bolt_setup_ms, + bolt_prove_ms, + bolt_verify_ms, + &monolithic_proof, + bolt_peak_rss_mb, + ); + if enforce_perf_gate { + let report = check_core_vs_bolt_gate( + &fixture.core_metrics, + &bolt_metrics, + CORE_VS_BOLT_PERF_THRESHOLDS, + ) + .expect("core-vs-Bolt perf oracle gate"); + print_core_vs_bolt_perf_summary(&fixture.core_metrics, &bolt_metrics, &report); + } + assert_state_history_match( + monolithic_prover_transcript.log(), + monolithic_verify_transcript.log(), + ); + + assert_monolithic_jolt_tamper_rejected(MonolithicJoltTamperInput { + preamble: &fixture, + proof: &monolithic_proof, + inputs: generated_jolt_inputs.full(&evaluation_setup), + programs: generated_programs, + }); + + assert_bolt_stage3_4_5_tamper_rejected(Stage345TamperInput { + preamble: &fixture, + commitment_verifier_trace: &commitment_verifier_trace, + stage1_artifacts: &stage1_artifacts, + stage2_artifacts: &stage2_artifacts, + stage3_artifacts: &stage3_artifacts, + stage4_artifacts: &stage4_artifacts, + generated_stage3_verifier_plan, + generated_stage3_openings: &generated_stage3_openings, + generated_stage3_start_transcript: &generated_stage3_start_transcript, + generated_stage4_verifier_plan, + generated_stage5_verifier_plan, + generated_stage4_start_transcript: &generated_stage4_start_transcript, + generated_stage4_openings: &generated_stage4_openings, + generated_stage5_openings: &generated_stage5_openings, + generated_stage5_proof: &generated_stage5_proof, + generated_stage5_start_transcript: &generated_stage5_start_transcript, + generated_jolt_inputs, + generated_programs, + }); + + assert_bolt_stage6_tamper_rejected(Stage6TamperInput { + preamble: &fixture, + commitment_verifier_trace: &commitment_verifier_trace, + verifier_transcript: &generated_verifier_transcript, + verifier_plan: generated_stage6_verifier_plan, + proof: &generated_stage6_proof, + openings: &generated_stage6_openings, + data: &generated_stage6_data, + stage1_artifacts: &stage1_artifacts, + stage2_artifacts: &stage2_artifacts, + stage3_artifacts: &stage3_artifacts, + stage4_artifacts: &stage4_artifacts, + stage5_proof: &generated_stage5_proof, + jolt_inputs: generated_jolt_inputs.through_stage6(), + programs: generated_programs, + }); + + assert_bolt_stage7_tamper_rejected(Stage7TamperInput { + preamble: &fixture, + commitment_verifier_trace: &commitment_verifier_trace, + verifier_transcript: &generated_stage6_transcript, + verifier_plan: generated_stage7_verifier_plan, + proof: &generated_stage7_proof, + openings: &generated_stage7_openings, + stage1_artifacts: &stage1_artifacts, + stage2_artifacts: &stage2_artifacts, + stage3_artifacts: &stage3_artifacts, + stage4_artifacts: &stage4_artifacts, + stage5_proof: &generated_stage5_proof, + stage6_proof: &generated_stage6_proof, + jolt_inputs: generated_jolt_inputs.through_stage7(), + programs: generated_programs, + }); +} diff --git a/crates/jolt-equivalence/src/bolt_programs.rs b/crates/jolt-equivalence/src/bolt_programs.rs new file mode 100644 index 0000000000..d678672c50 --- /dev/null +++ b/crates/jolt-equivalence/src/bolt_programs.rs @@ -0,0 +1,170 @@ +//! Bolt protocol-program construction helpers for equivalence tests. + +use bolt::protocols::jolt::{ + build_commitment_protocol, build_stage1_outer_protocol, build_stage2_protocol, + build_stage3_protocol, build_stage4_protocol, build_stage5_protocol, build_stage6_protocol, + build_stage7_protocol, build_stage8_protocol, commitment_cpu_program, + lower_commitment_to_compute, lower_compute_to_cpu, lower_stage1_to_compute, + lower_stage2_to_compute, lower_stage3_to_compute, lower_stage4_to_compute, + lower_stage5_to_compute, lower_stage6_to_compute, lower_stage7_to_compute, + lower_stage8_to_compute, resolve_compute_kernels, stage1_cpu_program, stage2_cpu_program, + stage3_cpu_program, stage4_cpu_program, stage5_cpu_program, stage6_cpu_program, + stage7_cpu_program, stage8_cpu_program, CommitmentCpuProgram, JoltProtocolParams, + Stage1CpuProgram as CompilerStage1CpuProgram, Stage2CpuProgram as CompilerStage2CpuProgram, + Stage3CpuProgram as CompilerStage3CpuProgram, Stage4CpuProgram as CompilerStage4CpuProgram, + Stage5CpuProgram as CompilerStage5CpuProgram, Stage6CpuProgram as CompilerStage6CpuProgram, + Stage7CpuProgram as CompilerStage7CpuProgram, Stage8CpuProgram as CompilerStage8CpuProgram, +}; +use bolt::{ + lower_piop_and_fiat_shamir, project_prover_party, project_verifier_party, MeliorContext, +}; + +pub fn bolt_commitment_programs() -> (CommitmentCpuProgram, CommitmentCpuProgram) { + let params = JoltProtocolParams::new(0, 0, 0); + bolt_commitment_programs_with_params(¶ms) +} + +macro_rules! define_bolt_programs_with_params { + ( + $function:ident, + $($rest:tt)* + ) => { + define_bolt_programs_with_params!(pub $function, $($rest)*); + }; + ( + $vis:vis $function:ident, + $program:ty, + $build:ident, + $lower:ident, + $extract:ident, + $label:literal, + resolve_kernels = $resolve_kernels:literal + ) => { + $vis fn $function(params: &JoltProtocolParams) -> ($program, $program) { + let context = MeliorContext::new(); + let protocol = $build(&context, params).expect(concat!("build ", $label, " protocol")); + let concrete = lower_piop_and_fiat_shamir(&context, &protocol).expect(concat!( + "lower ", + $label, + " protocol" + )); + let prover_party = project_prover_party(&context, &concrete).expect("project prover"); + let verifier_party = + project_verifier_party(&context, &concrete).expect("project verifier"); + let prover_compute = $lower(&context, &prover_party).expect(concat!( + "lower prover ", + $label, + " compute" + )); + let verifier_compute = $lower(&context, &verifier_party).expect(concat!( + "lower verifier ", + $label, + " compute" + )); + let (prover_compute, verifier_compute) = if $resolve_kernels { + ( + resolve_compute_kernels(&context, &prover_compute) + .expect("resolve prover kernels"), + resolve_compute_kernels(&context, &verifier_compute) + .expect("resolve verifier kernels"), + ) + } else { + (prover_compute, verifier_compute) + }; + let prover_cpu = + lower_compute_to_cpu(&context, &prover_compute).expect("lower prover CPU"); + let verifier_cpu = + lower_compute_to_cpu(&context, &verifier_compute).expect("lower verifier CPU"); + let prover_program = + $extract(&prover_cpu).expect(concat!("extract prover ", $label, " CPU program")); + let verifier_program = $extract(&verifier_cpu).expect(concat!( + "extract verifier ", + $label, + " CPU program" + )); + (prover_program, verifier_program) + } + }; +} + +define_bolt_programs_with_params!( + bolt_commitment_programs_with_params, + CommitmentCpuProgram, + build_commitment_protocol, + lower_commitment_to_compute, + commitment_cpu_program, + "commitment", + resolve_kernels = false +); +define_bolt_programs_with_params!( + bolt_stage1_programs_with_params, + CompilerStage1CpuProgram, + build_stage1_outer_protocol, + lower_stage1_to_compute, + stage1_cpu_program, + "Stage 1", + resolve_kernels = true +); +define_bolt_programs_with_params!( + bolt_stage2_programs_with_params, + CompilerStage2CpuProgram, + build_stage2_protocol, + lower_stage2_to_compute, + stage2_cpu_program, + "Stage 2", + resolve_kernels = true +); +define_bolt_programs_with_params!( + pub(crate) bolt_stage3_programs_with_params, + CompilerStage3CpuProgram, + build_stage3_protocol, + lower_stage3_to_compute, + stage3_cpu_program, + "Stage 3", + resolve_kernels = true +); +define_bolt_programs_with_params!( + pub(crate) bolt_stage4_programs_with_params, + CompilerStage4CpuProgram, + build_stage4_protocol, + lower_stage4_to_compute, + stage4_cpu_program, + "Stage 4", + resolve_kernels = true +); +define_bolt_programs_with_params!( + pub(crate) bolt_stage5_programs_with_params, + CompilerStage5CpuProgram, + build_stage5_protocol, + lower_stage5_to_compute, + stage5_cpu_program, + "Stage 5", + resolve_kernels = true +); +define_bolt_programs_with_params!( + pub(crate) bolt_stage6_programs_with_params, + CompilerStage6CpuProgram, + build_stage6_protocol, + lower_stage6_to_compute, + stage6_cpu_program, + "Stage 6", + resolve_kernels = true +); +define_bolt_programs_with_params!( + pub(crate) bolt_stage7_programs_with_params, + CompilerStage7CpuProgram, + build_stage7_protocol, + lower_stage7_to_compute, + stage7_cpu_program, + "Stage 7", + resolve_kernels = true +); +define_bolt_programs_with_params!( + pub(crate) bolt_stage8_programs_with_params, + CompilerStage8CpuProgram, + build_stage8_protocol, + lower_stage8_to_compute, + stage8_cpu_program, + "Stage 8", + resolve_kernels = false +); diff --git a/crates/jolt-equivalence/src/checkpoint.rs b/crates/jolt-equivalence/src/checkpoint.rs new file mode 100644 index 0000000000..4d602461c8 --- /dev/null +++ b/crates/jolt-equivalence/src/checkpoint.rs @@ -0,0 +1,273 @@ +//! Checkpoint transcript for fine-grained Fiat-Shamir comparison. +//! +//! Wraps any [`Transcript`] and records every append/squeeze operation as +//! a [`TranscriptEvent`]. Two event logs can then be compared element by +//! element to find the *exact* operation where two systems diverge. + +#![expect(clippy::panic, reason = "checkpoint assertions are test gates")] + +use std::fmt; + +use jolt_transcript::Transcript; + +/// A single transcript operation. +#[derive(Clone, PartialEq, Eq)] +pub enum TranscriptEvent { + /// Raw bytes were appended to the transcript. + Append { + /// The bytes that were absorbed. + bytes: Vec, + /// Transcript state *after* this append. + state_after: [u8; 32], + }, + /// A challenge was squeezed from the transcript. + Squeeze { + /// The 32-byte transcript state *after* the squeeze. + state_after: [u8; 32], + }, +} + +impl fmt::Debug for TranscriptEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn hex(b: &[u8]) -> String { + use std::fmt::Write; + let mut s = String::with_capacity(b.len() * 2); + for byte in b { + let _ = write!(s, "{byte:02x}"); + } + s + } + match self { + TranscriptEvent::Append { bytes, state_after } => { + let preview = if bytes.len() <= 32 { + hex(bytes) + } else { + format!("{}... ({} bytes)", hex(&bytes[..16]), bytes.len()) + }; + write!(f, "Append({preview}) -> {}", &hex(state_after)[..16]) + } + TranscriptEvent::Squeeze { state_after } => { + write!(f, "Squeeze -> {}", &hex(state_after)[..16]) + } + } + } +} + +/// Wraps a concrete transcript, recording every operation. +/// +/// Implements [`Transcript`] so it can be used as a drop-in replacement. After +/// the protocol runs, call [`log`](Self::log) to inspect the event sequence. +pub struct CheckpointTranscript { + inner: T, + log: Vec, +} + +impl CheckpointTranscript { + /// Borrow the event log without consuming. + pub fn log(&self) -> &[TranscriptEvent] { + &self.log + } +} + +impl Clone for CheckpointTranscript { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + log: self.log.clone(), + } + } +} + +impl Default for CheckpointTranscript { + fn default() -> Self { + Self { + inner: T::default(), + log: Vec::new(), + } + } +} + +impl Transcript for CheckpointTranscript { + type Challenge = ::Challenge; + + fn new(label: &'static [u8]) -> Self { + Self { + inner: T::new(label), + log: Vec::new(), + } + } + + fn append_bytes(&mut self, bytes: &[u8]) { + self.inner.append_bytes(bytes); + self.log.push(TranscriptEvent::Append { + bytes: bytes.to_vec(), + state_after: *self.inner.state(), + }); + } + + fn challenge(&mut self) -> Self::Challenge { + let c = self.inner.challenge(); + self.log.push(TranscriptEvent::Squeeze { + state_after: *self.inner.state(), + }); + c + } + + fn state(&self) -> &[u8; 32] { + self.inner.state() + } +} + +/// Describes where two transcript logs first diverge. +#[derive(Debug)] +pub struct TranscriptDivergence { + /// Zero-based index of the diverging operation. + pub op_index: usize, + /// What the reference system did. + pub expected: String, + /// What the candidate system did. + pub actual: String, +} + +impl fmt::Display for TranscriptDivergence { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "transcript divergence at op #{}: expected {}, got {}", + self.op_index, self.expected, self.actual + ) + } +} + +/// Compare two transcript event logs and return the first divergence. +/// +/// Returns `Ok(())` if the logs are identical (up to the length of the +/// shorter log — a length mismatch after identical prefixes is also reported). +pub fn find_divergence( + reference: &[TranscriptEvent], + candidate: &[TranscriptEvent], +) -> Result<(), TranscriptDivergence> { + let min_len = reference.len().min(candidate.len()); + + for i in 0..min_len { + let events_match = match (&reference[i], &candidate[i]) { + ( + TranscriptEvent::Append { + bytes: bytes_a, + state_after: sa, + .. + }, + TranscriptEvent::Append { + bytes: bytes_b, + state_after: sb, + .. + }, + ) => bytes_a == bytes_b && sa == sb, + ( + TranscriptEvent::Squeeze { state_after: sa }, + TranscriptEvent::Squeeze { state_after: sb }, + ) => sa == sb, + _ => false, // Different event kinds + }; + + if !events_match { + return Err(TranscriptDivergence { + op_index: i, + expected: format!("{:?}", reference[i]), + actual: format!("{:?}", candidate[i]), + }); + } + } + + if reference.len() != candidate.len() { + return Err(TranscriptDivergence { + op_index: min_len, + expected: if min_len < reference.len() { + format!("{:?}", reference[min_len]) + } else { + "".to_string() + }, + actual: if min_len < candidate.len() { + format!("{:?}", candidate[min_len]) + } else { + "".to_string() + }, + }); + } + + Ok(()) +} + +/// Compare two logs and panic with a detailed message on divergence. +#[expect(clippy::print_stderr)] +pub fn assert_transcripts_match(reference: &[TranscriptEvent], candidate: &[TranscriptEvent]) { + if let Err(div) = find_divergence(reference, candidate) { + // Print context: a few events before the divergence point. + let start = div.op_index.saturating_sub(3); + eprintln!("=== Transcript divergence at op #{} ===", div.op_index); + let end_ref = div.op_index.min(reference.len().saturating_sub(1)); + let end_cand = div.op_index.min(candidate.len().saturating_sub(1)); + eprintln!("Context (reference):"); + for (i, event) in reference.iter().enumerate().take(end_ref + 1).skip(start) { + let marker = if i == div.op_index { ">>>" } else { " " }; + eprintln!(" {marker} [{i}] {event:?}"); + } + eprintln!("Context (candidate):"); + for (i, event) in candidate.iter().enumerate().take(end_cand + 1).skip(start) { + let marker = if i == div.op_index { ">>>" } else { " " }; + eprintln!(" {marker} [{i}] {event:?}"); + } + panic!("{div}"); + } +} + +/// Extracts the transcript state after each recorded operation. +fn transcript_states(log: &[TranscriptEvent]) -> Vec<[u8; 32]> { + log.iter() + .map(|event| match event { + TranscriptEvent::Append { state_after, .. } + | TranscriptEvent::Squeeze { state_after } => *state_after, + }) + .collect() +} + +fn assert_state_prefix(expected: &[[u8; 32]], actual: &[[u8; 32]]) { + for index in 0..expected.len() { + assert_eq!( + expected[index], actual[index], + "transcript state mismatch at op #{index}" + ); + } +} + +/// Compare full transcript state histories. +pub fn assert_state_history_match( + expected_log: &[TranscriptEvent], + actual_log: &[TranscriptEvent], +) { + let expected = transcript_states(expected_log); + let actual = transcript_states(actual_log); + let min_len = expected.len().min(actual.len()); + assert_state_prefix(&expected[..min_len], &actual[..min_len]); + assert_eq!( + expected.len(), + actual.len(), + "transcript state count mismatch" + ); +} + +/// Compare an expected transcript state prefix against a full actual history. +pub(crate) fn assert_state_history_prefix_match( + expected_prefix_log: &[TranscriptEvent], + actual_log: &[TranscriptEvent], +) { + let expected_prefix = transcript_states(expected_prefix_log); + let actual = transcript_states(actual_log); + assert!( + actual.len() >= expected_prefix.len(), + "transcript state count mismatch: expected at least {}, got {}", + expected_prefix.len(), + actual.len() + ); + assert_state_prefix(&expected_prefix, &actual); +} diff --git a/crates/jolt-equivalence/src/checks.rs b/crates/jolt-equivalence/src/checks.rs new file mode 100644 index 0000000000..34e077080c --- /dev/null +++ b/crates/jolt-equivalence/src/checks.rs @@ -0,0 +1,565 @@ +//! Focused equivalence assertions shared by tests. + +#![expect( + clippy::expect_used, + clippy::panic, + reason = "equivalence assertions should fail fast with precise mismatch context" +)] + +use std::collections::BTreeMap; + +use jolt_core::curve::Bn254Curve; +use jolt_core::poly::commitment::dory::DoryCommitmentScheme; +use jolt_core::poly::opening_proof::SumcheckId as S; +use jolt_core::poly::opening_proof::{OpeningId, SumcheckId}; +use jolt_core::subprotocols::univariate_skip::UniSkipFirstRoundProofVariant; +use jolt_core::transcripts::Blake2bTranscript as CoreBlake2bTranscript; +use jolt_core::zkvm::instruction::{CircuitFlags, InstructionFlags}; +use jolt_core::zkvm::proof_serialization::JoltProof as CoreJoltProof; +use jolt_core::zkvm::witness::{CommittedPolynomial, VirtualPolynomial}; +use jolt_core::zkvm::witness::{CommittedPolynomial as C, VirtualPolynomial as V}; +use jolt_dory::DoryProof; +use jolt_field::{Field, Fr}; +use jolt_kernels::{ + stage1::{ + outer_uniskip_extended_evals_from_round_poly, outer_uniskip_targets, + Stage1ExecutionArtifacts, Stage1OuterR1csData, Stage1OuterRemainingEvaluator, + Stage1OuterRv64Data, Stage1SumcheckOutput, + }, + stage2::Stage2ExecutionArtifacts, + stage3::Stage3ExecutionArtifacts, +}; +use jolt_poly::UnivariatePoly; +use jolt_verifier::stages::stage6 as generated_stage6; +use jolt_verifier::{JoltStageExecutionArtifacts, JoltStageProof, JoltSumcheckOutput}; + +use crate::adapters::{ + canonical_generated_stage5_proof, canonical_generated_stage6_execution_artifacts, + canonical_generated_stage6_proof, canonical_generated_stage7_execution_artifacts, + canonical_generated_stage7_proof, +}; +use crate::artifacts::{EquivalenceRun, StageArtifacts}; +use crate::core_conversion::to_ark; + +pub type CoreProofForChecks = + CoreJoltProof; + +macro_rules! assert_core_compressed_sumcheck_match { + ($stage:literal, $core_proof:expr, $output:expr) => {{ + let core_polys = match $core_proof { + jolt_core::subprotocols::sumcheck::SumcheckInstanceProof::Clear(proof) => { + &proof.compressed_polys + } + jolt_core::subprotocols::sumcheck::SumcheckInstanceProof::Zk(_) => { + panic!("standard {} proof expected", $stage) + } + }; + assert_eq!( + core_polys.len(), + $output.proof.round_polynomials.len(), + "{} round count mismatch", + $stage + ); + for (round, (core, bolt)) in core_polys + .iter() + .zip(&$output.proof.round_polynomials) + .enumerate() + { + let bolt_coeffs = bolt.compress(); + let bolt_coeffs = bolt_coeffs + .coeffs_except_linear_term() + .iter() + .copied() + .map(to_ark) + .collect::>(); + assert_eq!( + core.coeffs_except_linear_term, bolt_coeffs, + "{} compressed coefficient mismatch at round {round}", + $stage + ); + } + }}; +} + +/// Assert byte-for-byte equality of Dory opening proofs. +pub(crate) fn assert_dory_proofs_match(expected: &DoryProof, actual: &DoryProof) { + assert_eq!( + dory_proof_bytes(expected), + dory_proof_bytes(actual), + "Dory joint opening proof mismatch" + ); +} + +fn dory_proof_bytes(proof: &DoryProof) -> Vec { + postcard::to_stdvec(proof).expect("serialize Dory proof") +} + +fn inverse_nonzero(value: Fr) -> Fr { + match value.inverse() { + Some(inverse) => inverse, + None => unreachable!("nonzero field element has an inverse"), + } +} + +fn assert_core_uniskip_coefficients_match( + stage: &str, + proof: &UniSkipFirstRoundProofVariant, + round_polynomials: &[UnivariatePoly], +) { + let core_coefficients = core_uniskip_coefficients(stage, proof); + assert_eq!(round_polynomials.len(), 1); + let bolt_coefficients = round_polynomials[0].coefficients(); + if let Some(index) = bolt_coefficients + .iter() + .zip(core_coefficients.iter()) + .position(|(bolt, core)| bolt != core) + { + let ratio = if core_coefficients[index] != Fr::from_u64(0) { + Some(bolt_coefficients[index] * inverse_nonzero(core_coefficients[index])) + } else { + None + }; + let next_ratio = bolt_coefficients + .iter() + .zip(core_coefficients.iter()) + .enumerate() + .skip(index + 1) + .find(|(_, (_, core))| **core != Fr::from_u64(0)) + .map(|(_, (bolt, core))| *bolt * inverse_nonzero(*core)); + panic!( + "{stage} uni-skip coefficient mismatch at {index}: bolt={:?} core={:?} ratio={:?} next_ratio={:?}", + bolt_coefficients[index], core_coefficients[index], ratio, next_ratio + ); + } + assert_eq!( + bolt_coefficients.len(), + core_coefficients.len(), + "{stage} uni-skip coefficient count mismatch" + ); +} + +fn core_uniskip_coefficients( + stage: &str, + proof: &UniSkipFirstRoundProofVariant, +) -> Vec { + match proof { + UniSkipFirstRoundProofVariant::Standard(proof) => proof + .uni_poly + .coeffs + .iter() + .copied() + .map(Fr::from) + .collect(), + UniSkipFirstRoundProofVariant::Zk(_) => panic!("standard {stage} proof expected"), + } +} + +pub fn assert_stage1_uniskip_extended_evals_match_core( + proof: &CoreProofForChecks, + typed_data: &Stage1OuterRv64Data<'_>, + generic_data: &Stage1OuterR1csData<'_, Fr>, + artifacts: &Stage1ExecutionArtifacts, +) { + let tau = artifacts + .challenge_vectors + .iter() + .find(|vector| vector.symbol == "stage1.tau") + .expect("Bolt stage1 tau") + .values + .as_slice(); + let typed_evals = typed_data + .uniskip_extended_evals(tau) + .expect("typed Stage 1 extended evals"); + let generic_evals = generic_data + .uniskip_extended_evals(tau) + .expect("generic Stage 1 extended evals"); + assert_stage1_extended_eval_vecs_match( + "typed RV64 vs generic R1CS", + &typed_evals, + &generic_evals, + ); + + let core_evals = core_stage1_uniskip_extended_evals(proof, tau[tau.len() - 1]); + assert_stage1_extended_eval_vecs_match( + "Bolt typed RV64 vs jolt-core", + &typed_evals, + &core_evals, + ); +} + +fn core_stage1_uniskip_extended_evals(proof: &CoreProofForChecks, tau_high: Fr) -> Vec { + let coefficients = + core_uniskip_coefficients("Stage 1", &proof.stage1_uni_skip_first_round_proof); + let s1 = UnivariatePoly::new(coefficients); + outer_uniskip_extended_evals_from_round_poly(&s1, tau_high) +} + +fn assert_stage1_extended_eval_vecs_match(label: &str, actual: &[Fr], expected: &[Fr]) { + assert_eq!( + actual.len(), + expected.len(), + "{label} extended eval count mismatch" + ); + let targets = outer_uniskip_targets(); + if let Some(index) = actual + .iter() + .zip(expected.iter()) + .position(|(actual, expected)| actual != expected) + { + panic!( + "{label} Stage 1 extended eval mismatch at target {} (index {index}): actual={:?} expected={:?}", + targets[index], actual[index], expected[index] + ); + } +} + +type CoreOpeningExpectation = (&'static str, OpeningId); + +fn expected_virtual_opening( + name: &'static str, + polynomial: VirtualPolynomial, + sumcheck: SumcheckId, +) -> CoreOpeningExpectation { + (name, OpeningId::virt(polynomial, sumcheck)) +} + +fn expected_committed_opening( + name: &'static str, + polynomial: CommittedPolynomial, + sumcheck: SumcheckId, +) -> CoreOpeningExpectation { + (name, OpeningId::committed(polynomial, sumcheck)) +} + +macro_rules! expected_opening { + (v, $name:literal, $polynomial:expr, $sumcheck:expr) => { + expected_virtual_opening($name, $polynomial, $sumcheck) + }; + (c, $name:literal, $polynomial:expr, $sumcheck:expr) => { + expected_committed_opening($name, $polynomial, $sumcheck) + }; +} + +macro_rules! expected_openings { + ($($kind:ident $name:literal $polynomial:expr => $sumcheck:expr;)+) => { + [$(expected_opening!($kind, $name, $polynomial, $sumcheck)),+] + }; +} + +fn assert_core_opening_claim_evals_match( + stage: &str, + proof: &CoreProofForChecks, + evals: impl IntoIterator, + expected: impl IntoIterator, +) { + let evals = evals.into_iter().collect::>(); + let mut matched_claims = 0usize; + + for (name, opening_id) in expected { + let Some((_, core_claim)) = proof.opening_claims.0.get(&opening_id) else { + continue; + }; + let Some(value) = evals.get(name) else { + panic!("{stage} proof missing expected opening eval {name}"); + }; + matched_claims += 1; + assert_eq!( + *value, + Fr::from(*core_claim), + "{stage} opening claim mismatch for {name}", + ); + } + assert!( + matched_claims > 0, + "{stage} opening claim check matched no core public claims" + ); +} + +/// Assert Stage 2 opening-claim evals against jolt-core public proof claims. +pub(crate) fn assert_core_stage2_opening_claims_match_bolt( + proof: &CoreProofForChecks, + artifacts: &Stage2ExecutionArtifacts, +) { + let expected = expected_openings! { + v "stage2.product_virtual.uniskip.eval.UnivariateSkip" V::UnivariateSkip => S::SpartanProductVirtualization; + v "stage2.ram_read_write.eval.RamVal" V::RamVal => S::RamReadWriteChecking; + v "stage2.ram_read_write.eval.RamRa" V::RamRa => S::RamReadWriteChecking; + c "stage2.ram_read_write.eval.RamInc" C::RamInc => S::RamReadWriteChecking; + v "stage2.product_virtual.remainder.eval.LeftInstructionInput" V::LeftInstructionInput => S::SpartanProductVirtualization; + v "stage2.product_virtual.remainder.eval.RightInstructionInput" V::RightInstructionInput => S::SpartanProductVirtualization; + v "stage2.product_virtual.remainder.eval.OpFlagJump" V::OpFlags(CircuitFlags::Jump) => S::SpartanProductVirtualization; + v "stage2.product_virtual.remainder.eval.OpFlagWriteLookupOutputToRD" V::OpFlags(CircuitFlags::WriteLookupOutputToRD) => S::SpartanProductVirtualization; + v "stage2.product_virtual.remainder.eval.LookupOutput" V::LookupOutput => S::SpartanProductVirtualization; + v "stage2.product_virtual.remainder.eval.InstructionFlagBranch" V::InstructionFlags(InstructionFlags::Branch) => S::SpartanProductVirtualization; + v "stage2.product_virtual.remainder.eval.NextIsNoop" V::NextIsNoop => S::SpartanProductVirtualization; + v "stage2.product_virtual.remainder.eval.OpFlagVirtualInstruction" V::OpFlags(CircuitFlags::VirtualInstruction) => S::SpartanProductVirtualization; + v "stage2.instruction_lookup.claim_reduction.eval.LookupOutput" V::LookupOutput => S::InstructionClaimReduction; + v "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand" V::LeftLookupOperand => S::InstructionClaimReduction; + v "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand" V::RightLookupOperand => S::InstructionClaimReduction; + v "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput" V::LeftInstructionInput => S::InstructionClaimReduction; + v "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput" V::RightInstructionInput => S::InstructionClaimReduction; + v "stage2.ram_raf.eval.RamRa" V::RamRa => S::RamRafEvaluation; + v "stage2.ram_output.eval.RamValFinal" V::RamValFinal => S::RamOutputCheck; + }; + assert_core_opening_claim_evals_match( + "Stage 2", + proof, + artifacts + .sumchecks + .iter() + .flat_map(|output| output.evals.iter().map(|eval| (eval.name, eval.value))), + expected, + ); +} + +/// Assert Stage 3 opening-claim evals against jolt-core public proof claims. +pub(crate) fn assert_core_stage3_opening_claims_match_bolt( + proof: &CoreProofForChecks, + artifacts: &Stage3ExecutionArtifacts, +) { + let expected = expected_openings! { + v "stage3.spartan_shift.eval.UnexpandedPC" V::UnexpandedPC => S::SpartanShift; + v "stage3.spartan_shift.eval.PC" V::PC => S::SpartanShift; + v "stage3.spartan_shift.eval.OpFlagVirtualInstruction" V::OpFlags(CircuitFlags::VirtualInstruction) => S::SpartanShift; + v "stage3.spartan_shift.eval.OpFlagIsFirstInSequence" V::OpFlags(CircuitFlags::IsFirstInSequence) => S::SpartanShift; + v "stage3.spartan_shift.eval.InstructionFlagIsNoop" V::InstructionFlags(InstructionFlags::IsNoop) => S::SpartanShift; + v "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value" V::InstructionFlags(InstructionFlags::LeftOperandIsRs1Value) => S::InstructionInputVirtualization; + v "stage3.instruction_input.eval.Rs1Value" V::Rs1Value => S::InstructionInputVirtualization; + v "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC" V::InstructionFlags(InstructionFlags::LeftOperandIsPC) => S::InstructionInputVirtualization; + v "stage3.instruction_input.eval.UnexpandedPC" V::UnexpandedPC => S::InstructionInputVirtualization; + v "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value" V::InstructionFlags(InstructionFlags::RightOperandIsRs2Value) => S::InstructionInputVirtualization; + v "stage3.instruction_input.eval.Rs2Value" V::Rs2Value => S::InstructionInputVirtualization; + v "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm" V::InstructionFlags(InstructionFlags::RightOperandIsImm) => S::InstructionInputVirtualization; + v "stage3.instruction_input.eval.Imm" V::Imm => S::InstructionInputVirtualization; + v "stage3.registers_claim_reduction.eval.RdWriteValue" V::RdWriteValue => S::RegistersClaimReduction; + v "stage3.registers_claim_reduction.eval.Rs1Value" V::Rs1Value => S::RegistersClaimReduction; + v "stage3.registers_claim_reduction.eval.Rs2Value" V::Rs2Value => S::RegistersClaimReduction; + }; + assert_core_opening_claim_evals_match( + "Stage 3", + proof, + artifacts + .sumchecks + .iter() + .flat_map(|output| output.evals.iter().map(|eval| (eval.name, eval.value))), + expected, + ); +} + +fn stage6_oracle_index(oracle: &'static str, prefix: &'static str) -> Option { + oracle.strip_prefix(prefix)?.parse().ok() +} + +fn stage6_committed_polynomial(oracle: &'static str) -> Option { + if let Some(index) = stage6_oracle_index(oracle, "InstructionRa_") { + return Some(CommittedPolynomial::InstructionRa(index)); + } + if let Some(index) = stage6_oracle_index(oracle, "BytecodeRa_") { + return Some(CommittedPolynomial::BytecodeRa(index)); + } + if let Some(index) = stage6_oracle_index(oracle, "RamRa_") { + return Some(CommittedPolynomial::RamRa(index)); + } + match oracle { + "RamInc" => Some(CommittedPolynomial::RamInc), + "RdInc" => Some(CommittedPolynomial::RdInc), + _ => None, + } +} + +fn stage6_opening_claim_id(claim: &generated_stage6::Stage6OpeningClaimPlan) -> Option { + if claim + .symbol + .starts_with("stage6.bytecode_read_raf.opening.") + { + return stage6_committed_polynomial(claim.oracle) + .map(|polynomial| OpeningId::committed(polynomial, SumcheckId::BytecodeReadRaf)); + } + if claim.symbol.starts_with("stage6.booleanity.opening.") { + return stage6_committed_polynomial(claim.oracle) + .map(|polynomial| OpeningId::committed(polynomial, SumcheckId::Booleanity)); + } + if claim.symbol == "stage6.hamming_booleanity.opening.HammingWeight" { + return Some(OpeningId::virt( + VirtualPolynomial::RamHammingWeight, + SumcheckId::RamHammingBooleanity, + )); + } + if claim.symbol.starts_with("stage6.ram_ra_virtual.opening.") { + return stage6_committed_polynomial(claim.oracle) + .map(|polynomial| OpeningId::committed(polynomial, SumcheckId::RamRaVirtualization)); + } + if claim + .symbol + .starts_with("stage6.instruction_ra_virtual.opening.") + { + return stage6_committed_polynomial(claim.oracle).map(|polynomial| { + OpeningId::committed(polynomial, SumcheckId::InstructionRaVirtualization) + }); + } + match claim.symbol { + "stage6.inc_claim_reduction.opening.RamInc" => Some(OpeningId::committed( + CommittedPolynomial::RamInc, + SumcheckId::IncClaimReduction, + )), + "stage6.inc_claim_reduction.opening.RdInc" => Some(OpeningId::committed( + CommittedPolynomial::RdInc, + SumcheckId::IncClaimReduction, + )), + _ => None, + } +} + +/// Assert Stage 6 opening-claim evals against jolt-core public proof claims. +pub(crate) fn assert_core_stage6_opening_claims_match_bolt( + proof: &CoreProofForChecks, + stage6_proof: &JoltStageProof, +) { + let evals = stage6_proof + .sumchecks + .iter() + .flat_map(|output| output.evals.iter()) + .map(|eval| (eval.name, eval.value)) + .collect::>(); + + for claim in generated_stage6::STAGE6_OPENING_CLAIMS { + let Some(opening_id) = stage6_opening_claim_id(claim) else { + panic!( + "Stage 6 opening claim has no core mapping: {}", + claim.symbol + ); + }; + let Some(value) = evals.get(claim.eval_source) else { + panic!( + "Stage 6 proof missing eval {} for opening claim {}", + claim.eval_source, claim.symbol + ); + }; + let Some((_, core_claim)) = proof.opening_claims.0.get(&opening_id) else { + panic!("Stage 6 core opening claim missing for {}", claim.symbol); + }; + assert_eq!( + *value, + Fr::from(*core_claim), + "Stage 6 opening claim mismatch for {}", + claim.symbol, + ); + } + assert!( + !generated_stage6::STAGE6_OPENING_CLAIMS.is_empty(), + "Stage 6 opening claim check was empty" + ); +} + +/// Assert Stage 1 uni-skip proof coefficients match jolt-core. +pub fn assert_core_stage1_uniskip_proof_matches_bolt( + proof: &CoreProofForChecks, + output: &Stage1SumcheckOutput, +) { + assert_core_uniskip_coefficients_match( + "Stage 1", + &proof.stage1_uni_skip_first_round_proof, + &output.proof.round_polynomials, + ); +} + +/// Assert Stage 2 uni-skip proof coefficients match jolt-core. +pub fn assert_core_stage2_uniskip_proof_matches_bolt( + proof: &CoreProofForChecks, + output: &jolt_kernels::stage2::Stage2SumcheckOutput, +) { + assert_core_uniskip_coefficients_match( + "Stage 2", + &proof.stage2_uni_skip_first_round_proof, + &output.proof.round_polynomials, + ); +} + +/// Assert Stage 2 compressed round polynomials match jolt-core. +pub(crate) fn assert_core_stage2_sumcheck_proof_matches_bolt( + proof: &CoreProofForChecks, + output: &jolt_kernels::stage2::Stage2SumcheckOutput, +) { + assert_core_compressed_sumcheck_match!("Stage 2", &proof.stage2_sumcheck_proof, output); +} + +/// Assert Stage 3 compressed round polynomials match jolt-core. +pub(crate) fn assert_core_stage3_sumcheck_proof_matches_bolt( + proof: &CoreProofForChecks, + output: &jolt_kernels::stage3::Stage3SumcheckOutput, +) { + assert_core_compressed_sumcheck_match!("Stage 3", &proof.stage3_sumcheck_proof, output); +} + +/// Assert Stage 6 compressed round polynomials match jolt-core. +pub(crate) fn assert_core_stage6_sumcheck_proof_matches_bolt( + proof: &CoreProofForChecks, + output: &JoltSumcheckOutput, +) { + assert_core_compressed_sumcheck_match!("Stage 6", &proof.stage6_sumcheck_proof, output); +} + +pub(crate) fn assert_canonical_stage_artifacts_match( + stage: &str, + expected: StageArtifacts, + actual: StageArtifacts, +) { + assert_eq!(expected, actual, "{stage} artifact mismatch"); +} + +pub(crate) fn assert_equivalence_run_artifacts_match( + label: &str, + expected: &EquivalenceRun, + actual: &EquivalenceRun, +) { + assert_eq!( + expected.commitments, actual.commitments, + "{label} commitment trace mismatch" + ); + assert_eq!(expected.stages, actual.stages, "{label} stage mismatch"); + assert_eq!( + expected.opening_claims, actual.opening_claims, + "{label} opening-claim mismatch" + ); + assert_eq!( + expected.verifier_result, actual.verifier_result, + "{label} verifier result mismatch" + ); +} + +macro_rules! define_stage_artifacts_match { + ($fn_name:ident, $stage:literal, $expected_ty:ty, $actual_ty:ty, $expected_adapter:path, $actual_adapter:path) => { + pub(crate) fn $fn_name(expected: &$expected_ty, actual: &$actual_ty) { + assert_canonical_stage_artifacts_match( + $stage, + $expected_adapter(expected), + $actual_adapter(actual), + ); + } + }; +} + +define_stage_artifacts_match!( + assert_stage5_artifacts_match, + "Stage 5", + JoltStageProof, + JoltStageProof, + canonical_generated_stage5_proof, + canonical_generated_stage5_proof +); +define_stage_artifacts_match!( + assert_stage6_artifacts_match, + "Stage 6", + JoltStageProof, + JoltStageExecutionArtifacts, + canonical_generated_stage6_proof, + canonical_generated_stage6_execution_artifacts +); +define_stage_artifacts_match!( + assert_stage7_artifacts_match, + "Stage 7", + JoltStageProof, + JoltStageExecutionArtifacts, + canonical_generated_stage7_proof, + canonical_generated_stage7_execution_artifacts +); diff --git a/crates/jolt-equivalence/src/commitment_oracle.rs b/crates/jolt-equivalence/src/commitment_oracle.rs new file mode 100644 index 0000000000..33f50f0554 --- /dev/null +++ b/crates/jolt-equivalence/src/commitment_oracle.rs @@ -0,0 +1,419 @@ +//! Commitment-phase oracle runner for Bolt/Jolt equivalence checks. +//! +//! This module owns the thin commitment bridge used by tests: run the +//! generated real-trace commitment phase, keep a small synthetic transcript +//! bridge for CPU-plan ordering tests, and expose transcript traces in the +//! canonical equivalence shape. + +#![expect( + clippy::expect_used, + clippy::panic, + reason = "commitment oracle gates should fail fast on malformed generated artifacts" +)] + +use std::borrow::Cow; + +use ark_serialize::CanonicalSerialize; +use bolt::protocols::jolt::CommitmentCpuProgram; +use common::jolt_device::JoltDevice; +use jolt_dory::{DoryCommitment, DoryProverSetup, DoryScheme}; +use jolt_field::Fr; +use jolt_prover::stages::commitment as generated_commitment; +use jolt_transcript::{Label, LabelWithCount, Transcript, U64Word}; +use jolt_verifier::stages::commitment as generated_verifier_commitment; +use jolt_witness::{ + commitment_trace_sources, optional_oracle_data, CommitmentTraceSources, CycleInput, +}; + +use crate::artifacts::{ + ArtifactSource, CommitmentArtifact, CommitmentTrace, EquivalenceRun, TranscriptTrace, +}; +use crate::checkpoint::{CheckpointTranscript, TranscriptEvent}; +use crate::core_conversion::{commitment_to_ark, CoreCommitment}; +use crate::plan_adapters::{ + leak_generated_commitment_prover_program, leak_generated_commitment_verifier_program, +}; + +const TRANSCRIPT_LABEL: &[u8] = b"Jolt"; + +pub type BoltTranscript = CheckpointTranscript>; + +#[derive(Clone, Debug)] +pub struct CommitmentRecord { + pub artifact: String, +} + +#[derive(Clone, Debug)] +pub struct BoltCommitmentTrace { + pub commitments: Vec>, + pub records: Vec, + pub log: Vec, +} + +impl BoltCommitmentTrace { + pub fn commitment_trace(&self) -> CommitmentTrace { + CommitmentTrace { + commitments: self + .records + .iter() + .zip(&self.commitments) + .map(|(record, commitment)| CommitmentArtifact { + label: record.artifact.clone(), + artifact: record.artifact.clone(), + bytes: commitment.as_ref().map(bolt_commitment_bytes), + }) + .collect(), + } + } + + pub fn committed_prefix(&self, len: usize) -> CommitmentTrace { + CommitmentTrace { + commitments: self + .records + .iter() + .zip(&self.commitments) + .filter_map(|(record, commitment)| { + commitment.as_ref().map(|commitment| CommitmentArtifact { + label: record.artifact.clone(), + artifact: record.artifact.clone(), + bytes: Some(bolt_commitment_bytes(commitment)), + }) + }) + .take(len) + .collect(), + } + } + + pub fn equivalence_run(&self, source: ArtifactSource) -> EquivalenceRun { + let mut run = EquivalenceRun::new(source); + run.commitments = self.commitment_trace(); + run.transcript = TranscriptTrace { + events: self.log.clone(), + }; + run + } +} + +pub fn core_commitment_trace(commitments: &[CoreCommitment], artifact: &str) -> CommitmentTrace { + CommitmentTrace { + commitments: commitments + .iter() + .map(|commitment| CommitmentArtifact { + label: artifact.to_owned(), + artifact: artifact.to_owned(), + bytes: Some(core_commitment_bytes(commitment)), + }) + .collect(), + } +} + +macro_rules! generated_commitment_trace_fns { + ($trace_fn:ident, $artifacts_to_trace_fn:ident, $artifacts:ty) => { + pub(crate) fn $trace_fn(artifacts: &$artifacts) -> CommitmentTrace { + CommitmentTrace { + commitments: artifacts + .records + .iter() + .zip(&artifacts.commitments) + .map(|(record, commitment)| CommitmentArtifact { + label: record.artifact.to_owned(), + artifact: record.artifact.to_owned(), + bytes: commitment.as_ref().map(bolt_commitment_bytes), + }) + .collect(), + } + } + + fn $artifacts_to_trace_fn( + artifacts: &$artifacts, + log: Vec, + ) -> BoltCommitmentTrace { + BoltCommitmentTrace { + commitments: artifacts.commitments.clone(), + records: artifacts + .records + .iter() + .map(|record| CommitmentRecord { + artifact: record.artifact.to_owned(), + }) + .collect(), + log, + } + } + }; +} + +generated_commitment_trace_fns!( + generated_commitment_trace, + generated_prover_commitment_artifacts_to_trace, + generated_commitment::CommitmentArtifacts +); +generated_commitment_trace_fns!( + generated_verifier_commitment_trace, + generated_verifier_commitment_artifacts_to_trace, + generated_verifier_commitment::CommitmentArtifacts +); + +fn bolt_commitment_bytes(commitment: &DoryCommitment) -> Vec { + core_commitment_bytes(&commitment_to_ark(commitment)) +} + +fn core_commitment_bytes(commitment: &CoreCommitment) -> Vec { + let mut bytes = Vec::new(); + commitment + .serialize_uncompressed(&mut bytes) + .expect("commitment serialization"); + bytes +} + +pub trait BoltPreambleSource { + fn program_io(&self) -> &JoltDevice; + fn preprocessing_digest(&self) -> [u8; 32]; + fn ram_k(&self) -> u64; + fn trace_length(&self) -> u64; + fn entry_address(&self) -> u64; + fn ram_rw_phase1_num_rounds(&self) -> u64; + fn ram_rw_phase2_num_rounds(&self) -> u64; + fn registers_rw_phase1_num_rounds(&self) -> u64; + fn registers_rw_phase2_num_rounds(&self) -> u64; + fn log_k_chunk(&self) -> u64; + fn lookups_ra_virtual_log_k_chunk(&self) -> u64; + fn dory_layout(&self) -> u64; +} + +fn append_bolt_preamble(transcript: &mut T, preamble: &P) +where + T: Transcript, + P: BoltPreambleSource, +{ + let program_io = preamble.program_io(); + let preprocessing_digest = preamble.preprocessing_digest(); + append_bytes(transcript, b"preprocessing_digest", &preprocessing_digest); + append_u64( + transcript, + b"max_input_size", + program_io.memory_layout.max_input_size, + ); + append_u64( + transcript, + b"max_output_size", + program_io.memory_layout.max_output_size, + ); + append_u64(transcript, b"heap_size", program_io.memory_layout.heap_size); + append_bytes(transcript, b"inputs", &program_io.inputs); + append_bytes(transcript, b"outputs", &program_io.outputs); + append_u64(transcript, b"panic", program_io.panic as u64); + append_u64(transcript, b"ram_K", preamble.ram_k()); + append_u64(transcript, b"trace_length", preamble.trace_length()); + append_u64(transcript, b"entry_address", preamble.entry_address()); + append_u64( + transcript, + b"ram_rw_phase1_num_rounds", + preamble.ram_rw_phase1_num_rounds(), + ); + append_u64( + transcript, + b"ram_rw_phase2_num_rounds", + preamble.ram_rw_phase2_num_rounds(), + ); + append_u64( + transcript, + b"registers_rw_phase1_num_rounds", + preamble.registers_rw_phase1_num_rounds(), + ); + append_u64( + transcript, + b"registers_rw_phase2_num_rounds", + preamble.registers_rw_phase2_num_rounds(), + ); + append_u64(transcript, b"log_k_chunk", preamble.log_k_chunk()); + append_u64( + transcript, + b"lookups_ra_virtual_log_k_chunk", + preamble.lookups_ra_virtual_log_k_chunk(), + ); + append_u64(transcript, b"dory_layout", preamble.dory_layout()); +} + +pub(crate) fn transcript_with_bolt_preamble

(preamble: &P) -> BoltTranscript +where + P: BoltPreambleSource, +{ + let mut transcript = BoltTranscript::new(TRANSCRIPT_LABEL); + append_bolt_preamble(&mut transcript, preamble); + transcript +} + +pub fn transcript_with_bolt_commitment_trace

( + preamble: &P, + trace: &BoltCommitmentTrace, +) -> BoltTranscript +where + P: BoltPreambleSource, +{ + let mut transcript = transcript_with_bolt_preamble(preamble); + append_bolt_commitment_trace(&mut transcript, trace); + transcript +} + +fn append_u64(transcript: &mut T, label: &'static [u8], value: u64) +where + T: Transcript, +{ + transcript.append(&Label(label)); + transcript.append(&U64Word(value)); +} + +fn append_bytes(transcript: &mut T, label: &'static [u8], bytes: &[u8]) +where + T: Transcript, +{ + transcript.append(&LabelWithCount(label, bytes.len() as u64)); + transcript.append_bytes(bytes); +} + +#[derive(Clone, Debug)] +pub(crate) struct GeneratedCommitmentInputStorage { + sources: CommitmentTraceSources, +} + +impl GeneratedCommitmentInputStorage { + pub(crate) fn from_cycles(cycle_inputs: &[CycleInput]) -> Self { + Self { + sources: commitment_trace_sources(cycle_inputs), + } + } + + pub(crate) fn sparse_inputs(&self) -> generated_commitment::SparseCommitmentInputs<'_> { + generated_commitment::SparseCommitmentInputs::new( + generated_commitment::CommitmentOracleInputs::from_trace_sources( + &self.sources, + None, + None, + ), + ) + } +} + +pub fn run_generated_bolt_commitment_pair_with_cpu_programs( + prover_program: &CommitmentCpuProgram, + verifier_program: &CommitmentCpuProgram, + setup: &DoryProverSetup, + cycle_inputs: &[CycleInput], +) -> (BoltCommitmentTrace, BoltCommitmentTrace) { + run_generated_bolt_commitment_pair_with_cycles( + leak_generated_commitment_prover_program(prover_program), + leak_generated_commitment_verifier_program(verifier_program), + setup, + cycle_inputs, + ) +} + +fn run_generated_synthetic_bolt_commitment_pair( + prover_program: &'static generated_commitment::CommitmentProverProgramPlan, + verifier_program: &'static generated_verifier_commitment::CommitmentVerifierProgramPlan, +) -> (BoltCommitmentTrace, BoltCommitmentTrace) { + let setup = DoryScheme::setup_prover(max_generated_num_vars(prover_program)); + let mut inputs = SyntheticCommitmentInputs; + let prover_trace = run_generated_bolt_commitment_prover(prover_program, &setup, &mut inputs); + let verifier_trace = + run_generated_bolt_commitment_verifier(verifier_program, &prover_trace.commitments); + (prover_trace, verifier_trace) +} + +pub fn run_generated_synthetic_bolt_commitment_pair_with_cpu_programs( + prover_program: &CommitmentCpuProgram, + verifier_program: &CommitmentCpuProgram, +) -> (BoltCommitmentTrace, BoltCommitmentTrace) { + run_generated_synthetic_bolt_commitment_pair( + leak_generated_commitment_prover_program(prover_program), + leak_generated_commitment_verifier_program(verifier_program), + ) +} + +pub(crate) fn run_generated_bolt_commitment_pair_with_cycles( + prover_program: &'static generated_commitment::CommitmentProverProgramPlan, + verifier_program: &'static generated_verifier_commitment::CommitmentVerifierProgramPlan, + setup: &DoryProverSetup, + cycle_inputs: &[CycleInput], +) -> (BoltCommitmentTrace, BoltCommitmentTrace) { + let storage = GeneratedCommitmentInputStorage::from_cycles(cycle_inputs); + let mut inputs = storage.sparse_inputs(); + let prover_trace = run_generated_bolt_commitment_prover(prover_program, setup, &mut inputs); + let verifier_trace = + run_generated_bolt_commitment_verifier(verifier_program, &prover_trace.commitments); + (prover_trace, verifier_trace) +} + +fn run_generated_bolt_commitment_prover( + program: &'static generated_commitment::CommitmentProverProgramPlan, + setup: &DoryProverSetup, + inputs: &mut I, +) -> BoltCommitmentTrace +where + I: generated_commitment::CommitmentInputProvider, +{ + let mut transcript = BoltTranscript::new(TRANSCRIPT_LABEL); + let artifacts = generated_commitment::prove_commitment_phase_with_program( + program, + inputs, + setup, + &mut transcript, + ) + .expect("generated Bolt commitment prover succeeds"); + generated_prover_commitment_artifacts_to_trace(&artifacts, transcript.log().to_vec()) +} + +fn run_generated_bolt_commitment_verifier( + program: &'static generated_verifier_commitment::CommitmentVerifierProgramPlan, + proof_commitments: &[Option], +) -> BoltCommitmentTrace { + let mut transcript = BoltTranscript::new(TRANSCRIPT_LABEL); + let artifacts = generated_verifier_commitment::verify_commitment_phase_with_program( + program, + proof_commitments, + &mut transcript, + ) + .expect("generated Bolt commitment verifier succeeds"); + generated_verifier_commitment_artifacts_to_trace(&artifacts, transcript.log().to_vec()) +} + +fn append_bolt_commitment_trace(transcript: &mut T, trace: &BoltCommitmentTrace) +where + T: Transcript, +{ + for event in &trace.log { + match event { + TranscriptEvent::Append { bytes, .. } => transcript.append_bytes(bytes), + TranscriptEvent::Squeeze { .. } => { + panic!("commitment transcript trace unexpectedly contains a squeeze") + } + } + } +} + +struct SyntheticCommitmentInputs; + +impl generated_commitment::CommitmentInputProvider for SyntheticCommitmentInputs { + fn materialize(&mut self, _oracle: &'static str) -> Option> { + None + } + + fn materialize_with_num_vars( + &mut self, + oracle: &'static str, + num_vars: usize, + ) -> Option> { + optional_oracle_data(oracle, num_vars).map(Cow::Owned) + } +} + +fn max_generated_num_vars(program: &generated_commitment::CommitmentProverProgramPlan) -> usize { + program + .batch_plans + .iter() + .map(|plan| plan.num_vars) + .chain(program.optional_plans.iter().map(|plan| plan.num_vars)) + .max() + .unwrap_or(0) +} diff --git a/crates/jolt-equivalence/src/core_conversion.rs b/crates/jolt-equivalence/src/core_conversion.rs new file mode 100644 index 0000000000..4daf754203 --- /dev/null +++ b/crates/jolt-equivalence/src/core_conversion.rs @@ -0,0 +1,372 @@ +//! Core proof conversion helpers for Bolt equivalence tests. +//! +//! The helpers here normalize field elements, commitments, and sumcheck +//! proofs into jolt-core shapes so Bolt equivalence tests can compare +//! generated artifacts against the reference implementation. +//! +#![expect( + clippy::expect_used, + clippy::panic, + clippy::too_many_arguments, + reason = "core proof adapters are test-oracle bridges and fail fast on malformed artifacts" +)] + +use ark_bn254::Fr as ArkFr; +use ark_serialize::CanonicalSerialize; +use bolt::protocols::jolt::TranscriptStep; +use jolt_core::curve::Bn254Curve; +use jolt_core::poly::commitment::commitment_scheme::CommitmentScheme; +use jolt_core::poly::commitment::dory::DoryCommitmentScheme; +use jolt_core::poly::unipoly::UniPoly; +use jolt_core::subprotocols::sumcheck::SumcheckInstanceProof; +use jolt_core::subprotocols::univariate_skip::{ + UniSkipFirstRoundProof, UniSkipFirstRoundProofVariant, +}; +use jolt_core::transcripts::{Blake2bTranscript, Transcript as _}; +use jolt_core::zkvm::proof_serialization::{Claims, JoltProof as CoreJoltProof}; +use jolt_field::Fr as NewFr; +use jolt_poly::UnivariatePoly; +use jolt_verifier::JoltStageProof; + +use crate::TranscriptEvent; + +pub type CoreCommitment = ::Commitment; +pub type CoreProofForConversion = + CoreJoltProof; + +/// Convert `NewFr` → `ArkFr`. Both are the BN254 scalar field; this is +/// a representation-only cast. +pub(crate) fn to_ark(f: NewFr) -> ArkFr { + f.into() +} + +/// Convert a modular `UnivariatePoly` into a jolt-core +/// `CompressedUniPoly`. `CompressedUniPoly` stores `[c0, c2, c3, +/// ...]` (linear term `c1` omitted; verifier reconstructs from `s(0) + s(1)`). +fn to_compressed_uni_poly( + poly: &UnivariatePoly, +) -> jolt_core::poly::unipoly::CompressedUniPoly { + let coeffs = poly.coefficients(); + assert!( + coeffs.len() >= 2, + "round poly must have at least 2 coefficients" + ); + let mut compressed = Vec::with_capacity(coeffs.len() - 1); + compressed.push(to_ark(coeffs[0])); + for c in &coeffs[2..] { + compressed.push(to_ark(*c)); + } + jolt_core::poly::unipoly::CompressedUniPoly { + coeffs_except_linear_term: compressed, + } +} + +fn to_core_sumcheck_proof( + round_polys: &[UnivariatePoly], +) -> SumcheckInstanceProof { + let compressed: Vec<_> = round_polys.iter().map(to_compressed_uni_poly).collect(); + SumcheckInstanceProof::Clear(jolt_core::subprotocols::sumcheck::ClearSumcheckProof::new( + compressed, + )) +} + +fn to_core_uniskip_proof_from_round_polys( + round_polys: &[UnivariatePoly], +) -> UniSkipFirstRoundProofVariant { + assert_eq!(round_polys.len(), 1); + let coefficients = round_polys[0] + .coefficients() + .iter() + .copied() + .map(to_ark) + .collect(); + UniSkipFirstRoundProofVariant::Standard(UniSkipFirstRoundProof::new(UniPoly::from_coeff( + coefficients, + ))) +} + +fn to_core_uniskip_proof( + output: &jolt_kernels::stage1::Stage1SumcheckOutput, +) -> UniSkipFirstRoundProofVariant { + to_core_uniskip_proof_from_round_polys(&output.proof.round_polynomials) +} + +fn to_core_stage2_uniskip_proof( + output: &jolt_kernels::stage2::Stage2SumcheckOutput, +) -> UniSkipFirstRoundProofVariant { + to_core_uniskip_proof_from_round_polys(&output.proof.round_polynomials) +} + +/// Clone a jolt-core proof without depending on private verifier state. +pub(crate) fn clone_core_proof(proof: &CoreProofForConversion) -> CoreProofForConversion { + CoreJoltProof { + commitments: proof.commitments.clone(), + stage1_uni_skip_first_round_proof: proof.stage1_uni_skip_first_round_proof.clone(), + stage1_sumcheck_proof: proof.stage1_sumcheck_proof.clone(), + stage2_uni_skip_first_round_proof: proof.stage2_uni_skip_first_round_proof.clone(), + stage2_sumcheck_proof: proof.stage2_sumcheck_proof.clone(), + stage3_sumcheck_proof: proof.stage3_sumcheck_proof.clone(), + stage4_sumcheck_proof: proof.stage4_sumcheck_proof.clone(), + stage5_sumcheck_proof: proof.stage5_sumcheck_proof.clone(), + stage6_sumcheck_proof: proof.stage6_sumcheck_proof.clone(), + stage7_sumcheck_proof: proof.stage7_sumcheck_proof.clone(), + joint_opening_proof: proof.joint_opening_proof.clone(), + untrusted_advice_commitment: proof.untrusted_advice_commitment, + opening_claims: Claims(proof.opening_claims.0.clone()), + trace_length: proof.trace_length, + ram_K: proof.ram_K, + rw_config: proof.rw_config.clone(), + one_hot_config: proof.one_hot_config.clone(), + dory_layout: proof.dory_layout, + } +} + +pub(crate) fn core_proof_with_bolt_stage1( + base: &CoreProofForConversion, + artifacts: &jolt_kernels::stage1::Stage1ExecutionArtifacts, +) -> CoreProofForConversion { + let mut proof = clone_core_proof(base); + proof.stage1_uni_skip_first_round_proof = to_core_uniskip_proof(&artifacts.sumchecks[0]); + proof.stage1_sumcheck_proof = + to_core_sumcheck_proof(&artifacts.sumchecks[1].proof.round_polynomials); + proof +} + +pub(crate) fn core_proof_with_bolt_stage2( + base: &CoreProofForConversion, + stage1_artifacts: &jolt_kernels::stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &jolt_kernels::stage2::Stage2ExecutionArtifacts, +) -> CoreProofForConversion { + let mut proof = core_proof_with_bolt_stage1(base, stage1_artifacts); + proof.stage2_uni_skip_first_round_proof = + to_core_stage2_uniskip_proof(&stage2_artifacts.sumchecks[0]); + proof.stage2_sumcheck_proof = + to_core_sumcheck_proof(&stage2_artifacts.sumchecks[1].proof.round_polynomials); + proof +} + +pub(crate) fn core_proof_with_bolt_stage3( + base: &CoreProofForConversion, + stage1_artifacts: &jolt_kernels::stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &jolt_kernels::stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &jolt_kernels::stage3::Stage3ExecutionArtifacts, +) -> CoreProofForConversion { + let mut proof = core_proof_with_bolt_stage2(base, stage1_artifacts, stage2_artifacts); + proof.stage3_sumcheck_proof = + to_core_sumcheck_proof(&stage3_artifacts.sumchecks[0].proof.round_polynomials); + proof +} + +pub(crate) fn core_proof_with_bolt_stage4( + base: &CoreProofForConversion, + stage1_artifacts: &jolt_kernels::stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &jolt_kernels::stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &jolt_kernels::stage3::Stage3ExecutionArtifacts, + stage4_artifacts: &jolt_kernels::stage4::Stage4ExecutionArtifacts, +) -> CoreProofForConversion { + let mut proof = + core_proof_with_bolt_stage3(base, stage1_artifacts, stage2_artifacts, stage3_artifacts); + proof.stage4_sumcheck_proof = + to_core_sumcheck_proof(&stage4_artifacts.sumchecks[0].proof.round_polynomials); + proof +} + +pub(crate) fn core_proof_with_bolt_stage5( + base: &CoreProofForConversion, + stage1_artifacts: &jolt_kernels::stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &jolt_kernels::stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &jolt_kernels::stage3::Stage3ExecutionArtifacts, + stage4_artifacts: &jolt_kernels::stage4::Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, +) -> CoreProofForConversion { + let mut proof = core_proof_with_bolt_stage4( + base, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + ); + proof.stage5_sumcheck_proof = + to_core_sumcheck_proof(&stage5_proof.sumchecks[0].proof.round_polynomials); + proof +} + +pub(crate) fn core_proof_with_bolt_stage6( + base: &CoreProofForConversion, + stage1_artifacts: &jolt_kernels::stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &jolt_kernels::stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &jolt_kernels::stage3::Stage3ExecutionArtifacts, + stage4_artifacts: &jolt_kernels::stage4::Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, + stage6_proof: &JoltStageProof, +) -> CoreProofForConversion { + let mut proof = core_proof_with_bolt_stage5( + base, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + ); + proof.stage6_sumcheck_proof = + to_core_sumcheck_proof(&stage6_proof.sumchecks[0].proof.round_polynomials); + proof +} + +pub(crate) fn core_proof_with_bolt_stage7( + base: &CoreProofForConversion, + stage1_artifacts: &jolt_kernels::stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &jolt_kernels::stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &jolt_kernels::stage3::Stage3ExecutionArtifacts, + stage4_artifacts: &jolt_kernels::stage4::Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, + stage6_proof: &JoltStageProof, + stage7_proof: &JoltStageProof, +) -> CoreProofForConversion { + let mut proof = core_proof_with_bolt_stage6( + base, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + stage6_proof, + ); + proof.stage7_sumcheck_proof = + to_core_sumcheck_proof(&stage7_proof.sumchecks[0].proof.round_polynomials); + proof +} + +pub(crate) fn core_proof_with_bolt_evaluation( + base: &CoreProofForConversion, + evaluation: &jolt_verifier::JoltEvaluationProof, +) -> CoreProofForConversion { + let mut proof = clone_core_proof(base); + proof.joint_opening_proof = evaluation.joint_opening_proof.0.clone(); + proof +} + +pub(crate) fn core_proof_with_full_bolt( + base: &CoreProofForConversion, + proof: &jolt_verifier::JoltProof, + artifacts: &jolt_prover::JoltProverArtifacts, +) -> CoreProofForConversion { + let stage5_proof = jolt_prover::stage5_proof(&artifacts.stage5); + let stage6_proof = jolt_prover::stage6_proof(&artifacts.stage6); + let stage7_proof = jolt_prover::stage7_proof(&artifacts.stage7); + let mut core_proof = core_proof_with_bolt_stage7( + base, + &artifacts.stage1_outer, + &artifacts.stage2, + &artifacts.stage3, + &artifacts.stage4, + &stage5_proof, + &stage6_proof, + &stage7_proof, + ); + core_proof.commitments = proof + .commitments + .iter() + .filter_map(|commitment| commitment.as_ref().map(commitment_to_ark)) + .collect(); + core_proof.joint_opening_proof = proof + .evaluation + .as_ref() + .expect("Bolt proof includes evaluation proof") + .joint_opening_proof + .0 + .clone(); + core_proof +} + +pub fn core_commitment_log<'a>( + records: impl IntoIterator)>, + transcript_steps: &[TranscriptStep], +) -> Vec { + let records = records.into_iter().collect::>(); + let mut transcript = Blake2bTranscript::new(b"Jolt"); + let mut events = Vec::new(); + + for step in transcript_steps { + let mut appended = false; + for (artifact, commitment) in &records { + if *artifact != step.source { + continue; + } + if let Some(commitment) = commitment { + let core_commitment = commitment_to_ark(commitment); + let label = static_transcript_label(&step.label); + for bytes in core_append_serializable_bytes(label, &core_commitment) { + transcript.raw_append_bytes(&bytes); + events.push(TranscriptEvent::Append { + bytes, + state_after: transcript.state, + }); + } + appended = true; + } + } + assert!(step.optional || appended, "missing core transcript source"); + } + + events +} + +pub fn core_commitments_transcript_log( + commitments: &[CoreCommitment], + transcript_steps: &[TranscriptStep], +) -> Vec { + let mut transcript = Blake2bTranscript::new(b"Jolt"); + let mut events = Vec::new(); + + for step in transcript_steps { + if step.source != "jolt.main_witness_commitments" { + assert!(step.optional, "unexpected non-main commitment source"); + continue; + } + for commitment in commitments { + let label = static_transcript_label(&step.label); + for bytes in core_append_serializable_bytes(label, commitment) { + transcript.raw_append_bytes(&bytes); + events.push(TranscriptEvent::Append { + bytes, + state_after: transcript.state, + }); + } + } + } + + events +} + +fn core_append_serializable_bytes( + label: &'static [u8], + data: &T, +) -> Vec> { + let mut payload = Vec::new(); + data.serialize_uncompressed(&mut payload) + .expect("core commitment serialization"); + let mut header = [0u8; 32]; + header[..label.len()].copy_from_slice(label); + let len = (payload.len() as u64).to_be_bytes(); + header[24..].copy_from_slice(&len); + payload.reverse(); + vec![header.to_vec(), payload] +} + +fn static_transcript_label(label: &str) -> &'static [u8] { + match label { + "commitment" => b"commitment", + "untrusted_advice" => b"untrusted_advice", + "trusted_advice" => b"trusted_advice", + _ => panic!("unsupported transcript label `{label}`"), + } +} + +/// Convert a jolt-dory `DoryCommitment` to jolt-core's `ArkGT` shape. +/// +/// Both are repr(transparent) wrappers over the same `Fq12` type. +pub(crate) fn commitment_to_ark(c: &jolt_dory::types::DoryCommitment) -> CoreCommitment { + // SAFETY: Bn254GT and ArkGT are both repr(transparent) over Fq12. + unsafe { std::mem::transmute_copy(&c.0) } +} diff --git a/crates/jolt-equivalence/src/core_oracle.rs b/crates/jolt-equivalence/src/core_oracle.rs new file mode 100644 index 0000000000..d04a91392e --- /dev/null +++ b/crates/jolt-equivalence/src/core_oracle.rs @@ -0,0 +1,518 @@ +//! Core-side public oracle fixture and acceptance checks for equivalence gates. +//! +//! This module runs jolt-core as the temporary reference oracle and exposes the +//! public data needed by the Bolt equivalence tests. It does not patch expected +//! values to compensate for Bolt mismatches. + +#![expect( + clippy::expect_used, + clippy::too_many_arguments, + reason = "oracle fixtures should fail fast when reference setup is malformed" +)] + +use std::time::Instant; + +use ark_serialize::{CanonicalSerialize, Compress}; +use bolt::protocols::jolt::JoltProtocolParams; +use common::constants::{RAM_START_ADDRESS, XLEN}; +use common::jolt_device::JoltDevice; +use jolt_core::curve::Bn254Curve; +use jolt_core::host; +use jolt_core::poly::commitment::commitment_scheme::CommitmentScheme as CoreCommitmentScheme; +use jolt_core::poly::commitment::dory::DoryCommitmentScheme; +use jolt_core::transcripts::Blake2bTranscript as CoreBlake2bTranscript; +use jolt_core::zkvm::instruction::InstructionLookup; +use jolt_core::zkvm::lookup_table::LookupTables as CoreLookupTables; +use jolt_core::zkvm::proof_serialization::JoltProof as CoreJoltProof; +use jolt_core::zkvm::prover::{JoltCpuProver, JoltProverPreprocessing}; +use jolt_core::zkvm::ram::remap_address; +use jolt_core::zkvm::verifier::{JoltSharedPreprocessing, JoltVerifier, JoltVerifierPreprocessing}; +use jolt_dory::DoryProverSetup; +use jolt_field::Fr; +use jolt_kernels::stage1::{ + Stage1ExecutionArtifacts, Stage1OuterR1csData, Stage1OuterRv64Data, Stage1Rv64Cycle, +}; +use jolt_kernels::stage2::{ + Stage2ExecutionArtifacts, Stage2InstructionLookupCycle, Stage2ProductVirtualCycle, + Stage2RamAccess, Stage2RamData, Stage2RamOutputLayout, +}; +use jolt_kernels::stage3::{Stage3Cycle, Stage3ExecutionArtifacts}; +use jolt_kernels::stage4::{Stage4ExecutionArtifacts, Stage4RegisterAccess}; +use jolt_kernels::stage6::Stage6WitnessParams; +use jolt_kernels::trace::{ + stage1_rv64_cycles, stage2_instruction_lookup_cycles, stage2_product_virtual_cycles, + stage2_ram_accesses, stage3_cycles, stage4_register_accesses, stage5_lookup_trace, + stage6_bytecode_entries, +}; +use jolt_r1cs::{constraints::rv64, R1csKey}; +use jolt_trace::{extract_trace, BytecodePreprocessing}; +use jolt_verifier::JoltStageProof; +use jolt_witness::{CycleInput, Stage6BytecodeEntry}; +use strum::EnumCount; + +use crate::checks::{ + assert_core_stage1_uniskip_proof_matches_bolt, assert_core_stage2_opening_claims_match_bolt, + assert_core_stage2_sumcheck_proof_matches_bolt, assert_core_stage3_opening_claims_match_bolt, + assert_core_stage3_sumcheck_proof_matches_bolt, assert_core_stage6_opening_claims_match_bolt, + assert_core_stage6_sumcheck_proof_matches_bolt, +}; +use crate::commitment_oracle::BoltPreambleSource; +use crate::core_conversion::{ + clone_core_proof, core_proof_with_bolt_evaluation, core_proof_with_bolt_stage1, + core_proof_with_bolt_stage2, core_proof_with_bolt_stage3, core_proof_with_bolt_stage4, + core_proof_with_bolt_stage5, core_proof_with_bolt_stage6, core_proof_with_bolt_stage7, + core_proof_with_full_bolt, +}; +use jolt_profiling::{observed_span_names_with_prefix, time_it, PeakRssSampler, PerfMetrics}; + +pub type CoreFr = ark_bn254::Fr; +pub type CoreCommitment = ::Commitment; +type CoreProver<'a> = + JoltCpuProver<'a, CoreFr, Bn254Curve, DoryCommitmentScheme, CoreBlake2bTranscript>; +pub type CoreProof = CoreJoltProof; +type CoreVerifier<'a> = + JoltVerifier<'a, CoreFr, Bn254Curve, DoryCommitmentScheme, CoreBlake2bTranscript>; +pub type CoreVerifierPreprocessing = + JoltVerifierPreprocessing; + +fn assert_core_verifies_proof( + fixture: &CoreMuldivCommitmentFixture, + proof: CoreProof, + message: &str, +) { + CoreVerifier::new( + fixture.verifier_preprocessing, + proof, + fixture.io.clone(), + None, + None, + ) + .expect("construct core verifier") + .verify() + .expect(message); +} + +pub struct CoreMuldivCommitmentFixture { + pub params: JoltProtocolParams, + pub(crate) core_metrics: PerfMetrics, + pub pcs_setup: DoryProverSetup, + pub proof: CoreProof, + pub(crate) verifier_preprocessing: &'static CoreVerifierPreprocessing, + pub(crate) io: JoltDevice, + pub(crate) entry_address: u64, + pub cycle_inputs: Vec, + pub(crate) r1cs_witness: Vec, + pub(crate) rv64_cycles: Vec, + pub product_virtual_cycles: Vec, + pub instruction_lookup_cycles: Vec, + pub(crate) stage3_cycles: Vec, + pub(crate) stage4_register_accesses: Vec, + pub(crate) stage5_lookup_indices: Vec, + pub(crate) stage5_lookup_table_indices: Vec>, + pub(crate) stage5_is_interleaved_operands: Vec, + pub(crate) stage6_bytecode_entries: Vec>, + pub(crate) stage6_entry_bytecode_index: usize, + pub(crate) stage6_num_lookup_tables: usize, + pub(crate) ram_accesses: Vec, + pub(crate) initial_ram_state: Vec, + pub(crate) final_ram_state: Vec, + pub(crate) ram_start_address: u64, + pub(crate) ram_output_layout: Stage2RamOutputLayout, + pub commitments: Vec, +} + +impl CoreMuldivCommitmentFixture { + pub fn stage2_ram_data(&self) -> Stage2RamData<'_> { + Stage2RamData { + log_k: self.params.log_k_ram, + start_address: self.ram_start_address, + initial_ram: &self.initial_ram_state, + final_ram: &self.final_ram_state, + accesses: &self.ram_accesses, + output_layout: Some(self.ram_output_layout), + } + } + + pub fn r1cs_key(&self) -> R1csKey { + R1csKey::new(rv64::rv64_constraints::(), self.proof.trace_length) + } + + pub fn stage1_outer_rv64_data<'a>( + &'a self, + r1cs_key: &'a R1csKey, + ) -> Stage1OuterRv64Data<'a> { + Stage1OuterRv64Data::new(r1cs_key, &self.r1cs_witness, &self.rv64_cycles) + .expect("valid RV64-backed stage1 data") + } + + pub fn stage1_outer_r1cs_data<'a>( + &'a self, + r1cs_key: &'a R1csKey, + ) -> Stage1OuterR1csData<'a, Fr> { + Stage1OuterR1csData::new(r1cs_key, &self.r1cs_witness).expect("valid R1CS witness shape") + } + + pub(crate) fn stage6_witness_params(&self) -> Stage6WitnessParams { + Stage6WitnessParams { + trace_len: self.proof.trace_length, + log_k_chunk: self.params.log_k_chunk, + log_k_bytecode: self.params.log_k_bytecode, + log_k_ram: self.params.log_k_ram, + lookups_ra_virtual_log_k_chunk: self.params.lookups_ra_virtual_log_k_chunk, + instruction_d: self.params.instruction_d, + instruction_ra_virtual_d: self.params.instruction_ra_virtual_d, + bytecode_d: self.params.bytecode_d, + ram_d: self.params.ram_d, + } + } +} + +impl BoltPreambleSource for CoreMuldivCommitmentFixture { + fn program_io(&self) -> &JoltDevice { + &self.io + } + + fn preprocessing_digest(&self) -> [u8; 32] { + self.verifier_preprocessing.shared.digest() + } + + fn ram_k(&self) -> u64 { + self.proof.ram_K as u64 + } + + fn trace_length(&self) -> u64 { + self.proof.trace_length as u64 + } + + fn entry_address(&self) -> u64 { + self.entry_address + } + + fn ram_rw_phase1_num_rounds(&self) -> u64 { + self.proof.rw_config.ram_rw_phase1_num_rounds as u64 + } + + fn ram_rw_phase2_num_rounds(&self) -> u64 { + self.proof.rw_config.ram_rw_phase2_num_rounds as u64 + } + + fn registers_rw_phase1_num_rounds(&self) -> u64 { + self.proof.rw_config.registers_rw_phase1_num_rounds as u64 + } + + fn registers_rw_phase2_num_rounds(&self) -> u64 { + self.proof.rw_config.registers_rw_phase2_num_rounds as u64 + } + + fn log_k_chunk(&self) -> u64 { + self.proof.one_hot_config.log_k_chunk as u64 + } + + fn lookups_ra_virtual_log_k_chunk(&self) -> u64 { + self.proof.one_hot_config.lookups_ra_virtual_log_k_chunk as u64 + } + + fn dory_layout(&self) -> u64 { + self.proof.dory_layout as u64 + } +} + +pub fn core_muldiv_commitment_fixture() -> CoreMuldivCommitmentFixture { + let inputs = postcard::to_stdvec(&[9u32, 5u32, 3u32]).expect("muldiv inputs"); + core_guest_commitment_fixture("muldiv-guest", inputs, 1 << 16) +} + +pub fn core_sha2_chain_commitment_fixture(log_t: usize) -> CoreMuldivCommitmentFixture { + let target_cycles = ((1usize << log_t) as f64 * 0.9) as usize; + let num_iters = std::cmp::max(1, (target_cycles as f64 / 3396.0) as u32); + let mut inputs = Vec::new(); + inputs.extend(postcard::to_stdvec(&[5u8; 32]).expect("sha2-chain input")); + inputs.extend(postcard::to_stdvec(&num_iters).expect("sha2-chain iterations")); + core_guest_commitment_fixture("sha2-chain-guest", inputs, 1usize << log_t) +} + +fn core_guest_commitment_fixture( + guest_name: &str, + inputs: Vec, + max_trace_length: usize, +) -> CoreMuldivCommitmentFixture { + let setup_start = Instant::now(); + let _core_setup_span = tracing::info_span!("core.setup").entered(); + + let mut core_program = host::Program::new(guest_name); + let (core_bytecode, init_memory_state, _, entry_address) = core_program.decode(); + let core_bytecode_for_bolt = core_bytecode.clone(); + let (_, trace, _, host_io_device) = core_program.trace(&inputs, &[], &[]); + let shared_preprocessing = JoltSharedPreprocessing::new( + core_bytecode, + host_io_device.memory_layout.clone(), + init_memory_state, + max_trace_length, + entry_address, + ) + .expect("shared preprocessing"); + let prover_preprocessing = JoltProverPreprocessing::new(shared_preprocessing); + let elf_contents = core_program.get_elf_contents().expect("guest elf"); + let prover: CoreProver<'_> = CoreProver::gen_from_elf( + &prover_preprocessing, + &elf_contents, + &inputs, + &[], + &[], + None, + None, + None, + ); + let setup_ms = setup_start.elapsed().as_secs_f64() * 1_000.0; + drop(_core_setup_span); + let io = prover.program_io.clone(); + let initial_ram_state = prover.initial_ram_state.clone(); + let final_ram_state = prover.final_ram_state.clone(); + let core_rss_sampler = PeakRssSampler::start().expect("start core RSS sampler"); + let _core_prove_span = tracing::info_span!("core.prove").entered(); + let (prove_ms, (proof, _debug)) = time_it(|| prover.prove()); + drop(_core_prove_span); + let peak_rss_mb = core_rss_sampler.finish(); + let proof_bytes = proof.serialized_size(Compress::Yes) as u64; + let verifier_preprocessing: &'static _ = Box::leak(Box::new(JoltVerifierPreprocessing::from( + &prover_preprocessing, + ))); + let core_verifier = CoreVerifier::new( + verifier_preprocessing, + clone_core_proof(&proof), + io.clone(), + None, + None, + ) + .expect("construct core verifier"); + let _core_verify_span = tracing::info_span!("core.verify").entered(); + let (verify_ms, verify_result) = time_it(|| core_verifier.verify()); + drop(_core_verify_span); + verify_result.expect("core verifier accepts proof"); + let core_metrics = PerfMetrics { + setup_ms: Some(setup_ms), + prove_ms: Some(prove_ms), + verify_ms: Some(verify_ms), + proof_bytes: Some(proof_bytes), + peak_rss_mb: Some(peak_rss_mb), + span_names: observed_span_names_with_prefix("core."), + }; + + let mut padded_trace = trace.clone(); + padded_trace.resize(proof.trace_length, jolt_trace::Cycle::NoOp); + let product_virtual_cycles = stage2_product_virtual_cycles(&padded_trace, proof.trace_length); + let instruction_lookup_cycles = + stage2_instruction_lookup_cycles(&padded_trace, proof.trace_length); + let ram_accesses = stage2_ram_accesses(&padded_trace, proof.trace_length, |address| { + remap_address(address, &host_io_device.memory_layout).map(|address| address as usize) + }); + let ram_start_address = host_io_device.memory_layout.get_lowest_address(); + let ram_output_layout = Stage2RamOutputLayout { + io_start: remap_address( + host_io_device.memory_layout.input_start, + &host_io_device.memory_layout, + ) + .expect("input start remaps") as usize, + io_end: remap_address(RAM_START_ADDRESS, &host_io_device.memory_layout) + .expect("RAM start remaps") as usize, + }; + let bytecode = BytecodePreprocessing::preprocess(core_bytecode_for_bolt, entry_address); + let stage6_bytecode_entries = stage6_bytecode_entries(&bytecode, |instruction| { + InstructionLookup::::lookup_table(instruction) + .map(|table| CoreLookupTables::::enum_index(&table)) + }); + let stage6_entry_bytecode_index = bytecode.entry_bytecode_index(); + let stage6_num_lookup_tables = CoreLookupTables::::COUNT; + let r1cs_key = R1csKey::new(rv64::rv64_constraints::(), proof.trace_length); + let (cycle_inputs, r1cs_witness, _) = extract_trace::<_, Fr>( + &trace, + proof.trace_length, + &bytecode, + &host_io_device.memory_layout, + r1cs_key.num_vars_padded, + ); + let rv64_cycles = stage1_rv64_cycles(&trace, proof.trace_length, &bytecode); + let stage3_cycles = stage3_cycles(&trace, proof.trace_length, &bytecode); + let stage4_register_accesses = stage4_register_accesses(&trace, proof.trace_length); + let stage5_trace = stage5_lookup_trace(&padded_trace, proof.trace_length, |cycle| { + InstructionLookup::::lookup_table(cycle) + .map(|table| CoreLookupTables::::enum_index(&table)) + }); + let log_t = proof.trace_length.trailing_zeros() as usize; + let log_k_bytecode = prover_preprocessing + .shared + .bytecode + .code_size + .trailing_zeros() as usize; + let log_k_ram = proof.ram_K.trailing_zeros() as usize; + let params = JoltProtocolParams::new(log_t, log_k_bytecode, log_k_ram); + + let commitments = proof.commitments.clone(); + + CoreMuldivCommitmentFixture { + params, + core_metrics, + pcs_setup: DoryProverSetup(prover_preprocessing.generators.clone()), + proof, + verifier_preprocessing, + io, + entry_address, + cycle_inputs, + r1cs_witness, + rv64_cycles, + product_virtual_cycles, + instruction_lookup_cycles, + stage3_cycles, + stage4_register_accesses, + stage5_lookup_indices: stage5_trace.lookup_indices, + stage5_lookup_table_indices: stage5_trace.lookup_table_indices, + stage5_is_interleaved_operands: stage5_trace.is_interleaved_operands, + stage6_bytecode_entries, + stage6_entry_bytecode_index, + stage6_num_lookup_tables, + ram_accesses, + initial_ram_state, + final_ram_state, + ram_start_address, + ram_output_layout, + commitments, + } +} + +pub fn assert_core_accepts_bolt_stage1( + fixture: &CoreMuldivCommitmentFixture, + artifacts: &Stage1ExecutionArtifacts, +) { + assert_core_stage1_uniskip_proof_matches_bolt(&fixture.proof, &artifacts.sumchecks[0]); + let proof = core_proof_with_bolt_stage1(&fixture.proof, artifacts); + assert_core_verifies_proof(fixture, proof, "jolt-core accepts Bolt Stage 1 proof"); +} + +pub fn assert_core_accepts_bolt_stage2( + fixture: &CoreMuldivCommitmentFixture, + stage1_artifacts: &Stage1ExecutionArtifacts, + stage2_artifacts: &Stage2ExecutionArtifacts, +) { + let proof = core_proof_with_bolt_stage2(&fixture.proof, stage1_artifacts, stage2_artifacts); + assert_core_stage2_sumcheck_proof_matches_bolt(&fixture.proof, &stage2_artifacts.sumchecks[1]); + assert_core_stage2_opening_claims_match_bolt(&fixture.proof, stage2_artifacts); + + assert_core_verifies_proof(fixture, proof, "core accepts Bolt Stage 2"); +} + +pub(crate) fn assert_core_accepts_bolt_stage3( + fixture: &CoreMuldivCommitmentFixture, + stage1_artifacts: &Stage1ExecutionArtifacts, + stage2_artifacts: &Stage2ExecutionArtifacts, + stage3_artifacts: &Stage3ExecutionArtifacts, +) { + let proof = core_proof_with_bolt_stage3( + &fixture.proof, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + ); + assert_core_stage3_sumcheck_proof_matches_bolt(&fixture.proof, &stage3_artifacts.sumchecks[0]); + assert_core_stage3_opening_claims_match_bolt(&fixture.proof, stage3_artifacts); + + assert_core_verifies_proof(fixture, proof, "core accepts Bolt Stage 3"); +} + +pub(crate) fn assert_core_accepts_bolt_stage4( + fixture: &CoreMuldivCommitmentFixture, + stage1_artifacts: &Stage1ExecutionArtifacts, + stage2_artifacts: &Stage2ExecutionArtifacts, + stage3_artifacts: &Stage3ExecutionArtifacts, + stage4_artifacts: &Stage4ExecutionArtifacts, +) { + let proof = core_proof_with_bolt_stage4( + &fixture.proof, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + ); + assert_core_verifies_proof(fixture, proof, "core accepts Bolt Stage 4"); +} + +pub(crate) fn assert_core_accepts_bolt_stage5( + fixture: &CoreMuldivCommitmentFixture, + stage1_artifacts: &Stage1ExecutionArtifacts, + stage2_artifacts: &Stage2ExecutionArtifacts, + stage3_artifacts: &Stage3ExecutionArtifacts, + stage4_artifacts: &Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, +) { + let proof = core_proof_with_bolt_stage5( + &fixture.proof, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + ); + assert_core_verifies_proof(fixture, proof, "core accepts Bolt Stage 5"); +} + +pub(crate) fn assert_core_accepts_bolt_stage6( + fixture: &CoreMuldivCommitmentFixture, + stage1_artifacts: &Stage1ExecutionArtifacts, + stage2_artifacts: &Stage2ExecutionArtifacts, + stage3_artifacts: &Stage3ExecutionArtifacts, + stage4_artifacts: &Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, + stage6_proof: &JoltStageProof, +) { + let proof = core_proof_with_bolt_stage6( + &fixture.proof, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + stage6_proof, + ); + assert_core_stage6_sumcheck_proof_matches_bolt(&fixture.proof, &stage6_proof.sumchecks[0]); + assert_core_stage6_opening_claims_match_bolt(&fixture.proof, stage6_proof); + assert_core_verifies_proof(fixture, proof, "core accepts Bolt Stage 6"); +} + +pub(crate) fn assert_core_accepts_bolt_stage7( + fixture: &CoreMuldivCommitmentFixture, + stage1_artifacts: &Stage1ExecutionArtifacts, + stage2_artifacts: &Stage2ExecutionArtifacts, + stage3_artifacts: &Stage3ExecutionArtifacts, + stage4_artifacts: &Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, + stage6_proof: &JoltStageProof, + stage7_proof: &JoltStageProof, +) { + let proof = core_proof_with_bolt_stage7( + &fixture.proof, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + stage6_proof, + stage7_proof, + ); + assert_core_verifies_proof(fixture, proof, "core accepts Bolt Stage 7"); +} + +pub(crate) fn assert_core_accepts_bolt_evaluation_proof( + fixture: &CoreMuldivCommitmentFixture, + evaluation: &jolt_verifier::JoltEvaluationProof, +) { + let proof = core_proof_with_bolt_evaluation(&fixture.proof, evaluation); + assert_core_verifies_proof(fixture, proof, "core accepts Bolt evaluation proof"); +} + +pub(crate) fn assert_core_accepts_full_bolt_proof( + fixture: &CoreMuldivCommitmentFixture, + proof: &jolt_verifier::JoltProof, + artifacts: &jolt_prover::JoltProverArtifacts, +) { + let core_proof = core_proof_with_full_bolt(&fixture.proof, proof, artifacts); + assert_core_verifies_proof(fixture, core_proof, "core accepts full Bolt proof"); +} diff --git a/crates/jolt-equivalence/src/lib.rs b/crates/jolt-equivalence/src/lib.rs new file mode 100644 index 0000000000..0624fcf4ce --- /dev/null +++ b/crates/jolt-equivalence/src/lib.rs @@ -0,0 +1,28 @@ +//! Cross-system equivalence testing between jolt-core and Bolt-generated Jolt artifacts. +//! +//! The crate exposes representation-only artifact snapshots plus focused +//! oracle, checker, tamper, and perf helpers. Protocol semantics should live in +//! Bolt, generated artifacts, kernels, or `jolt-witness`, not in this crate. + +mod adapters; +mod artifacts; +pub mod bolt_oracle; +pub mod bolt_programs; +pub mod checkpoint; +pub mod checks; +pub mod commitment_oracle; +pub mod core_conversion; +pub mod core_oracle; +pub mod perf; +pub mod plan_adapters; +pub mod tamper; + +pub use artifacts::{ + ArtifactSource, CommitmentArtifact, CommitmentTrace, EquivalenceRun, NamedScalar, + OpeningBatchArtifacts, OpeningClaim, OpeningClaimKind, OpeningClaims, StageArtifacts, + SumcheckArtifacts, TranscriptTrace, VerifierResult, +}; +pub use checkpoint::{ + assert_transcripts_match, find_divergence, CheckpointTranscript, TranscriptDivergence, + TranscriptEvent, +}; diff --git a/crates/jolt-equivalence/src/perf.rs b/crates/jolt-equivalence/src/perf.rs new file mode 100644 index 0000000000..7a594ebe61 --- /dev/null +++ b/crates/jolt-equivalence/src/perf.rs @@ -0,0 +1,128 @@ +//! Perf-oracle helpers for equivalence tests. + +#![expect( + clippy::panic, + clippy::print_stdout, + reason = "perf oracle gates should fail fast and print successful ratio reports" +)] + +use std::sync::Once; + +use jolt_profiling::{ + observed_span_names_with_prefix, setup_tracing, CoreVsBoltGateReport, PerfGateThresholds, + PerfMetrics, TracingFormat, +}; +use serde::Serialize; + +static PERF_TRACING: Once = Once::new(); + +pub const CORE_VS_BOLT_PERF_THRESHOLDS: PerfGateThresholds = PerfGateThresholds { + max_setup_ratio: None, + max_prove_ratio: Some(100.0), + max_verify_ratio: Some(100.0), + max_proof_size_ratio: Some(100.0), + max_peak_rss_ratio: Some(100.0), +}; + +/// Installs perf span observation, and enables a Chrome/Perfetto trace when +/// `JOLT_BOLT_PERF_TRACE` is set. +pub fn maybe_setup_perf_trace(trace_name: &'static str) { + PERF_TRACING.call_once(|| { + let formats: &[TracingFormat] = if std::env::var_os("JOLT_BOLT_PERF_TRACE").is_some() { + &[TracingFormat::Chrome] + } else { + &[] + }; + let _ = Box::leak(Box::new(setup_tracing(formats, trace_name))); + }); +} + +pub fn generated_bolt_perf_metrics( + setup_ms: f64, + prove_ms: f64, + verify_ms: f64, + proof: &jolt_verifier::JoltProof, + peak_rss_mb: u64, +) -> PerfMetrics { + PerfMetrics { + setup_ms: Some(setup_ms), + prove_ms: Some(prove_ms), + verify_ms: Some(verify_ms), + proof_bytes: Some(generated_jolt_proof_bytes(proof)), + peak_rss_mb: Some(peak_rss_mb), + span_names: observed_span_names_with_prefix("bolt."), + } +} + +pub fn print_core_vs_bolt_perf_summary( + core: &PerfMetrics, + bolt: &PerfMetrics, + report: &CoreVsBoltGateReport, +) { + println!("core-vs-Bolt perf summary:"); + print_f64_metric("setup_ms", core.setup_ms, bolt.setup_ms); + print_f64_metric("prove_ms", core.prove_ms, bolt.prove_ms); + print_f64_metric("verify_ms", core.verify_ms, bolt.verify_ms); + print_u64_metric("proof_bytes", core.proof_bytes, bolt.proof_bytes); + print_u64_metric("peak_rss_mb", core.peak_rss_mb, bolt.peak_rss_mb); + for ratio in &report.ratios { + println!( + " gated {}: core={:.3}, bolt={:.3}, ratio={:.3}x, threshold={:.3}x", + ratio.metric, ratio.reference, ratio.candidate, ratio.ratio, ratio.threshold + ); + } +} + +fn print_f64_metric(label: &str, core: Option, bolt: Option) { + match (core, bolt) { + (Some(core), Some(bolt)) if core > 0.0 => { + println!( + " {label}: core={core:.3}, bolt={bolt:.3}, ratio={:.3}x", + bolt / core + ); + } + (Some(core), Some(bolt)) => { + println!(" {label}: core={core:.3}, bolt={bolt:.3}, ratio=n/a"); + } + _ => println!(" {label}: unavailable"), + } +} + +fn print_u64_metric(label: &str, core: Option, bolt: Option) { + match (core, bolt) { + (Some(core), Some(bolt)) if core > 0 => { + println!( + " {label}: core={core}, bolt={bolt}, ratio={:.3}x", + bolt as f64 / core as f64 + ); + } + (Some(core), Some(bolt)) => { + println!(" {label}: core={core}, bolt={bolt}, ratio=n/a"); + } + _ => println!(" {label}: unavailable"), + } +} + +fn generated_jolt_proof_bytes(proof: &jolt_verifier::JoltProof) -> u64 { + [ + serialized_component_size(&proof.commitments, "jolt proof commitments"), + serialized_component_size(&proof.stage1_outer, "jolt proof stage1"), + serialized_component_size(&proof.stage2, "jolt proof stage2"), + serialized_component_size(&proof.stage3, "jolt proof stage3"), + serialized_component_size(&proof.stage4, "jolt proof stage4"), + serialized_component_size(&proof.stage5, "jolt proof stage5"), + serialized_component_size(&proof.stage6, "jolt proof stage6"), + serialized_component_size(&proof.stage7, "jolt proof stage7"), + proof.evaluation.as_ref().map_or(0, |evaluation| { + serialized_component_size(&evaluation.joint_opening_proof, "jolt proof evaluation") + }), + ] + .into_iter() + .sum() +} + +fn serialized_component_size(component: &T, label: &str) -> u64 { + postcard::to_stdvec(component) + .unwrap_or_else(|error| panic!("serialize {label}: {error}")) + .len() as u64 +} diff --git a/crates/jolt-equivalence/src/plan_adapters.rs b/crates/jolt-equivalence/src/plan_adapters.rs new file mode 100644 index 0000000000..b577452cbf --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters.rs @@ -0,0 +1,755 @@ +//! Static plan adapters from Bolt compiler plans to generated/kernel plans. +//! +//! These are compatibility shims for the equivalence oracle. They translate +//! Bolt's owned compiler plans into the currently generated static plan shape +//! expected by jolt-kernels, jolt-prover, and jolt-verifier. + +macro_rules! stage_list { + (kernel, $values:expr) => { + super::leak_str_slice($values) + }; + (generated, $values:expr) => { + super::leak_symbol_list($values) + }; +} + +macro_rules! stage_field_expr { + (kernel, $module:ident, $field_expr:ident, $plan:ident) => { + $module::$field_expr { + symbol: super::leak_str(&$plan.symbol), + kind: super::leak_str(&$plan.kind), + formula: super::leak_str(&$plan.formula), + operand_names: super::leak_str_slice(&$plan.operand_names), + operands: super::leak_str_slice(&$plan.operands), + } + }; + (generated, $module:ident, $field_expr:ident, $plan:ident) => { + $module::$field_expr { + symbol: super::leak_str(&$plan.symbol), + kind: super::leak_str(&$plan.kind), + formula: super::leak_str(&$plan.formula), + operands: super::leak_symbol_list(&$plan.operands), + } + }; +} + +macro_rules! stage_claim { + ($mode:ident, $module:ident, $claim:ident, $plan:ident) => { + $module::$claim { + symbol: super::leak_str(&$plan.symbol), + stage: super::leak_str(&$plan.stage), + domain: super::leak_str(&$plan.domain), + num_rounds: $plan.num_rounds, + degree: $plan.degree, + claim: super::leak_str(&$plan.claim), + kernel: $plan.kernel.as_deref().map(super::leak_str), + relation: $plan.relation.as_deref().map(super::leak_str), + claim_value: super::leak_str(&$plan.claim_value), + input_openings: stage_list!($mode, &$plan.input_openings), + } + }; +} + +macro_rules! stage_driver { + ($module:ident, $driver:ident, $plan:ident) => { + $module::$driver { + symbol: super::leak_str(&$plan.symbol), + stage: super::leak_str(&$plan.stage), + proof_slot: super::leak_str(&$plan.proof_slot), + kernel: $plan.kernel.as_deref().map(super::leak_str), + relation: $plan.relation.as_deref().map(super::leak_str), + batch: super::leak_str(&$plan.batch), + policy: super::leak_str(&$plan.policy), + round_schedule: super::leak_usize_slice(&$plan.round_schedule), + claim_label: super::leak_str(&$plan.claim_label), + round_label: super::leak_str(&$plan.round_label), + num_rounds: $plan.num_rounds, + degree: $plan.degree, + } + }; +} + +macro_rules! define_stage_adapter_impl { + ( + $mode:ident, + $function:ident, + $compiler:ty, + $module:ident, + $program:ident, + $params:ident, + $step:ident, + $squeeze:ident, + $opening_input:ident, + $field_constant:ident, + $field_expr:ident, + $claim:ident, + $batch:ident, + $driver:ident, + $instance_result:ident, + $eval:ident, + $point_slice:ident, + $point_concat:ident, + $opening_claim:ident, + $opening_batch:ident + $(, role = $role_field:ident)? + $(, transcript_absorb_bytes = $absorb:ident)? + $(, kernels = $kernel:ident)? + $(, point_zeros = $point_zero:ident)? + $(, opening_equalities = $opening_equality:ident)? + ) => { + pub fn $function(program: &$compiler) -> &'static $module::$program { + Box::leak(Box::new($module::$program { + $( + $role_field: super::role_name(&program.role), + )? + params: $module::$params { + field: super::leak_str(&program.params.field), + pcs: super::leak_str(&program.params.pcs), + transcript: super::leak_str(&program.params.transcript), + }, + steps: super::leak_slice( + program + .steps + .iter() + .map(|plan| $module::$step { + kind: super::leak_str(&plan.kind), + symbol: super::leak_str(&plan.symbol), + }) + .collect(), + ), + transcript_squeezes: super::leak_slice( + program + .transcript_squeezes + .iter() + .map(|plan| $module::$squeeze { + symbol: super::leak_str(&plan.symbol), + label: super::leak_str(&plan.label), + kind: super::leak_str(&plan.kind), + count: plan.count, + }) + .collect(), + ), + $( + transcript_absorb_bytes: super::leak_slice( + program + .transcript_absorb_bytes + .iter() + .map(|plan| $module::$absorb { + symbol: super::leak_str(&plan.symbol), + label: super::leak_str(&plan.label), + payload: super::leak_str(&plan.payload), + }) + .collect(), + ), + )? + opening_inputs: super::leak_slice( + program + .opening_inputs + .iter() + .map(|plan| $module::$opening_input { + symbol: super::leak_str(&plan.symbol), + source_stage: super::leak_str(&plan.source_stage), + source_claim: super::leak_str(&plan.source_claim), + oracle: super::leak_str(&plan.oracle), + domain: super::leak_str(&plan.domain), + point_arity: plan.point_arity, + claim_kind: super::leak_str(&plan.claim_kind), + }) + .collect(), + ), + field_constants: super::leak_slice( + program + .field_constants + .iter() + .map(|plan| $module::$field_constant { + symbol: super::leak_str(&plan.symbol), + field: super::leak_str(&plan.field), + value: plan.value, + }) + .collect(), + ), + field_exprs: super::leak_slice( + program + .field_exprs + .iter() + .map(|plan| stage_field_expr!($mode, $module, $field_expr, plan)) + .collect(), + ), + $( + kernels: super::leak_slice( + program + .kernels + .iter() + .map(|plan| $module::$kernel { + symbol: super::leak_str(&plan.symbol), + relation: super::leak_str(&plan.relation), + kind: super::leak_str(&plan.kind), + backend: super::leak_str(&plan.backend), + abi: super::leak_str(&plan.abi), + }) + .collect(), + ), + )? + claims: super::leak_slice( + program + .claims + .iter() + .map(|plan| stage_claim!($mode, $module, $claim, plan)) + .collect(), + ), + batches: super::leak_slice( + program + .batches + .iter() + .map(|plan| $module::$batch { + symbol: super::leak_str(&plan.symbol), + stage: super::leak_str(&plan.stage), + proof_slot: super::leak_str(&plan.proof_slot), + policy: super::leak_str(&plan.policy), + count: plan.count, + ordered_claims: stage_list!($mode, &plan.ordered_claims), + claim_operands: stage_list!($mode, &plan.claim_operands), + claim_label: super::leak_str(&plan.claim_label), + round_label: super::leak_str(&plan.round_label), + round_schedule: super::leak_usize_slice(&plan.round_schedule), + }) + .collect(), + ), + drivers: super::leak_slice( + program + .drivers + .iter() + .map(|plan| stage_driver!($module, $driver, plan)) + .collect(), + ), + instance_results: super::leak_slice( + program + .instance_results + .iter() + .map(|plan| $module::$instance_result { + symbol: super::leak_str(&plan.symbol), + source: super::leak_str(&plan.source), + claim: super::leak_str(&plan.claim), + relation: super::leak_str(&plan.relation), + index: plan.index, + point_arity: plan.point_arity, + num_rounds: plan.num_rounds, + round_offset: plan.round_offset, + point_order: super::leak_str(&plan.point_order), + degree: plan.degree, + }) + .collect(), + ), + evals: super::leak_slice( + program + .evals + .iter() + .map(|plan| $module::$eval { + symbol: super::leak_str(&plan.symbol), + source: super::leak_str(&plan.source), + name: super::leak_str(&plan.name), + index: plan.index, + oracle: super::leak_str(&plan.oracle), + }) + .collect(), + ), + $( + point_zeros: super::leak_slice( + program + .point_zeros + .iter() + .map(|plan| $module::$point_zero { + symbol: super::leak_str(&plan.symbol), + field: super::leak_str(&plan.field), + arity: plan.arity, + }) + .collect(), + ), + )? + point_slices: super::leak_slice( + program + .point_slices + .iter() + .map(|plan| $module::$point_slice { + symbol: super::leak_str(&plan.symbol), + source: super::leak_str(&plan.source), + offset: plan.offset, + length: plan.length, + input: super::leak_str(&plan.input), + }) + .collect(), + ), + point_concats: super::leak_slice( + program + .point_concats + .iter() + .map(|plan| $module::$point_concat { + symbol: super::leak_str(&plan.symbol), + layout: super::leak_str(&plan.layout), + arity: plan.arity, + inputs: stage_list!($mode, &plan.inputs), + }) + .collect(), + ), + opening_claims: super::leak_slice( + program + .opening_claims + .iter() + .map(|plan| $module::$opening_claim { + symbol: super::leak_str(&plan.symbol), + oracle: super::leak_str(&plan.oracle), + domain: super::leak_str(&plan.domain), + point_arity: plan.point_arity, + claim_kind: super::leak_str(&plan.claim_kind), + point_source: super::leak_str(&plan.point_source), + eval_source: super::leak_str(&plan.eval_source), + }) + .collect(), + ), + $( + opening_equalities: super::leak_slice( + program + .opening_equalities + .iter() + .map(|plan| $module::$opening_equality { + symbol: super::leak_str(&plan.symbol), + mode: super::leak_str(&plan.mode), + lhs: super::leak_str(&plan.lhs), + rhs: super::leak_str(&plan.rhs), + }) + .collect(), + ), + )? + opening_batches: super::leak_slice( + program + .opening_batches + .iter() + .map(|plan| $module::$opening_batch { + symbol: super::leak_str(&plan.symbol), + stage: super::leak_str(&plan.stage), + proof_slot: super::leak_str(&plan.proof_slot), + policy: super::leak_str(&plan.policy), + count: plan.count, + ordered_claims: stage_list!($mode, &plan.ordered_claims), + claim_operands: stage_list!($mode, &plan.claim_operands), + }) + .collect(), + ), + })) + } + }; +} + +macro_rules! define_stage_adapter { + ( + $mode:ident, + $function:ident, + $compiler:ty, + $module:ident, + $program:ident, + $params:ident, + $step:ident, + $squeeze:ident, + $absorb:ident, + $opening_input:ident, + $field_constant:ident, + $field_expr:ident, + $kernel:ident, + $claim:ident, + $batch:ident, + $driver:ident, + $instance_result:ident, + $eval:ident, + $point_slice:ident, + $point_concat:ident, + $opening_claim:ident, + $opening_equality:ident, + $opening_batch:ident + $(, point_zero = $point_zero:ident)? + ) => { + define_stage_adapter_impl!( + $mode, + $function, + $compiler, + $module, + $program, + $params, + $step, + $squeeze, + $opening_input, + $field_constant, + $field_expr, + $claim, + $batch, + $driver, + $instance_result, + $eval, + $point_slice, + $point_concat, + $opening_claim, + $opening_batch, + role = role, + transcript_absorb_bytes = $absorb, + kernels = $kernel + $(, point_zeros = $point_zero)?, + opening_equalities = $opening_equality + ); + }; +} + +macro_rules! define_stage_adapter_no_absorb { + ( + $mode:ident, + $function:ident, + $compiler:ty, + $module:ident, + $program:ident, + $params:ident, + $step:ident, + $squeeze:ident, + $opening_input:ident, + $field_constant:ident, + $field_expr:ident, + $claim:ident, + $batch:ident, + $driver:ident, + $instance_result:ident, + $eval:ident, + $point_slice:ident, + $point_concat:ident, + $opening_claim:ident, + $opening_batch:ident + $(, kernels = $kernel:ident)? + $(, opening_equalities = $opening_equality:ident)? + ) => { + define_stage_adapter_impl!( + $mode, + $function, + $compiler, + $module, + $program, + $params, + $step, + $squeeze, + $opening_input, + $field_constant, + $field_expr, + $claim, + $batch, + $driver, + $instance_result, + $eval, + $point_slice, + $point_concat, + $opening_claim, + $opening_batch + $(, kernels = $kernel)? + $(, opening_equalities = $opening_equality)? + ); + }; +} + +macro_rules! define_stage1_adapter { + ( + $mode:ident, + $function:ident, + $compiler:ty, + $module:ident, + $program:ident, + $params:ident, + $squeeze:ident, + $claim:ident, + $batch:ident, + $driver:ident, + $instance_result:ident, + $eval:ident, + $opening_claim:ident, + $opening_batch:ident + $(, kernels = $kernel:ident)? + ) => { + pub fn $function(program: &$compiler) -> &'static $module::$program { + Box::leak(Box::new($module::$program { + params: $module::$params { + field: super::leak_str(&program.params.field), + pcs: super::leak_str(&program.params.pcs), + transcript: super::leak_str(&program.params.transcript), + }, + transcript_squeezes: super::leak_slice( + program + .transcript_squeezes + .iter() + .map(|plan| $module::$squeeze { + symbol: super::leak_str(&plan.symbol), + label: super::leak_str(&plan.label), + kind: super::leak_str(&plan.kind), + count: plan.count, + }) + .collect(), + ), + $( + kernels: super::leak_slice( + program + .kernels + .iter() + .map(|plan| $module::$kernel { + symbol: super::leak_str(&plan.symbol), + relation: super::leak_str(&plan.relation), + kind: super::leak_str(&plan.kind), + backend: super::leak_str(&plan.backend), + abi: super::leak_str(&plan.abi), + }) + .collect(), + ), + )? + claims: super::leak_slice( + program + .claims + .iter() + .map(|plan| stage_claim!($mode, $module, $claim, plan)) + .collect(), + ), + batches: super::leak_slice( + program + .batches + .iter() + .map(|plan| $module::$batch { + symbol: super::leak_str(&plan.symbol), + stage: super::leak_str(&plan.stage), + proof_slot: super::leak_str(&plan.proof_slot), + policy: super::leak_str(&plan.policy), + count: plan.count, + ordered_claims: stage_list!($mode, &plan.ordered_claims), + claim_operands: stage_list!($mode, &plan.claim_operands), + claim_label: super::leak_str(&plan.claim_label), + round_label: super::leak_str(&plan.round_label), + round_schedule: super::leak_usize_slice(&plan.round_schedule), + }) + .collect(), + ), + drivers: super::leak_slice( + program + .drivers + .iter() + .map(|plan| stage_driver!($module, $driver, plan)) + .collect(), + ), + instance_results: super::leak_slice( + program + .instance_results + .iter() + .map(|plan| $module::$instance_result { + symbol: super::leak_str(&plan.symbol), + source: super::leak_str(&plan.source), + claim: super::leak_str(&plan.claim), + relation: super::leak_str(&plan.relation), + index: plan.index, + point_arity: plan.point_arity, + num_rounds: plan.num_rounds, + round_offset: plan.round_offset, + point_order: super::leak_str(&plan.point_order), + degree: plan.degree, + }) + .collect(), + ), + evals: super::leak_slice( + program + .evals + .iter() + .map(|plan| $module::$eval { + symbol: super::leak_str(&plan.symbol), + source: super::leak_str(&plan.source), + name: super::leak_str(&plan.name), + index: plan.index, + oracle: super::leak_str(&plan.oracle), + }) + .collect(), + ), + opening_claims: super::leak_slice( + program + .opening_claims + .iter() + .map(|plan| $module::$opening_claim { + symbol: super::leak_str(&plan.symbol), + oracle: super::leak_str(&plan.oracle), + domain: super::leak_str(&plan.domain), + point_arity: plan.point_arity, + claim_kind: super::leak_str(&plan.claim_kind), + point_source: super::leak_str(&plan.point_source), + eval_source: super::leak_str(&plan.eval_source), + }) + .collect(), + ), + opening_batches: super::leak_slice( + program + .opening_batches + .iter() + .map(|plan| $module::$opening_batch { + symbol: super::leak_str(&plan.symbol), + stage: super::leak_str(&plan.stage), + proof_slot: super::leak_str(&plan.proof_slot), + policy: super::leak_str(&plan.policy), + count: plan.count, + ordered_claims: stage_list!($mode, &plan.ordered_claims), + claim_operands: stage_list!($mode, &plan.claim_operands), + }) + .collect(), + ), + })) + } + }; +} + +mod generated_commitment; +mod generated_stage1; +mod generated_stage2; +mod generated_stage3; +mod generated_stage4; +mod generated_stage5; +mod generated_stage6; +mod generated_stage7; +mod stage1; +mod stage2; +mod stage3; +mod stage4; +mod stage5; +mod stage6; +mod stage7; + +pub(crate) use generated_commitment::{ + leak_generated_commitment_prover_program, leak_generated_commitment_verifier_program, +}; +pub use generated_stage1::leak_generated_stage1_verifier_program; +pub use generated_stage2::leak_generated_stage2_verifier_program; +pub(crate) use generated_stage3::leak_generated_stage3_verifier_program; +pub(crate) use generated_stage4::leak_generated_stage4_verifier_program; +pub(crate) use generated_stage5::leak_generated_stage5_verifier_program; +pub(crate) use generated_stage6::leak_generated_stage6_verifier_program; +pub(crate) use generated_stage7::leak_generated_stage7_verifier_program; +pub use stage1::leak_stage1_program; +pub use stage2::leak_stage2_program; +pub(crate) use stage3::leak_stage3_program; +pub(crate) use stage4::leak_stage4_program; +pub(crate) use stage5::leak_stage5_program; +pub(crate) use stage6::leak_stage6_program; +pub(crate) use stage7::leak_stage7_program; + +use bolt::protocols::jolt::Stage8CpuProgram as CompilerStage8CpuProgram; +use bolt::Role; +use jolt_prover::stages::stage8 as generated_prover_stage8; +use jolt_verifier::stages::stage8 as generated_stage8; + +macro_rules! define_stage8_adapter { + ($function:ident, $module:ident) => { + pub(crate) fn $function( + program: &CompilerStage8CpuProgram, + ) -> &'static $module::Stage8EvaluationProgramPlan { + let evaluation_point_source = program + .opening_inputs + .iter() + .find(|input| input.symbol == "stage8.evaluation.point_source") + .expect("stage8 evaluation point source exists"); + Box::leak(Box::new($module::Stage8EvaluationProgramPlan { + role: role_name(&program.role), + function: leak_str(&program.function), + params: $module::Stage8Params { + field: leak_str(&program.params.field), + pcs: leak_str(&program.params.pcs), + transcript: leak_str(&program.params.transcript), + }, + evaluation_point_source: $module::Stage8OpeningInputPlan { + symbol: leak_str(&evaluation_point_source.symbol), + source_stage: leak_str(&evaluation_point_source.source_stage), + source_claim: leak_str(&evaluation_point_source.source_claim), + oracle: leak_str(&evaluation_point_source.oracle), + domain: leak_str(&evaluation_point_source.domain), + point_arity: evaluation_point_source.point_arity, + claim_kind: leak_str(&evaluation_point_source.claim_kind), + }, + opening_inputs: leak_slice( + program + .opening_inputs + .iter() + .map(|plan| $module::Stage8OpeningInputPlan { + symbol: leak_str(&plan.symbol), + source_stage: leak_str(&plan.source_stage), + source_claim: leak_str(&plan.source_claim), + oracle: leak_str(&plan.oracle), + domain: leak_str(&plan.domain), + point_arity: plan.point_arity, + claim_kind: leak_str(&plan.claim_kind), + }) + .collect(), + ), + opening_claims: leak_slice( + program + .opening_claims + .iter() + .map(|plan| $module::Stage8OpeningClaimPlan { + symbol: leak_str(&plan.symbol), + oracle: leak_str(&plan.oracle), + family: leak_str(&plan.family), + domain: leak_str(&plan.domain), + point_arity: plan.point_arity, + point_source: leak_str(&plan.point_source), + eval_source: leak_str(&plan.eval_source), + source_stage: leak_str(&plan.source_stage), + source_claim: leak_str(&plan.source_claim), + }) + .collect(), + ), + opening_batch: $module::Stage8OpeningBatchPlan { + symbol: leak_str(&program.opening_batches[0].symbol), + proof_slot: leak_str(&program.opening_batches[0].proof_slot), + policy: leak_str(&program.opening_batches[0].policy), + count: program.opening_batches[0].count, + ordered_claims: leak_str_slice(&program.opening_batches[0].ordered_claims), + }, + pcs_proof: $module::Stage8PcsProofPlan { + symbol: leak_str(&program.pcs_proofs[0].symbol), + mode: leak_str(&program.pcs_proofs[0].mode), + pcs: leak_str(&program.pcs_proofs[0].pcs), + proof_slot: leak_str(&program.pcs_proofs[0].proof_slot), + transcript_label: leak_str(&program.pcs_proofs[0].transcript_label), + batch: leak_str(&program.pcs_proofs[0].batch), + }, + })) + } + }; +} + +define_stage8_adapter!( + leak_generated_stage8_prover_program, + generated_prover_stage8 +); +define_stage8_adapter!(leak_generated_stage8_verifier_program, generated_stage8); + +fn role_name(role: &Role) -> &'static str { + match role { + Role::Prover => "prover", + Role::Verifier => "verifier", + } +} + +fn leak_str(value: &str) -> &'static str { + Box::leak(value.to_owned().into_boxed_str()) +} + +fn leak_str_slice(values: &[String]) -> &'static [&'static str] { + let leaked = values + .iter() + .map(|value| leak_str(value)) + .collect::>(); + Box::leak(leaked.into_boxed_slice()) +} + +fn leak_symbol_list(values: &[String]) -> &'static str { + leak_str(&values.join("|")) +} + +fn leak_usize_slice(values: &[usize]) -> &'static [usize] { + Box::leak(values.to_vec().into_boxed_slice()) +} + +fn leak_slice(values: Vec) -> &'static [T] { + Box::leak(values.into_boxed_slice()) +} diff --git a/crates/jolt-equivalence/src/plan_adapters/generated_commitment.rs b/crates/jolt-equivalence/src/plan_adapters/generated_commitment.rs new file mode 100644 index 0000000000..9893eba1f1 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/generated_commitment.rs @@ -0,0 +1,87 @@ +use bolt::protocols::jolt::{CommitmentCpuProgram, OptionalSkipPolicy}; +use jolt_prover::stages::commitment as generated_prover_commitment; +use jolt_verifier::stages::commitment as generated_commitment; + +use super::{leak_slice, leak_str, leak_str_slice}; + +macro_rules! define_generated_commitment_adapter { + ($function:ident, $module:ident, $program_plan:ident) => { + pub fn $function(program: &CommitmentCpuProgram) -> &'static $module::$program_plan { + Box::leak(Box::new($module::$program_plan { + params: $module::CommitmentParams { + field: leak_str(&program.params.field), + pcs: leak_str(&program.params.pcs), + transcript: leak_str(&program.params.transcript), + }, + oracle_plans: leak_slice( + program + .oracle_plans + .iter() + .map(|plan| $module::OraclePlan { + oracle: leak_str(&plan.oracle), + domain: leak_str(&plan.domain), + num_vars: plan.num_vars, + }) + .collect(), + ), + batch_plans: leak_slice( + program + .batch_plans + .iter() + .map(|plan| $module::CommitmentBatchPlan { + artifact: leak_str(&plan.artifact), + pcs: leak_str(&plan.pcs), + oracle_family: leak_str(&plan.oracle_family), + label: leak_str(&plan.label), + oracles: leak_str_slice(&plan.oracles), + count: plan.count, + domain: leak_str(&plan.domain), + num_vars: plan.num_vars, + }) + .collect(), + ), + optional_plans: leak_slice( + program + .optional_plans + .iter() + .map(|plan| $module::OptionalCommitmentPlan { + artifact: leak_str(&plan.artifact), + pcs: leak_str(&plan.pcs), + oracle: leak_str(&plan.oracle), + label: leak_str(&plan.label), + domain: leak_str(&plan.domain), + num_vars: plan.num_vars, + skip_policy: match plan.skip_policy { + OptionalSkipPolicy::MissingOrZero => { + $module::OptionalSkipPolicy::MissingOrZero + } + }, + }) + .collect(), + ), + transcript_steps: leak_slice( + program + .transcript_steps + .iter() + .map(|step| $module::TranscriptStep { + label: leak_str(&step.label), + source: leak_str(&step.source), + optional: step.optional, + }) + .collect(), + ), + })) + } + }; +} + +define_generated_commitment_adapter!( + leak_generated_commitment_prover_program, + generated_prover_commitment, + CommitmentProverProgramPlan +); +define_generated_commitment_adapter!( + leak_generated_commitment_verifier_program, + generated_commitment, + CommitmentVerifierProgramPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/generated_stage1.rs b/crates/jolt-equivalence/src/plan_adapters/generated_stage1.rs new file mode 100644 index 0000000000..865e762ee4 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/generated_stage1.rs @@ -0,0 +1,19 @@ +use bolt::protocols::jolt::Stage1CpuProgram as CompilerStage1CpuProgram; +use jolt_verifier::stages::stage1_outer as generated_stage1; + +define_stage1_adapter!( + generated, + leak_generated_stage1_verifier_program, + CompilerStage1CpuProgram, + generated_stage1, + Stage1VerifierProgramPlan, + Stage1Params, + Stage1TranscriptSqueezePlan, + Stage1SumcheckClaimPlan, + Stage1SumcheckBatchPlan, + Stage1SumcheckDriverPlan, + Stage1SumcheckInstanceResultPlan, + Stage1SumcheckEvalPlan, + Stage1OpeningClaimPlan, + Stage1OpeningBatchPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/generated_stage2.rs b/crates/jolt-equivalence/src/plan_adapters/generated_stage2.rs new file mode 100644 index 0000000000..517e934a68 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/generated_stage2.rs @@ -0,0 +1,25 @@ +use bolt::protocols::jolt::Stage2CpuProgram as CompilerStage2CpuProgram; +use jolt_verifier::stages::stage2 as generated_stage2; + +define_stage_adapter_no_absorb!( + generated, + leak_generated_stage2_verifier_program, + CompilerStage2CpuProgram, + generated_stage2, + Stage2VerifierProgramPlan, + Stage2Params, + Stage2ProgramStepPlan, + Stage2TranscriptSqueezePlan, + Stage2OpeningInputPlan, + Stage2FieldConstantPlan, + Stage2FieldExprPlan, + Stage2SumcheckClaimPlan, + Stage2SumcheckBatchPlan, + Stage2SumcheckDriverPlan, + Stage2SumcheckInstanceResultPlan, + Stage2SumcheckEvalPlan, + Stage2PointSlicePlan, + Stage2PointConcatPlan, + Stage2OpeningClaimPlan, + Stage2OpeningBatchPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/generated_stage3.rs b/crates/jolt-equivalence/src/plan_adapters/generated_stage3.rs new file mode 100644 index 0000000000..5dcb77bf10 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/generated_stage3.rs @@ -0,0 +1,26 @@ +use bolt::protocols::jolt::Stage3CpuProgram as CompilerStage3CpuProgram; +use jolt_verifier::stages::stage3 as generated_stage3; + +define_stage_adapter_no_absorb!( + generated, + leak_generated_stage3_verifier_program, + CompilerStage3CpuProgram, + generated_stage3, + Stage3VerifierProgramPlan, + Stage3Params, + Stage3ProgramStepPlan, + Stage3TranscriptSqueezePlan, + Stage3OpeningInputPlan, + Stage3FieldConstantPlan, + Stage3FieldExprPlan, + Stage3SumcheckClaimPlan, + Stage3SumcheckBatchPlan, + Stage3SumcheckDriverPlan, + Stage3SumcheckInstanceResultPlan, + Stage3SumcheckEvalPlan, + Stage3PointSlicePlan, + Stage3PointConcatPlan, + Stage3OpeningClaimPlan, + Stage3OpeningBatchPlan, + opening_equalities = Stage3OpeningClaimEqualityPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/generated_stage4.rs b/crates/jolt-equivalence/src/plan_adapters/generated_stage4.rs new file mode 100644 index 0000000000..a9340f0907 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/generated_stage4.rs @@ -0,0 +1,28 @@ +use bolt::protocols::jolt::Stage4CpuProgram as CompilerStage4CpuProgram; +use jolt_verifier::stages::stage4 as generated_stage4; + +define_stage_adapter!( + generated, + leak_generated_stage4_verifier_program, + CompilerStage4CpuProgram, + generated_stage4, + Stage4VerifierProgramPlan, + Stage4Params, + Stage4ProgramStepPlan, + Stage4TranscriptSqueezePlan, + Stage4TranscriptAbsorbBytesPlan, + Stage4OpeningInputPlan, + Stage4FieldConstantPlan, + Stage4FieldExprPlan, + Stage4KernelPlan, + Stage4SumcheckClaimPlan, + Stage4SumcheckBatchPlan, + Stage4SumcheckDriverPlan, + Stage4SumcheckInstanceResultPlan, + Stage4SumcheckEvalPlan, + Stage4PointSlicePlan, + Stage4PointConcatPlan, + Stage4OpeningClaimPlan, + Stage4OpeningClaimEqualityPlan, + Stage4OpeningBatchPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/generated_stage5.rs b/crates/jolt-equivalence/src/plan_adapters/generated_stage5.rs new file mode 100644 index 0000000000..0033acb89a --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/generated_stage5.rs @@ -0,0 +1,28 @@ +use bolt::protocols::jolt::Stage5CpuProgram as CompilerStage5CpuProgram; +use jolt_verifier::stages::stage5 as generated_stage5; + +define_stage_adapter!( + generated, + leak_generated_stage5_verifier_program, + CompilerStage5CpuProgram, + generated_stage5, + Stage5VerifierProgramPlan, + Stage5Params, + Stage5ProgramStepPlan, + Stage5TranscriptSqueezePlan, + Stage5TranscriptAbsorbBytesPlan, + Stage5OpeningInputPlan, + Stage5FieldConstantPlan, + Stage5FieldExprPlan, + Stage5KernelPlan, + Stage5SumcheckClaimPlan, + Stage5SumcheckBatchPlan, + Stage5SumcheckDriverPlan, + Stage5SumcheckInstanceResultPlan, + Stage5SumcheckEvalPlan, + Stage5PointSlicePlan, + Stage5PointConcatPlan, + Stage5OpeningClaimPlan, + Stage5OpeningClaimEqualityPlan, + Stage5OpeningBatchPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/generated_stage6.rs b/crates/jolt-equivalence/src/plan_adapters/generated_stage6.rs new file mode 100644 index 0000000000..76072dd614 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/generated_stage6.rs @@ -0,0 +1,29 @@ +use bolt::protocols::jolt::Stage6CpuProgram as CompilerStage6CpuProgram; +use jolt_verifier::stages::stage6 as generated_stage6; + +define_stage_adapter!( + generated, + leak_generated_stage6_verifier_program, + CompilerStage6CpuProgram, + generated_stage6, + Stage6VerifierProgramPlan, + Stage6Params, + Stage6ProgramStepPlan, + Stage6TranscriptSqueezePlan, + Stage6TranscriptAbsorbBytesPlan, + Stage6OpeningInputPlan, + Stage6FieldConstantPlan, + Stage6FieldExprPlan, + Stage6KernelPlan, + Stage6SumcheckClaimPlan, + Stage6SumcheckBatchPlan, + Stage6SumcheckDriverPlan, + Stage6SumcheckInstanceResultPlan, + Stage6SumcheckEvalPlan, + Stage6PointSlicePlan, + Stage6PointConcatPlan, + Stage6OpeningClaimPlan, + Stage6OpeningClaimEqualityPlan, + Stage6OpeningBatchPlan, + point_zero = Stage6PointZeroPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/generated_stage7.rs b/crates/jolt-equivalence/src/plan_adapters/generated_stage7.rs new file mode 100644 index 0000000000..457ac30812 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/generated_stage7.rs @@ -0,0 +1,29 @@ +use bolt::protocols::jolt::Stage7CpuProgram as CompilerStage7CpuProgram; +use jolt_verifier::stages::stage7 as generated_stage7; + +define_stage_adapter!( + generated, + leak_generated_stage7_verifier_program, + CompilerStage7CpuProgram, + generated_stage7, + Stage7VerifierProgramPlan, + Stage7Params, + Stage7ProgramStepPlan, + Stage7TranscriptSqueezePlan, + Stage7TranscriptAbsorbBytesPlan, + Stage7OpeningInputPlan, + Stage7FieldConstantPlan, + Stage7FieldExprPlan, + Stage7KernelPlan, + Stage7SumcheckClaimPlan, + Stage7SumcheckBatchPlan, + Stage7SumcheckDriverPlan, + Stage7SumcheckInstanceResultPlan, + Stage7SumcheckEvalPlan, + Stage7PointSlicePlan, + Stage7PointConcatPlan, + Stage7OpeningClaimPlan, + Stage7OpeningClaimEqualityPlan, + Stage7OpeningBatchPlan, + point_zero = Stage7PointZeroPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/stage1.rs b/crates/jolt-equivalence/src/plan_adapters/stage1.rs new file mode 100644 index 0000000000..f6263d2e9d --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/stage1.rs @@ -0,0 +1,20 @@ +use bolt::protocols::jolt::Stage1CpuProgram as CompilerStage1CpuProgram; +use jolt_kernels::stage1 as kernel_stage1; + +define_stage1_adapter!( + kernel, + leak_stage1_program, + CompilerStage1CpuProgram, + kernel_stage1, + Stage1CpuProgramPlan, + Stage1Params, + Stage1TranscriptSqueezePlan, + Stage1SumcheckClaimPlan, + Stage1SumcheckBatchPlan, + Stage1SumcheckDriverPlan, + Stage1SumcheckInstanceResultPlan, + Stage1SumcheckEvalPlan, + Stage1OpeningClaimPlan, + Stage1OpeningBatchPlan, + kernels = Stage1KernelPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/stage2.rs b/crates/jolt-equivalence/src/plan_adapters/stage2.rs new file mode 100644 index 0000000000..e67ecd6c86 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/stage2.rs @@ -0,0 +1,26 @@ +use bolt::protocols::jolt::Stage2CpuProgram as CompilerStage2CpuProgram; +use jolt_kernels::stage2 as kernel_stage2; + +define_stage_adapter_no_absorb!( + kernel, + leak_stage2_program, + CompilerStage2CpuProgram, + kernel_stage2, + Stage2CpuProgramPlan, + Stage2Params, + Stage2ProgramStepPlan, + Stage2TranscriptSqueezePlan, + Stage2OpeningInputPlan, + Stage2FieldConstantPlan, + Stage2FieldExprPlan, + Stage2SumcheckClaimPlan, + Stage2SumcheckBatchPlan, + Stage2SumcheckDriverPlan, + Stage2SumcheckInstanceResultPlan, + Stage2SumcheckEvalPlan, + Stage2PointSlicePlan, + Stage2PointConcatPlan, + Stage2OpeningClaimPlan, + Stage2OpeningBatchPlan, + kernels = Stage2KernelPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/stage3.rs b/crates/jolt-equivalence/src/plan_adapters/stage3.rs new file mode 100644 index 0000000000..6824047c1f --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/stage3.rs @@ -0,0 +1,27 @@ +use bolt::protocols::jolt::Stage3CpuProgram as CompilerStage3CpuProgram; +use jolt_kernels::stage3 as kernel_stage3; + +define_stage_adapter_no_absorb!( + kernel, + leak_stage3_program, + CompilerStage3CpuProgram, + kernel_stage3, + Stage3CpuProgramPlan, + Stage3Params, + Stage3ProgramStepPlan, + Stage3TranscriptSqueezePlan, + Stage3OpeningInputPlan, + Stage3FieldConstantPlan, + Stage3FieldExprPlan, + Stage3SumcheckClaimPlan, + Stage3SumcheckBatchPlan, + Stage3SumcheckDriverPlan, + Stage3SumcheckInstanceResultPlan, + Stage3SumcheckEvalPlan, + Stage3PointSlicePlan, + Stage3PointConcatPlan, + Stage3OpeningClaimPlan, + Stage3OpeningBatchPlan, + kernels = Stage3KernelPlan, + opening_equalities = Stage3OpeningClaimEqualityPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/stage4.rs b/crates/jolt-equivalence/src/plan_adapters/stage4.rs new file mode 100644 index 0000000000..5a973a7c98 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/stage4.rs @@ -0,0 +1,28 @@ +use bolt::protocols::jolt::Stage4CpuProgram as CompilerStage4CpuProgram; +use jolt_kernels::stage4 as kernel_stage4; + +define_stage_adapter!( + kernel, + leak_stage4_program, + CompilerStage4CpuProgram, + kernel_stage4, + Stage4CpuProgramPlan, + Stage4Params, + Stage4ProgramStepPlan, + Stage4TranscriptSqueezePlan, + Stage4TranscriptAbsorbBytesPlan, + Stage4OpeningInputPlan, + Stage4FieldConstantPlan, + Stage4FieldExprPlan, + Stage4KernelPlan, + Stage4SumcheckClaimPlan, + Stage4SumcheckBatchPlan, + Stage4SumcheckDriverPlan, + Stage4SumcheckInstanceResultPlan, + Stage4SumcheckEvalPlan, + Stage4PointSlicePlan, + Stage4PointConcatPlan, + Stage4OpeningClaimPlan, + Stage4OpeningClaimEqualityPlan, + Stage4OpeningBatchPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/stage5.rs b/crates/jolt-equivalence/src/plan_adapters/stage5.rs new file mode 100644 index 0000000000..58bfe583c8 --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/stage5.rs @@ -0,0 +1,28 @@ +use bolt::protocols::jolt::Stage5CpuProgram as CompilerStage5CpuProgram; +use jolt_kernels::stage5 as kernel_stage5; + +define_stage_adapter!( + kernel, + leak_stage5_program, + CompilerStage5CpuProgram, + kernel_stage5, + Stage5CpuProgramPlan, + Stage5Params, + Stage5ProgramStepPlan, + Stage5TranscriptSqueezePlan, + Stage5TranscriptAbsorbBytesPlan, + Stage5OpeningInputPlan, + Stage5FieldConstantPlan, + Stage5FieldExprPlan, + Stage5KernelPlan, + Stage5SumcheckClaimPlan, + Stage5SumcheckBatchPlan, + Stage5SumcheckDriverPlan, + Stage5SumcheckInstanceResultPlan, + Stage5SumcheckEvalPlan, + Stage5PointSlicePlan, + Stage5PointConcatPlan, + Stage5OpeningClaimPlan, + Stage5OpeningClaimEqualityPlan, + Stage5OpeningBatchPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/stage6.rs b/crates/jolt-equivalence/src/plan_adapters/stage6.rs new file mode 100644 index 0000000000..8423eec48c --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/stage6.rs @@ -0,0 +1,29 @@ +use bolt::protocols::jolt::Stage6CpuProgram as CompilerStage6CpuProgram; +use jolt_kernels::stage6 as kernel_stage6; + +define_stage_adapter!( + kernel, + leak_stage6_program, + CompilerStage6CpuProgram, + kernel_stage6, + Stage6CpuProgramPlan, + Stage6Params, + Stage6ProgramStepPlan, + Stage6TranscriptSqueezePlan, + Stage6TranscriptAbsorbBytesPlan, + Stage6OpeningInputPlan, + Stage6FieldConstantPlan, + Stage6FieldExprPlan, + Stage6KernelPlan, + Stage6SumcheckClaimPlan, + Stage6SumcheckBatchPlan, + Stage6SumcheckDriverPlan, + Stage6SumcheckInstanceResultPlan, + Stage6SumcheckEvalPlan, + Stage6PointSlicePlan, + Stage6PointConcatPlan, + Stage6OpeningClaimPlan, + Stage6OpeningClaimEqualityPlan, + Stage6OpeningBatchPlan, + point_zero = Stage6PointZeroPlan +); diff --git a/crates/jolt-equivalence/src/plan_adapters/stage7.rs b/crates/jolt-equivalence/src/plan_adapters/stage7.rs new file mode 100644 index 0000000000..e83b4ff5ec --- /dev/null +++ b/crates/jolt-equivalence/src/plan_adapters/stage7.rs @@ -0,0 +1,29 @@ +use bolt::protocols::jolt::Stage7CpuProgram as CompilerStage7CpuProgram; +use jolt_kernels::stage7 as kernel_stage7; + +define_stage_adapter!( + kernel, + leak_stage7_program, + CompilerStage7CpuProgram, + kernel_stage7, + Stage7CpuProgramPlan, + Stage7Params, + Stage7ProgramStepPlan, + Stage7TranscriptSqueezePlan, + Stage7TranscriptAbsorbBytesPlan, + Stage7OpeningInputPlan, + Stage7FieldConstantPlan, + Stage7FieldExprPlan, + Stage7KernelPlan, + Stage7SumcheckClaimPlan, + Stage7SumcheckBatchPlan, + Stage7SumcheckDriverPlan, + Stage7SumcheckInstanceResultPlan, + Stage7SumcheckEvalPlan, + Stage7PointSlicePlan, + Stage7PointConcatPlan, + Stage7OpeningClaimPlan, + Stage7OpeningClaimEqualityPlan, + Stage7OpeningBatchPlan, + point_zero = Stage7PointZeroPlan +); diff --git a/crates/jolt-equivalence/src/tamper.rs b/crates/jolt-equivalence/src/tamper.rs new file mode 100644 index 0000000000..27a808ae75 --- /dev/null +++ b/crates/jolt-equivalence/src/tamper.rs @@ -0,0 +1,894 @@ +//! Malformed proof fixtures for verifier rejection tests. + +#![expect( + clippy::expect_used, + reason = "tamper gates should fail fast when honest prefix setup fails" +)] + +use jolt_dory::{DoryProof, DoryScheme}; +use jolt_field::{Field, Fr}; +use jolt_kernels::stage1::{ + Stage1CpuProgramPlan as KernelStage1CpuProgramPlan, Stage1ExecutionArtifacts, Stage1Proof, +}; +use jolt_kernels::stage2::{ + Stage2CpuProgramPlan as KernelStage2CpuProgramPlan, Stage2ExecutionArtifacts, + Stage2OpeningInputValue, Stage2Proof, Stage2RamData, +}; +use jolt_kernels::stage3::Stage3ExecutionArtifacts; +use jolt_kernels::stage4::Stage4ExecutionArtifacts; +use jolt_openings::CommitmentScheme; +use jolt_poly::Polynomial; +use jolt_poly::UnivariatePoly; +use jolt_transcript::Transcript; +use jolt_verifier::{ + JoltStage6VerifierData, JoltStageOpeningInputValue, JoltStageProof, Stage1VerifierProgramPlan, + Stage2VerifierProgramPlan, Stage3VerifierProgramPlan, Stage4VerifierProgramPlan, + Stage5VerifierProgramPlan, Stage6VerifierProgramPlan, Stage7VerifierProgramPlan, +}; + +use crate::checkpoint::assert_state_history_match; +use crate::commitment_oracle::{ + transcript_with_bolt_commitment_trace, transcript_with_bolt_preamble, BoltCommitmentTrace, + BoltPreambleSource, BoltTranscript, +}; +use crate::core_oracle::CoreMuldivCommitmentFixture; + +fn stage2_opening_inputs_from_artifacts( + program: &'static KernelStage2CpuProgramPlan, + stage1_artifacts: &Stage1ExecutionArtifacts, +) -> Vec> { + jolt_prover::stage2_opening_inputs_from_artifacts(program, stage1_artifacts) + .expect("generated prover derives Stage 2 opening inputs from artifacts") +} + +macro_rules! tampered_sumcheck_coefficient { + ($proof:expr, $sumcheck_index:expr) => {{ + let mut tampered = $proof.clone(); + let round_poly = &mut tampered.sumchecks[$sumcheck_index].proof.round_polynomials[0]; + let mut coefficients = round_poly.clone().into_coefficients(); + coefficients[0] += Fr::from_u64(1); + *round_poly = UnivariatePoly::new(coefficients); + tampered + }}; +} + +macro_rules! tampered_sumcheck_eval { + ($proof:expr, $sumcheck_index:expr) => {{ + let mut tampered = $proof.clone(); + tampered.sumchecks[$sumcheck_index].evals[0].value += Fr::from_u64(1); + tampered + }}; +} + +macro_rules! tampered_sumcheck_point { + ($proof:expr, $sumcheck_index:expr) => {{ + let mut tampered = $proof.clone(); + tampered.sumchecks[$sumcheck_index].point[0] += Fr::from_u64(1); + tampered + }}; +} + +macro_rules! assert_batched_sumcheck_tampers { + ($assert_fn:ident, $proof:expr, $sumcheck_index:expr, $prefix:literal) => {{ + $assert_fn( + tampered_sumcheck_coefficient!($proof, $sumcheck_index), + concat!($prefix, " accepted a tampered batched sumcheck coefficient"), + ); + $assert_fn( + tampered_sumcheck_eval!($proof, $sumcheck_index), + concat!($prefix, " accepted a tampered batched opening evaluation"), + ); + $assert_fn( + tampered_sumcheck_point!($proof, $sumcheck_index), + concat!($prefix, " accepted a tampered batched sumcheck point"), + ); + }}; +} + +fn assert_opening_input_tampers( + openings: &[jolt_verifier::JoltStageOpeningInputValue], + stage: &str, + mut assert_tamper_rejected: F, +) where + F: FnMut(&[jolt_verifier::JoltStageOpeningInputValue], &str), +{ + let tampered_openings = tampered_opening_input_eval(openings); + assert_tamper_rejected( + &tampered_openings, + &format!("{stage} verifier accepted a tampered opening-claim input evaluation"), + ); + let missing_openings = opening_inputs_without_first(openings); + assert_tamper_rejected( + &missing_openings, + &format!("{stage} verifier accepted a missing opening-claim input"), + ); + let extra_openings = opening_inputs_with_extra_first(openings); + assert_tamper_rejected( + &extra_openings, + &format!("{stage} verifier accepted an extra opening-claim input"), + ); + let short_point_openings = opening_inputs_with_short_first_point(openings); + assert_tamper_rejected( + &short_point_openings, + &format!("{stage} verifier accepted an opening-claim input with invalid point arity"), + ); +} + +fn tampered_opening_input_eval( + openings: &[jolt_verifier::JoltStageOpeningInputValue], +) -> Vec { + let mut tampered = openings.to_vec(); + tampered + .get_mut(0) + .expect("stage has at least one opening input") + .eval += Fr::from_u64(1); + tampered +} + +fn opening_inputs_without_first(openings: &[T]) -> Vec { + assert!(!openings.is_empty(), "stage has at least one opening input"); + openings[1..].to_vec() +} + +fn opening_inputs_with_extra_first(openings: &[T]) -> Vec { + let mut tampered = openings.to_vec(); + tampered.push( + openings + .first() + .expect("stage has at least one opening input") + .clone(), + ); + tampered +} + +fn opening_inputs_with_short_first_point( + openings: &[jolt_verifier::JoltStageOpeningInputValue], +) -> Vec { + let mut tampered = openings.to_vec(); + let _removed_coordinate = tampered + .first_mut() + .expect("stage has at least one opening input") + .point + .pop() + .expect("opening input has at least one point coordinate"); + tampered +} + +fn tampered_opening_input_suffix_point( + openings: &[jolt_verifier::JoltStageOpeningInputValue], + symbol: &'static str, + prefix_len: usize, +) -> Vec { + let mut tampered = openings.to_vec(); + let point = tampered + .iter_mut() + .find(|opening| opening.symbol == symbol) + .expect("stage has opening input symbol") + .point + .get_mut(prefix_len) + .expect("opening input has a suffix point coordinate"); + *point += Fr::from_u64(1); + tampered +} + +fn assert_generated_jolt_prefix_tamper_rejected

( + preamble: &P, + proof: &jolt_verifier::JoltProof, + inputs: jolt_verifier::JoltVerifierInputs<'_>, + programs: jolt_verifier::JoltVerifierPrograms, + message: &str, +) where + P: BoltPreambleSource, +{ + let mut transcript = transcript_with_bolt_preamble(preamble); + let result = if !inputs.stage7_openings.is_empty() { + jolt_verifier::verify_jolt_through_stage7_with_programs( + proof, + inputs, + programs, + &mut transcript, + ) + } else if !inputs.stage6_openings.is_empty() { + jolt_verifier::verify_jolt_through_stage6_with_programs( + proof, + inputs, + programs, + &mut transcript, + ) + } else { + jolt_verifier::verify_jolt_through_stage5_with_programs( + proof, + inputs, + programs, + &mut transcript, + ) + }; + assert!(result.is_err(), "generated monolithic {message}"); +} + +macro_rules! assert_generated_stage_and_prefix_tamper_rejected { + ($preamble:expr, $message:expr, $inputs:expr, $programs:expr, $stage_result:expr, $prefix_proof:expr $(,)?) => {{ + assert!($stage_result.is_err(), "generated {}", $message); + assert_generated_jolt_prefix_tamper_rejected( + $preamble, + &$prefix_proof, + $inputs, + $programs, + $message, + ); + }}; +} + +/// Produces a valid Dory proof for an unrelated polynomial/opening claim. +fn unrelated_dory_proof() -> DoryProof { + let prover_setup = DoryScheme::setup_prover(1); + let poly = Polynomial::new(vec![Fr::from_u64(0), Fr::from_u64(1)]); + let point = vec![Fr::from_u64(7)]; + let mut transcript = jolt_transcript::Blake2bTranscript::new(b"unrelated-dory-proof"); + DoryScheme::open( + &poly, + &point, + point[0], + &prover_setup, + None, + &mut transcript, + ) +} + +pub(crate) struct MonolithicJoltTamperInput<'a> { + pub(crate) preamble: &'a CoreMuldivCommitmentFixture, + pub(crate) proof: &'a jolt_verifier::JoltProof, + pub(crate) inputs: jolt_verifier::JoltVerifierInputs<'a>, + pub(crate) programs: jolt_verifier::JoltVerifierPrograms, +} + +pub(crate) fn assert_monolithic_jolt_tamper_rejected(input: MonolithicJoltTamperInput<'_>) { + let mut inputs_without_setup = input.inputs; + inputs_without_setup.evaluation_setup = None; + + let verify = |proof: &jolt_verifier::JoltProof, + inputs: jolt_verifier::JoltVerifierInputs<'_>| { + let mut transcript = transcript_with_bolt_preamble(input.preamble); + jolt_verifier::verify_jolt_with_programs(proof, inputs, input.programs, &mut transcript) + }; + + macro_rules! assert_verify_error { + ($result:expr, $error:pat, $message:expr $(,)?) => { + assert!(matches!($result, $error), $message); + }; + } + + assert_verify_error!( + verify(input.proof, inputs_without_setup), + Err(jolt_verifier::JoltVerifyError::Evaluation( + jolt_verifier::JoltEvaluationProofError::MissingVerifierSetup + )), + "generated monolithic verifier accepted evaluation proof without verifier setup", + ); + + let mut missing_evaluation_proof = input.proof.clone(); + missing_evaluation_proof.evaluation = None; + assert_verify_error!( + verify(&missing_evaluation_proof, input.inputs), + Err(jolt_verifier::JoltVerifyError::Evaluation( + jolt_verifier::JoltEvaluationProofError::MissingProof + )), + "generated monolithic verifier accepted missing evaluation proof with verifier setup", + ); + + assert_verify_error!( + verify(&missing_evaluation_proof, inputs_without_setup), + Err(jolt_verifier::JoltVerifyError::Evaluation( + jolt_verifier::JoltEvaluationProofError::MissingProof + )), + "generated monolithic verifier accepted missing evaluation proof without verifier setup", + ); + + let mut tampered_evaluation_proof = input.proof.clone(); + tampered_evaluation_proof + .evaluation + .as_mut() + .expect("evaluation proof") + .joint_opening_proof = unrelated_dory_proof(); + assert_verify_error!( + verify(&tampered_evaluation_proof, input.inputs), + Err(jolt_verifier::JoltVerifyError::Evaluation(_)), + "generated monolithic verifier accepted a tampered evaluation proof", + ); + + let stage8_source_symbol = input.programs.stage8.evaluation_point_source.source_claim; + let stage7_address_prefix_len = input + .proof + .stage7 + .sumchecks + .first() + .expect("monolithic proof has a Stage 7 sumcheck") + .point + .len(); + let tampered_stage7_openings = tampered_opening_input_suffix_point( + input.inputs.stage7_openings, + stage8_source_symbol, + stage7_address_prefix_len, + ); + let mut tampered_stage7_opening_inputs = input.inputs; + tampered_stage7_opening_inputs.stage7_openings = &tampered_stage7_openings; + assert_verify_error!( + verify(input.proof, tampered_stage7_opening_inputs), + Err(jolt_verifier::JoltVerifyError::Evaluation(_)), + "generated monolithic verifier accepted a tampered Stage 8 opening-point suffix", + ); + + let mut missing_commitment_proof = input.proof.clone(); + *missing_commitment_proof + .commitments + .get_mut(0) + .expect("monolithic proof has a main commitment") = None; + assert_verify_error!( + verify(&missing_commitment_proof, input.inputs), + Err(jolt_verifier::JoltVerifyError::Commitment(_)), + "generated monolithic verifier accepted a missing required commitment", + ); + + macro_rules! assert_missing_stage_rejected { + ($field:ident, $variant:ident, $stage:literal) => {{ + let mut missing_stage_proof = input.proof.clone(); + missing_stage_proof.$field.sumchecks.clear(); + assert_verify_error!( + verify(&missing_stage_proof, input.inputs), + Err(jolt_verifier::JoltVerifyError::$variant(_)), + concat!( + "generated monolithic verifier accepted a missing ", + $stage, + " proof" + ), + ); + }}; + } + + assert_missing_stage_rejected!(stage1_outer, Stage1Outer, "Stage 1 outer"); + assert_missing_stage_rejected!(stage2, Stage2, "Stage 2"); + assert_missing_stage_rejected!(stage3, Stage3, "Stage 3"); + assert_missing_stage_rejected!(stage4, Stage4, "Stage 4"); + assert_missing_stage_rejected!(stage5, Stage5, "Stage 5"); + assert_missing_stage_rejected!(stage6, Stage6, "Stage 6"); + assert_missing_stage_rejected!(stage7, Stage7, "Stage 7"); + + let mut wrong_stage_slot_proof = input.proof.clone(); + std::mem::swap( + &mut wrong_stage_slot_proof.stage6, + &mut wrong_stage_slot_proof.stage7, + ); + assert_verify_error!( + verify(&wrong_stage_slot_proof, input.inputs), + Err(jolt_verifier::JoltVerifyError::Stage6(_)), + "generated monolithic verifier accepted a stage proof in the wrong slot" + ); +} + +pub fn assert_bolt_stage1_tamper_rejected( + stage1_verifier_plan: &'static KernelStage1CpuProgramPlan, + generated_stage1_verifier_plan: &'static Stage1VerifierProgramPlan, + proof: &Stage1Proof, + stage1_start_transcript: &BoltTranscript, +) { + let assert_stage1_tamper_rejected = |tampered: Stage1Proof, message: &str| { + let mut transcript = stage1_start_transcript.clone(); + + let result = jolt_prover::replay_stage1_outer_proof_with_program( + stage1_verifier_plan, + &tampered, + &mut transcript, + ); + assert!(result.is_err(), "{message}"); + + let mut generated_transcript = stage1_start_transcript.clone(); + let generated_tampered = jolt_prover::stage1_outer_proof_from_kernel_proof(&tampered); + let generated_result = jolt_verifier::verify_stage1_outer_with_program( + generated_stage1_verifier_plan, + &generated_tampered, + &mut generated_transcript, + ); + assert!(generated_result.is_err(), "generated {message}"); + }; + + assert_stage1_tamper_rejected( + tampered_sumcheck_coefficient!(proof, 1), + "Bolt Stage 1 verifier accepted a tampered remaining sumcheck coefficient", + ); + + assert_stage1_tamper_rejected( + tampered_sumcheck_coefficient!(proof, 0), + "Bolt Stage 1 verifier accepted a tampered uniskip sumcheck coefficient", + ); + + assert_stage1_tamper_rejected( + tampered_sumcheck_point!(proof, 0), + "Bolt Stage 1 verifier accepted a tampered uniskip point", + ); + + assert_stage1_tamper_rejected( + tampered_sumcheck_eval!(proof, 0), + "Bolt Stage 1 verifier accepted a tampered uniskip eval", + ); + + assert_stage1_tamper_rejected( + tampered_sumcheck_point!(proof, 1), + "Bolt Stage 1 verifier accepted a tampered remaining sumcheck point", + ); +} + +pub struct BoltStage2ChainVerifierInput<'a> { + pub fixture: &'a CoreMuldivCommitmentFixture, + pub commitment_verifier_trace: &'a BoltCommitmentTrace, + pub stage1_prover_plan: &'static KernelStage1CpuProgramPlan, + pub stage2_prover_plan: &'static KernelStage2CpuProgramPlan, + pub generated_stage2_verifier_plan: &'static Stage2VerifierProgramPlan, + pub stage1_artifacts: &'a Stage1ExecutionArtifacts, + pub stage2_artifacts: &'a Stage2ExecutionArtifacts, + pub ram_data: &'a Stage2RamData<'a>, + pub prover_transcript: &'a BoltTranscript, +} + +pub fn assert_bolt_chain_verifier_accepts_stage2_product_uniskip( + input: BoltStage2ChainVerifierInput<'_>, +) { + let mut verifier_transcript = + transcript_with_bolt_commitment_trace(input.fixture, input.commitment_verifier_trace); + + let stage1_proof = Stage1Proof::from(input.stage1_artifacts.clone()); + let verified_stage1 = jolt_prover::replay_stage1_outer_proof_with_program( + input.stage1_prover_plan, + &stage1_proof, + &mut verifier_transcript, + ) + .expect("Bolt Stage 1 verifier accepts Bolt prover proof"); + assert_eq!( + input.stage1_artifacts.sumchecks.len(), + verified_stage1.sumchecks.len() + ); + assert_eq!( + input.stage1_artifacts.opening_values.len(), + verified_stage1.opening_values.len() + ); + + let stage2_openings = + stage2_opening_inputs_from_artifacts(input.stage2_prover_plan, &verified_stage1); + let generated_stage2_openings = + jolt_prover::verifier_opening_inputs_from_kernel(&stage2_openings); + let generated_ram_data_storage = jolt_prover::stage2_verifier_ram_data(input.ram_data); + let generated_ram_data = generated_ram_data_storage.as_input(); + let stage2_proof = Stage2Proof::from(input.stage2_artifacts.clone()); + let stage2_start_transcript = verifier_transcript.clone(); + let verified_stage2 = jolt_prover::replay_stage2_proof_with_program( + input.stage2_prover_plan, + &stage2_proof, + &stage2_openings, + Some(input.ram_data), + &mut verifier_transcript, + ) + .expect("Bolt Stage 2 verifier accepts Bolt prover proof"); + + assert_eq!( + input.stage2_artifacts.sumchecks.len(), + verified_stage2.sumchecks.len() + ); + assert_eq!( + input.stage2_artifacts.sumchecks[0].point, + verified_stage2.sumchecks[0].point + ); + assert_eq!( + input.stage2_artifacts.sumchecks[0].evals[0].value, + verified_stage2.sumchecks[0].evals[0].value + ); + assert_state_history_match(input.prover_transcript.log(), verifier_transcript.log()); + + let assert_stage2_product_tamper_rejected = + |tampered_stage2_artifacts: Stage2ExecutionArtifacts, message: &str| { + let mut tamper_transcript = stage2_start_transcript.clone(); + let tampered_stage2_proof = Stage2Proof::from(tampered_stage2_artifacts.clone()); + let tamper_result = jolt_prover::replay_stage2_proof_with_program( + input.stage2_prover_plan, + &tampered_stage2_proof, + &stage2_openings, + Some(input.ram_data), + &mut tamper_transcript, + ); + assert!(tamper_result.is_err(), "{message}"); + + let mut generated_tamper_transcript = stage2_start_transcript.clone(); + let generated_tampered_stage2_proof = + jolt_prover::stage2_proof(&tampered_stage2_artifacts); + let generated_tamper_result = jolt_verifier::verify_stage2_with_program( + input.generated_stage2_verifier_plan, + &generated_tampered_stage2_proof, + &generated_stage2_openings, + Some(&generated_ram_data), + &mut generated_tamper_transcript, + ); + assert!(generated_tamper_result.is_err(), "generated {message}"); + }; + + assert_stage2_product_tamper_rejected( + tampered_sumcheck_coefficient!(input.stage2_artifacts, 0), + "Bolt Stage 2 verifier accepted a tampered product uni-skip coefficient", + ); + + assert_stage2_product_tamper_rejected( + tampered_sumcheck_eval!(input.stage2_artifacts, 0), + "Bolt Stage 2 verifier accepted a tampered product uni-skip opening evaluation", + ); + + assert_stage2_product_tamper_rejected( + tampered_sumcheck_point!(input.stage2_artifacts, 0), + "Bolt Stage 2 verifier accepted a tampered product uni-skip point", + ); +} + +pub struct Stage2BatchedTamperInput<'a> { + pub stage2_prover_plan: &'static KernelStage2CpuProgramPlan, + pub generated_stage2_verifier_plan: &'static Stage2VerifierProgramPlan, + pub stage2_start_transcript: &'a BoltTranscript, + pub stage2_openings: &'a [Stage2OpeningInputValue], + pub stage2_artifacts: &'a Stage2ExecutionArtifacts, + pub ram_data: &'a Stage2RamData<'a>, +} + +pub fn assert_bolt_stage2_batched_tamper_rejected(input: Stage2BatchedTamperInput<'_>) { + let generated_stage2_openings = + jolt_prover::verifier_opening_inputs_from_kernel(input.stage2_openings); + let generated_ram_data_storage = jolt_prover::stage2_verifier_ram_data(input.ram_data); + let generated_ram_data = generated_ram_data_storage.as_input(); + + let assert_stage2_tamper_rejected = + |tampered_stage2_artifacts: Stage2ExecutionArtifacts, message: &str| { + let mut tamper_transcript = input.stage2_start_transcript.clone(); + let tampered_stage2_proof = Stage2Proof::from(tampered_stage2_artifacts.clone()); + let tamper_result = jolt_prover::replay_stage2_proof_with_program( + input.stage2_prover_plan, + &tampered_stage2_proof, + input.stage2_openings, + Some(input.ram_data), + &mut tamper_transcript, + ); + assert!(tamper_result.is_err(), "{message}"); + + let mut generated_tamper_transcript = input.stage2_start_transcript.clone(); + let generated_tampered_stage2_proof = + jolt_prover::stage2_proof(&tampered_stage2_artifacts); + let generated_tamper_result = jolt_verifier::verify_stage2_with_program( + input.generated_stage2_verifier_plan, + &generated_tampered_stage2_proof, + &generated_stage2_openings, + Some(&generated_ram_data), + &mut generated_tamper_transcript, + ); + assert!(generated_tamper_result.is_err(), "generated {message}"); + }; + + assert_batched_sumcheck_tampers!( + assert_stage2_tamper_rejected, + input.stage2_artifacts, + 1, + "Bolt Stage 2 verifier" + ); +} + +pub(crate) struct Stage6TamperInput<'a> { + pub(crate) preamble: &'a CoreMuldivCommitmentFixture, + pub(crate) commitment_verifier_trace: &'a BoltCommitmentTrace, + pub(crate) verifier_transcript: &'a BoltTranscript, + pub(crate) verifier_plan: &'static Stage6VerifierProgramPlan, + pub(crate) proof: &'a JoltStageProof, + pub(crate) openings: &'a [JoltStageOpeningInputValue], + pub(crate) data: &'a JoltStage6VerifierData, + pub(crate) stage1_artifacts: &'a Stage1ExecutionArtifacts, + pub(crate) stage2_artifacts: &'a Stage2ExecutionArtifacts, + pub(crate) stage3_artifacts: &'a Stage3ExecutionArtifacts, + pub(crate) stage4_artifacts: &'a Stage4ExecutionArtifacts, + pub(crate) stage5_proof: &'a JoltStageProof, + pub(crate) jolt_inputs: jolt_verifier::JoltVerifierInputs<'a>, + pub(crate) programs: jolt_verifier::JoltVerifierPrograms, +} + +pub(crate) fn assert_bolt_stage6_tamper_rejected(input: Stage6TamperInput<'_>) { + let assert_tamper_rejected = |tampered_stage6_proof: JoltStageProof, message: &str| { + assert_generated_stage_and_prefix_tamper_rejected!( + input.preamble, + message, + input.jolt_inputs, + input.programs, + { + let mut transcript = input.verifier_transcript.clone(); + jolt_verifier::verify_stage6_with_program( + input.verifier_plan, + &tampered_stage6_proof, + input.openings, + Some(input.data), + &mut transcript, + ) + }, + jolt_prover::jolt_proof_through_stage6( + &input.commitment_verifier_trace.commitments, + input.stage1_artifacts, + input.stage2_artifacts, + input.stage3_artifacts, + input.stage4_artifacts, + input.stage5_proof, + &tampered_stage6_proof, + ), + ); + }; + + assert_batched_sumcheck_tampers!(assert_tamper_rejected, input.proof, 0, "Stage 6 verifier"); + + let assert_opening_input_tamper_rejected = + |tampered_openings: &[JoltStageOpeningInputValue], message: &str| { + let mut generated_tamper_transcript = input.verifier_transcript.clone(); + let generated_tamper_result = jolt_verifier::verify_stage6_with_program( + input.verifier_plan, + input.proof, + tampered_openings, + Some(input.data), + &mut generated_tamper_transcript, + ); + assert!(generated_tamper_result.is_err(), "generated {message}"); + + let generated_jolt_proof = jolt_prover::jolt_proof_through_stage6( + &input.commitment_verifier_trace.commitments, + input.stage1_artifacts, + input.stage2_artifacts, + input.stage3_artifacts, + input.stage4_artifacts, + input.stage5_proof, + input.proof, + ); + let mut tampered_inputs = input.jolt_inputs; + tampered_inputs.stage6_openings = tampered_openings; + assert_generated_jolt_prefix_tamper_rejected( + input.preamble, + &generated_jolt_proof, + tampered_inputs, + input.programs, + message, + ); + }; + + assert_opening_input_tampers( + input.openings, + "Stage 6", + assert_opening_input_tamper_rejected, + ); +} + +pub(crate) struct Stage7TamperInput<'a> { + pub(crate) preamble: &'a CoreMuldivCommitmentFixture, + pub(crate) commitment_verifier_trace: &'a BoltCommitmentTrace, + pub(crate) verifier_transcript: &'a BoltTranscript, + pub(crate) verifier_plan: &'static Stage7VerifierProgramPlan, + pub(crate) proof: &'a JoltStageProof, + pub(crate) openings: &'a [JoltStageOpeningInputValue], + pub(crate) stage1_artifacts: &'a Stage1ExecutionArtifacts, + pub(crate) stage2_artifacts: &'a Stage2ExecutionArtifacts, + pub(crate) stage3_artifacts: &'a Stage3ExecutionArtifacts, + pub(crate) stage4_artifacts: &'a Stage4ExecutionArtifacts, + pub(crate) stage5_proof: &'a JoltStageProof, + pub(crate) stage6_proof: &'a JoltStageProof, + pub(crate) jolt_inputs: jolt_verifier::JoltVerifierInputs<'a>, + pub(crate) programs: jolt_verifier::JoltVerifierPrograms, +} + +pub(crate) fn assert_bolt_stage7_tamper_rejected(input: Stage7TamperInput<'_>) { + let assert_tamper_rejected = |tampered_stage7_proof: JoltStageProof, message: &str| { + assert_generated_stage_and_prefix_tamper_rejected!( + input.preamble, + message, + input.jolt_inputs, + input.programs, + { + let mut transcript = input.verifier_transcript.clone(); + jolt_verifier::verify_stage7_with_program( + input.verifier_plan, + &tampered_stage7_proof, + input.openings, + &mut transcript, + ) + }, + jolt_prover::jolt_proof_through_stage7( + &input.commitment_verifier_trace.commitments, + input.stage1_artifacts, + input.stage2_artifacts, + input.stage3_artifacts, + input.stage4_artifacts, + input.stage5_proof, + input.stage6_proof, + &tampered_stage7_proof, + ), + ); + }; + + assert_batched_sumcheck_tampers!(assert_tamper_rejected, input.proof, 0, "Stage 7 verifier"); + + let assert_opening_input_tamper_rejected = + |tampered_openings: &[JoltStageOpeningInputValue], message: &str| { + let mut generated_tamper_transcript = input.verifier_transcript.clone(); + let generated_tamper_result = jolt_verifier::verify_stage7_with_program( + input.verifier_plan, + input.proof, + tampered_openings, + &mut generated_tamper_transcript, + ); + assert!(generated_tamper_result.is_err(), "generated {message}"); + + let generated_jolt_proof = jolt_prover::jolt_proof_through_stage7( + &input.commitment_verifier_trace.commitments, + input.stage1_artifacts, + input.stage2_artifacts, + input.stage3_artifacts, + input.stage4_artifacts, + input.stage5_proof, + input.stage6_proof, + input.proof, + ); + let mut tampered_inputs = input.jolt_inputs; + tampered_inputs.stage7_openings = tampered_openings; + assert_generated_jolt_prefix_tamper_rejected( + input.preamble, + &generated_jolt_proof, + tampered_inputs, + input.programs, + message, + ); + }; + + assert_opening_input_tampers( + input.openings, + "Stage 7", + assert_opening_input_tamper_rejected, + ); +} + +pub(crate) struct Stage345TamperInput<'a> { + pub(crate) preamble: &'a CoreMuldivCommitmentFixture, + pub(crate) commitment_verifier_trace: &'a BoltCommitmentTrace, + pub(crate) stage1_artifacts: &'a Stage1ExecutionArtifacts, + pub(crate) stage2_artifacts: &'a Stage2ExecutionArtifacts, + pub(crate) stage3_artifacts: &'a Stage3ExecutionArtifacts, + pub(crate) stage4_artifacts: &'a Stage4ExecutionArtifacts, + pub(crate) generated_stage3_verifier_plan: &'static Stage3VerifierProgramPlan, + pub(crate) generated_stage3_openings: &'a [JoltStageOpeningInputValue], + pub(crate) generated_stage3_start_transcript: &'a BoltTranscript, + pub(crate) generated_stage4_verifier_plan: &'static Stage4VerifierProgramPlan, + pub(crate) generated_stage4_start_transcript: &'a BoltTranscript, + pub(crate) generated_stage5_verifier_plan: &'static Stage5VerifierProgramPlan, + pub(crate) generated_stage4_openings: &'a [JoltStageOpeningInputValue], + pub(crate) generated_stage5_openings: &'a [JoltStageOpeningInputValue], + pub(crate) generated_stage5_proof: &'a JoltStageProof, + pub(crate) generated_stage5_start_transcript: &'a BoltTranscript, + pub(crate) generated_jolt_inputs: jolt_verifier::JoltVerifierInputs<'a>, + pub(crate) generated_programs: jolt_verifier::JoltVerifierPrograms, +} + +pub(crate) fn assert_bolt_stage3_4_5_tamper_rejected(input: Stage345TamperInput<'_>) { + let Stage345TamperInput { + preamble: fixture, + commitment_verifier_trace, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + generated_stage3_verifier_plan, + generated_stage3_openings, + generated_stage3_start_transcript, + generated_stage4_verifier_plan, + generated_stage4_start_transcript, + generated_stage5_verifier_plan, + generated_stage4_openings, + generated_stage5_openings, + generated_stage5_proof, + generated_stage5_start_transcript, + generated_jolt_inputs, + generated_programs, + } = input; + let assert_stage3_tamper_rejected = + |tampered_stage3_artifacts: Stage3ExecutionArtifacts, message: &str| { + assert_generated_stage_and_prefix_tamper_rejected!( + fixture, + message, + generated_jolt_inputs.through_stage5(), + generated_programs, + { + let mut transcript = generated_stage3_start_transcript.clone(); + let proof = jolt_prover::stage3_proof(&tampered_stage3_artifacts); + jolt_verifier::verify_stage3_with_program( + generated_stage3_verifier_plan, + &proof, + generated_stage3_openings, + &mut transcript, + ) + }, + jolt_prover::jolt_proof_through_stage5( + &commitment_verifier_trace.commitments, + stage1_artifacts, + stage2_artifacts, + &tampered_stage3_artifacts, + stage4_artifacts, + generated_stage5_proof, + ), + ); + }; + + assert_batched_sumcheck_tampers!( + assert_stage3_tamper_rejected, + stage3_artifacts, + 0, + "Bolt Stage 3 verifier" + ); + + let assert_stage4_tamper_rejected = + |tampered_stage4_artifacts: Stage4ExecutionArtifacts, message: &str| { + assert_generated_stage_and_prefix_tamper_rejected!( + fixture, + message, + generated_jolt_inputs.through_stage5(), + generated_programs, + { + let mut transcript = generated_stage4_start_transcript.clone(); + let proof = jolt_prover::stage4_proof(&tampered_stage4_artifacts); + jolt_verifier::verify_stage4_with_program( + generated_stage4_verifier_plan, + &proof, + generated_stage4_openings, + &mut transcript, + ) + }, + jolt_prover::jolt_proof_through_stage5( + &commitment_verifier_trace.commitments, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + &tampered_stage4_artifacts, + generated_stage5_proof, + ), + ); + }; + + assert_batched_sumcheck_tampers!( + assert_stage4_tamper_rejected, + stage4_artifacts, + 0, + "Stage 4 verifier" + ); + + let assert_stage5_tamper_rejected = |tampered_stage5_proof: JoltStageProof, message: &str| { + assert_generated_stage_and_prefix_tamper_rejected!( + fixture, + message, + generated_jolt_inputs.through_stage5(), + generated_programs, + { + let mut transcript = generated_stage5_start_transcript.clone(); + jolt_verifier::verify_stage5_with_program( + generated_stage5_verifier_plan, + &tampered_stage5_proof, + generated_stage5_openings, + &mut transcript, + ) + }, + jolt_prover::jolt_proof_through_stage5( + &commitment_verifier_trace.commitments, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + &tampered_stage5_proof, + ), + ); + }; + + assert_batched_sumcheck_tampers!( + assert_stage5_tamper_rejected, + generated_stage5_proof, + 0, + "Stage 5 verifier" + ); +} diff --git a/crates/jolt-equivalence/tests/bolt_commitment.rs b/crates/jolt-equivalence/tests/bolt_commitment.rs new file mode 100644 index 0000000000..544d7a3f01 --- /dev/null +++ b/crates/jolt-equivalence/tests/bolt_commitment.rs @@ -0,0 +1,317 @@ +//! Commitment-phase transcript bridge between Bolt IR and jolt-core. +//! +//! This keeps the first oracle intentionally narrow: Bolt owns the commitment +//! ordering through its CPU IR, while jolt-core owns the reference +//! `append_serializable` transcript semantics for the same Dory commitments. + +#![expect( + clippy::expect_used, + reason = "integration gates should fail fast when positive prover/verifier setup fails" +)] + +use jolt_equivalence::bolt_oracle::assert_bolt_full_real_trace_self_parity; +use jolt_equivalence::bolt_programs::{ + bolt_commitment_programs, bolt_commitment_programs_with_params, + bolt_stage1_programs_with_params, bolt_stage2_programs_with_params, +}; +use jolt_equivalence::checkpoint::{assert_state_history_match, assert_transcripts_match}; +use jolt_equivalence::checks::{ + assert_core_stage2_uniskip_proof_matches_bolt, assert_stage1_uniskip_extended_evals_match_core, +}; +use jolt_equivalence::commitment_oracle::{ + core_commitment_trace, run_generated_bolt_commitment_pair_with_cpu_programs, + run_generated_synthetic_bolt_commitment_pair_with_cpu_programs, + transcript_with_bolt_commitment_trace, +}; +use jolt_equivalence::core_conversion::{core_commitment_log, core_commitments_transcript_log}; +use jolt_equivalence::core_oracle::{ + assert_core_accepts_bolt_stage1, assert_core_accepts_bolt_stage2, + core_muldiv_commitment_fixture, +}; +use jolt_equivalence::plan_adapters::{ + leak_generated_stage1_verifier_program, leak_generated_stage2_verifier_program, + leak_stage1_program, leak_stage2_program, +}; +use jolt_equivalence::tamper::{ + assert_bolt_chain_verifier_accepts_stage2_product_uniskip, assert_bolt_stage1_tamper_rejected, + assert_bolt_stage2_batched_tamper_rejected, BoltStage2ChainVerifierInput, + Stage2BatchedTamperInput, +}; +use jolt_equivalence::ArtifactSource; + +#[test] +fn bolt_commitment_transcript_matches_jolt_core_append_serializable() { + let (prover_program, verifier_program) = bolt_commitment_programs(); + let (prover_trace, verifier_trace) = + run_generated_synthetic_bolt_commitment_pair_with_cpu_programs( + &prover_program, + &verifier_program, + ); + let core_log = core_commitment_log( + prover_trace + .records + .iter() + .zip(&prover_trace.commitments) + .map(|(record, commitment)| (record.artifact.as_str(), commitment.as_ref())), + &prover_program.transcript_steps, + ); + + let prover_run = prover_trace.equivalence_run(ArtifactSource::Bolt); + let verifier_run = verifier_trace.equivalence_run(ArtifactSource::Bolt); + assert_eq!(prover_run.commitments, verifier_run.commitments); + assert_transcripts_match(&prover_trace.log, &verifier_trace.log); + assert_eq!(prover_run.transcript, verifier_run.transcript); + assert_transcripts_match(&core_log, &prover_trace.log); +} + +#[test] +fn bolt_commitment_real_muldiv_trace_matches_jolt_core() { + let fixture = core_muldiv_commitment_fixture(); + let (prover_program, verifier_program) = bolt_commitment_programs_with_params(&fixture.params); + + let (prover_trace, verifier_trace) = run_generated_bolt_commitment_pair_with_cpu_programs( + &prover_program, + &verifier_program, + &fixture.pcs_setup, + &fixture.cycle_inputs, + ); + + assert_eq!( + prover_trace.committed_prefix(fixture.commitments.len()), + core_commitment_trace(&fixture.commitments, "jolt.main_witness_commitments"), + ); + + let core_log = + core_commitments_transcript_log(&fixture.commitments, &prover_program.transcript_steps); + let prover_run = prover_trace.equivalence_run(ArtifactSource::Bolt); + let verifier_run = verifier_trace.equivalence_run(ArtifactSource::Bolt); + assert_eq!(prover_run.commitments, verifier_run.commitments); + assert_transcripts_match(&prover_trace.log, &verifier_trace.log); + assert_eq!(prover_run.transcript, verifier_run.transcript); + assert_transcripts_match(&core_log, &prover_trace.log); +} + +#[test] +fn bolt_commitment_stage1_real_muldiv_parity_checks() { + let fixture = core_muldiv_commitment_fixture(); + let (commitment_prover_program, commitment_verifier_program) = + bolt_commitment_programs_with_params(&fixture.params); + let (stage1_prover_program, stage1_verifier_program) = + bolt_stage1_programs_with_params(&fixture.params); + + let (commitment_prover_trace, commitment_verifier_trace) = + run_generated_bolt_commitment_pair_with_cpu_programs( + &commitment_prover_program, + &commitment_verifier_program, + &fixture.pcs_setup, + &fixture.cycle_inputs, + ); + + let stage1_prover_plan = leak_stage1_program(&stage1_prover_program); + let generated_stage1_verifier_plan = + leak_generated_stage1_verifier_program(&stage1_verifier_program); + let r1cs_key = fixture.r1cs_key(); + let data = fixture.stage1_outer_rv64_data(&r1cs_key); + let generic_data = fixture.stage1_outer_r1cs_data(&r1cs_key); + + let mut prover_transcript = + transcript_with_bolt_commitment_trace(&fixture, &commitment_prover_trace); + let stage1_artifacts = jolt_prover::prove_stage1_outer_with_witness_inputs( + stage1_prover_plan, + r1cs_key.num_cycle_vars(), + &data, + &mut prover_transcript, + ) + .expect("Bolt Stage 1 prover succeeds"); + assert_stage1_uniskip_extended_evals_match_core( + &fixture.proof, + &data, + &generic_data, + &stage1_artifacts, + ); + + let stage1_proof = stage1_artifacts.clone().into(); + let mut verifier_transcript = + transcript_with_bolt_commitment_trace(&fixture, &commitment_verifier_trace); + let stage1_start_transcript = verifier_transcript.clone(); + let verified_stage1 = jolt_prover::replay_stage1_outer_proof_with_program( + stage1_prover_plan, + &stage1_proof, + &mut verifier_transcript, + ) + .expect("Bolt Stage 1 verifier accepts prover proof"); + + assert_eq!( + stage1_artifacts.sumchecks.len(), + verified_stage1.sumchecks.len() + ); + assert_transcripts_match(prover_transcript.log(), verifier_transcript.log()); + + assert_core_accepts_bolt_stage1(&fixture, &stage1_artifacts); + assert_bolt_stage1_tamper_rejected( + stage1_prover_plan, + generated_stage1_verifier_plan, + &stage1_proof, + &stage1_start_transcript, + ); +} + +#[test] +fn bolt_stage2_product_uniskip_real_muldiv_matches_jolt_core() { + let fixture = core_muldiv_commitment_fixture(); + let (commitment_prover_program, commitment_verifier_program) = + bolt_commitment_programs_with_params(&fixture.params); + let (stage1_prover_program, _) = bolt_stage1_programs_with_params(&fixture.params); + let (stage2_prover_program, stage2_verifier_program) = + bolt_stage2_programs_with_params(&fixture.params); + + let (commitment_prover_trace, commitment_verifier_trace) = + run_generated_bolt_commitment_pair_with_cpu_programs( + &commitment_prover_program, + &commitment_verifier_program, + &fixture.pcs_setup, + &fixture.cycle_inputs, + ); + + let stage1_prover_plan = leak_stage1_program(&stage1_prover_program); + let r1cs_key = fixture.r1cs_key(); + let data = fixture.stage1_outer_rv64_data(&r1cs_key); + + let mut bolt_transcript = + transcript_with_bolt_commitment_trace(&fixture, &commitment_prover_trace); + let stage1_artifacts = jolt_prover::prove_stage1_outer_with_witness_inputs( + stage1_prover_plan, + r1cs_key.num_cycle_vars(), + &data, + &mut bolt_transcript, + ) + .expect("Bolt Stage 1 prover succeeds"); + + let stage2_prover_plan = leak_stage2_program(&stage2_prover_program); + let stage2_openings = + jolt_prover::stage2_opening_inputs_from_artifacts(stage2_prover_plan, &stage1_artifacts) + .expect("generated prover derives Stage 2 opening inputs from artifacts"); + let ram_data = fixture.stage2_ram_data(); + let generated_stage2_verifier_plan = + leak_generated_stage2_verifier_program(&stage2_verifier_program); + let stage2_artifacts = jolt_prover::prove_stage2_with_witness_inputs( + stage2_prover_plan, + &stage2_openings, + &fixture.product_virtual_cycles, + &fixture.instruction_lookup_cycles, + &ram_data, + &mut bolt_transcript, + ) + .expect("Bolt Stage 2 prover succeeds"); + + assert_core_stage2_uniskip_proof_matches_bolt(&fixture.proof, &stage2_artifacts.sumchecks[0]); + assert_bolt_chain_verifier_accepts_stage2_product_uniskip(BoltStage2ChainVerifierInput { + fixture: &fixture, + commitment_verifier_trace: &commitment_verifier_trace, + stage1_prover_plan, + stage2_prover_plan, + generated_stage2_verifier_plan, + stage1_artifacts: &stage1_artifacts, + stage2_artifacts: &stage2_artifacts, + ram_data: &ram_data, + prover_transcript: &bolt_transcript, + }); + + assert_eq!( + commitment_prover_trace.commitments, + commitment_verifier_trace.commitments + ); +} + +#[test] +fn bolt_stage2_batched_real_muldiv_self_parity() { + let fixture = core_muldiv_commitment_fixture(); + let (commitment_prover_program, commitment_verifier_program) = + bolt_commitment_programs_with_params(&fixture.params); + let (stage1_prover_program, _) = bolt_stage1_programs_with_params(&fixture.params); + let (stage2_prover_program, stage2_verifier_program) = + bolt_stage2_programs_with_params(&fixture.params); + + let (commitment_prover_trace, commitment_verifier_trace) = + run_generated_bolt_commitment_pair_with_cpu_programs( + &commitment_prover_program, + &commitment_verifier_program, + &fixture.pcs_setup, + &fixture.cycle_inputs, + ); + + let stage1_prover_plan = leak_stage1_program(&stage1_prover_program); + let r1cs_key = fixture.r1cs_key(); + let data = fixture.stage1_outer_rv64_data(&r1cs_key); + + let mut prover_transcript = + transcript_with_bolt_commitment_trace(&fixture, &commitment_prover_trace); + let stage1_artifacts = jolt_prover::prove_stage1_outer_with_witness_inputs( + stage1_prover_plan, + r1cs_key.num_cycle_vars(), + &data, + &mut prover_transcript, + ) + .expect("Bolt Stage 1 prover succeeds"); + + let stage2_prover_plan = leak_stage2_program(&stage2_prover_program); + let stage2_openings = + jolt_prover::stage2_opening_inputs_from_artifacts(stage2_prover_plan, &stage1_artifacts) + .expect("generated prover derives Stage 2 opening inputs from artifacts"); + let ram_data = fixture.stage2_ram_data(); + let generated_stage2_verifier_plan = + leak_generated_stage2_verifier_program(&stage2_verifier_program); + let stage2_artifacts = jolt_prover::prove_stage2_with_witness_inputs( + stage2_prover_plan, + &stage2_openings, + &fixture.product_virtual_cycles, + &fixture.instruction_lookup_cycles, + &ram_data, + &mut prover_transcript, + ) + .expect("Bolt Stage 2 prover succeeds"); + + let mut verifier_transcript = + transcript_with_bolt_commitment_trace(&fixture, &commitment_verifier_trace); + let stage1_proof = stage1_artifacts.clone().into(); + let verified_stage1 = jolt_prover::replay_stage1_outer_proof_with_program( + stage1_prover_plan, + &stage1_proof, + &mut verifier_transcript, + ) + .expect("Bolt Stage 1 verifier accepts"); + let stage2_verifier_openings = + jolt_prover::stage2_opening_inputs_from_artifacts(stage2_prover_plan, &verified_stage1) + .expect("generated prover derives Stage 2 verifier opening inputs from artifacts"); + let stage2_proof = stage2_artifacts.clone().into(); + let stage2_start_transcript = verifier_transcript.clone(); + let verified_stage2 = jolt_prover::replay_stage2_proof_with_program( + stage2_prover_plan, + &stage2_proof, + &stage2_verifier_openings, + Some(&ram_data), + &mut verifier_transcript, + ) + .expect("Bolt Stage 2 verifier accepts"); + + assert_eq!( + stage2_artifacts.sumchecks.len(), + verified_stage2.sumchecks.len() + ); + assert_state_history_match(prover_transcript.log(), verifier_transcript.log()); + assert_core_accepts_bolt_stage2(&fixture, &stage1_artifacts, &stage2_artifacts); + + assert_bolt_stage2_batched_tamper_rejected(Stage2BatchedTamperInput { + stage2_prover_plan, + generated_stage2_verifier_plan, + stage2_start_transcript: &stage2_start_transcript, + stage2_openings: &stage2_verifier_openings, + stage2_artifacts: &stage2_artifacts, + ram_data: &ram_data, + }); +} + +#[test] +fn bolt_stage3_batched_real_muldiv_self_parity() { + assert_bolt_full_real_trace_self_parity(core_muldiv_commitment_fixture(), false); +} diff --git a/crates/jolt-equivalence/tests/bolt_perf.rs b/crates/jolt-equivalence/tests/bolt_perf.rs new file mode 100644 index 0000000000..9aff818425 --- /dev/null +++ b/crates/jolt-equivalence/tests/bolt_perf.rs @@ -0,0 +1,20 @@ +//! Dedicated perf-oracle gates for real-data core-vs-Bolt runs. + +use jolt_equivalence::bolt_oracle::assert_bolt_full_real_trace_self_parity; +use jolt_equivalence::core_oracle::core_sha2_chain_commitment_fixture; +use jolt_equivalence::perf::maybe_setup_perf_trace; +use jolt_inlines_sha2 as _; + +#[test] +#[ignore = "run by the Bolt perf-oracle CI workflow"] +fn bolt_sha2_chain_2_16_core_vs_bolt_perf_oracle() { + maybe_setup_perf_trace("bolt_sha2_chain_2_16_core_vs_bolt"); + assert_bolt_full_real_trace_self_parity(core_sha2_chain_commitment_fixture(16), true); +} + +#[test] +#[ignore = "run by the Bolt perf-oracle CI workflow"] +fn bolt_sha2_chain_2_20_core_vs_bolt_perf_oracle() { + maybe_setup_perf_trace("bolt_sha2_chain_2_20_core_vs_bolt"); + assert_bolt_full_real_trace_self_parity(core_sha2_chain_commitment_fixture(20), true); +} diff --git a/crates/jolt-equivalence/tests/generated_role_crates.rs b/crates/jolt-equivalence/tests/generated_role_crates.rs new file mode 100644 index 0000000000..bb166e401e --- /dev/null +++ b/crates/jolt-equivalence/tests/generated_role_crates.rs @@ -0,0 +1,142 @@ +use jolt_core::transcripts::{ + Blake2bTranscript as CoreBlake2bTranscript, Transcript as CoreTranscript, +}; +use jolt_core::zkvm::lookup_table::LookupTables as CoreLookupTables; +use jolt_field::Fr; +use jolt_lookup_tables::LookupTableKind; +use jolt_transcript::{Blake2bTranscript, Transcript as GeneratedTranscript}; +use strum::{EnumCount, IntoEnumIterator}; + +#[test] +fn generated_role_crates_expose_matching_stage_prefix() { + let prover_stages = jolt_prover::generated_stage_names().collect::>(); + let verifier_stages = jolt_verifier::generated_stage_names().collect::>(); + + assert_eq!(prover_stages, verifier_stages); + assert_eq!( + prover_stages, + vec![ + "commitment", + "stage1_outer", + "stage2", + "stage3", + "stage4", + "stage5", + "stage6", + "stage7", + "stage8" + ] + ); + + assert!(!jolt_prover::stages::commitment::TRANSCRIPT_PLAN.is_empty()); + assert!(!jolt_verifier::stages::commitment::TRANSCRIPT_PLAN.is_empty()); + macro_rules! assert_stage_drivers { + ($(($prover:expr, $verifier:expr)),+ $(,)?) => { + $( + assert!(!$prover.drivers.is_empty()); + assert!(!$verifier.drivers.is_empty()); + )+ + }; + } + assert_stage_drivers!( + ( + jolt_prover::stages::stage1_outer::STAGE1_PROGRAM, + jolt_verifier::stages::stage1_outer::STAGE1_PROGRAM + ), + ( + jolt_prover::stages::stage2::STAGE2_PROGRAM, + jolt_verifier::stages::stage2::STAGE2_PROGRAM + ), + ( + jolt_prover::stages::stage3::STAGE3_PROGRAM, + jolt_verifier::stages::stage3::STAGE3_PROGRAM + ), + ( + jolt_prover::stages::stage4::STAGE4_PROGRAM, + jolt_verifier::stages::stage4::STAGE4_PROGRAM + ), + ( + jolt_prover::stages::stage5::STAGE5_PROGRAM, + jolt_verifier::stages::stage5::STAGE5_PROGRAM + ), + ( + jolt_prover::stages::stage6::STAGE6_PROGRAM, + jolt_verifier::stages::stage6::STAGE6_PROGRAM + ), + ( + jolt_prover::stages::stage7::STAGE7_PROGRAM, + jolt_verifier::stages::stage7::STAGE7_PROGRAM + ), + ); + assert!(!jolt_prover::stages::stage8::STAGE8_PROGRAM + .opening_claims + .is_empty()); + assert!(!jolt_verifier::stages::stage8::STAGE8_PROGRAM + .opening_claims + .is_empty()); + assert_eq!( + jolt_prover::stages::stage8::STAGE8_PROGRAM.pcs_proof.mode, + "open" + ); + assert_eq!( + jolt_verifier::stages::stage8::STAGE8_PROGRAM.pcs_proof.mode, + "verify" + ); + let _proof = jolt_verifier::JoltProof { + commitments: Vec::new(), + stage1_outer: jolt_verifier::JoltStageProof::default(), + stage2: jolt_verifier::JoltStageProof::default(), + stage3: jolt_verifier::JoltStageProof::default(), + stage4: jolt_verifier::JoltStageProof::default(), + stage5: jolt_verifier::JoltStageProof::default(), + stage6: jolt_verifier::JoltStageProof::default(), + stage7: jolt_verifier::JoltStageProof::default(), + evaluation: None, + }; +} + +#[test] +fn monolithic_transcript_challenges_match_core_full_field_path() { + let mut core = ::new(b"Jolt"); + let mut generated = as GeneratedTranscript>::new(b"Jolt"); + + let core_challenge: ark_bn254::Fr = core.challenge_scalar_optimized::().into(); + let generated_challenge: ark_bn254::Fr = generated.challenge().into(); + + assert_eq!(core_challenge, generated_challenge); + assert_eq!(core.state, *generated.state()); +} + +#[test] +fn modular_lookup_table_list_matches_core_order() { + const XLEN: usize = 64; + + let modular_tables = LookupTableKind::::all(); + assert_eq!(modular_tables.len(), CoreLookupTables::::COUNT); + assert_eq!( + modular_tables.len(), + as EnumCount>::COUNT + ); + + for (index, (core_table, modular_table)) in CoreLookupTables::::iter() + .zip(modular_tables) + .enumerate() + { + let core_name: &'static str = core_table.into(); + assert_eq!( + modular_table.name(), + core_name, + "table name at index {index}" + ); + assert_eq!( + LookupTableKind::index(&modular_table), + index, + "modular table index for {core_name}" + ); + assert_eq!( + CoreLookupTables::enum_index(&core_table), + index, + "core table index for {core_name}" + ); + } +} diff --git a/crates/jolt-field/benches/field_arith.rs b/crates/jolt-field/benches/field_arith.rs index cfe784f76c..2125b1035a 100644 --- a/crates/jolt-field/benches/field_arith.rs +++ b/crates/jolt-field/benches/field_arith.rs @@ -3,14 +3,14 @@ use std::hint::black_box; use criterion::{criterion_group, criterion_main, Criterion}; -use jolt_field::{FixedBytes, Fr, MulPrimitiveInt, RandomSampling}; +use jolt_field::{Field, Fr}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; fn bench_field_mul(c: &mut Criterion) { let mut rng = ChaCha20Rng::seed_from_u64(0); - let a: Fr = ::random(&mut rng); - let b: Fr = ::random(&mut rng); + let a: Fr = Field::random(&mut rng); + let b: Fr = Field::random(&mut rng); c.bench_function("Fr * Fr", |bench| { bench.iter(|| black_box(a) * black_box(b)); @@ -19,35 +19,35 @@ fn bench_field_mul(c: &mut Criterion) { fn bench_mul_u64(c: &mut Criterion) { let mut rng = ChaCha20Rng::seed_from_u64(1); - let a: Fr = ::random(&mut rng); + let a: Fr = Field::random(&mut rng); let n = 0xDEAD_BEEF_CAFE_BABEu64; c.bench_function("Fr::mul_u64", |bench| { - bench.iter(|| ::mul_u64(black_box(&a), black_box(n))); + bench.iter(|| ::mul_u64(black_box(&a), black_box(n))); }); } fn bench_mul_u128(c: &mut Criterion) { let mut rng = ChaCha20Rng::seed_from_u64(2); - let a: Fr = ::random(&mut rng); + let a: Fr = Field::random(&mut rng); let n = 0xDEAD_BEEF_CAFE_BABE_1234_5678_9ABC_DEF0u128; c.bench_function("Fr::mul_u128", |bench| { - bench.iter(|| ::mul_u128(black_box(&a), black_box(n))); + bench.iter(|| ::mul_u128(black_box(&a), black_box(n))); }); } fn bench_to_from_bytes(c: &mut Criterion) { let mut rng = ChaCha20Rng::seed_from_u64(4); - let a: Fr = ::random(&mut rng); - let bytes = a.to_bytes_array(); + let a: Fr = Field::random(&mut rng); + let bytes = a.to_bytes(); c.bench_function("Fr::to_bytes", |bench| { - bench.iter(|| black_box(a).to_bytes_array()); + bench.iter(|| black_box(a).to_bytes()); }); c.bench_function("Fr::from_bytes", |bench| { - bench.iter(|| Fr::from_bytes_array(black_box(&bytes))); + bench.iter(|| ::from_bytes(black_box(&bytes))); }); } diff --git a/crates/jolt-field/fuzz/fuzz_targets/field_arith.rs b/crates/jolt-field/fuzz/fuzz_targets/field_arith.rs index 4e71b313d7..e8dd4901e5 100644 --- a/crates/jolt-field/fuzz/fuzz_targets/field_arith.rs +++ b/crates/jolt-field/fuzz/fuzz_targets/field_arith.rs @@ -1,5 +1,5 @@ #![no_main] -use jolt_field::{Fr, FromPrimitiveInt, Invertible, ReducingBytes}; +use jolt_field::{Field, Fr}; use libfuzzer_sys::fuzz_target; use num_traits::Zero; @@ -7,8 +7,8 @@ fuzz_target!(|data: &[u8]| { if data.len() < 64 { return; } - let a = ::from_le_bytes_mod_order(&data[..32]); - let b = ::from_le_bytes_mod_order(&data[32..64]); + let a = ::from_bytes(&data[..32]); + let b = ::from_bytes(&data[32..64]); // Arithmetic operations must not panic let sum = a + b; diff --git a/crates/jolt-field/fuzz/fuzz_targets/from_bytes.rs b/crates/jolt-field/fuzz/fuzz_targets/from_bytes.rs index d8fcbf633d..6cafc11ef2 100644 --- a/crates/jolt-field/fuzz/fuzz_targets/from_bytes.rs +++ b/crates/jolt-field/fuzz/fuzz_targets/from_bytes.rs @@ -1,14 +1,14 @@ #![no_main] -use jolt_field::{FixedBytes, Fr, ReducingBytes}; +use jolt_field::{Field, Fr}; use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { // from_bytes should never panic on arbitrary input - let a = ::from_le_bytes_mod_order(data); + let a = ::from_bytes(data); // Round-trip: from_bytes → to_bytes → from_bytes must be stable - let bytes = a.to_bytes_array(); - let b = ::from_le_bytes_mod_order(&bytes); - let bytes2 = b.to_bytes_array(); + let bytes = a.to_bytes(); + let b = ::from_bytes(&bytes); + let bytes2 = b.to_bytes(); assert_eq!(bytes, bytes2, "from_bytes round-trip is not stable"); }); diff --git a/crates/jolt-field/fuzz/fuzz_targets/wide_accumulator_fmadd.rs b/crates/jolt-field/fuzz/fuzz_targets/wide_accumulator_fmadd.rs index 84c149a022..58ca65edc8 100644 --- a/crates/jolt-field/fuzz/fuzz_targets/wide_accumulator_fmadd.rs +++ b/crates/jolt-field/fuzz/fuzz_targets/wide_accumulator_fmadd.rs @@ -1,5 +1,5 @@ #![no_main] -use jolt_field::{AdditiveAccumulator, Fr, ReducingBytes, RingAccumulator, WideAccumulator}; +use jolt_field::{Field, FieldAccumulator, Fr, WideAccumulator}; use libfuzzer_sys::fuzz_target; use num_traits::Zero; @@ -16,9 +16,8 @@ fuzz_target!(|data: &[u8]| { let pairs = data.len() / 64; for i in 0..pairs { let offset = i * 64; - let a = ::from_le_bytes_mod_order(&data[offset..offset + 32]); - let b = - ::from_le_bytes_mod_order(&data[offset + 32..offset + 64]); + let a = ::from_bytes(&data[offset..offset + 32]); + let b = ::from_bytes(&data[offset + 32..offset + 64]); acc.fmadd(a, b); naive_sum += a * b; diff --git a/crates/jolt-field/fuzz/fuzz_targets/wide_accumulator_merge.rs b/crates/jolt-field/fuzz/fuzz_targets/wide_accumulator_merge.rs index d82049deea..e43b3a1233 100644 --- a/crates/jolt-field/fuzz/fuzz_targets/wide_accumulator_merge.rs +++ b/crates/jolt-field/fuzz/fuzz_targets/wide_accumulator_merge.rs @@ -1,5 +1,5 @@ #![no_main] -use jolt_field::{AdditiveAccumulator, Fr, ReducingBytes, RingAccumulator, WideAccumulator}; +use jolt_field::{Field, FieldAccumulator, Fr, WideAccumulator}; use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { @@ -17,9 +17,8 @@ fuzz_target!(|data: &[u8]| { for i in 0..pairs { let offset = i * 64; - let a = ::from_le_bytes_mod_order(&data[offset..offset + 32]); - let b = - ::from_le_bytes_mod_order(&data[offset + 32..offset + 64]); + let a = ::from_bytes(&data[offset..offset + 32]); + let b = ::from_bytes(&data[offset + 32..offset + 64]); if i < split { acc1.fmadd(a, b); diff --git a/crates/jolt-field/src/accumulator.rs b/crates/jolt-field/src/accumulator.rs index 74143bdb55..b2adc8edef 100644 --- a/crates/jolt-field/src/accumulator.rs +++ b/crates/jolt-field/src/accumulator.rs @@ -1,7 +1,7 @@ -//! Deferred-reduction accumulators. +//! Deferred-reduction accumulator for fused multiply-add. //! //! In sumcheck inner loops, many products are summed before the final result -//! is needed. [`RingAccumulator`] lets implementations defer modular reduction +//! is needed. [`FieldAccumulator`] lets implementations defer modular reduction //! by accumulating in wider integer types, reducing once at the end. This //! amortizes the expensive reduction across hundreds of multiply-add steps. //! @@ -9,31 +9,16 @@ //! - `WideAccumulator` (BN254, in `arkworks/`) — 9-limb wide integer accumulator //! that defers Montgomery reduction. -use crate::{AdditiveGroup, FromPrimitiveInt, RingCore}; +use crate::Field; use num_traits::One; -/// Accumulates additive values with potentially deferred reduction. -pub trait AdditiveAccumulator: Default + Copy + Send + Sync { - /// The element type this accumulator reduces to. - type Element: AdditiveGroup; - - /// Adds one element into the accumulator. - fn add(&mut self, value: Self::Element); - - /// Merge another accumulator's partial sum into this one. - fn merge(&mut self, other: Self); - - /// Finalize: reduce the accumulated value to an element. - fn reduce(self) -> Self::Element; -} - /// Accumulates products with potentially deferred modular reduction. /// /// The hot loop pattern `acc += a * b` repeated hundreds of times per output /// slot dominates the CPU prover. Standard field arithmetic reduces mod p /// after every multiply and every add. Implementations for specific fields /// (e.g., BN254 Fr) can instead accumulate unreduced wide products and -/// reduce once at the end via [`AdditiveAccumulator::reduce`]. +/// reduce once at the end via [`reduce`](Self::reduce). /// /// # Invariants /// @@ -42,63 +27,104 @@ pub trait AdditiveAccumulator: Default + Copy + Send + Sync { /// partial result (used for parallel reduction). /// - [`reduce`](Self::reduce) must return the field element equal to the /// accumulated sum of products. -pub trait RingAccumulator: AdditiveAccumulator -where - Self::Element: RingCore + FromPrimitiveInt, -{ +pub trait FieldAccumulator: Default + Copy + Send + Sync { + /// The field type this accumulator operates over. + type Field: crate::Field; + /// Fused multiply-add: `self += a * b` without intermediate reduction. - fn fmadd(&mut self, a: Self::Element, b: Self::Element); + fn fmadd(&mut self, a: Self::Field, b: Self::Field); /// Fused multiply-add with a `u8` scalar: `self += a * F::from(b)`. /// /// Implementations may override for optimized small-scalar multiplication /// (e.g., 4×1 limb schoolbook instead of 4×4). #[inline] - fn fmadd_u8(&mut self, a: Self::Element, b: u8) { - self.fmadd(a, Self::Element::from_u8(b)); + fn fmadd_u8(&mut self, a: Self::Field, b: u8) { + self.fmadd(a, Self::Field::from_u8(b)); } /// Fused multiply-add with a `u64` scalar: `self += a * F::from(b)`. #[inline] - fn fmadd_u64(&mut self, a: Self::Element, b: u64) { - self.fmadd(a, Self::Element::from_u64(b)); + fn fmadd_u64(&mut self, a: Self::Field, b: u64) { + self.fmadd(a, Self::Field::from_u64(b)); } /// Fused multiply-add with an `i64` scalar: `self += a * F::from(b)`. #[inline] - fn fmadd_i64(&mut self, a: Self::Element, b: i64) { - self.fmadd(a, Self::Element::from_i64(b)); + fn fmadd_i64(&mut self, a: Self::Field, b: i64) { + self.fmadd(a, Self::Field::from_i64(b)); } /// Fused multiply-add with a `bool` scalar: `self += a` when `b` is true. #[inline] - fn fmadd_bool(&mut self, a: Self::Element, b: bool) { + fn fmadd_bool(&mut self, a: Self::Field, b: bool) { if b { - self.fmadd(a, ::one()); + self.fmadd(a, ::one()); } } + + /// Accumulate a field element with unit weight: `self += val`. + #[inline] + fn acc_add(&mut self, val: Self::Field) { + self.fmadd(::one(), val); + } + + /// Merge another accumulator's partial sum into this one. + /// + /// Used in parallel reduction (e.g., Rayon fold+reduce) where each thread + /// accumulates independently, then results are combined. + fn merge(&mut self, other: Self); + + /// Finalize: reduce the accumulated value to a field element. + fn reduce(self) -> Self::Field; +} + +/// Accumulates products of field elements with small integer scalars. +/// +/// This is the raw-scalar analogue of [`FieldAccumulator`]. It is useful when a +/// hot loop repeatedly adds terms of the form `a * n`, where `n` is a `u64` or +/// `u128` known outside the field. Implementations may defer the modular +/// reduction across many bucketed additions. +pub trait FieldScalarAccumulator: Default + Copy + Send + Sync { + /// The field type this accumulator operates over. + type Field: crate::Field; + + /// Accumulate a field element with unit scalar. + fn add(&mut self, value: Self::Field); + + /// Fused multiply-add with a `u64` scalar: `self += value * scalar`. + fn add_mul_u64(&mut self, value: Self::Field, scalar: u64); + + /// Fused multiply-add with a `u128` scalar: `self += value * scalar`. + fn add_mul_u128(&mut self, value: Self::Field, scalar: u128); + + /// Merge another accumulator's partial sum into this one. + fn merge(&mut self, other: Self); + + /// Finalize to a field element. + fn reduce(self) -> Self::Field; } /// Naive accumulator using standard field arithmetic. /// -/// Every [`fmadd`](RingAccumulator::fmadd) performs a full modular multiply +/// Every [`fmadd`](FieldAccumulator::fmadd) performs a full modular multiply /// and add. Used as a fallback for fields without wide-integer optimization. #[derive(Clone, Copy)] -pub struct NaiveAccumulator(R); +pub struct NaiveAccumulator(F); -impl Default for NaiveAccumulator { +impl Default for NaiveAccumulator { #[inline] fn default() -> Self { - Self(R::zero()) + Self(F::zero()) } } -impl AdditiveAccumulator for NaiveAccumulator { - type Element = R; +impl FieldAccumulator for NaiveAccumulator { + type Field = F; #[inline] - fn add(&mut self, value: R) { - self.0 += value; + fn fmadd(&mut self, a: F, b: F) { + self.0 += a * b; } #[inline] @@ -107,14 +133,47 @@ impl AdditiveAccumulator for NaiveAccumulator } #[inline] - fn reduce(self) -> R { + fn reduce(self) -> F { self.0 } } -impl RingAccumulator for NaiveAccumulator { +/// Naive raw-scalar accumulator using ordinary field arithmetic. +#[derive(Clone, Copy)] +pub struct NaiveScalarAccumulator(F); + +impl Default for NaiveScalarAccumulator { #[inline] - fn fmadd(&mut self, a: R, b: R) { - self.0 += a * b; + fn default() -> Self { + Self(F::zero()) + } +} + +impl FieldScalarAccumulator for NaiveScalarAccumulator { + type Field = F; + + #[inline] + fn add(&mut self, value: F) { + self.0 += value; + } + + #[inline] + fn add_mul_u64(&mut self, value: F, scalar: u64) { + self.0 += value.mul_u64(scalar); + } + + #[inline] + fn add_mul_u128(&mut self, value: F, scalar: u128) { + self.0 += value.mul_u128(scalar); + } + + #[inline] + fn merge(&mut self, other: Self) { + self.0 += other.0; + } + + #[inline] + fn reduce(self) -> F { + self.0 } } diff --git a/crates/jolt-field/src/additive_group.rs b/crates/jolt-field/src/additive_group.rs deleted file mode 100644 index cd7146d885..0000000000 --- a/crates/jolt-field/src/additive_group.rs +++ /dev/null @@ -1,20 +0,0 @@ -use num_traits::Zero; -use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; - -/// Minimal additive group operations shared by fields, rings, and accumulators. -pub trait AdditiveGroup: - Sized - + Clone - + Copy - + Send - + Sync - + Zero - + Add - + for<'a> Add<&'a Self, Output = Self> - + AddAssign - + Sub - + for<'a> Sub<&'a Self, Output = Self> - + SubAssign - + Neg -{ -} diff --git a/crates/jolt-field/src/arkworks/bn254.rs b/crates/jolt-field/src/arkworks/bn254.rs index df579d70ad..2c4161429a 100644 --- a/crates/jolt-field/src/arkworks/bn254.rs +++ b/crates/jolt-field/src/arkworks/bn254.rs @@ -2,11 +2,7 @@ //! //! [`Fr`] is `#[repr(transparent)]` over the inner arkworks scalar field element, //! so it has identical layout and can be transmuted where needed. -use crate::{ - AdditiveGroup, CanonicalBitLength, CanonicalBytes, CanonicalU64, Field, FieldCore, - FixedByteSize, FixedBytes, FromPrimitiveInt, Invertible, Limbs, MulPrimitiveInt, - RandomSampling, ReducingBytes, RingCore, TranscriptChallenge, WithAccumulator, -}; +use crate::{Field, Limbs}; use ark_ff::{prelude::*, PrimeField, UniformRand}; use rand_core::RngCore; @@ -38,56 +34,56 @@ impl From for ark_bn254::Fr { impl From for Fr { #[inline(always)] fn from(v: bool) -> Self { - ::from_bool(v) + ::from_bool(v) } } impl From for Fr { #[inline(always)] fn from(v: u8) -> Self { - ::from_u64(v as u64) + ::from_u64(v as u64) } } impl From for Fr { #[inline(always)] fn from(v: u16) -> Self { - ::from_u64(v as u64) + ::from_u64(v as u64) } } impl From for Fr { #[inline(always)] fn from(v: u32) -> Self { - ::from_u64(v as u64) + ::from_u64(v as u64) } } impl From for Fr { #[inline(always)] fn from(v: u64) -> Self { - ::from_u64(v) + ::from_u64(v) } } impl From for Fr { #[inline(always)] fn from(v: i64) -> Self { - ::from_i64(v) + ::from_i64(v) } } impl From for Fr { #[inline(always)] fn from(v: i128) -> Self { - ::from_i128(v) + ::from_i128(v) } } impl From for Fr { #[inline(always)] fn from(v: u128) -> Self { - ::from_u128(v) + ::from_u128(v) } } @@ -298,6 +294,12 @@ impl Fr { Fr(bn254_ops::from_bigint_unchecked(limbs.into())) } + /// Barrett-reduces an unreduced internal-representation integer. + #[inline(always)] + pub fn from_barrett_reduced_limbs(limbs: Limbs) -> Self { + Fr(bn254_ops::from_barrett_reduce(limbs.into())) + } + /// Access the internal Montgomery-form limbs. /// /// Used by [`WideAccumulator`](super::wide_accumulator::WideAccumulator) @@ -314,86 +316,54 @@ impl Fr { } } -impl AdditiveGroup for Fr {} - -impl RingCore for Fr { - #[inline] - fn square(&self) -> Self { - Fr(::square(&self.0)) - } -} - -impl Invertible for Fr { - #[inline] - fn inverse(&self) -> Option { - ::inverse(&self.0).map(Fr) - } -} - -impl FieldCore for Fr {} +impl Field for Fr { + type Accumulator = super::wide_accumulator::WideAccumulator; + type ScalarAccumulator = super::scalar_accumulator::ScalarAccumulator; -impl FixedByteSize for Fr { const NUM_BYTES: usize = 32; -} -impl CanonicalBytes for Fr { #[expect(clippy::expect_used)] - #[inline] - fn to_bytes_le(&self, out: &mut [u8]) { - assert_eq!(out.len(), ::NUM_BYTES); + fn to_bytes(&self) -> [u8; 32] { use ark_serialize::CanonicalSerialize; + let mut buf = [0u8; 32]; self.0 - .serialize_compressed(out) + .serialize_compressed(&mut buf[..]) .expect("BN254 Fr always serializes to 32 bytes"); + buf } -} -impl ReducingBytes for Fr { - #[inline] - fn from_le_bytes_mod_order(bytes: &[u8]) -> Self { - Fr::from_le_bytes_mod_order(bytes) + fn random(rng: &mut R) -> Self { + Fr(::rand(rng)) } -} -impl TranscriptChallenge for Fr { - #[inline] - fn from_challenge_bytes(bytes: &[u8]) -> Self { + fn from_bytes(bytes: &[u8]) -> Self { Fr::from_le_bytes_mod_order(bytes) } -} -impl FixedBytes<32> for Fr {} - -impl CanonicalU64 for Fr { - #[inline] - fn to_canonical_u64_checked(&self) -> Option { + fn to_u64(&self) -> Option { let bigint = ::into_bigint(self.0); let limbs: &[u64] = bigint.as_ref(); let result = limbs[0]; - if ::from_u64(result) != *self { + if ::from_u64(result) != *self { None } else { Some(result) } } -} -impl CanonicalBitLength for Fr { - #[inline] fn num_bits(&self) -> u32 { ::into_bigint(self.0).num_bits() } -} -impl RandomSampling for Fr { - #[inline] - fn random(rng: &mut R) -> Self { - Fr(::rand(rng)) + fn square(&self) -> Self { + Fr(::square(&self.0)) + } + + fn inverse(&self) -> Option { + ::inverse(&self.0).map(Fr) } -} -impl FromPrimitiveInt for Fr { #[inline] fn from_u64(n: u64) -> Self { Fr(bn254_ops::from_u64(n)) @@ -421,15 +391,7 @@ impl FromPrimitiveInt for Fr { fn from_u128(val: u128) -> Self { Fr(bn254_ops::from_u128(val)) } -} - -impl WithAccumulator for Fr { - type Accumulator = super::wide_accumulator::WideAccumulator; -} - -impl crate::MulPow2 for Fr {} -impl MulPrimitiveInt for Fr { #[inline] fn mul_u64(&self, n: u64) -> Self { Fr(bn254_ops::mul_u64(self.0, n)) @@ -451,13 +413,11 @@ impl MulPrimitiveInt for Fr { } } -impl Field for Fr {} - #[cfg(test)] #[expect(clippy::unwrap_used)] mod tests { use super::*; - use crate::{CanonicalU64, FixedBytes}; + use crate::Field; #[test] fn field_arithmetic_basic() { @@ -482,8 +442,8 @@ mod tests { #[test] fn serialization_roundtrip() { let val = Fr::from_u64(123_456_789); - let bytes = val.to_bytes_array(); - let recovered = Fr::from_bytes_array(&bytes); + let bytes = val.to_bytes(); + let recovered = Fr::from_bytes(&bytes); assert_eq!(val, recovered); } @@ -499,10 +459,10 @@ mod tests { #[test] fn to_u64_roundtrip() { - assert_eq!(Fr::from_u64(999).to_canonical_u64_checked(), Some(999)); + assert_eq!(Fr::from_u64(999).to_u64(), Some(999)); // Large field element should not fit in u64 let big = Fr::from_u128(u128::MAX / 2); - assert_eq!(big.to_canonical_u64_checked(), None); + assert_eq!(big.to_u64(), None); } #[test] diff --git a/crates/jolt-field/src/arkworks/bn254_ops.rs b/crates/jolt-field/src/arkworks/bn254_ops.rs index 3a1d061791..a1026c8202 100644 --- a/crates/jolt-field/src/arkworks/bn254_ops.rs +++ b/crates/jolt-field/src/arkworks/bn254_ops.rs @@ -385,6 +385,19 @@ fn from_unchecked_nplus2(element: BigInt<6>) -> Fr { Fp::new_unchecked(r2) } +/// Barrett-reduce an arbitrary little-endian limb integer into a field element. +#[inline(always)] +pub(crate) fn from_barrett_reduce(element: BigInt) -> Fr { + let mut acc = BigInt::([0u64; N]); + let mut i = L; + while i > 0 { + i -= 1; + let chunk = nplus1_from_low_and_high(element.0[i], acc.0); + acc = barrett_reduce_5_to_4(chunk); + } + Fp::new_unchecked(acc) +} + /// Multiply a field element by u64. #[inline(always)] pub(crate) fn mul_u64(a: Fr, b: u64) -> Fr { diff --git a/crates/jolt-field/src/arkworks/mod.rs b/crates/jolt-field/src/arkworks/mod.rs index 841398bb79..b5778f5394 100644 --- a/crates/jolt-field/src/arkworks/mod.rs +++ b/crates/jolt-field/src/arkworks/mod.rs @@ -9,6 +9,7 @@ use ark_ff::BigInt; pub mod bn254; pub(crate) mod bn254_ops; pub mod montgomery_impl; +pub mod scalar_accumulator; pub mod wide_accumulator; impl From> for BigInt { diff --git a/crates/jolt-field/src/arkworks/scalar_accumulator.rs b/crates/jolt-field/src/arkworks/scalar_accumulator.rs new file mode 100644 index 0000000000..ff2d94a407 --- /dev/null +++ b/crates/jolt-field/src/arkworks/scalar_accumulator.rs @@ -0,0 +1,78 @@ +//! Deferred-reduction accumulator for BN254 Fr times raw integer scalars. + +use crate::accumulator::FieldScalarAccumulator; +use crate::arkworks::bn254::Fr; +use crate::Limbs; + +/// Accumulates sums of `Fr * u64/u128` in Montgomery form. +#[derive(Clone, Copy)] +pub struct ScalarAccumulator { + /// A field element has 4 limbs; multiplying by a u128 gives 6 limbs, and + /// the extra limb provides carry headroom for many bucket additions. + limbs: Limbs<7>, +} + +impl Default for ScalarAccumulator { + #[inline] + fn default() -> Self { + Self { + limbs: Limbs::zero(), + } + } +} + +impl FieldScalarAccumulator for ScalarAccumulator { + type Field = Fr; + + #[inline(always)] + fn add(&mut self, value: Fr) { + self.add_mul_u64(value, 1); + } + + #[inline(always)] + fn add_mul_u64(&mut self, value: Fr, scalar: u64) { + if scalar != 0 { + self.limbs + .fmadd::<4, 1>(&value.inner_limbs(), &Limbs::<1>::from_u64(scalar)); + } + } + + #[inline(always)] + fn add_mul_u128(&mut self, value: Fr, scalar: u128) { + if scalar >> 64 == 0 { + self.add_mul_u64(value, scalar as u64); + } else { + let scalar_limbs = Limbs::<2>::new([scalar as u64, (scalar >> 64) as u64]); + self.limbs + .fmadd::<4, 2>(&value.inner_limbs(), &scalar_limbs); + } + } + + #[inline(always)] + fn merge(&mut self, other: Self) { + self.limbs.add_assign_trunc::<7>(&other.limbs); + } + + #[inline(always)] + fn reduce(self) -> Fr { + Fr::from_barrett_reduced_limbs(self.limbs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Field; + + #[test] + fn accumulates_raw_scalars() { + let mut acc = ScalarAccumulator::default(); + acc.add(Fr::from_u64(3)); + acc.add_mul_u64(Fr::from_u64(5), 7); + acc.add_mul_u128(Fr::from_u64(11), 1u128 << 80); + + let expected = + Fr::from_u64(3) + Fr::from_u64(5).mul_u64(7) + Fr::from_u64(11).mul_u128(1u128 << 80); + assert_eq!(acc.reduce(), expected); + } +} diff --git a/crates/jolt-field/src/arkworks/wide_accumulator.rs b/crates/jolt-field/src/arkworks/wide_accumulator.rs index ab76c66ab4..b08420c86c 100644 --- a/crates/jolt-field/src/arkworks/wide_accumulator.rs +++ b/crates/jolt-field/src/arkworks/wide_accumulator.rs @@ -9,7 +9,7 @@ //! elements is 8 limbs (512 bits). A 9-limb accumulator (576 bits) can //! hold up to 2^64 such products without overflow. -use crate::accumulator::{AdditiveAccumulator, RingAccumulator}; +use crate::accumulator::FieldAccumulator; use crate::arkworks::bn254::Fr; use crate::Limbs; @@ -19,7 +19,7 @@ use super::bn254_ops; /// /// Stores the running sum of Montgomery-form products as a 576-bit integer. /// Converting to a field element requires a single Montgomery reduction -/// via [`AdditiveAccumulator::reduce`]. +/// via [`reduce`](FieldAccumulator::reduce). #[derive(Clone, Copy)] pub struct WideAccumulator { /// 9 limbs = 2×4 (product width) + 1 (addition headroom). @@ -35,12 +35,12 @@ impl Default for WideAccumulator { } } -impl AdditiveAccumulator for WideAccumulator { - type Element = Fr; +impl FieldAccumulator for WideAccumulator { + type Field = Fr; #[inline(always)] - fn add(&mut self, value: Fr) { - self.fmadd(value, ::one()); + fn fmadd(&mut self, a: Fr, b: Fr) { + self.limbs.fmadd::<4, 4>(&a.inner_limbs(), &b.inner_limbs()); } #[inline(always)] @@ -49,25 +49,18 @@ impl AdditiveAccumulator for WideAccumulator { } fn reduce(self) -> Fr { - // The accumulator holds Montgomery-form products and/or elements. - // Montgomery reduction divides product terms by R; raw element additions - // are already in Montgomery form and live in the low limbs. + // The accumulator holds sum_i (a_i_mont × b_i_mont). + // Montgomery reduction divides by R, yielding the Montgomery form + // of sum_i (a_i × b_i). let bigint = self.limbs.into(); Fr::from_inner(bn254_ops::from_montgomery_reduce(bigint)) } } -impl RingAccumulator for WideAccumulator { - #[inline(always)] - fn fmadd(&mut self, a: Fr, b: Fr) { - self.limbs.fmadd::<4, 4>(&a.inner_limbs(), &b.inner_limbs()); - } -} - #[cfg(test)] mod tests { use super::*; - use crate::{AdditiveAccumulator, FromPrimitiveInt}; + use crate::Field; #[test] fn single_fmadd() { diff --git a/crates/jolt-field/src/canonical_bit_length.rs b/crates/jolt-field/src/canonical_bit_length.rs deleted file mode 100644 index 89664ed862..0000000000 --- a/crates/jolt-field/src/canonical_bit_length.rs +++ /dev/null @@ -1,7 +0,0 @@ -/// Significant-bit introspection for canonical representatives. -pub trait CanonicalBitLength { - /// Number of significant bits in this element's canonical representative. - /// - /// Zero is considered to have zero significant bits. - fn num_bits(&self) -> u32; -} diff --git a/crates/jolt-field/src/canonical_bytes.rs b/crates/jolt-field/src/canonical_bytes.rs deleted file mode 100644 index 6b0363f65c..0000000000 --- a/crates/jolt-field/src/canonical_bytes.rs +++ /dev/null @@ -1,13 +0,0 @@ -/// Canonical little-endian byte encoding. -pub trait CanonicalBytes: Sized + crate::FixedByteSize { - /// Writes the canonical little-endian encoding into `out`. - fn to_bytes_le(&self, out: &mut [u8]); - - /// Returns the canonical little-endian encoding as a vector. - #[inline] - fn to_bytes_le_vec(&self) -> Vec { - let mut out = vec![0u8; Self::NUM_BYTES]; - self.to_bytes_le(&mut out); - out - } -} diff --git a/crates/jolt-field/src/canonical_u64.rs b/crates/jolt-field/src/canonical_u64.rs deleted file mode 100644 index 9d2b8f1cab..0000000000 --- a/crates/jolt-field/src/canonical_u64.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// Checked extraction of canonical representatives that fit in `u64`. -pub trait CanonicalU64 { - /// Returns the canonical representative as `u64` if it fits. - fn to_canonical_u64_checked(&self) -> Option; -} diff --git a/crates/jolt-field/src/field.rs b/crates/jolt-field/src/field.rs index 80c1da6778..0ea3c23409 100644 --- a/crates/jolt-field/src/field.rs +++ b/crates/jolt-field/src/field.rs @@ -1,15 +1,11 @@ #[cfg(feature = "allocative")] use allocative::Allocative; -use serde::{de::DeserializeOwned, Serialize}; +use num_traits::{One, Zero}; +use rand_core::RngCore; +use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; use std::hash::Hash; -use std::ops::Mul; - -use crate::{ - CanonicalBitLength, CanonicalBytes, CanonicalU64, FieldCore, FixedByteSize, FixedBytes, - FromPrimitiveInt, MulPow2, MulPrimitiveInt, RandomSampling, ReducingBytes, RingCore, - TranscriptChallenge, WithAccumulator, -}; +use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}; /// Prime field element abstraction used throughout Jolt. /// @@ -22,31 +18,116 @@ use crate::{ pub trait Field: 'static + Sized + + Zero + + One + + Neg + + Add + + for<'a> Add<&'a Self, Output = Self> + + Sub + + for<'a> Sub<&'a Self, Output = Self> + + Mul + + for<'a> Mul<&'a Self, Output = Self> + + Div + + for<'a> Div<&'a Self, Output = Self> + + AddAssign + + SubAssign + + MulAssign + + core::iter::Sum + + for<'a> core::iter::Sum<&'a Self> + + core::iter::Product + + for<'a> core::iter::Product<&'a Self> + + Eq + Copy + Sync + Send - + Default - + Eq - + Hash + Display + Debug - + FieldCore - + FromPrimitiveInt - + CanonicalBytes - + ReducingBytes - + TranscriptChallenge - + FixedBytes<32> - + FixedByteSize - + CanonicalBitLength - + CanonicalU64 - + RandomSampling - + WithAccumulator - + MulPow2 - + MulPrimitiveInt + + Default + + Hash + Serialize - + DeserializeOwned + + for<'de> Deserialize<'de> + MaybeAllocative { + /// Accumulator for deferred-reduction fused multiply-add. + /// + /// For BN254 Fr, this is a wide 9-limb integer that defers Montgomery + /// reduction. For other fields, use [`NaiveAccumulator`](crate::NaiveAccumulator). + type Accumulator: crate::FieldAccumulator; + + /// Accumulator for deferred-reduction field × raw-integer additions. + type ScalarAccumulator: crate::FieldScalarAccumulator; + + /// Byte length of a canonical (compressed) serialized element. + const NUM_BYTES: usize; + + /// Serializes to compressed canonical form (little-endian, 32 bytes). + fn to_bytes(&self) -> [u8; 32]; + + /// Samples a uniformly random field element. + fn random(rng: &mut R) -> Self; + /// Deserializes from little-endian bytes, reducing modulo the field prime. + fn from_bytes(bytes: &[u8]) -> Self; + /// Returns the value as `u64` if it fits, or `None` if >= 2^64. + fn to_u64(&self) -> Option; + /// Number of significant bits in the canonical representation. + fn num_bits(&self) -> u32; + /// Returns `self * self`. + fn square(&self) -> Self; + /// Multiplicative inverse, or `None` for the zero element. + fn inverse(&self) -> Option; + + fn from_bool(val: bool) -> Self { + if val { + Self::one() + } else { + Self::zero() + } + } + fn from_u8(n: u8) -> Self { + Self::from_u64(n as u64) + } + fn from_u16(n: u16) -> Self { + Self::from_u64(n as u64) + } + fn from_u32(n: u32) -> Self { + Self::from_u64(n as u64) + } + fn from_u64(n: u64) -> Self; + /// Maps a signed integer to its canonical field representative: negative + /// values become `p - |val|`. + fn from_i64(val: i64) -> Self; + /// Maps a signed integer to its canonical field representative: negative + /// values become `p - |val|`. + fn from_i128(val: i128) -> Self; + fn from_u128(val: u128) -> Self; + + fn mul_u64(&self, n: u64) -> Self { + *self * Self::from_u64(n) + } + + fn mul_i64(&self, n: i64) -> Self { + *self * Self::from_i64(n) + } + + fn mul_u128(&self, n: u128) -> Self { + *self * Self::from_u128(n) + } + + fn mul_i128(&self, n: i128) -> Self { + *self * Self::from_i128(n) + } + + /// Multiplication of a field element and a power of 2. + /// Split into chunks of 63 bits, then multiply and accumulate. + fn mul_pow_2(&self, mut pow: usize) -> Self { + assert!(pow <= 255, "pow > 255"); + let mut res = *self; + while pow >= 64 { + res = res.mul_u64(1 << 63); + pow -= 63; + } + res.mul_u64(1 << pow) + } } #[cfg(feature = "allocative")] @@ -73,7 +154,7 @@ pub trait OptimizedMul: Sized + Mul { impl OptimizedMul for F where - F: RingCore, + F: Field, { #[inline(always)] fn mul_0_optimized(self, other: F) -> F { diff --git a/crates/jolt-field/src/field_core.rs b/crates/jolt-field/src/field_core.rs deleted file mode 100644 index d9a6f5eee7..0000000000 --- a/crates/jolt-field/src/field_core.rs +++ /dev/null @@ -1,4 +0,0 @@ -use crate::{Invertible, RingCore}; - -/// Algebraic field marker: ring arithmetic plus explicit inversion. -pub trait FieldCore: RingCore + Invertible {} diff --git a/crates/jolt-field/src/fixed_byte_size.rs b/crates/jolt-field/src/fixed_byte_size.rs deleted file mode 100644 index 09e1ac47a4..0000000000 --- a/crates/jolt-field/src/fixed_byte_size.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// Fixed byte-size metadata for canonical encodings. -pub trait FixedByteSize { - /// Byte length of the fixed-size encoding. - const NUM_BYTES: usize; -} diff --git a/crates/jolt-field/src/fixed_bytes.rs b/crates/jolt-field/src/fixed_bytes.rs deleted file mode 100644 index 6f465be63b..0000000000 --- a/crates/jolt-field/src/fixed_bytes.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{CanonicalBytes, FixedByteSize, ReducingBytes}; - -/// Fixed-array convenience API for canonical field/value encodings. -pub trait FixedBytes: CanonicalBytes + ReducingBytes + FixedByteSize { - /// Returns the canonical fixed-size byte encoding. - #[inline] - fn to_bytes_array(&self) -> [u8; N] { - debug_assert_eq!(Self::NUM_BYTES, N); - let mut out = [0u8; N]; - self.to_bytes_le(&mut out); - out - } - - /// Reducing constructor from a fixed-size byte array. - #[inline] - fn from_bytes_array(bytes: &[u8; N]) -> Self { - Self::from_le_bytes_mod_order(bytes) - } -} diff --git a/crates/jolt-field/src/from_primitive_int.rs b/crates/jolt-field/src/from_primitive_int.rs deleted file mode 100644 index 31cf1aef12..0000000000 --- a/crates/jolt-field/src/from_primitive_int.rs +++ /dev/null @@ -1,46 +0,0 @@ -/// Embed primitive integer values into a scalar object. -pub trait FromPrimitiveInt: Sized { - #[inline] - fn from_bool(v: bool) -> Self { - if v { - Self::from_u64(1) - } else { - Self::from_u64(0) - } - } - - #[inline] - fn from_u8(v: u8) -> Self { - Self::from_u64(v as u64) - } - - #[inline] - fn from_i8(v: i8) -> Self { - Self::from_i64(v as i64) - } - - #[inline] - fn from_u16(v: u16) -> Self { - Self::from_u64(v as u64) - } - - #[inline] - fn from_i16(v: i16) -> Self { - Self::from_i64(v as i64) - } - - #[inline] - fn from_u32(v: u32) -> Self { - Self::from_u64(v as u64) - } - - #[inline] - fn from_i32(v: i32) -> Self { - Self::from_i64(v as i64) - } - - fn from_u64(v: u64) -> Self; - fn from_i64(v: i64) -> Self; - fn from_u128(v: u128) -> Self; - fn from_i128(v: i128) -> Self; -} diff --git a/crates/jolt-field/src/invertible.rs b/crates/jolt-field/src/invertible.rs deleted file mode 100644 index 5e15491d5a..0000000000 --- a/crates/jolt-field/src/invertible.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::RingCore; - -/// Ring-level inversion capability with explicit zero handling. -pub trait Invertible: RingCore { - /// Multiplicative inverse, or `None` for the zero element. - fn inverse(&self) -> Option; - - /// Multiplicative inverse with zero mapped to zero. - #[inline] - fn inv_or_zero(self) -> Self { - self.inverse().unwrap_or_else(Self::zero) - } -} diff --git a/crates/jolt-field/src/lib.rs b/crates/jolt-field/src/lib.rs index 143c649521..0f514c4269 100644 --- a/crates/jolt-field/src/lib.rs +++ b/crates/jolt-field/src/lib.rs @@ -1,21 +1,12 @@ -//! Field and ring abstractions for the Jolt zkVM. +//! Field abstractions for the Jolt zkVM. //! -//! This crate exposes a slim algebraic hierarchy under Jolt's compatibility -//! [`Field`] bundle: -//! -//! ```text -//! AdditiveGroup -> RingCore -> FieldCore -//! \-> Invertible -//! ``` -//! -//! Serialization, sampling, transcript challenges, primitive-integer embedding, -//! and accumulator support are separate capabilities so non-BN254 fields can -//! opt into only the surface they actually provide. +//! Backend-agnostic interface over prime-order scalar fields, currently +//! implemented for BN254 Fr. Leaf crate with no internal Jolt dependencies. //! //! # Core traits //! -//! - [`Field`] — Jolt compatibility umbrella -//! - [`RingAccumulator`] — deferred-reduction fused multiply-add +//! - [`Field`] — prime field element (`Copy`, thread-safe, serializable) +//! - [`FieldAccumulator`] — deferred-reduction fused multiply-add //! - [`OptimizedMul`] — fast-path short-circuits for zero/one //! - [`MontgomeryConstants`] — Montgomery form constants for GPU backends //! @@ -29,45 +20,14 @@ //! - [`Limbs`] — fixed-width limb array for unreduced arithmetic //! - [`signed`] module — `S64`, `S128`, `S192`, `S256` and half-limb variants -mod accumulator; -mod additive_group; -mod canonical_bit_length; -mod canonical_bytes; -mod canonical_u64; mod field; -mod field_core; -mod fixed_byte_size; -mod fixed_bytes; -mod from_primitive_int; -mod invertible; -mod montgomery_constants; -mod mul_pow_2; -mod mul_primitive_int; -mod random_sampling; -mod reducing_bytes; -mod ring_core; -mod transcript_challenge; -mod with_accumulator; - -pub use accumulator::{AdditiveAccumulator, NaiveAccumulator, RingAccumulator}; -pub use additive_group::AdditiveGroup; -pub use canonical_bit_length::CanonicalBitLength; -pub use canonical_bytes::CanonicalBytes; -pub use canonical_u64::CanonicalU64; pub use field::{Field, MaybeAllocative, OptimizedMul}; -pub use field_core::FieldCore; -pub use fixed_byte_size::FixedByteSize; -pub use fixed_bytes::FixedBytes; -pub use from_primitive_int::FromPrimitiveInt; -pub use invertible::Invertible; +mod accumulator; +pub use accumulator::{ + FieldAccumulator, FieldScalarAccumulator, NaiveAccumulator, NaiveScalarAccumulator, +}; +mod montgomery_constants; pub use montgomery_constants::MontgomeryConstants; -pub use mul_pow_2::MulPow2; -pub use mul_primitive_int::MulPrimitiveInt; -pub use random_sampling::RandomSampling; -pub use reducing_bytes::ReducingBytes; -pub use ring_core::RingCore; -pub use transcript_challenge::TranscriptChallenge; -pub use with_accumulator::WithAccumulator; pub mod limbs; pub use limbs::Limbs; diff --git a/crates/jolt-field/src/mul_pow_2.rs b/crates/jolt-field/src/mul_pow_2.rs deleted file mode 100644 index dbc30ee975..0000000000 --- a/crates/jolt-field/src/mul_pow_2.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::{FromPrimitiveInt, RingCore}; - -/// Multiplication by powers of two. -pub trait MulPow2: RingCore + FromPrimitiveInt { - /// Multiplies this ring element by the integer `2^pow`. - #[inline] - fn mul_pow_2(&self, pow: usize) -> Self { - assert!(pow <= 255, "pow > 255"); - let mut res = *self; - let mut p = pow; - while p >= 64 { - res *= Self::from_u64(1 << 63); - p -= 63; - } - res * Self::from_u64(1 << p) - } -} diff --git a/crates/jolt-field/src/mul_primitive_int.rs b/crates/jolt-field/src/mul_primitive_int.rs deleted file mode 100644 index 4980596de0..0000000000 --- a/crates/jolt-field/src/mul_primitive_int.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::{FromPrimitiveInt, RingCore}; - -/// Multiplication by primitive integer scalars. -pub trait MulPrimitiveInt: RingCore + FromPrimitiveInt { - /// Multiplies by a `u64`. - #[inline(always)] - fn mul_u64(&self, n: u64) -> Self { - *self * Self::from_u64(n) - } - - /// Multiplies by an `i64`. - #[inline(always)] - fn mul_i64(&self, n: i64) -> Self { - *self * Self::from_i64(n) - } - - /// Multiplies by a `u128`. - #[inline(always)] - fn mul_u128(&self, n: u128) -> Self { - *self * Self::from_u128(n) - } - - /// Multiplies by an `i128`. - #[inline(always)] - fn mul_i128(&self, n: i128) -> Self { - *self * Self::from_i128(n) - } -} diff --git a/crates/jolt-field/src/random_sampling.rs b/crates/jolt-field/src/random_sampling.rs deleted file mode 100644 index ea56da35fb..0000000000 --- a/crates/jolt-field/src/random_sampling.rs +++ /dev/null @@ -1,7 +0,0 @@ -use rand_core::RngCore; - -/// RNG-backed sampling for tests and witnesses. -pub trait RandomSampling { - /// Samples a random element. - fn random(rng: &mut R) -> Self; -} diff --git a/crates/jolt-field/src/reducing_bytes.rs b/crates/jolt-field/src/reducing_bytes.rs deleted file mode 100644 index 89d4302105..0000000000 --- a/crates/jolt-field/src/reducing_bytes.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// Reducing little-endian byte constructor. -pub trait ReducingBytes: Sized { - /// Deserializes little-endian bytes by reducing into this type. - fn from_le_bytes_mod_order(bytes: &[u8]) -> Self; -} diff --git a/crates/jolt-field/src/ring_core.rs b/crates/jolt-field/src/ring_core.rs deleted file mode 100644 index 5c28b820d5..0000000000 --- a/crates/jolt-field/src/ring_core.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::AdditiveGroup; -use num_traits::One; -use std::{ - fmt::{Debug, Display}, - hash::Hash, - iter::{Product, Sum}, - ops::{Mul, MulAssign}, -}; - -/// Core ring arithmetic: additive group plus multiplication and one. -pub trait RingCore: - AdditiveGroup - + One - + PartialEq - + Eq - + Default - + Debug - + Display - + Hash - + Mul - + for<'a> Mul<&'a Self, Output = Self> - + MulAssign - + Sum - + for<'a> Sum<&'a Self> - + Product - + for<'a> Product<&'a Self> -{ - /// Returns `self * self`. - #[inline] - fn square(&self) -> Self { - *self * *self - } -} diff --git a/crates/jolt-field/src/transcript_challenge.rs b/crates/jolt-field/src/transcript_challenge.rs deleted file mode 100644 index 9fd5a4d2e8..0000000000 --- a/crates/jolt-field/src/transcript_challenge.rs +++ /dev/null @@ -1,7 +0,0 @@ -/// Fiat-Shamir challenge decoding from squeezed transcript bytes. -pub trait TranscriptChallenge: - Sized + Copy + Default + PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Sync + Send + 'static -{ - /// Constructs a challenge from transcript bytes. - fn from_challenge_bytes(bytes: &[u8]) -> Self; -} diff --git a/crates/jolt-field/src/with_accumulator.rs b/crates/jolt-field/src/with_accumulator.rs deleted file mode 100644 index 65c50cc90d..0000000000 --- a/crates/jolt-field/src/with_accumulator.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::{AdditiveAccumulator, AdditiveGroup}; - -/// Associates an additive redundant accumulator with an element type. -pub trait WithAccumulator: AdditiveGroup { - /// Accumulator type. - type Accumulator: AdditiveAccumulator; -} diff --git a/crates/jolt-field/tests/coverage.rs b/crates/jolt-field/tests/coverage.rs index e083e2696b..d59d073440 100644 --- a/crates/jolt-field/tests/coverage.rs +++ b/crates/jolt-field/tests/coverage.rs @@ -7,43 +7,34 @@ use ark_std::test_rng; use jolt_field::signed::*; -use jolt_field::{ - AdditiveAccumulator, FixedBytes, Fr, FromPrimitiveInt, Limbs, MulPow2, NaiveAccumulator, - OptimizedMul, RandomSampling, RingAccumulator, -}; +use jolt_field::{Field, FieldAccumulator, Fr, Limbs, NaiveAccumulator, OptimizedMul}; use num_traits::{One, Zero}; #[test] fn naive_accumulator_fmadd() { - let a = ::from_u64(7); - let b = ::from_u64(11); - let c = ::from_u64(3); - let d = ::from_u64(5); + let a = ::from_u64(7); + let b = ::from_u64(11); + let c = ::from_u64(3); + let d = ::from_u64(5); let mut acc = NaiveAccumulator::::default(); acc.fmadd(a, b); acc.fmadd(c, d); // 7*11 + 3*5 = 77 + 15 = 92 - assert_eq!(acc.reduce(), ::from_u64(92)); + assert_eq!(acc.reduce(), ::from_u64(92)); } #[test] fn naive_accumulator_merge() { let mut acc1 = NaiveAccumulator::::default(); - acc1.fmadd( - ::from_u64(2), - ::from_u64(3), - ); + acc1.fmadd(::from_u64(2), ::from_u64(3)); let mut acc2 = NaiveAccumulator::::default(); - acc2.fmadd( - ::from_u64(4), - ::from_u64(5), - ); + acc2.fmadd(::from_u64(4), ::from_u64(5)); acc1.merge(acc2); // 2*3 + 4*5 = 6 + 20 = 26 - assert_eq!(acc1.reduce(), ::from_u64(26)); + assert_eq!(acc1.reduce(), ::from_u64(26)); } #[test] @@ -56,12 +47,12 @@ fn naive_accumulator_reduce_empty() { fn wide_accumulator_fmadd() { use jolt_field::WideAccumulator; - let a = ::from_u64(13); - let b = ::from_u64(17); + let a = ::from_u64(13); + let b = ::from_u64(17); let mut acc = WideAccumulator::default(); acc.fmadd(a, b); - assert_eq!(acc.reduce(), ::from_u64(13 * 17)); + assert_eq!(acc.reduce(), ::from_u64(13 * 17)); } #[test] @@ -69,20 +60,14 @@ fn wide_accumulator_merge() { use jolt_field::WideAccumulator; let mut acc1 = WideAccumulator::default(); - acc1.fmadd( - ::from_u64(10), - ::from_u64(20), - ); + acc1.fmadd(::from_u64(10), ::from_u64(20)); let mut acc2 = WideAccumulator::default(); - acc2.fmadd( - ::from_u64(30), - ::from_u64(40), - ); + acc2.fmadd(::from_u64(30), ::from_u64(40)); acc1.merge(acc2); // 10*20 + 30*40 = 200 + 1200 = 1400 - assert_eq!(acc1.reduce(), ::from_u64(1400)); + assert_eq!(acc1.reduce(), ::from_u64(1400)); } #[test] @@ -101,8 +86,8 @@ fn wide_accumulator_many_fmadds() { let mut expected = Fr::zero(); let mut rng = test_rng(); for _ in 0..500 { - let a: Fr = ::random(&mut rng); - let b: Fr = ::random(&mut rng); + let a: Fr = Field::random(&mut rng); + let b: Fr = Field::random(&mut rng); acc.fmadd(a, b); expected += a * b; } @@ -112,8 +97,8 @@ fn wide_accumulator_many_fmadds() { #[test] fn optimized_mul_blanket_impl() { let mut rng = test_rng(); - let a: Fr = ::random(&mut rng); - let b: Fr = ::random(&mut rng); + let a: Fr = Field::random(&mut rng); + let b: Fr = Field::random(&mut rng); // mul_0_optimized: both nonzero assert_eq!(a.mul_0_optimized(b), a * b); @@ -147,41 +132,35 @@ fn optimized_mul_blanket_impl() { #[test] fn field_from_bool_edge() { - assert_eq!(::from_bool(true), Fr::one()); - assert_eq!(::from_bool(false), Fr::zero()); + assert_eq!(::from_bool(true), Fr::one()); + assert_eq!(::from_bool(false), Fr::zero()); } #[test] fn field_from_small_types_boundary() { - assert_eq!(::from_u8(0), Fr::zero()); + assert_eq!(::from_u8(0), Fr::zero()); + assert_eq!(::from_u8(255), ::from_u64(255)); + assert_eq!(::from_u16(0), Fr::zero()); assert_eq!( - ::from_u8(255), - ::from_u64(255) + ::from_u16(65535), + ::from_u64(65535) ); - assert_eq!(::from_u16(0), Fr::zero()); + assert_eq!(::from_u32(0), Fr::zero()); assert_eq!( - ::from_u16(65535), - ::from_u64(65535) - ); - assert_eq!(::from_u32(0), Fr::zero()); - assert_eq!( - ::from_u32(u32::MAX), - ::from_u64(u32::MAX as u64) + ::from_u32(u32::MAX), + ::from_u64(u32::MAX as u64) ); } #[test] fn field_mul_pow_2_boundary() { - let f = ::from_u64(1); + let f = ::from_u64(1); // pow=0 -> f * 1 = f - assert_eq!(::mul_pow_2(&f, 0), f); + assert_eq!(::mul_pow_2(&f, 0), f); // pow=1 -> f * 2 - assert_eq!( - ::mul_pow_2(&f, 1), - ::from_u64(2) - ); + assert_eq!(::mul_pow_2(&f, 1), ::from_u64(2)); // pow=64 -> goes through while loop at least once - let result = ::mul_pow_2(&f, 64); + let result = ::mul_pow_2(&f, 64); let mut expected = f; for _ in 0..64 { expected = expected + expected; @@ -192,8 +171,8 @@ fn field_mul_pow_2_boundary() { #[test] #[should_panic(expected = "pow > 255")] fn field_mul_pow_2_overflow() { - let f = ::from_u64(1); - let _ = ::mul_pow_2(&f, 256); + let f = ::from_u64(1); + let _ = ::mul_pow_2(&f, 256); } #[test] @@ -957,8 +936,8 @@ fn fr_neg() { fn fr_inner_roundtrip() { // Test Fr(ark_bn254::Fr) -> ark_bn254::Fr conversion (inner type) let a = Fr::from_u64(12345); - let bytes = a.to_bytes_array(); - let b = Fr::from_bytes_array(&bytes); + let bytes = a.to_bytes(); + let b = Fr::from_bytes(&bytes); assert_eq!(a, b); } diff --git a/crates/jolt-field/tests/field_operations.rs b/crates/jolt-field/tests/field_operations.rs index 2f56bd304c..b648dd6932 100644 --- a/crates/jolt-field/tests/field_operations.rs +++ b/crates/jolt-field/tests/field_operations.rs @@ -2,9 +2,8 @@ use ark_std::rand::Rng; use ark_std::{test_rng, One, Zero}; -use jolt_field::{ - CanonicalU64, Fr, FromPrimitiveInt, MulPow2, MulPrimitiveInt, RandomSampling, ReducingBytes, -}; +use jolt_field::Field; +use jolt_field::Fr; use rand_chacha::rand_core::RngCore; #[test] @@ -14,17 +13,17 @@ fn implicit_montgomery_conversion() { for _ in 0..256 { let x = rng.next_u64(); assert_eq!( - ::from_u64(x), - Fr::one() * ::from_u64(x) + ::from_u64(x), + Fr::one() * ::from_u64(x) ); } for _ in 0..256 { let x = rng.next_u64(); - let y: Fr = ::random(&mut rng); + let y: Fr = Field::random(&mut rng); assert_eq!( - y * ::from_u64(x), - y * ::from_u64(x) + y * ::from_u64(x), + y * ::from_u64(x) ); } } @@ -33,8 +32,8 @@ fn implicit_montgomery_conversion() { fn field_arithmetic() { let mut rng = test_rng(); - let x = ::from_u64(rng.next_u64()); - let y = ::from_u64(rng.next_u64()); + let x = ::from_u64(rng.next_u64()); + let y = ::from_u64(rng.next_u64()); let sum = x + y; assert_eq!(sum, y + x); @@ -55,30 +54,30 @@ fn field_arithmetic() { fn field_conversions() { let mut rng = test_rng(); - assert_eq!(::from_bool(true), Fr::one()); - assert_eq!(::from_bool(false), Fr::zero()); + assert_eq!(::from_bool(true), Fr::one()); + assert_eq!(::from_bool(false), Fr::zero()); for _ in 0..100 { let val = rng.gen::(); - let field_elem = ::from_u8(val); - assert_eq!(field_elem, ::from_u64(val as u64)); + let field_elem = ::from_u8(val); + assert_eq!(field_elem, ::from_u64(val as u64)); } for _ in 0..100 { let val = rng.gen::(); - let field_elem = ::from_u16(val); - assert_eq!(field_elem, ::from_u64(val as u64)); + let field_elem = ::from_u16(val); + assert_eq!(field_elem, ::from_u64(val as u64)); } for _ in 0..100 { let val = rng.gen::(); - let field_elem = ::from_u32(val); - assert_eq!(field_elem, ::from_u64(val as u64)); + let field_elem = ::from_u32(val); + assert_eq!(field_elem, ::from_u64(val as u64)); } for _ in 0..100 { let val = rng.gen::(); - let field_elem = ::from_u128(val); + let field_elem = ::from_u128(val); assert!(!field_elem.is_zero() || val == 0); } } @@ -90,7 +89,7 @@ fn bytes_conversion() { for &len in &[1, 8, 16, 32, 48, 64] { let mut bytes = vec![0u8; len]; rng.fill_bytes(&mut bytes); - let _field_elem = ::from_le_bytes_mod_order(&bytes); + let _field_elem = ::from_bytes(&bytes); } } @@ -100,29 +99,23 @@ fn signed_conversions() { for _ in 0..100 { let val = rng.gen::(); - let field_elem = ::from_i64(val); + let field_elem = ::from_i64(val); if val >= 0 { - assert_eq!(field_elem, ::from_u64(val as u64)); + assert_eq!(field_elem, ::from_u64(val as u64)); } else { - assert_eq!( - field_elem, - -::from_u64(val.unsigned_abs()) - ); + assert_eq!(field_elem, -::from_u64(val.unsigned_abs())); } } for _ in 0..100 { let val = rng.gen::(); - let field_elem = ::from_i128(val); + let field_elem = ::from_i128(val); if val >= 0 { - assert_eq!(field_elem, ::from_u128(val as u128)); + assert_eq!(field_elem, ::from_u128(val as u128)); } else { - assert_eq!( - field_elem, - -::from_u128(val.unsigned_abs()) - ); + assert_eq!(field_elem, -::from_u128(val.unsigned_abs())); } } } @@ -132,12 +125,12 @@ fn mul_u64_method() { let mut rng = test_rng(); for _ in 0..100 { - let field_elem: Fr = ::random(&mut rng); + let field_elem: Fr = Field::random(&mut rng); let n = rng.next_u64(); // Use UFCS to call trait method (arkworks has inherent mul_u64 with different signature) - let result = ::mul_u64(&field_elem, n); - let expected = field_elem * ::from_u64(n); + let result = ::mul_u64(&field_elem, n); + let expected = field_elem * ::from_u64(n); assert_eq!(result, expected); } } @@ -147,11 +140,11 @@ fn mul_i64_method() { let mut rng = test_rng(); for _ in 0..100 { - let field_elem: Fr = ::random(&mut rng); + let field_elem: Fr = Field::random(&mut rng); let n = rng.gen::(); - let result = ::mul_i64(&field_elem, n); - let expected = field_elem * ::from_i64(n); + let result = ::mul_i64(&field_elem, n); + let expected = field_elem * ::from_i64(n); assert_eq!(result, expected); } } @@ -161,11 +154,11 @@ fn mul_u128_method() { let mut rng = test_rng(); for _ in 0..100 { - let field_elem: Fr = ::random(&mut rng); + let field_elem: Fr = Field::random(&mut rng); let n = rng.gen::(); - let result = ::mul_u128(&field_elem, n); - let expected = field_elem * ::from_u128(n); + let result = ::mul_u128(&field_elem, n); + let expected = field_elem * ::from_u128(n); assert_eq!(result, expected); } } @@ -175,11 +168,11 @@ fn mul_i128_method() { let mut rng = test_rng(); for _ in 0..100 { - let field_elem: Fr = ::random(&mut rng); + let field_elem: Fr = Field::random(&mut rng); let n = rng.gen::(); - let result = ::mul_i128(&field_elem, n); - let expected = field_elem * ::from_i128(n); + let result = ::mul_i128(&field_elem, n); + let expected = field_elem * ::from_i128(n); assert_eq!(result, expected); } } @@ -189,10 +182,10 @@ fn mul_pow_2_method() { let mut rng = test_rng(); for _ in 0..10 { - let field_elem: Fr = ::random(&mut rng); + let field_elem: Fr = Field::random(&mut rng); for pow in [0, 1, 2, 7, 16, 32, 63, 64, 127, 128, 255] { - let result = ::mul_pow_2(&field_elem, pow); + let result = ::mul_pow_2(&field_elem, pow); let mut expected = field_elem; for _ in 0..pow { expected = expected + expected; @@ -207,10 +200,10 @@ fn mul_by_small_values() { let mut rng = test_rng(); for _ in 0..100 { - let field_elem: Fr = ::random(&mut rng); + let field_elem: Fr = Field::random(&mut rng); let small_val = rng.gen_range(0u64..1000); - let result1 = field_elem * ::from_u64(small_val); + let result1 = field_elem * ::from_u64(small_val); let mut result2 = Fr::zero(); for _ in 0..small_val { @@ -224,34 +217,25 @@ fn mul_by_small_values() { #[test] fn special_values() { let mut rng = test_rng(); - let field_elem: Fr = ::random(&mut rng); - - assert_eq!( - field_elem * ::from_u64(0), - Fr::zero() - ); - assert_eq!( - field_elem * ::from_u64(1), - field_elem - ); - assert!((Fr::zero() * ::from_u64(rng.next_u64())).is_zero()); - - assert_eq!(::mul_u64(&field_elem, 0), Fr::zero()); - assert_eq!(::mul_u64(&field_elem, 1), field_elem); - assert_eq!( - ::mul_u64(&Fr::zero(), 42), - Fr::zero() - ); + let field_elem: Fr = Field::random(&mut rng); + + assert_eq!(field_elem * ::from_u64(0), Fr::zero()); + assert_eq!(field_elem * ::from_u64(1), field_elem); + assert!((Fr::zero() * ::from_u64(rng.next_u64())).is_zero()); + + assert_eq!(::mul_u64(&field_elem, 0), Fr::zero()); + assert_eq!(::mul_u64(&field_elem, 1), field_elem); + assert_eq!(::mul_u64(&Fr::zero(), 42), Fr::zero()); } #[test] fn to_u64_conversion() { for i in 0..1000u64 { - let field_elem = ::from_u64(i); - assert_eq!(field_elem.to_canonical_u64_checked(), Some(i)); + let field_elem = ::from_u64(i); + assert_eq!(field_elem.to_u64(), Some(i)); } let mut rng = test_rng(); - let large_field: Fr = ::random(&mut rng); - let _ = large_field.to_canonical_u64_checked(); + let large_field: Fr = Field::random(&mut rng); + let _ = large_field.to_u64(); } diff --git a/crates/jolt-host/Cargo.toml b/crates/jolt-host/Cargo.toml new file mode 100644 index 0000000000..44d198d55d --- /dev/null +++ b/crates/jolt-host/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "jolt-host" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Host-side modular prove entry point — bridges Program + tracer + jolt-prover stages" +repository = "https://github.com/a16z/jolt" + +[lints] +workspace = true + +[dev-dependencies] +postcard = { workspace = true, features = ["alloc"] } + +[dependencies] +common.workspace = true +jolt-dory.workspace = true +jolt-field.workspace = true +jolt-kernels.workspace = true +jolt-lookup-tables.workspace = true +jolt-openings.workspace = true +jolt-prover.workspace = true +jolt-r1cs.workspace = true +jolt-riscv.workspace = true +jolt-trace.workspace = true +jolt-transcript.workspace = true +jolt-verifier.workspace = true +jolt-witness.workspace = true +tracer = { workspace = true, features = ["std"] } +tracing.workspace = true diff --git a/crates/jolt-host/src/lib.rs b/crates/jolt-host/src/lib.rs new file mode 100644 index 0000000000..10497ce305 --- /dev/null +++ b/crates/jolt-host/src/lib.rs @@ -0,0 +1,870 @@ +//! Host-side entry point for the modular Bolt-based prover. +//! +//! `prove_program` takes a guest `Program` + inputs and returns a verifiable +//! `JoltProof` by driving the modular stack (jolt-prover + jolt-verifier + +//! Bolt goldens) end-to-end. The companion `verify_proof` rebuilds verifier +//! inputs from the prover artifacts and closes the soundness loop without +//! re-tracing the guest. Together they form the host-side surface that +//! `#[jolt::provable(backend = "modular")]` lowers to. +//! +//! The implementation mirrors `crates/jolt-equivalence/src/bolt_oracle.rs` +//! (the canonical staged-prove chain) with three substitutions: +//! 1. Bolt program plans come from +//! `jolt_prover::default_prover_programs()` (committed `pub const` +//! goldens) instead of being regenerated by Bolt at runtime. +//! 2. Trace-derived data (cycle_inputs, r1cs_witness, rv64_cycles, etc.) +//! is built from a real tracer run on the supplied ELF rather than +//! borrowed from a fixture. +//! 3. The redundant `prove_jolt_with_witness_inputs` call at +//! `bolt_oracle.rs:644` is omitted — the staged chain produces the +//! proof directly. + +use common::constants::{BYTES_PER_INSTRUCTION, RAM_START_ADDRESS}; +use common::jolt_device::{JoltDevice, MemoryLayout}; +use jolt_dory::{DoryProverSetup, DoryScheme}; +use jolt_field::Fr; +use jolt_kernels::stage1::{Stage1OuterRv64Data, Stage1Rv64Cycle}; +use jolt_kernels::stage2::{Stage2RamAccess, Stage2RamData, Stage2RamOutputLayout}; +use jolt_kernels::stage3::Stage3Cycle; +use jolt_kernels::stage4::Stage4RegisterAccess; +use jolt_kernels::trace::{ + stage1_rv64_cycles, stage2_instruction_lookup_cycles, stage2_product_virtual_cycles, + stage2_ram_accesses, stage3_cycles, stage4_register_accesses, stage5_lookup_trace, + stage6_bytecode_entries, Stage5LookupTrace, +}; +use jolt_lookup_tables::traits::InstructionLookupTable; +use jolt_lookup_tables::XLEN; +use jolt_openings::CommitmentScheme as _; +use jolt_r1cs::{constraints::rv64, R1csKey}; +use jolt_trace::ram::build_ram_states; +use jolt_trace::{extract_trace, with_isa_struct, BytecodePreprocessing, CycleRow, Program}; +use jolt_transcript::{Blake2bTranscript, Label, LabelWithCount, Transcript, U64Word}; +use jolt_verifier::{ + default_verifier_programs, verify_jolt_with_programs, JoltProof, JoltVerifierInputs, + JoltVerifyError, +}; +use jolt_witness::{ + Stage6BytecodeEntry as WitnessStage6BytecodeEntry, Stage6WitnessParams, +}; +use tracer::instruction::Instruction; + +use jolt_prover::{default_prover_programs, JoltProveError, JoltProverArtifacts}; + +/// Dispatch `&tracer::Instruction` to its `InstructionLookupTable` impl +/// in jolt-lookup-tables and return the table discriminant index. +/// Matches jolt-core's +/// `InstructionLookup::lookup_table().map(LookupTables::enum_index)` +/// across all 77 instruction variants (including the virtual ones that +/// shifts and DIVs expand into). Used as the closure for +/// `stage5_lookup_trace` and `stage6_bytecode_entries`. +fn instruction_lookup_table_index(instr: &Instruction) -> Option { + with_isa_struct!( + instr, + |i| <_ as InstructionLookupTable>::lookup_table(&i).map(|t| t.index()), + noop => None + ) +} + +/// Transcript label used by the modular prover (matches jolt-equivalence's +/// `TRANSCRIPT_LABEL`). +pub const TRANSCRIPT_LABEL: &[u8] = b"Jolt"; + +/// Goldens-baked shape. Must equal `JoltProtocolParams::fixture()` in bolt. +pub const FIXTURE_LOG_T: usize = 18; +pub const FIXTURE_LOG_K_BYTECODE: usize = 14; +pub const FIXTURE_LOG_K_RAM: usize = 14; + +/// Errors produced by `prove_program`. +#[derive(Debug)] +pub enum ProveProgramError { + /// Inner prover failure (commitment / Stage 1-7 / evaluation). + Prove(JoltProveError), + /// Opening-input derivation failure between stages. + Openings(jolt_prover::prover::JoltOpeningInputError), + /// The supplied guest program produced a trace shape (log_t, + /// log_k_bytecode, log_k_ram) that doesn't match the goldens-baked + /// fixture. Larger guests require regenerated goldens. + UnsupportedShape { + log_t: usize, + log_k_bytecode: usize, + log_k_ram: usize, + }, +} + +impl From for ProveProgramError { + fn from(value: JoltProveError) -> Self { + Self::Prove(value) + } +} + +impl From for ProveProgramError { + fn from(value: jolt_prover::prover::JoltOpeningInputError) -> Self { + Self::Openings(value) + } +} + +/// Errors produced by `verify_proof`. +#[derive(Debug)] +pub enum VerifyProgramError { + /// Inner verifier failure (commitment / Stage 1-7 / evaluation). + Verify(JoltVerifyError), + /// Opening-input re-derivation failed when reconstructing verifier inputs. + Openings(jolt_prover::prover::JoltOpeningInputError), + /// `prove_program` did not produce an evaluation proof, so the verifier + /// cannot complete the round-trip. + MissingEvaluation, + /// Re-decoded bytecode shape doesn't match the proven program's shape + /// (would indicate a stale `ProofBundle` paired with a mutated `Program`). + UnsupportedShape { + log_t: usize, + log_k_bytecode: usize, + log_k_ram: usize, + }, +} + +impl From for VerifyProgramError { + fn from(value: JoltVerifyError) -> Self { + Self::Verify(value) + } +} + +impl From for VerifyProgramError { + fn from(value: jolt_prover::prover::JoltOpeningInputError) -> Self { + Self::Openings(value) + } +} + +/// Bundle returned by `prove_program` carrying everything needed to verify +/// the proof end-to-end via `verify_proof`. Owning the auxiliary state +/// (bytecode entries, RAM bufs, PCS setup, etc.) lets `verify_proof` rebuild +/// verifier inputs without re-tracing the guest. +pub struct ProofBundle { + pub proof: JoltProof, + pub io_device: JoltDevice, + pub artifacts: JoltProverArtifacts, + pub bytecode_entries: Vec>, + pub entry_bytecode_index: usize, + pub lookup_table_count: usize, + pub initial_ram_state: Vec, + pub final_ram_state: Vec, + pub lowest_addr: u64, + pub entry_address: u64, + params: ModularJoltParams, + pcs_setup: DoryProverSetup, +} + +/// JoltProtocolParams equivalent without the MLIR/LLVM dependency. +/// +/// Hand-port of `JoltProtocolParams::new` from `crates/bolt/src/protocols/ +/// jolt/params.rs`, keeping only the numeric fields needed at runtime. +/// Stays in sync with that file by construction (same formulas, same +/// constants). +#[derive(Clone, Copy, Debug)] +struct ModularJoltParams { + log_t: usize, + trace_length: usize, + log_k_bytecode: usize, + #[expect(dead_code, reason = "kept for symmetry with JoltProtocolParams::new")] + bytecode_k: usize, + log_k_ram: usize, + ram_k: usize, + log_k_chunk: usize, + lookups_ra_virtual_log_k_chunk: usize, + register_log_k: usize, + instruction_d: usize, + instruction_ra_virtual_d: usize, + bytecode_d: usize, + ram_d: usize, +} + +impl ModularJoltParams { + fn new(log_t: usize, log_k_bytecode: usize, log_k_ram: usize) -> Self { + let log_k_chunk = if log_t < 25 { 4 } else { 8 }; + let instruction_log_k = 128; + let lookups_ra_virtual_log_k_chunk = if log_t < 25 { + instruction_log_k / 8 + } else { + instruction_log_k / 4 + }; + let instruction_d = instruction_log_k / log_k_chunk; + let instruction_ra_virtual_d = instruction_log_k / lookups_ra_virtual_log_k_chunk; + let bytecode_d = log_k_bytecode.div_ceil(log_k_chunk); + let ram_d = log_k_ram.div_ceil(log_k_chunk); + Self { + log_t, + trace_length: 1usize << log_t, + log_k_bytecode, + bytecode_k: 1usize << log_k_bytecode, + log_k_ram, + ram_k: 1usize << log_k_ram, + log_k_chunk, + lookups_ra_virtual_log_k_chunk, + register_log_k: 7, + instruction_d, + instruction_ra_virtual_d, + bytecode_d, + ram_d, + } + } + + fn stage6_witness_params(&self) -> Stage6WitnessParams { + Stage6WitnessParams { + trace_len: self.trace_length, + log_k_chunk: self.log_k_chunk, + log_k_bytecode: self.log_k_bytecode, + log_k_ram: self.log_k_ram, + lookups_ra_virtual_log_k_chunk: self.lookups_ra_virtual_log_k_chunk, + instruction_d: self.instruction_d, + instruction_ra_virtual_d: self.instruction_ra_virtual_d, + bytecode_d: self.bytecode_d, + ram_d: self.ram_d, + } + } +} + +/// Top-level entry: build a Jolt proof from a guest program + inputs by +/// driving the modular Bolt-based prover end-to-end. +/// +/// # Constraints +/// - Guest's padded trace, bytecode size, and RAM size must match the +/// goldens-baked fixture shape (`FIXTURE_LOG_T` etc). Larger guests +/// need a fresh `JOLT_UPDATE_GOLDENS=1` regen. +/// - Trusted advice commitments are not threaded; only untrusted advice +/// is wired through. +/// +/// # Returns +/// A [`ProofBundle`] containing the proof and everything +/// [`verify_proof`] needs to run the modular verifier round-trip without +/// re-tracing the guest. +pub fn prove_program( + program: &mut Program, + inputs: &[u8], + untrusted_advice: &[u8], + trusted_advice: &[u8], +) -> Result { + let (_lazy_trace, trace, final_memory, io_device) = + program.trace(inputs, untrusted_advice, trusted_advice); + let (bytecode_raw, init_mem, _program_size, entry_address) = program.decode(); + + let shape = check_shape(&trace, &io_device, &init_mem, bytecode_raw, entry_address)?; + let params = ModularJoltParams::new(shape.log_t, shape.log_k_bytecode, shape.log_k_ram); + + let stages = assemble_and_prove( + &trace, + &shape.bytecode, + &io_device, + &init_mem, + &final_memory, + entry_address, + ¶ms, + shape.trace_length, + shape.ram_k, + )?; + + Ok(ProofBundle { + proof: stages.proof, + io_device, + artifacts: stages.artifacts, + bytecode_entries: stages.bytecode_entries, + entry_bytecode_index: stages.entry_bytecode_index, + lookup_table_count: stages.lookup_table_count, + initial_ram_state: stages.initial_ram_state, + final_ram_state: stages.final_ram_state, + lowest_addr: stages.lowest_addr, + entry_address, + params, + pcs_setup: stages.pcs_setup, + }) +} + +/// Resolved shape parameters after padding small guests up to the goldens +/// fixture. `prove_program` rejects anything whose natural padded shape +/// doesn't equal the fixture in `(log_t, log_k_bytecode, log_k_ram)`. +struct ResolvedShape { + bytecode: BytecodePreprocessing, + trace_length: usize, + ram_k: usize, + log_t: usize, + log_k_bytecode: usize, + log_k_ram: usize, +} + +/// Pad trace/bytecode/RAM up to the goldens-baked fixture and reject +/// anything that doesn't match. Both the d-regime and the absolute +/// fixture log_t are baked in (dory tier-1 streaming sizes its buffers +/// at the fixture trace length). +fn check_shape( + trace: &[tracer::instruction::Cycle], + io_device: &JoltDevice, + init_mem: &[(u64, u8)], + bytecode_raw: Vec, + entry_address: u64, +) -> Result { + let natural_trace_length = trace.len().next_power_of_two().max(256); + let trace_length = natural_trace_length.max(1usize << FIXTURE_LOG_T); + let bytecode = BytecodePreprocessing::preprocess_padded( + bytecode_raw, + entry_address, + 1usize << FIXTURE_LOG_K_BYTECODE, + ); + let memory_layout = io_device.memory_layout.clone(); + let natural_ram_k = compute_min_ram_k(init_mem, trace, &memory_layout); + let ram_k = natural_ram_k.max(1usize << FIXTURE_LOG_K_RAM); + + let log_t = trace_length.trailing_zeros() as usize; + let log_k_bytecode = bytecode.code_size.trailing_zeros() as usize; + let log_k_ram = ram_k.trailing_zeros() as usize; + + if log_t != FIXTURE_LOG_T + || log_k_bytecode != FIXTURE_LOG_K_BYTECODE + || log_k_ram != FIXTURE_LOG_K_RAM + { + return Err(ProveProgramError::UnsupportedShape { + log_t, + log_k_bytecode, + log_k_ram, + }); + } + + Ok(ResolvedShape { + bytecode, + trace_length, + ram_k, + log_t, + log_k_bytecode, + log_k_ram, + }) +} + +/// Phase 3+4+6 output bundle, owned by [`assemble_and_prove`]. +struct ProveStageOutput { + proof: JoltProof, + artifacts: JoltProverArtifacts, + bytecode_entries: Vec>, + entry_bytecode_index: usize, + lookup_table_count: usize, + initial_ram_state: Vec, + final_ram_state: Vec, + lowest_addr: u64, + pcs_setup: DoryProverSetup, +} + +/// Drive the full Bolt stage chain on a shape-checked trace. Mirrors +/// `bolt_oracle.rs::assert_bolt_full_real_trace_self_parity` (the canonical +/// staged-prove reference) modulo the two SDK-specific substitutions +/// captured in the file header. +#[expect( + clippy::too_many_arguments, + reason = "thin internal helper; arguments are derived state from prove_program" +)] +fn assemble_and_prove( + trace: &[tracer::instruction::Cycle], + bytecode: &BytecodePreprocessing, + io_device: &JoltDevice, + init_mem: &[(u64, u8)], + final_memory: &tracer::emulator::memory::Memory, + entry_address: u64, + params: &ModularJoltParams, + trace_length: usize, + ram_k: usize, +) -> Result { + let memory_layout = io_device.memory_layout.clone(); + + let r1cs_key = R1csKey::new(rv64::rv64_constraints::(), trace_length); + let (cycle_inputs, r1cs_witness, _instruction_flags) = extract_trace::<_, Fr>( + trace, + trace_length, + bytecode, + &memory_layout, + r1cs_key.num_vars_padded, + ); + let rv64_cycles: Vec = stage1_rv64_cycles(trace, trace_length, bytecode); + let product_virtual_cycles = stage2_product_virtual_cycles(trace, trace_length); + let instruction_lookup_cycles = stage2_instruction_lookup_cycles(trace, trace_length); + let lowest_addr = memory_layout.get_lowest_address(); + let remap_addr = |addr: u64| { + if addr == 0 || addr < lowest_addr { + None + } else { + Some(((addr - lowest_addr) / 8) as usize) + } + }; + let ram_accesses: Vec = stage2_ram_accesses(trace, trace_length, remap_addr); + let stage3_cycles: Vec = stage3_cycles(trace, trace_length, bytecode); + let stage4_register_accesses: Vec = + stage4_register_accesses(trace, trace_length); + let lookup_trace: Stage5LookupTrace = stage5_lookup_trace(trace, trace_length, |cycle| { + let instr = cycle.instruction(); + instruction_lookup_table_index(&instr) + }); + let stage6_bytecode_entries_vec: Vec> = + stage6_bytecode_entries::(bytecode, |instruction| { + instruction_lookup_table_index(instruction) + }); + let (initial_ram_state, final_ram_state) = + build_ram_states(init_mem, final_memory, io_device, ram_k); + + let pcs_setup = DoryScheme::setup_prover(params.log_t + params.log_k_chunk); + + let programs = default_prover_programs(); + let mut transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); + append_bolt_preamble( + &mut transcript, + &io_event_data_for_preamble(io_device, params, entry_address), + ); + + let commitment_sources = jolt_witness::commitment_trace_sources(&cycle_inputs); + let oracle_inputs = jolt_prover::stages::commitment::CommitmentOracleInputs::from_trace_sources( + &commitment_sources, + None, + None, + ); + let mut commitment_inputs = + jolt_prover::stages::commitment::SparseCommitmentInputs::new(oracle_inputs); + let commitment_artifacts = + jolt_prover::stages::commitment::prove_commitment_phase_with_program( + programs.commitment, + &mut commitment_inputs, + &pcs_setup, + &mut transcript, + ) + .map_err(JoltProveError::Commitment)?; + + let stage1_data = Stage1OuterRv64Data::new(&r1cs_key, &r1cs_witness, &rv64_cycles) + .map_err(JoltProveError::Stage1Outer)?; + let stage1_artifacts = jolt_prover::prove_stage1_outer_with_witness_inputs( + programs.stage1_outer, + r1cs_key.num_cycle_vars(), + &stage1_data, + &mut transcript, + ) + .map_err(JoltProveError::Stage1Outer)?; + + let ram_output_layout = Stage2RamOutputLayout { + io_start: ((memory_layout.input_start - lowest_addr) / 8) as usize, + io_end: ((RAM_START_ADDRESS - lowest_addr) / 8) as usize, + }; + let ram_data = Stage2RamData { + log_k: params.log_k_ram, + start_address: lowest_addr, + initial_ram: &initial_ram_state, + final_ram: &final_ram_state, + accesses: &ram_accesses, + output_layout: Some(ram_output_layout), + }; + let stage2_openings = + jolt_prover::stage2_opening_inputs_from_artifacts(programs.stage2, &stage1_artifacts)?; + let stage2_artifacts = jolt_prover::prove_stage2_with_witness_inputs( + programs.stage2, + &stage2_openings, + &product_virtual_cycles, + &instruction_lookup_cycles, + &ram_data, + &mut transcript, + ) + .map_err(JoltProveError::Stage2)?; + + let stage3_openings = jolt_prover::stage3_opening_inputs_from_artifacts( + programs.stage3, + &stage1_artifacts, + &stage2_artifacts, + )?; + let stage3_artifacts = jolt_prover::prove_stage3_with_witness_inputs( + programs.stage3, + &stage3_openings, + &stage3_cycles, + &mut transcript, + ) + .map_err(JoltProveError::Stage3)?; + + let stage4_openings = jolt_prover::stage4_opening_inputs_from_artifacts( + programs.stage4, + &initial_ram_state, + &stage2_artifacts, + &stage3_artifacts, + )?; + let stage4_artifacts = jolt_prover::prove_stage4_with_trace_witness_inputs( + programs.stage4, + &stage4_openings, + 1usize << params.register_log_k, + params.trace_length, + params.ram_k, + &stage4_register_accesses, + &ram_accesses, + &mut transcript, + ) + .map_err(JoltProveError::Stage4)?; + + let stage5_openings = jolt_prover::stage5_opening_inputs_from_artifacts( + programs.stage5, + &stage2_artifacts, + &stage4_artifacts, + )?; + let stage5_artifacts = jolt_prover::prove_stage5_with_trace_witness_inputs( + programs.stage5, + &stage5_openings, + params.trace_length, + params.ram_k, + 1usize << params.register_log_k, + &lookup_trace.lookup_indices, + &lookup_trace.lookup_table_indices, + &lookup_trace.is_interleaved_operands, + params.lookups_ra_virtual_log_k_chunk, + &stage4_register_accesses, + &ram_accesses, + &mut transcript, + ) + .map_err(JoltProveError::Stage5)?; + + let stage6_openings = jolt_prover::stage6_opening_inputs_from_artifacts( + programs.stage6, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &stage5_artifacts, + )?; + let entry_bytecode_index = bytecode.entry_bytecode_index(); + // lookup_table_count = 40, baked into JoltProtocolParams. + let lookup_table_count = 40; + let stage6_bytecode_data = jolt_prover::stage6_bytecode_read_raf_data_from_witness_entries( + &stage6_bytecode_entries_vec, + entry_bytecode_index, + lookup_table_count, + ); + let stage6_artifacts = jolt_prover::prove_stage6_with_trace_witness_inputs( + programs.stage6, + &stage6_openings, + stage6_bytecode_data.as_input(), + params.stage6_witness_params(), + &cycle_inputs, + params.instruction_ra_virtual_d, + &mut transcript, + ) + .map_err(JoltProveError::Stage6)?; + + let stage7_openings = jolt_prover::stage7_opening_inputs_from_stage6_artifacts_with_program( + programs.stage7, + &stage6_artifacts, + )?; + let stage7_artifacts = jolt_prover::prove_stage7_with_trace_witness_inputs( + programs.stage7, + &stage7_openings, + params.stage6_witness_params(), + &cycle_inputs, + &stage6_openings, + &mut transcript, + ) + .map_err(JoltProveError::Stage7)?; + + let evaluation = jolt_prover::prove_jolt_evaluation_proof( + programs.stage8, + &mut commitment_inputs, + &pcs_setup, + &commitment_artifacts, + &stage6_artifacts, + &stage7_artifacts, + &stage7_openings, + &mut transcript, + ) + .map_err(JoltProveError::Evaluation)?; + + let stage5_proof = jolt_prover::stage5_proof(&stage5_artifacts); + let stage6_proof = jolt_prover::stage6_proof(&stage6_artifacts); + let stage7_proof = jolt_prover::stage7_proof(&stage7_artifacts); + let mut proof = jolt_prover::jolt_proof_through_stage7( + &commitment_artifacts.commitments, + &stage1_artifacts, + &stage2_artifacts, + &stage3_artifacts, + &stage4_artifacts, + &stage5_proof, + &stage6_proof, + &stage7_proof, + ); + proof.evaluation = Some(evaluation); + + let artifacts = JoltProverArtifacts { + commitment: commitment_artifacts, + stage1_outer: stage1_artifacts, + stage2: stage2_artifacts, + stage3: stage3_artifacts, + stage4: stage4_artifacts, + stage5: stage5_artifacts, + stage6: stage6_artifacts, + stage7: stage7_artifacts, + }; + + Ok(ProveStageOutput { + proof, + artifacts, + bytecode_entries: stage6_bytecode_entries_vec, + entry_bytecode_index, + lookup_table_count, + initial_ram_state, + final_ram_state, + lowest_addr, + pcs_setup, + }) +} + +/// Round-trip verify a proof produced by [`prove_program`] using the same +/// modular verifier stack the prover used. Rebuilds verifier inputs from +/// `ProofBundle`'s captured state plus a re-decoding of `program`'s +/// bytecode — does NOT re-trace the guest. +/// +/// Mirrors the canonical verify pattern at +/// `crates/jolt-equivalence/src/bolt_oracle.rs::assert_bolt_full_real_trace_self_parity` +/// (lines 519-547 build inputs, 728-743 call verify). +pub fn verify_proof(output: &ProofBundle, program: &mut Program) -> Result<(), VerifyProgramError> { + if output.proof.evaluation.is_none() { + return Err(VerifyProgramError::MissingEvaluation); + } + + let (bytecode_raw, _init_mem, _program_size, decoded_entry_address) = program.decode(); + let bytecode = BytecodePreprocessing::preprocess_padded( + bytecode_raw, + decoded_entry_address, + 1usize << output.params.log_k_bytecode, + ); + let log_k_bytecode = bytecode.code_size.trailing_zeros() as usize; + if log_k_bytecode != output.params.log_k_bytecode + || decoded_entry_address != output.entry_address + { + return Err(VerifyProgramError::UnsupportedShape { + log_t: output.params.log_t, + log_k_bytecode, + log_k_ram: output.params.log_k_ram, + }); + } + + let programs = default_verifier_programs(); + let prover_programs = default_prover_programs(); + + let stage2_kernel_openings = jolt_prover::stage2_opening_inputs_from_artifacts( + prover_programs.stage2, + &output.artifacts.stage1_outer, + )?; + let stage3_kernel_openings = jolt_prover::stage3_opening_inputs_from_artifacts( + prover_programs.stage3, + &output.artifacts.stage1_outer, + &output.artifacts.stage2, + )?; + let stage4_kernel_openings = jolt_prover::stage4_opening_inputs_from_artifacts( + prover_programs.stage4, + &output.initial_ram_state, + &output.artifacts.stage2, + &output.artifacts.stage3, + )?; + let stage5_kernel_openings = jolt_prover::stage5_opening_inputs_from_artifacts( + prover_programs.stage5, + &output.artifacts.stage2, + &output.artifacts.stage4, + )?; + let stage6_kernel_openings = jolt_prover::stage6_opening_inputs_from_artifacts( + prover_programs.stage6, + &output.artifacts.stage1_outer, + &output.artifacts.stage2, + &output.artifacts.stage3, + &output.artifacts.stage4, + &output.artifacts.stage5, + )?; + let stage7_kernel_openings = + jolt_prover::stage7_opening_inputs_from_stage6_artifacts_with_program( + prover_programs.stage7, + &output.artifacts.stage6, + )?; + + let stage2_openings = jolt_prover::verifier_opening_inputs_from_kernel(&stage2_kernel_openings); + let stage3_openings = jolt_prover::verifier_opening_inputs_from_kernel(&stage3_kernel_openings); + let stage4_openings = jolt_prover::verifier_opening_inputs_from_kernel(&stage4_kernel_openings); + let stage5_openings = jolt_prover::verifier_opening_inputs_from_kernel(&stage5_kernel_openings); + let stage6_openings = jolt_prover::verifier_opening_inputs_from_kernel(&stage6_kernel_openings); + let stage7_openings = jolt_prover::verifier_opening_inputs_from_kernel(&stage7_kernel_openings); + + let memory_layout = output.io_device.memory_layout.clone(); + let ram_output_layout = Stage2RamOutputLayout { + io_start: ((memory_layout.input_start - output.lowest_addr) / 8) as usize, + io_end: ((RAM_START_ADDRESS - output.lowest_addr) / 8) as usize, + }; + let kernel_ram_data = Stage2RamData { + log_k: output.params.log_k_ram, + start_address: output.lowest_addr, + initial_ram: &output.initial_ram_state, + final_ram: &output.final_ram_state, + accesses: &[], + output_layout: Some(ram_output_layout), + }; + let verifier_ram_storage = jolt_prover::stage2_verifier_ram_data(&kernel_ram_data); + let verifier_ram = verifier_ram_storage.as_input(); + + let stage6_data = jolt_prover::stage6_verifier_data_from_witness_entries( + &output.bytecode_entries, + output.entry_bytecode_index, + output.lookup_table_count, + ); + + let evaluation_setup = DoryScheme::verifier_setup(&output.pcs_setup); + + let inputs = JoltVerifierInputs { + stage2_openings: &stage2_openings, + stage2_ram: Some(&verifier_ram), + stage3_openings: &stage3_openings, + stage4_openings: &stage4_openings, + stage5_openings: &stage5_openings, + stage6_openings: &stage6_openings, + stage6_data: Some(&stage6_data), + stage7_openings: &stage7_openings, + evaluation_setup: Some(&evaluation_setup), + }; + + let mut transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); + append_bolt_preamble( + &mut transcript, + &io_event_data_for_preamble(&output.io_device, &output.params, output.entry_address), + ); + + let _ = verify_jolt_with_programs(&output.proof, inputs, programs, &mut transcript)?; + Ok(()) +} + +/// Compact view of (program_io, params, entry_address) for transcript +/// preamble — mirrors `BoltPreambleSource` in jolt-equivalence without the +/// trait machinery. +struct PreambleData<'a> { + io: &'a JoltDevice, + params: &'a ModularJoltParams, + entry_address: u64, +} + +fn io_event_data_for_preamble<'a>( + io: &'a JoltDevice, + params: &'a ModularJoltParams, + entry_address: u64, +) -> PreambleData<'a> { + PreambleData { + io, + params, + entry_address, + } +} + +/// Append the Bolt preamble (preprocessing digest + memory_layout + I/O + +/// protocol params) to the transcript. +/// +/// Mirrors `crates/jolt-equivalence/src/commitment_oracle.rs:184-236` +/// (`append_bolt_preamble`) but takes its inputs from `PreambleData` +/// rather than the `BoltPreambleSource` trait. +fn append_bolt_preamble(transcript: &mut T, data: &PreambleData<'_>) +where + T: Transcript, +{ + let preprocessing_digest = [0u8; 32]; + append_bytes(transcript, b"preprocessing_digest", &preprocessing_digest); + let layout = &data.io.memory_layout; + append_u64(transcript, b"max_input_size", layout.max_input_size); + append_u64(transcript, b"max_output_size", layout.max_output_size); + append_u64(transcript, b"heap_size", layout.heap_size); + append_bytes(transcript, b"inputs", &data.io.inputs); + append_bytes(transcript, b"outputs", &data.io.outputs); + append_u64(transcript, b"panic", data.io.panic as u64); + append_u64(transcript, b"ram_K", data.params.ram_k as u64); + append_u64(transcript, b"trace_length", data.params.trace_length as u64); + append_u64(transcript, b"entry_address", data.entry_address); + append_u64( + transcript, + b"ram_rw_phase1_num_rounds", + data.params.log_t as u64, + ); + append_u64( + transcript, + b"ram_rw_phase2_num_rounds", + data.params.log_k_ram as u64, + ); + append_u64( + transcript, + b"registers_rw_phase1_num_rounds", + data.params.log_t as u64, + ); + append_u64( + transcript, + b"registers_rw_phase2_num_rounds", + data.params.register_log_k as u64, + ); + append_u64(transcript, b"log_k_chunk", data.params.log_k_chunk as u64); + append_u64( + transcript, + b"lookups_ra_virtual_log_k_chunk", + data.params.lookups_ra_virtual_log_k_chunk as u64, + ); + // dory_layout: 0 = CycleMajor, 1 = AddressMajor. Fixture uses CycleMajor. + append_u64(transcript, b"dory_layout", 0); +} + +fn append_u64(transcript: &mut T, label: &'static [u8], value: u64) +where + T: Transcript, +{ + transcript.append(&Label(label)); + transcript.append(&U64Word(value)); +} + +fn append_bytes(transcript: &mut T, label: &'static [u8], bytes: &[u8]) +where + T: Transcript, +{ + transcript.append(&LabelWithCount(label, bytes.len() as u64)); + transcript.append_bytes(bytes); +} + +/// Compute the minimum `ram_K` needed for this trace. Mirrors the inline +/// computation in `feat/fr-coprocessor-v2`'s poseidon2_sdk_e2e test +/// (which is also the formula behind `jolt_core::zkvm::ram::compute_min_ram_K`): +/// +/// `ram_K = max(remapped_trace_max_addr, bytecode_end_remapped)`, then +/// rounded up to the next power of two. +fn compute_min_ram_k( + init_mem: &[(u64, u8)], + trace: &[impl CycleRow], + memory_layout: &MemoryLayout, +) -> usize { + let lowest_addr = memory_layout.get_lowest_address(); + let min_bc_addr = init_mem + .iter() + .map(|(a, _)| *a) + .min() + .unwrap_or(lowest_addr); + let max_bc_addr = init_mem + .iter() + .map(|(a, _)| *a) + .max() + .unwrap_or(lowest_addr) + + (BYTES_PER_INSTRUCTION as u64 - 1); + let num_bc_words = max_bc_addr.div_ceil(8) - min_bc_addr / 8 + 1; + let bytecode_start_remapped = if min_bc_addr >= lowest_addr && min_bc_addr != 0 { + (min_bc_addr - lowest_addr) / 8 + } else { + 0 + }; + let trace_max_remapped: u64 = trace + .iter() + .filter_map(|cycle| { + let addr = cycle.ram_access_address()?; + if addr == 0 || addr < lowest_addr { + None + } else { + Some((addr - lowest_addr) / 8) + } + }) + .max() + .unwrap_or(0); + let io_end_remapped = if RAM_START_ADDRESS >= lowest_addr { + (RAM_START_ADDRESS - lowest_addr) / 8 + } else { + 0 + }; + let ram_k_min = trace_max_remapped + .max(bytecode_start_remapped + num_bc_words + 1) + .max(io_end_remapped); + (ram_k_min as usize).next_power_of_two() +} diff --git a/crates/jolt-host/tests/prove_program_smoke.rs b/crates/jolt-host/tests/prove_program_smoke.rs new file mode 100644 index 0000000000..a8b5831c71 --- /dev/null +++ b/crates/jolt-host/tests/prove_program_smoke.rs @@ -0,0 +1,50 @@ +//! Smoke test for `jolt_host::prove_program` — drives a guest ELF through +//! the modular prove pipeline + verify round-trip. +//! +//! Uses `muldiv` from `examples/muldiv` because it's the smallest, lowest- +//! dep guest that fits the fixture shape (max_trace_length = 65536, +//! i.e. log_t = 16). No rayon, no String allocator, just `a * b / c`. +//! +//! Requires the `jolt` CLI to be installed (`cargo install --path .`). + +#![expect( + clippy::expect_used, + clippy::panic, + clippy::print_stderr +)] + +use jolt_host::{prove_program, verify_proof, ProveProgramError}; +use jolt_trace::Program; + +#[test] +fn muldiv_modular_prove_smoke() { + let mut program = Program::new("muldiv-guest"); + let inputs = postcard::to_stdvec(&[9u32, 5u32, 3u32]).expect("postcard encode muldiv inputs"); + + let result = prove_program(&mut program, &inputs, &[], &[]); + + match result { + Ok(output) => { + eprintln!( + "[prove_program_smoke] proof generated, io output bytes = {}, \ + evaluation present = {}", + output.io_device.outputs.len(), + output.proof.evaluation.is_some() + ); + verify_proof(&output, &mut program).expect("modular verifier accepts muldiv proof"); + } + Err(ProveProgramError::UnsupportedShape { + log_t, + log_k_bytecode, + log_k_ram, + }) => { + panic!( + "muldiv shape mismatch: actual=({log_t}, {log_k_bytecode}, {log_k_ram}), \ + fixture=(18, 14, 14). Goldens need regen at the new shape." + ); + } + Err(other) => { + panic!("prove_program failed unexpectedly: {other:?}"); + } + } +} diff --git a/crates/jolt-hyperkzg/Cargo.toml b/crates/jolt-hyperkzg/Cargo.toml new file mode 100644 index 0000000000..9986c5f33f --- /dev/null +++ b/crates/jolt-hyperkzg/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "jolt-hyperkzg" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "HyperKZG multilinear polynomial commitment scheme for the Jolt zkVM" + +[lints] +workspace = true + +[dependencies] +jolt-crypto = { path = "../jolt-crypto" } +jolt-field = { path = "../jolt-field" } +jolt-poly = { path = "../jolt-poly" } +jolt-transcript = { path = "../jolt-transcript" } +jolt-openings = { path = "../jolt-openings" } +serde = { workspace = true, features = ["derive"] } +tracing.workspace = true +num-traits = { workspace = true } +rayon = { workspace = true } +thiserror = { workspace = true } +rand_core = { workspace = true, features = ["getrandom"] } +rand_chacha = { workspace = true } + +[dev-dependencies] +jolt-field = { path = "../jolt-field", features = ["bn254"] } +criterion = { workspace = true } +rand = { workspace = true } + +[[bench]] +name = "hyperkzg" +harness = false + +[package.metadata.cargo-machete] +ignored = ["rand_core", "rand_chacha", "rand"] diff --git a/crates/jolt-hyperkzg/README.md b/crates/jolt-hyperkzg/README.md new file mode 100644 index 0000000000..a71a555d61 --- /dev/null +++ b/crates/jolt-hyperkzg/README.md @@ -0,0 +1,49 @@ +# jolt-hyperkzg + +HyperKZG multilinear polynomial commitment scheme for the Jolt zkVM. + +Part of the [Jolt](https://github.com/a16z/jolt) zkVM. + +## Overview + +HyperKZG reduces multilinear polynomial commitments to univariate KZG using the Gemini transformation ([section 2.4.2](https://eprint.iacr.org/2022/420.pdf)), operating directly on evaluation-form polynomials (no FFT/interpolation). + +This crate is generic over `PairingGroup` from `jolt-crypto` and implements `CommitmentScheme` and `AdditivelyHomomorphic` from `jolt-openings`. + +### Protocol + +1. **Commit** — MSM of evaluations against SRS G1 powers. +2. **Open** (Gemini reduction) — fold the multilinear polynomial `ℓ-1` times producing intermediate commitments, derive challenge `r`, batch KZG open at `[r, -r, r²]`. +3. **Verify** — evaluation consistency check, then batch KZG pairing check. + +## Public API + +- **`HyperKZGScheme

`** — Main entry point. Implements `CommitmentScheme` and `AdditivelyHomomorphic`. +- **`HyperKZGCommitment

`** — A commitment (G1 point). +- **`HyperKZGProof

`** — Opening proof containing intermediate commitments and evaluations. +- **`HyperKZGProverSetup

`** / **`HyperKZGVerifierSetup

`** — Structured reference strings. + +### Submodules + +- **`kzg`** — Univariate KZG primitives (commit, open, batch verify). +- **`error`** — Error types. + +## Dependency Position + +``` +jolt-field ─┐ +jolt-crypto ─┤ +jolt-poly ─┼─► jolt-hyperkzg +jolt-transcript ─┤ +jolt-openings ─┘ +``` + +Used by `jolt-zkvm`. + +## Feature Flags + +This crate has no feature flags. + +## License + +MIT diff --git a/crates/jolt-hyperkzg/benches/hyperkzg.rs b/crates/jolt-hyperkzg/benches/hyperkzg.rs new file mode 100644 index 0000000000..56e4796198 --- /dev/null +++ b/crates/jolt-hyperkzg/benches/hyperkzg.rs @@ -0,0 +1,178 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +use jolt_crypto::Bn254; +use jolt_field::{Field, Fr}; +use jolt_hyperkzg::{HyperKZGProverSetup, HyperKZGScheme, HyperKZGVerifierSetup}; +use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme}; +use jolt_poly::Polynomial; +use jolt_transcript::Transcript; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +type TestScheme = HyperKZGScheme; + +fn make_setup(max_degree: usize) -> (HyperKZGProverSetup, HyperKZGVerifierSetup) { + let mut rng = ChaCha20Rng::seed_from_u64(0xbe0c); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let pk = TestScheme::setup(&mut rng, max_degree, g1, g2); + let vk = TestScheme::verifier_setup(&pk); + (pk, vk) +} + +fn bench_commit(c: &mut Criterion) { + let mut group = c.benchmark_group("hyperkzg_commit"); + for num_vars in [8, 10, 12, 14] { + let n = 1 << num_vars; + let (pk, _) = make_setup(n); + let _ = group.bench_with_input( + BenchmarkId::from_parameter(num_vars), + &num_vars, + |b, &nv| { + b.iter_batched( + || { + let mut rng = ChaCha20Rng::seed_from_u64(0); + Polynomial::::random(nv, &mut rng) + }, + |poly| TestScheme::commit(poly.evaluations(), &pk), + criterion::BatchSize::SmallInput, + ); + }, + ); + } + group.finish(); +} + +fn bench_open(c: &mut Criterion) { + let mut group = c.benchmark_group("hyperkzg_open"); + for num_vars in [8, 10, 12, 14] { + let n = 1 << num_vars; + let (pk, _) = make_setup(n); + let _ = group.bench_with_input( + BenchmarkId::from_parameter(num_vars), + &num_vars, + |b, &nv| { + b.iter_batched( + || { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let poly = Polynomial::::random(nv, &mut rng); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + (poly, point, eval) + }, + |(poly, point, eval)| { + let mut transcript = jolt_transcript::Blake2bTranscript::new(b"bench-open"); + ::open( + &poly, + &point, + eval, + &pk, + None, + &mut transcript, + ) + }, + criterion::BatchSize::SmallInput, + ); + }, + ); + } + group.finish(); +} + +fn bench_verify(c: &mut Criterion) { + let mut group = c.benchmark_group("hyperkzg_verify"); + for num_vars in [8, 10, 12, 14] { + let n = 1 << num_vars; + let (pk, vk) = make_setup(n); + let _ = group.bench_with_input( + BenchmarkId::from_parameter(num_vars), + &num_vars, + |b, &nv| { + b.iter_batched( + || { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let poly = Polynomial::::random(nv, &mut rng); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + let mut transcript = + jolt_transcript::Blake2bTranscript::new(b"bench-verify"); + let proof = ::open( + &poly, + &point, + eval, + &pk, + None, + &mut transcript, + ); + (commitment, point, eval, proof) + }, + |(commitment, point, eval, proof)| { + let mut transcript = + jolt_transcript::Blake2bTranscript::new(b"bench-verify"); + ::verify( + &commitment, + &point, + eval, + &proof, + &vk, + &mut transcript, + ) + }, + criterion::BatchSize::SmallInput, + ); + }, + ); + } + group.finish(); +} + +fn bench_combine(c: &mut Criterion) { + let mut group = c.benchmark_group("hyperkzg_combine"); + for count in [2, 4, 8, 16] { + let num_vars = 10; + let n = 1 << num_vars; + let (pk, _) = make_setup(n); + let mut rng = ChaCha20Rng::seed_from_u64(0); + + let commitments: Vec<_> = (0..count) + .map(|_| { + let poly = Polynomial::::random(num_vars, &mut rng); + let (c, ()) = TestScheme::commit(poly.evaluations(), &pk); + c + }) + .collect(); + let scalars: Vec = (0..count).map(|_| Fr::random(&mut rng)).collect(); + + let _ = group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, _| { + b.iter(|| TestScheme::combine(&commitments, &scalars)); + }); + } + group.finish(); +} + +fn bench_setup(c: &mut Criterion) { + let mut group = c.benchmark_group("hyperkzg_setup"); + for num_vars in [8, 10, 12] { + let n = 1 << num_vars; + let _ = group.bench_with_input(BenchmarkId::from_parameter(num_vars), &num_vars, |b, _| { + b.iter(|| { + let mut rng = ChaCha20Rng::seed_from_u64(0xbe0c); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + TestScheme::setup(&mut rng, n, g1, g2) + }); + }); + } + group.finish(); +} + +criterion_group!( + benches, + bench_setup, + bench_commit, + bench_open, + bench_verify, + bench_combine, +); +criterion_main!(benches); diff --git a/crates/jolt-hyperkzg/fuzz/Cargo.toml b/crates/jolt-hyperkzg/fuzz/Cargo.toml new file mode 100644 index 0000000000..dc2b94ca42 --- /dev/null +++ b/crates/jolt-hyperkzg/fuzz/Cargo.toml @@ -0,0 +1,36 @@ +[workspace] + +[package] +name = "jolt-hyperkzg-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +jolt-hyperkzg = { path = ".." } +jolt-crypto = { path = "../../jolt-crypto" } +jolt-field = { path = "../../jolt-field", features = ["bn254"] } +jolt-openings = { path = "../../jolt-openings" } +jolt-poly = { path = "../../jolt-poly" } +jolt-transcript = { path = "../../jolt-transcript" } +rand_chacha = "0.3" +rand_core = "0.6" + +[[bin]] +name = "commit_open_verify" +path = "fuzz_targets/commit_open_verify.rs" +doc = false + +[[bin]] +name = "tampered_proof" +path = "fuzz_targets/tampered_proof.rs" +doc = false + +[[bin]] +name = "wrong_eval" +path = "fuzz_targets/wrong_eval.rs" +doc = false diff --git a/crates/jolt-hyperkzg/fuzz/fuzz_targets/commit_open_verify.rs b/crates/jolt-hyperkzg/fuzz/fuzz_targets/commit_open_verify.rs new file mode 100644 index 0000000000..51c8079b54 --- /dev/null +++ b/crates/jolt-hyperkzg/fuzz/fuzz_targets/commit_open_verify.rs @@ -0,0 +1,45 @@ +#![no_main] + +//! Fuzz: random polynomial + random point must always commit-open-verify successfully. + +use jolt_crypto::Bn254; +use jolt_field::{Field, Fr}; +use jolt_hyperkzg::HyperKZGScheme; +use jolt_openings::CommitmentScheme; +use jolt_poly::Polynomial; +use jolt_transcript::{Blake2bTranscript, Transcript}; +use libfuzzer_sys::fuzz_target; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +type TestScheme = HyperKZGScheme; + +fuzz_target!(|data: &[u8]| { + if data.len() < 8 { + return; + } + + let seed = u64::from_le_bytes(data[..8].try_into().unwrap()); + let num_vars = (data.len() % 4) + 1; // 1..=4 variables + let n = 1usize << num_vars; + + let mut rng = ChaCha20Rng::seed_from_u64(seed); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let pk = TestScheme::setup(&mut rng, n, g1, g2); + let vk = TestScheme::verifier_setup(&pk); + + let poly = Polynomial::::random(num_vars, &mut rng); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut pt = Blake2bTranscript::new(b"fuzz"); + let proof = + ::open(&poly, &point, eval, &pk, None, &mut pt); + + let mut vt = Blake2bTranscript::new(b"fuzz"); + ::verify(&commitment, &point, eval, &proof, &vk, &mut vt) + .expect("valid proof must verify"); +}); diff --git a/crates/jolt-hyperkzg/fuzz/fuzz_targets/tampered_proof.rs b/crates/jolt-hyperkzg/fuzz/fuzz_targets/tampered_proof.rs new file mode 100644 index 0000000000..12dc6cf59c --- /dev/null +++ b/crates/jolt-hyperkzg/fuzz/fuzz_targets/tampered_proof.rs @@ -0,0 +1,65 @@ +#![no_main] + +//! Fuzz: tamper with proof evaluation bytes and verify that verification rejects. +//! +//! We generate a valid proof, then corrupt an evaluation entry using fuzzer-chosen bytes. + +use jolt_crypto::Bn254; +use jolt_field::{Field, Fr}; +use jolt_hyperkzg::HyperKZGScheme; +use jolt_openings::CommitmentScheme; +use jolt_poly::Polynomial; +use jolt_transcript::{Blake2bTranscript, Transcript}; +use libfuzzer_sys::fuzz_target; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +type TestScheme = HyperKZGScheme; + +fuzz_target!(|data: &[u8]| { + if data.len() < 10 { + return; + } + + let num_vars = 3; + let n = 1usize << num_vars; + + let mut rng = ChaCha20Rng::seed_from_u64(0xfade); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let pk = TestScheme::setup(&mut rng, n, g1, g2); + let vk = TestScheme::verifier_setup(&pk); + + let poly = Polynomial::::random(num_vars, &mut rng); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut pt = Blake2bTranscript::new(b"fuzz-tamper"); + let proof = + ::open(&poly, &point, eval, &pk, None, &mut pt); + + let tamper_row = (data[0] as usize) % proof.v.len(); + let tamper_col = (data[1] as usize) % proof.v[tamper_row].len(); + let tamper_val = Fr::from_bytes(&data[2..]); + + // Skip if the corruption is a no-op + if tamper_val == proof.v[tamper_row][tamper_col] { + return; + } + + let mut tampered = proof.clone(); + tampered.v[tamper_row][tamper_col] = tamper_val; + + let mut vt = Blake2bTranscript::new(b"fuzz-tamper"); + let result = ::verify( + &commitment, + &point, + eval, + &tampered, + &vk, + &mut vt, + ); + assert!(result.is_err(), "tampered proof must be rejected"); +}); diff --git a/crates/jolt-hyperkzg/fuzz/fuzz_targets/wrong_eval.rs b/crates/jolt-hyperkzg/fuzz/fuzz_targets/wrong_eval.rs new file mode 100644 index 0000000000..4c15d1af2f --- /dev/null +++ b/crates/jolt-hyperkzg/fuzz/fuzz_targets/wrong_eval.rs @@ -0,0 +1,59 @@ +#![no_main] + +//! Fuzz: claim a wrong evaluation and verify that verification rejects. +//! +//! The prover generates a valid proof for the correct evaluation. The +//! verifier checks against a fuzzer-derived wrong evaluation. Must reject. + +use jolt_crypto::Bn254; +use jolt_field::{Field, Fr}; +use jolt_hyperkzg::HyperKZGScheme; +use jolt_openings::CommitmentScheme; +use jolt_poly::Polynomial; +use jolt_transcript::{Blake2bTranscript, Transcript}; +use libfuzzer_sys::fuzz_target; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +type TestScheme = HyperKZGScheme; + +fuzz_target!(|data: &[u8]| { + if data.len() < 32 { + return; + } + + let num_vars = 3; + let n = 1usize << num_vars; + + let mut rng = ChaCha20Rng::seed_from_u64(0xface); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let pk = TestScheme::setup(&mut rng, n, g1, g2); + let vk = TestScheme::verifier_setup(&pk); + + let poly = Polynomial::::random(num_vars, &mut rng); + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let wrong_eval = Fr::from_bytes(data); + if wrong_eval == eval { + return; + } + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut pt = Blake2bTranscript::new(b"fuzz-wrong-eval"); + let proof = + ::open(&poly, &point, eval, &pk, None, &mut pt); + + let mut vt = Blake2bTranscript::new(b"fuzz-wrong-eval"); + let result = ::verify( + &commitment, + &point, + wrong_eval, + &proof, + &vk, + &mut vt, + ); + assert!(result.is_err(), "wrong evaluation must be rejected"); +}); diff --git a/crates/jolt-hyperkzg/src/error.rs b/crates/jolt-hyperkzg/src/error.rs new file mode 100644 index 0000000000..9a22a896b4 --- /dev/null +++ b/crates/jolt-hyperkzg/src/error.rs @@ -0,0 +1,14 @@ +//! Error types for HyperKZG operations. + +/// Errors produced by the HyperKZG commitment scheme. +#[derive(Debug, thiserror::Error)] +pub enum HyperKZGError { + #[error("SRS too small: have {have} powers, need {need}")] + SrsTooSmall { have: usize, need: usize }, + + #[error("proof verification failed")] + VerificationFailed, + + #[error("invalid proof structure: {0}")] + InvalidProof(&'static str), +} diff --git a/crates/jolt-hyperkzg/src/kzg.rs b/crates/jolt-hyperkzg/src/kzg.rs new file mode 100644 index 0000000000..ae3dcdce20 --- /dev/null +++ b/crates/jolt-hyperkzg/src/kzg.rs @@ -0,0 +1,259 @@ +//! Univariate KZG primitives: commit, witness polynomial, batch open/verify. +//! +//! These are the building blocks consumed by the HyperKZG protocol. +//! All operations are generic over `P: PairingGroup`. + +use jolt_crypto::{JoltGroup, PairingGroup}; +use jolt_field::Field; +use jolt_transcript::{AppendToTranscript, Transcript}; +use num_traits::{One, Zero}; +use rayon::prelude::*; + +use crate::error::HyperKZGError; +use crate::types::{HyperKZGProverSetup, HyperKZGVerifierSetup}; + +/// Commits to a polynomial (given as evaluation/coefficient vector) using MSM against SRS G1 powers. +pub(crate) fn kzg_commit( + coeffs: &[P::ScalarField], + setup: &HyperKZGProverSetup

, +) -> Result { + if setup.g1_powers.len() < coeffs.len() { + return Err(HyperKZGError::SrsTooSmall { + have: setup.g1_powers.len(), + need: coeffs.len(), + }); + } + Ok(P::G1::msm(&setup.g1_powers[..coeffs.len()], coeffs)) +} + +/// Computes the KZG witness polynomial `h(x) = f(x) / (x - u)`. +/// +/// Uses Horner's method in reverse: `h[i-1] = f[i] + h[i] * u`. +/// The remainder is `f(u)`, but we don't need it since the verifier +/// can derive it from the evaluation vectors. +pub(crate) fn compute_witness_polynomial(f: &[F], u: F) -> Vec { + let d = f.len(); + let mut h = vec![F::zero(); d]; + for i in (1..d).rev() { + h[i - 1] = f[i] + h[i] * u; + } + h +} + +/// Evaluates a polynomial (in evaluation/coefficient form) at a point. +/// +/// Standard Horner evaluation: `f(u) = f[0] + f[1]*u + f[2]*u^2 + ...` +pub(crate) fn eval_univariate(coeffs: &[F], u: F) -> F { + let mut result = F::zero(); + let mut power = F::one(); + for &c in coeffs { + result += c * power; + power *= u; + } + result +} + +/// Batch KZG opening: commits to witness polynomials for each evaluation point. +/// +/// Given polynomials `f[0..k]` and evaluation points `u[0..t]`, computes: +/// - `v[i][j]` = f_j(u_i) for all i, j +/// - Linear combination `B = sum_j q^j * f_j` using Fiat-Shamir challenge +/// - Witness commitments `w[i]` = commit(B(x) / (x - u_i)) +/// +/// Returns `(w, v)`. +pub(crate) fn kzg_open_batch( + f: &[Vec], + u: &[P::ScalarField], + setup: &HyperKZGProverSetup

, + transcript: &mut T, +) -> (Vec, Vec>) +where + P: PairingGroup, + T: Transcript, + P::ScalarField: AppendToTranscript, + P::G1: AppendToTranscript, +{ + let k = f.len(); + + // Compute evaluations v[i][j] = f_j(u_i) + let v: Vec> = u + .par_iter() + .map(|ui| f.iter().map(|fj| eval_univariate(fj, *ui)).collect()) + .collect(); + + // Absorb all evaluations into transcript + for row in &v { + for val in row { + transcript.append(val); + } + } + + // Derive batching challenge and compute powers q, q^2, ..., q^{k-1} + let q: P::ScalarField = transcript.challenge(); + let q_powers = challenge_powers(q, k); + + // B(x) = sum_j q^j * f_j(x) + let poly_len = f[0].len(); + let mut b_poly = vec![P::ScalarField::zero(); poly_len]; + for (fj, &qj) in f.iter().zip(q_powers.iter()) { + for (b, &c) in b_poly.iter_mut().zip(fj.iter()) { + *b += qj * c; + } + } + + // Compute witness polynomials and commit + let w: Vec = u + .par_iter() + .map(|ui| { + let h = compute_witness_polynomial::(&b_poly, *ui); + P::G1::msm(&setup.g1_powers[..h.len()], &h) + }) + .collect(); + + // Absorb witness commitments and derive one more challenge to keep + // prover/verifier transcripts in sync + for wi in &w { + transcript.append(wi); + } + let _: P::ScalarField = transcript.challenge(); + + (w, v) +} + +/// Batch KZG verification: checks that commitments open correctly at all points. +/// +/// Optimized for the t=3 case used by HyperKZG. The pairing check verifies: +/// `e(L, g2) == e(R, beta_g2)` +pub(crate) fn kzg_verify_batch( + vk: &HyperKZGVerifierSetup

, + com: &[P::G1], + wit: &[P::G1], + u: &[P::ScalarField], + v: &[Vec], + transcript: &mut T, +) -> bool +where + P: PairingGroup, + T: Transcript, + P::ScalarField: AppendToTranscript, + P::G1: AppendToTranscript, +{ + let k = com.len(); + + if wit.len() != 3 || u.len() != 3 || v.len() != 3 || v.iter().any(|row| row.len() != k) { + return false; + } + + // Absorb evaluations + for row in v { + for val in row { + transcript.append(val); + } + } + + let q: P::ScalarField = transcript.challenge(); + let q_powers = challenge_powers(q, k); + + // Absorb witness commitments + for wi in wit { + transcript.append(wi); + } + let d_0: P::ScalarField = transcript.challenge(); + let d_1 = d_0 * d_0; + + // q_power_multiplier = 1 + d_0 + d_1 + let q_power_multiplier = P::ScalarField::one() + d_0 + d_1; + let q_powers_multiplied: Vec = + q_powers.iter().map(|qp| *qp * q_power_multiplier).collect(); + + // B(u_i) = sum_j q^j * v[i][j] + let b_u: Vec = v + .iter() + .map(|v_i| { + v_i.iter() + .zip(q_powers.iter()) + .map(|(&a, &b)| a * b) + .fold(P::ScalarField::zero(), |acc, x| acc + x) + }) + .collect(); + + // L = MSM over [C_0..C_{k-1}, W_0, W_1, W_2, g1] with scalars + // [q_powers_multiplied, u_0, u_1*d_0, u_2*d_1, -(b_u[0] + d_0*b_u[1] + d_1*b_u[2])] + let mut bases = Vec::with_capacity(k + 4); + bases.extend_from_slice(&com[..k]); + bases.push(wit[0]); + bases.push(wit[1]); + bases.push(wit[2]); + bases.push(vk.g1); + + let mut scalars = Vec::with_capacity(k + 4); + scalars.extend_from_slice(&q_powers_multiplied[..k]); + scalars.push(u[0]); + scalars.push(u[1] * d_0); + scalars.push(u[2] * d_1); + scalars.push(-(b_u[0] + d_0 * b_u[1] + d_1 * b_u[2])); + + let lhs = P::G1::msm(&bases, &scalars); + + // R = W[0] + d_0*W[1] + d_1*W[2] + let rhs = wit[0] + wit[1].scalar_mul(&d_0) + wit[2].scalar_mul(&d_1); + + // e(L, g2) * e(-R, beta_g2) == identity + let result = P::multi_pairing(&[lhs, -rhs], &[vk.g2, vk.beta_g2]); + result.is_identity() +} + +/// Computes `[1, c, c^2, ..., c^{n-1}]`. +pub(crate) fn challenge_powers(c: F, n: usize) -> Vec { + let mut powers = Vec::with_capacity(n); + let mut cur = F::one(); + for _ in 0..n { + powers.push(cur); + cur *= c; + } + powers +} + +#[cfg(test)] +mod tests { + use super::*; + use jolt_field::{Field, Fr}; + use num_traits::Zero; + + #[test] + fn witness_polynomial_division() { + // f(x) = 1 + 2x + 3x^2 + 4x^3 + // f(2) = 1 + 4 + 12 + 32 = 49 + // h(x) = f(x)/(x-2), so f(x) = (x-2)*h(x) + f(2) + let f = vec![ + Fr::from_u64(1), + Fr::from_u64(2), + Fr::from_u64(3), + Fr::from_u64(4), + ]; + let u = Fr::from_u64(2); + let h = compute_witness_polynomial::(&f, u); + + // Verify: (x-u)*h(x) + f(u) should reconstruct f(x) + for x_val in [0u64, 1, 3, 5, 100] { + let x = Fr::from_u64(x_val); + let fx = eval_univariate(&f, x); + let hx = eval_univariate(&h, x); + let fu = eval_univariate(&f, u); + assert_eq!(fx, (x - u) * hx + fu); + } + } + + #[test] + fn eval_univariate_at_zero() { + let f = vec![Fr::from_u64(42), Fr::from_u64(7), Fr::from_u64(3)]; + assert_eq!(eval_univariate(&f, Fr::zero()), Fr::from_u64(42)); + } + + #[test] + fn eval_univariate_linear() { + // f(x) = 3 + 5x, f(2) = 13 + let f = vec![Fr::from_u64(3), Fr::from_u64(5)]; + assert_eq!(eval_univariate(&f, Fr::from_u64(2)), Fr::from_u64(13)); + } +} diff --git a/crates/jolt-hyperkzg/src/lib.rs b/crates/jolt-hyperkzg/src/lib.rs new file mode 100644 index 0000000000..532f02d01e --- /dev/null +++ b/crates/jolt-hyperkzg/src/lib.rs @@ -0,0 +1,28 @@ +//! HyperKZG multilinear polynomial commitment scheme. +//! +//! HyperKZG reduces multilinear polynomial commitments to univariate KZG using +//! the Gemini transformation (section 2.4.2 of ), +//! operating directly on evaluation-form polynomials (no FFT/interpolation). +//! +//! This crate is generic over `PairingGroup` from `jolt-crypto` and implements +//! the `CommitmentScheme` and `AdditivelyHomomorphic` traits from `jolt-openings`. +//! +//! # Protocol overview +//! +//! 1. **Commit**: MSM of evaluations against SRS G1 powers (treating the +//! multilinear evaluation table as univariate coefficients). +//! 2. **Open** (Gemini reduction): +//! - Phase 1: Fold the multilinear polynomial `ell - 1` times, producing +//! intermediate polynomial commitments. +//! - Phase 2: Derive challenge `r` and evaluation points `[r, -r, r^2]`. +//! - Phase 3: Batch KZG opening of all intermediate polynomials at three points. +//! 3. **Verify**: Check evaluation consistency across the three evaluation vectors, +//! then batch KZG pairing check. + +pub mod error; +pub mod kzg; +pub mod scheme; +pub mod types; + +pub use scheme::HyperKZGScheme; +pub use types::{HyperKZGCommitment, HyperKZGProof, HyperKZGProverSetup, HyperKZGVerifierSetup}; diff --git a/crates/jolt-hyperkzg/src/scheme.rs b/crates/jolt-hyperkzg/src/scheme.rs new file mode 100644 index 0000000000..4e6bbe2b7e --- /dev/null +++ b/crates/jolt-hyperkzg/src/scheme.rs @@ -0,0 +1,701 @@ +//! HyperKZG commitment scheme implementing `jolt-openings` traits. +//! +//! [`HyperKZGScheme`] is generic over `P: PairingGroup` — instantiate with +//! `Bn254` for the concrete BN254 curve. + +#![expect( + clippy::expect_used, + reason = "KZG operations return Result for API symmetry; with a correctly-sized SRS and well-formed inputs these errors are unreachable" +)] + +use std::marker::PhantomData; + +use jolt_crypto::{Commitment, DeriveSetup, JoltGroup, PairingGroup, PedersenSetup}; +use jolt_field::Field; +use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, OpeningsError}; +use jolt_poly::Polynomial; +use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript}; +use num_traits::{One, Zero}; +use rayon::prelude::*; + +use crate::error::HyperKZGError; +use crate::kzg::{self, kzg_open_batch, kzg_verify_batch}; +use crate::types::{HyperKZGCommitment, HyperKZGProof, HyperKZGProverSetup, HyperKZGVerifierSetup}; + +/// HyperKZG multilinear polynomial commitment scheme. +/// +/// Generic over `P: PairingGroup`. Implements [`CommitmentScheme`] and +/// [`AdditivelyHomomorphic`] from `jolt-openings`. +#[derive(Clone)] +pub struct HyperKZGScheme { + _phantom: PhantomData

, +} + +impl HyperKZGScheme

+where + P::ScalarField: AppendToTranscript, + P::G1: AppendToTranscript, +{ + /// Generates an SRS from a random generator and secret scalar. + /// + /// `max_degree` is the maximum polynomial length (number of evaluations). + /// The SRS will contain `max_degree + 1` G1 powers and 2 G2 powers. + pub fn setup( + rng: &mut R, + max_degree: usize, + g1: P::G1, + g2: P::G2, + ) -> HyperKZGProverSetup

{ + let beta = P::ScalarField::random(rng); + Self::setup_from_secret(beta, max_degree, g1, g2) + } + + /// Generates SRS from a known secret. + /// + /// WARNING: this is only appropriate for deterministic tests or trusted + /// setup tooling that destroys `beta`; anyone who knows `beta` can break + /// KZG binding. + pub fn setup_from_secret( + beta: P::ScalarField, + max_degree: usize, + g1: P::G1, + g2: P::G2, + ) -> HyperKZGProverSetup

{ + let mut g1_powers = Vec::with_capacity(max_degree + 1); + let mut cur = g1; + for _ in 0..=max_degree { + g1_powers.push(cur); + cur = cur.scalar_mul(&beta); + } + + let g2_powers = vec![g2, g2.scalar_mul(&beta)]; + + HyperKZGProverSetup { + g1_powers, + g2_powers, + } + } + + /// Phase 1 of the HyperKZG protocol: fold the multilinear polynomial. + /// + /// Given polynomial $P$ with $2^\ell$ evaluations and opening point + /// $x = (x_1, \ldots, x_\ell)$, produces $\ell$ polynomials + /// $P_0 = P, P_1, \ldots, P_{\ell-1}$ where each $P_i$ has half + /// the length of $P_{i-1}$. + /// + /// The folding relation is: + /// $P_i[j] = (1 - x_{\ell-i}) \cdot P_{i-1}[2j] + x_{\ell-i} \cdot P_{i-1}[2j+1]$ + fn fold_polynomials( + evals: &[P::ScalarField], + point: &[P::ScalarField], + ) -> Vec> { + let ell = point.len(); + let mut polys = Vec::with_capacity(ell); + polys.push(evals.to_vec()); + + for i in 0..ell - 1 { + let prev = &polys[i]; + let half = prev.len() / 2; + let xi = point[ell - i - 1]; + let mut pi = vec![P::ScalarField::zero(); half]; + pi.par_iter_mut().enumerate().for_each(|(j, pj)| { + *pj = prev[2 * j] + xi * (prev[2 * j + 1] - prev[2 * j]); + }); + polys.push(pi); + } + + polys + } + + /// Full HyperKZG opening proof. + #[tracing::instrument(skip_all, name = "HyperKZG::open")] + pub fn open>( + setup: &HyperKZGProverSetup

, + evals: &[P::ScalarField], + point: &[P::ScalarField], + transcript: &mut T, + ) -> Result, HyperKZGError> { + let ell = point.len(); + let n = evals.len(); + assert_eq!(n, 1 << ell, "evaluation count must be 2^ell"); + + // Phase 1: fold + let polys = Self::fold_polynomials(evals, point); + assert_eq!(polys.len(), ell); + assert_eq!(polys[ell - 1].len(), 2); + + // Commit to intermediate polynomials (skip polys[0] — already committed) + let com: Vec = polys[1..] + .par_iter() + .map(|p| kzg::kzg_commit::

(p, setup).expect("SRS large enough for intermediate")) + .collect(); + + // Phase 2: derive challenge r + for c in &com { + transcript.append(c); + } + let r: P::ScalarField = transcript.challenge(); + let u = vec![r, -r, r * r]; + + // Phase 3: batch open all polynomials at the three points + let (w, v) = kzg_open_batch::(&polys, &u, setup, transcript); + + Ok(HyperKZGProof { com, w, v }) + } + + /// HyperKZG verification. + #[tracing::instrument(skip_all, name = "HyperKZG::verify")] + pub fn verify>( + vk: &HyperKZGVerifierSetup

, + commitment: &HyperKZGCommitment

, + point: &[P::ScalarField], + claimed_eval: &P::ScalarField, + proof: &HyperKZGProof

, + transcript: &mut T, + ) -> Result<(), HyperKZGError> { + let ell = point.len(); + + if proof.com.len() + 1 != ell { + return Err(HyperKZGError::InvalidProof( + "com must contain ell - 1 intermediate commitments", + )); + } + + // Validate proof dimensions before mutating the transcript. + let v = &proof.v; + if v.len() != 3 { + return Err(HyperKZGError::InvalidProof("v must have 3 evaluation rows")); + } + if v[0].len() != ell || v[1].len() != ell || v[2].len() != ell { + return Err(HyperKZGError::InvalidProof( + "each v row must have ell entries", + )); + } + if proof.w.len() != 3 { + return Err(HyperKZGError::InvalidProof( + "w must have 3 witness commitments", + )); + } + + // Absorb intermediate commitments + for c in &proof.com { + transcript.append(c); + } + let r: P::ScalarField = transcript.challenge(); + + if r.is_zero() { + return Err(HyperKZGError::VerificationFailed); + } + + // Prepend the original commitment as C_0 + let mut com = Vec::with_capacity(ell); + com.push(commitment.point); + com.extend_from_slice(&proof.com); + + let u = vec![r, -r, r * r]; + + let ypos = &v[0]; // evaluations at r + let yneg = &v[1]; // evaluations at -r + let mut y_sq = v[2].clone(); // evaluations at r^2 + y_sq.push(*claimed_eval); + + // Consistency check: the folding relation must hold across evaluations + // + // For each level i, the polynomial P_i is defined by: + // P_i(x) = (1 - x_{ell-i}) * P_{i-1,even}(x) + x_{ell-i} * P_{i-1,odd}(x) + // + // This implies: + // 2*r * P_{i+1}(r^2) = r * (1 - x_{ell-i-1}) * (P_i(r) + P_i(-r)) + // + x_{ell-i-1} * (P_i(r) - P_i(-r)) + let two = P::ScalarField::from_u64(2); + for i in 0..ell { + let lhs = two * r * y_sq[i + 1]; + let rhs = r * (P::ScalarField::one() - point[ell - i - 1]) * (ypos[i] + yneg[i]) + + point[ell - i - 1] * (ypos[i] - yneg[i]); + if lhs != rhs { + return Err(HyperKZGError::VerificationFailed); + } + } + + // Batch KZG pairing check + if !kzg_verify_batch::(vk, &com, &proof.w, &u, &proof.v, transcript) { + return Err(HyperKZGError::VerificationFailed); + } + + Ok(()) + } +} + +impl DeriveSetup> for PedersenSetup { + fn derive(source: &HyperKZGProverSetup

, capacity: usize) -> Self { + assert!( + source.g1_powers.len() > capacity, + "SRS has {} G1 powers, need at least {} (capacity + 1 for blinding)", + source.g1_powers.len(), + capacity + 1, + ); + let message_generators = source.g1_powers[..capacity].to_vec(); + let blinding_generator = source.g1_powers[capacity]; + PedersenSetup::new(message_generators, blinding_generator) + } +} + +impl Commitment for HyperKZGScheme

{ + type Output = HyperKZGCommitment

; +} + +impl CommitmentScheme for HyperKZGScheme

+where + P::ScalarField: AppendToTranscript, + P::G1: AppendToTranscript, +{ + type Field = P::ScalarField; + type Proof = HyperKZGProof

; + type ProverSetup = HyperKZGProverSetup

; + type VerifierSetup = HyperKZGVerifierSetup

; + type Polynomial = Polynomial; + type OpeningHint = (); + type SetupParams = (usize, P::G1, P::G2); + + fn setup( + (max_num_vars, g1, g2): Self::SetupParams, + ) -> (Self::ProverSetup, Self::VerifierSetup) { + let mut rng = rand_core::OsRng; + let max_degree = 1usize << max_num_vars; + let prover = HyperKZGScheme::setup(&mut rng, max_degree, g1, g2); + let verifier = Self::verifier_setup(&prover); + (prover, verifier) + } + + fn verifier_setup(prover_setup: &Self::ProverSetup) -> Self::VerifierSetup { + HyperKZGVerifierSetup::from(prover_setup) + } + + fn commit + ?Sized>( + poly: &S, + setup: &Self::ProverSetup, + ) -> (Self::Output, Self::OpeningHint) { + // HyperKZG always works on dense evaluations. + let mut evaluations = Vec::with_capacity(1 << poly.num_vars()); + poly.for_each_row(poly.num_vars(), &mut |_, row| { + evaluations.extend_from_slice(row); + }); + let point = kzg::kzg_commit::

(&evaluations, setup) + .expect("SRS must be large enough for the polynomial"); + (HyperKZGCommitment { point }, ()) + } + + fn open( + poly: &Self::Polynomial, + point: &[Self::Field], + _eval: Self::Field, + setup: &Self::ProverSetup, + _hint: Option, + transcript: &mut impl Transcript, + ) -> Self::Proof { + Self::open(setup, poly.evaluations(), point, transcript) + .expect("HyperKZG open should not fail with valid inputs") + } + + fn verify( + commitment: &Self::Output, + point: &[Self::Field], + eval: Self::Field, + proof: &Self::Proof, + setup: &Self::VerifierSetup, + transcript: &mut impl Transcript, + ) -> Result<(), OpeningsError> { + Self::verify(setup, commitment, point, &eval, proof, transcript) + .map_err(|_| OpeningsError::VerificationFailed) + } + + fn bind_opening_inputs( + transcript: &mut impl Transcript, + point: &[Self::Field], + eval: &Self::Field, + ) { + transcript.append(&LabelWithCount( + b"hyperkzg_opening_point", + point.len() as u64, + )); + for p in point { + p.append_to_transcript(transcript); + } + transcript.append(&Label(b"hyperkzg_opening_eval")); + eval.append_to_transcript(transcript); + } +} + +impl AdditivelyHomomorphic for HyperKZGScheme

+where + P::ScalarField: AppendToTranscript, + P::G1: AppendToTranscript, +{ + fn combine(commitments: &[Self::Output], scalars: &[Self::Field]) -> Self::Output { + assert_eq!(commitments.len(), scalars.len()); + let combined = commitments + .iter() + .zip(scalars.iter()) + .map(|(c, s)| c.point.scalar_mul(s)) + .fold(P::G1::identity(), |acc, x| acc + x); + HyperKZGCommitment { point: combined } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use jolt_crypto::Bn254; + use jolt_field::Fr; + use jolt_poly::Polynomial; + use jolt_transcript::Blake2bTranscript; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + type TestScheme = HyperKZGScheme; + + fn test_setup(max_degree: usize) -> (HyperKZGProverSetup, HyperKZGVerifierSetup) { + let mut rng = ChaCha20Rng::seed_from_u64(0xdead_beef); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let prover = TestScheme::setup(&mut rng, max_degree, g1, g2); + let verifier = TestScheme::verifier_setup(&prover); + (prover, verifier) + } + + #[test] + fn commit_open_verify_roundtrip() { + for ell in [2, 3, 4, 6, 8] { + let n = 1 << ell; + let mut rng = ChaCha20Rng::seed_from_u64(ell as u64); + let (pk, vk) = test_setup(n); + + let poly = Polynomial::::random(ell, &mut rng); + let point: Vec = (0..ell).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut prover_transcript = Blake2bTranscript::new(b"test"); + let proof = ::open( + &poly, + &point, + eval, + &pk, + None, + &mut prover_transcript, + ); + + let mut verifier_transcript = Blake2bTranscript::new(b"test"); + let result = ::verify( + &commitment, + &point, + eval, + &proof, + &vk, + &mut verifier_transcript, + ); + assert!(result.is_ok(), "ell={ell}: verification failed: {result:?}"); + } + } + + #[test] + fn wrong_eval_rejects() { + let ell = 4; + let n = 1 << ell; + let mut rng = ChaCha20Rng::seed_from_u64(42); + let (pk, vk) = test_setup(n); + + let poly = Polynomial::::random(ell, &mut rng); + let point: Vec = (0..ell).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + let wrong_eval = eval + Fr::from_u64(1); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut prover_transcript = Blake2bTranscript::new(b"test-bad"); + let proof = ::open( + &poly, + &point, + eval, + &pk, + None, + &mut prover_transcript, + ); + + let mut verifier_transcript = Blake2bTranscript::new(b"test-bad"); + let result = ::verify( + &commitment, + &point, + wrong_eval, + &proof, + &vk, + &mut verifier_transcript, + ); + assert!(result.is_err(), "wrong evaluation should be rejected"); + } + + #[test] + fn missing_intermediate_commitment_rejects() { + let ell = 4; + let n = 1 << ell; + let mut rng = ChaCha20Rng::seed_from_u64(43); + let (pk, vk) = test_setup(n); + + let poly = Polynomial::::random(ell, &mut rng); + let point: Vec = (0..ell).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut prover_transcript = Blake2bTranscript::new(b"test-missing-com"); + let mut proof = ::open( + &poly, + &point, + eval, + &pk, + None, + &mut prover_transcript, + ); + let _ = proof.com.pop(); + + let mut verifier_transcript = Blake2bTranscript::new(b"test-missing-com"); + let result = TestScheme::verify( + &vk, + &commitment, + &point, + &eval, + &proof, + &mut verifier_transcript, + ); + assert!(matches!(result, Err(HyperKZGError::InvalidProof(_)))); + } + + #[test] + fn malformed_witness_commitments_reject_without_panic() { + let ell = 4; + let n = 1 << ell; + let mut rng = ChaCha20Rng::seed_from_u64(44); + let (pk, vk) = test_setup(n); + + let poly = Polynomial::::random(ell, &mut rng); + let point: Vec = (0..ell).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut prover_transcript = Blake2bTranscript::new(b"test-short-w"); + let mut proof = ::open( + &poly, + &point, + eval, + &pk, + None, + &mut prover_transcript, + ); + let _ = proof.w.pop(); + + let mut verifier_transcript = Blake2bTranscript::new(b"test-short-w"); + let result = TestScheme::verify( + &vk, + &commitment, + &point, + &eval, + &proof, + &mut verifier_transcript, + ); + assert!(matches!(result, Err(HyperKZGError::InvalidProof(_)))); + } + + #[test] + fn trait_setup_uses_fresh_randomness() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + + let (_pk1, vk1) = ::setup((4, g1, g2)); + let (_pk2, vk2) = ::setup((4, g1, g2)); + + assert_ne!(vk1.beta_g2, vk2.beta_g2); + } + + #[test] + fn tampered_proof_rejects() { + let ell = 4; + let n = 1 << ell; + let mut rng = ChaCha20Rng::seed_from_u64(99); + let (pk, vk) = test_setup(n); + + let poly = Polynomial::::random(ell, &mut rng); + let point: Vec = (0..ell).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut prover_transcript = Blake2bTranscript::new(b"test-tamper"); + let mut proof = ::open( + &poly, + &point, + eval, + &pk, + None, + &mut prover_transcript, + ); + + // Tamper with proof: swap v[0] and v[1] + let v1 = proof.v[1].clone(); + proof.v[0].clone_from(&v1); + + let mut verifier_transcript = Blake2bTranscript::new(b"test-tamper"); + let result = ::verify( + &commitment, + &point, + eval, + &proof, + &vk, + &mut verifier_transcript, + ); + assert!(result.is_err(), "tampered proof should be rejected"); + } + + #[test] + fn combine_is_homomorphic() { + let ell = 3; + let n = 1 << ell; + let mut rng = ChaCha20Rng::seed_from_u64(300); + let (pk, _vk) = test_setup(n); + + let poly_a = Polynomial::::random(ell, &mut rng); + let poly_b = Polynomial::::random(ell, &mut rng); + + let (ca, ()) = TestScheme::commit(poly_a.evaluations(), &pk); + let (cb, ()) = TestScheme::commit(poly_b.evaluations(), &pk); + + let sum_evals: Vec = poly_a + .evaluations() + .iter() + .zip(poly_b.evaluations().iter()) + .map(|(a, b)| *a + *b) + .collect(); + let (c_sum_direct, ()) = TestScheme::commit(&sum_evals, &pk); + + let c_sum_combined = TestScheme::combine(&[ca, cb], &[Fr::from_u64(1), Fr::from_u64(1)]); + + assert_eq!( + c_sum_direct, c_sum_combined, + "combine([1,1]) must match commitment to sum" + ); + } + + #[test] + fn combine_with_scalars() { + let ell = 3; + let n = 1 << ell; + let mut rng = ChaCha20Rng::seed_from_u64(400); + let (pk, _vk) = test_setup(n); + + let poly_a = Polynomial::::random(ell, &mut rng); + let poly_b = Polynomial::::random(ell, &mut rng); + let s_a = Fr::random(&mut rng); + let s_b = Fr::random(&mut rng); + + let (ca, ()) = TestScheme::commit(poly_a.evaluations(), &pk); + let (cb, ()) = TestScheme::commit(poly_b.evaluations(), &pk); + + let combined_evals: Vec = poly_a + .evaluations() + .iter() + .zip(poly_b.evaluations().iter()) + .map(|(a, b)| s_a * *a + s_b * *b) + .collect(); + let (c_direct, ()) = TestScheme::commit(&combined_evals, &pk); + + let c_combined = TestScheme::combine(&[ca, cb], &[s_a, s_b]); + + assert_eq!(c_direct, c_combined); + } + + #[test] + fn open_verify_with_random_points() { + let mut rng = ChaCha20Rng::seed_from_u64(0xcafe); + + for _ in 0..5 { + let ell = 4; + let n = 1 << ell; + let (pk, vk) = test_setup(n); + + let poly = Polynomial::::random(ell, &mut rng); + let point: Vec = (0..ell).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut pt = Blake2bTranscript::new(b"rand-test"); + let proof = + ::open(&poly, &point, eval, &pk, None, &mut pt); + + let mut vt = Blake2bTranscript::new(b"rand-test"); + ::verify( + &commitment, + &point, + eval, + &proof, + &vk, + &mut vt, + ) + .expect("random instance should verify"); + } + } + + #[test] + fn extract_vc_setup_produces_valid_pedersen() { + use jolt_crypto::{Pedersen, VectorCommitment}; + + let n = 1 << 4; + let (pk, _vk) = test_setup(n); + + let capacity = 5; + let vc_setup = PedersenSetup::::derive(&pk, capacity); + + assert_eq!( + as VectorCommitment>::capacity(&vc_setup), + capacity, + ); + + // Commit and verify a small vector. + let values = vec![Fr::one(), Fr::from_u64(2), Fr::from_u64(3)]; + let blinding = Fr::from_u64(42); + let commitment = as VectorCommitment>::commit( + &vc_setup, &values, &blinding, + ); + assert!( + as VectorCommitment>::verify( + &vc_setup, + &commitment, + &values, + &blinding, + ) + ); + } + + #[test] + fn trivial_polynomial() { + // 1-variable polynomial: [a, b] + let ell = 1; + let n = 1 << ell; + let mut rng = ChaCha20Rng::seed_from_u64(777); + let (pk, vk) = test_setup(n); + + let poly = Polynomial::::random(ell, &mut rng); + let point: Vec = (0..ell).map(|_| Fr::random(&mut rng)).collect(); + let eval = poly.evaluate(&point); + + let (commitment, ()) = TestScheme::commit(poly.evaluations(), &pk); + + let mut pt = Blake2bTranscript::new(b"trivial"); + let proof = ::open(&poly, &point, eval, &pk, None, &mut pt); + + let mut vt = Blake2bTranscript::new(b"trivial"); + ::verify(&commitment, &point, eval, &proof, &vk, &mut vt) + .expect("trivial polynomial should verify"); + } +} diff --git a/crates/jolt-hyperkzg/src/types.rs b/crates/jolt-hyperkzg/src/types.rs new file mode 100644 index 0000000000..f482172ff0 --- /dev/null +++ b/crates/jolt-hyperkzg/src/types.rs @@ -0,0 +1,118 @@ +//! Commitment, proof, and setup types for HyperKZG. +//! +//! All types are generic over `P: PairingGroup` — no arkworks leakage. + +use jolt_crypto::{HomomorphicCommitment, JoltGroup, PairingGroup}; +use serde::{Deserialize, Serialize}; + +/// Commitment to a multilinear polynomial: a single G1 element. +#[derive(Serialize, Deserialize)] +#[serde(bound( + serialize = "P::G1: Serialize", + deserialize = "P::G1: for<'a> Deserialize<'a>" +))] +pub struct HyperKZGCommitment { + pub(crate) point: P::G1, +} + +impl Copy for HyperKZGCommitment

{} + +#[expect( + clippy::expl_impl_clone_on_copy, + reason = "explicit impl is required because PairingGroup is not bounded by Clone" +)] +impl Clone for HyperKZGCommitment

{ + fn clone(&self) -> Self { + *self + } +} + +impl std::fmt::Debug for HyperKZGCommitment

{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HyperKZGCommitment") + .field("point", &self.point) + .finish() + } +} + +impl PartialEq for HyperKZGCommitment

{ + fn eq(&self, other: &Self) -> bool { + self.point == other.point + } +} + +impl Eq for HyperKZGCommitment

{} + +impl HomomorphicCommitment for HyperKZGCommitment

{ + #[inline] + fn linear_combine(c1: &Self, c2: &Self, scalar: &F) -> Self { + Self { + point: HomomorphicCommitment::linear_combine(&c1.point, &c2.point, scalar), + } + } +} + +impl Default for HyperKZGCommitment

{ + fn default() -> Self { + Self { + point: P::G1::identity(), + } + } +} + +/// Opening proof for the HyperKZG protocol. +/// +/// - `com`: intermediate polynomial commitments from the Gemini folding (ell - 1 elements) +/// - `w`: KZG witness commitments for the three evaluation points `[r, -r, r^2]` +/// - `v`: evaluations of all intermediate polynomials at the three points +/// (`v[t][k]` = polynomial k evaluated at point t) +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound( + serialize = "P::G1: Serialize, P::ScalarField: Serialize", + deserialize = "P::G1: for<'a> Deserialize<'a>, P::ScalarField: for<'a> Deserialize<'a>" +))] +pub struct HyperKZGProof { + pub com: Vec, + pub w: Vec, + pub v: Vec>, +} + +/// Prover setup: SRS G1 and G2 powers. +/// +/// G1 powers: `[g1, beta * g1, beta^2 * g1, ..., beta^n * g1]` +/// G2 powers: `[g2, beta * g2]` (only two needed for KZG verification). +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound( + serialize = "P::G1: Serialize, P::G2: Serialize", + deserialize = "P::G1: for<'a> Deserialize<'a>, P::G2: for<'a> Deserialize<'a>" +))] +pub struct HyperKZGProverSetup { + pub(crate) g1_powers: Vec, + pub(crate) g2_powers: Vec, +} + +/// Verifier setup: the four G1/G2 elements needed for pairing checks. +/// +/// - `g1`: generator $g$ +/// - `g2`: generator $h$ +/// - `beta_g2`: $\beta \cdot h$ (for KZG pairing check) +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[serde(bound( + serialize = "P::G1: Serialize, P::G2: Serialize", + deserialize = "P::G1: for<'a> Deserialize<'a>, P::G2: for<'a> Deserialize<'a>" +))] +pub struct HyperKZGVerifierSetup { + pub(crate) g1: P::G1, + pub(crate) g2: P::G2, + pub(crate) beta_g2: P::G2, +} + +impl From<&HyperKZGProverSetup

> for HyperKZGVerifierSetup

{ + fn from(prover: &HyperKZGProverSetup

) -> Self { + Self { + g1: prover.g1_powers[0], + g2: prover.g2_powers[0], + beta_g2: prover.g2_powers[1], + } + } +} diff --git a/crates/jolt-hyperkzg/tests/commit_open_verify.rs b/crates/jolt-hyperkzg/tests/commit_open_verify.rs new file mode 100644 index 0000000000..9ee2e6eba2 --- /dev/null +++ b/crates/jolt-hyperkzg/tests/commit_open_verify.rs @@ -0,0 +1,224 @@ +//! Integration tests for HyperKZG commit → open → verify pipeline with BN254. + +#![expect(clippy::expect_used, reason = "tests may panic on assertion failures")] + +use jolt_crypto::Bn254; +use jolt_field::{Field, Fr}; +use jolt_hyperkzg::{HyperKZGProverSetup, HyperKZGScheme, HyperKZGVerifierSetup}; +use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme}; +use jolt_poly::Polynomial; +use jolt_transcript::{Blake2bTranscript, Transcript}; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +type KzgPCS = HyperKZGScheme; + +fn make_setup(max_degree: usize) -> (HyperKZGProverSetup, HyperKZGVerifierSetup) { + let mut rng = ChaCha20Rng::seed_from_u64(0xdead_beef); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let pk = KzgPCS::setup(&mut rng, max_degree, g1, g2); + let vk = KzgPCS::verifier_setup(&pk); + (pk, vk) +} + +fn commit_open_verify( + poly: &Polynomial, + point: &[Fr], + pk: &HyperKZGProverSetup, + vk: &HyperKZGVerifierSetup, + label: &'static [u8], +) { + let eval = poly.evaluate(point); + let (commitment, ()) = ::commit(poly.evaluations(), pk); + + let mut t_p = Blake2bTranscript::new(label); + let proof = ::open(poly, point, eval, pk, None, &mut t_p); + + let mut t_v = Blake2bTranscript::new(label); + ::verify(&commitment, point, eval, &proof, vk, &mut t_v) + .expect("verification should succeed"); +} + +// Basic roundtrip for various polynomial sizes + +#[test] +fn roundtrip_num_vars_1_to_8() { + let mut rng = ChaCha20Rng::seed_from_u64(1000); + for nv in 1..=8 { + let (pk, vk) = make_setup(1 << nv); + let poly = Polynomial::::random(nv, &mut rng); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); + commit_open_verify(&poly, &point, &pk, &vk, b"kzg-sizes"); + } +} + +// Edge cases + +/// All-zero polynomial commits to identity point and still verifies. +#[test] +fn zero_polynomial_roundtrip() { + let nv = 3; + let (pk, vk) = make_setup(1 << nv); + let poly = Polynomial::::zeros(nv); + let point = vec![Fr::from_u64(42); nv]; + commit_open_verify(&poly, &point, &pk, &vk, b"kzg-zero"); +} + +/// Single-variable polynomial (2 evaluations). +#[test] +fn single_variable_polynomial() { + let mut rng = ChaCha20Rng::seed_from_u64(2000); + let (pk, vk) = make_setup(2); + let poly = Polynomial::::random(1, &mut rng); + let point = vec![Fr::random(&mut rng)]; + commit_open_verify(&poly, &point, &pk, &vk, b"kzg-single-var"); +} + +/// Constant polynomial (all evaluations are the same value). +#[test] +fn constant_polynomial() { + let nv = 3; + let (pk, vk) = make_setup(1 << nv); + let val = Fr::from_u64(42); + let poly = Polynomial::new(vec![val; 1 << nv]); + let mut rng = ChaCha20Rng::seed_from_u64(2001); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); + commit_open_verify(&poly, &point, &pk, &vk, b"kzg-constant"); +} + +// Wrong evaluation rejection + +#[test] +fn wrong_eval_rejected() { + let mut rng = ChaCha20Rng::seed_from_u64(3000); + let nv = 4; + let (pk, vk) = make_setup(1 << nv); + let poly = Polynomial::::random(nv, &mut rng); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); + + let correct_eval = poly.evaluate(&point); + let wrong_eval = correct_eval + Fr::from_u64(1); + let (commitment, ()) = ::commit(poly.evaluations(), &pk); + + // Prover opens with correct eval + let mut t_p = Blake2bTranscript::new(b"kzg-wrong"); + let proof = + ::open(&poly, &point, correct_eval, &pk, None, &mut t_p); + + // Verifier checks with wrong eval + let mut t_v = Blake2bTranscript::new(b"kzg-wrong"); + let result = ::verify( + &commitment, + &point, + wrong_eval, + &proof, + &vk, + &mut t_v, + ); + assert!(result.is_err(), "wrong evaluation must be rejected"); +} + +// Homomorphic properties + +/// combine([C_a, C_b], [1, 1]) == commit(a + b). +#[test] +fn homomorphic_sum() { + let mut rng = ChaCha20Rng::seed_from_u64(4000); + let nv = 4; + let (pk, vk) = make_setup(1 << nv); + let a = Polynomial::::random(nv, &mut rng); + let b = Polynomial::::random(nv, &mut rng); + + let (com_a, ()) = ::commit(a.evaluations(), &pk); + let (com_b, ()) = ::commit(b.evaluations(), &pk); + let combined_com = ::combine( + &[com_a, com_b], + &[Fr::from_u64(1), Fr::from_u64(1)], + ); + + let sum_poly = a + b; + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); + let eval = sum_poly.evaluate(&point); + + let mut t_p = Blake2bTranscript::new(b"kzg-homo"); + let proof = ::open(&sum_poly, &point, eval, &pk, None, &mut t_p); + + let mut t_v = Blake2bTranscript::new(b"kzg-homo"); + ::verify(&combined_com, &point, eval, &proof, &vk, &mut t_v) + .expect("homomorphic sum must verify"); +} + +/// combine with arbitrary scalars: s_a·C_a + s_b·C_b == commit(s_a·a + s_b·b). +#[test] +fn homomorphic_weighted_combination() { + let mut rng = ChaCha20Rng::seed_from_u64(4001); + let nv = 3; + let (pk, vk) = make_setup(1 << nv); + let a = Polynomial::::random(nv, &mut rng); + let b = Polynomial::::random(nv, &mut rng); + let s_a = Fr::random(&mut rng); + let s_b = Fr::random(&mut rng); + + let (com_a, ()) = ::commit(a.evaluations(), &pk); + let (com_b, ()) = ::commit(b.evaluations(), &pk); + let combined_com = ::combine(&[com_a, com_b], &[s_a, s_b]); + + let weighted_poly = a * s_a + b * s_b; + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); + let eval = weighted_poly.evaluate(&point); + + let mut t_p = Blake2bTranscript::new(b"kzg-weighted"); + let proof = + ::open(&weighted_poly, &point, eval, &pk, None, &mut t_p); + + let mut t_v = Blake2bTranscript::new(b"kzg-weighted"); + ::verify(&combined_com, &point, eval, &proof, &vk, &mut t_v) + .expect("weighted combination must verify"); +} + +// Deterministic setup + +#[test] +fn deterministic_setup_from_secret() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let beta = Fr::from_u64(12345); + + let pk1 = KzgPCS::setup_from_secret(beta, 16, g1, g2); + let pk2 = KzgPCS::setup_from_secret(beta, 16, g1, g2); + let _vk1 = KzgPCS::verifier_setup(&pk1); + let vk2 = KzgPCS::verifier_setup(&pk2); + + // Same setup yields same commitments + let poly = Polynomial::new(vec![Fr::from_u64(1), Fr::from_u64(2)]); + let (com1, ()) = ::commit(poly.evaluations(), &pk1); + let (com2, ()) = ::commit(poly.evaluations(), &pk2); + assert_eq!( + com1, com2, + "deterministic setups must produce same commitments" + ); + + // Verify with either setup + let point = vec![Fr::from_u64(7)]; + let eval = poly.evaluate(&point); + let mut t = Blake2bTranscript::new(b"det-setup"); + let proof = ::open(&poly, &point, eval, &pk1, None, &mut t); + let mut t = Blake2bTranscript::new(b"det-setup"); + ::verify(&com1, &point, eval, &proof, &vk2, &mut t) + .expect("cross-setup verification must work"); +} + +// Property test: random polynomials always verify + +#[test] +fn property_random_polynomials_always_verify() { + for seed in 5000..5010 { + let mut rng = ChaCha20Rng::seed_from_u64(seed); + let nv = 2 + (seed as usize % 5); // 2..6 + let (pk, vk) = make_setup(1 << nv); + let poly = Polynomial::::random(nv, &mut rng); + let point: Vec = (0..nv).map(|_| Fr::random(&mut rng)).collect(); + commit_open_verify(&poly, &point, &pk, &vk, b"kzg-property"); + } +} diff --git a/crates/jolt-kernels/Cargo.toml b/crates/jolt-kernels/Cargo.toml new file mode 100644 index 0000000000..a60c11cfc6 --- /dev/null +++ b/crates/jolt-kernels/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "jolt-kernels" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +authors = ["Jolt Contributors"] +repository = "https://github.com/a16z/jolt" +description = "Coarse CPU kernels targeted by the Bolt-shaped Jolt compiler" +keywords = ["SNARK", "compiler", "sumcheck", "zkvm"] +categories = ["cryptography"] + +[lints] +workspace = true + +[dependencies] +jolt-field = { path = "../jolt-field" } +jolt-lookup-tables = { path = "../jolt-lookup-tables" } +jolt-poly = { path = "../jolt-poly" } +jolt-r1cs = { path = "../jolt-r1cs" } +jolt-sumcheck = { path = "../jolt-sumcheck" } +jolt-transcript = { path = "../jolt-transcript" } +jolt-trace = { path = "../jolt-trace" } +jolt-witness = { path = "../jolt-witness" } +rayon = { workspace = true } +tracing = { workspace = true } diff --git a/crates/jolt-kernels/src/dense.rs b/crates/jolt-kernels/src/dense.rs new file mode 100644 index 0000000000..c964703db0 --- /dev/null +++ b/crates/jolt-kernels/src/dense.rs @@ -0,0 +1,32 @@ +use jolt_field::Field; +use rayon::prelude::*; + +pub(crate) const DENSE_BIND_PAR_THRESHOLD: usize = 1024; + +#[inline] +pub(crate) fn bind_dense_evals_reuse( + values: &mut Vec, + scratch: &mut Vec, + challenge: F, +) { + let half = values.len() / 2; + scratch.resize(half, F::zero()); + if half >= DENSE_BIND_PAR_THRESHOLD { + scratch + .par_iter_mut() + .enumerate() + .for_each(|(index, output)| { + let low = values[index << 1]; + let high = values[(index << 1) + 1]; + *output = low + challenge * (high - low); + }); + } else { + for (index, output) in scratch.iter_mut().enumerate() { + let low = values[index << 1]; + let high = values[(index << 1) + 1]; + *output = low + challenge * (high - low); + } + } + std::mem::swap(values, scratch); + scratch.clear(); +} diff --git a/crates/jolt-kernels/src/lib.rs b/crates/jolt-kernels/src/lib.rs new file mode 100644 index 0000000000..882e8b132c --- /dev/null +++ b/crates/jolt-kernels/src/lib.rs @@ -0,0 +1,17 @@ +//! Coarse CPU kernels used by generated Bolt/Jolt Rust. +//! +//! This crate is intentionally above the primitive protocol crates and below +//! generated code. It owns the temporary coarse CPU ABI while the compiler +//! grows finer compute lowerings. + +mod dense; +mod split_eq; + +pub mod stage1; +pub mod stage2; +pub mod stage3; +pub mod stage4; +pub mod stage5; +pub mod stage6; +pub mod stage7; +pub mod trace; diff --git a/crates/jolt-kernels/src/split_eq.rs b/crates/jolt-kernels/src/split_eq.rs new file mode 100644 index 0000000000..debd0251e4 --- /dev/null +++ b/crates/jolt-kernels/src/split_eq.rs @@ -0,0 +1,65 @@ +use jolt_field::Field; +use jolt_poly::EqPolynomial; + +use crate::dense::bind_dense_evals_reuse; + +#[derive(Clone)] +pub(crate) struct SplitEqState { + low_point: Vec, + high_point: Vec, + e_in: Vec, + e_out: Vec, + e_in_scratch: Vec, + e_out_scratch: Vec, +} + +impl SplitEqState { + #[inline] + pub(crate) fn new_low_to_high(point: &[F], scaling: Option) -> Self { + let (high_point, low_point) = point.split_at(point.len() / 2); + Self { + low_point: low_point.to_vec(), + high_point: high_point.to_vec(), + e_in: EqPolynomial::::evals(low_point, scaling), + e_out: EqPolynomial::::evals(high_point, None), + e_in_scratch: Vec::new(), + e_out_scratch: Vec::new(), + } + } + + #[inline] + pub(crate) fn e_in(&self) -> &[F] { + &self.e_in + } + + #[inline] + pub(crate) fn e_out(&self) -> &[F] { + &self.e_out + } + + #[inline] + pub(crate) fn current_target(&self) -> F { + debug_assert!(self.e_in.len() > 1 || self.e_out.len() > 1); + if self.e_in.len() > 1 { + let remaining = self.e_in.len().trailing_zeros() as usize; + self.low_point[remaining - 1] + } else { + let remaining = self.e_out.len().trailing_zeros() as usize; + self.high_point[remaining - 1] + } + } + + #[inline] + pub(crate) fn eval(&self) -> F { + self.e_in[0] * self.e_out[0] + } + + #[inline] + pub(crate) fn bind(&mut self, challenge: F) { + if self.e_in.len() > 1 { + bind_dense_evals_reuse(&mut self.e_in, &mut self.e_in_scratch, challenge); + } else { + bind_dense_evals_reuse(&mut self.e_out, &mut self.e_out_scratch, challenge); + } + } +} diff --git a/crates/jolt-kernels/src/stage1.rs b/crates/jolt-kernels/src/stage1.rs new file mode 100644 index 0000000000..6414dc72cb --- /dev/null +++ b/crates/jolt-kernels/src/stage1.rs @@ -0,0 +1,2948 @@ +//! Stage 1 outer-sumcheck kernel ABI. + +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use jolt_field::{Field, FieldAccumulator}; +use jolt_poly::lagrange::{interpolate_to_coeffs, lagrange_evals, lagrange_kernel_eval}; +use jolt_poly::{EqPolynomial, UnivariatePoly}; +use jolt_r1cs::{constraints::rv64, R1csKey, R1csRowDotSlice, R1csRowDotTable}; +use jolt_sumcheck::SumcheckProof; +use jolt_transcript::{Label, LabelWithCount, Transcript}; +use rayon::prelude::*; + +mod rv64_typed; +pub use rv64_typed::{Stage1OuterRv64Data, Stage1Rv64Cycle}; + +const OUTER_UNISKIP_DOMAIN_SIZE: usize = 10; +const OUTER_UNISKIP_DEGREE: usize = 9; +const OUTER_UNISKIP_EXTENDED_SIZE: usize = 19; +const OUTER_UNISKIP_NUM_COEFFS: usize = 28; +const OUTER_UNISKIP_DEGREE_BOUND: usize = OUTER_UNISKIP_NUM_COEFFS - 1; +const OUTER_UNISKIP_EXTENDED_START: i64 = -(OUTER_UNISKIP_DEGREE as i64); +const OUTER_UNISKIP_BASE_START: i64 = -((OUTER_UNISKIP_DOMAIN_SIZE as i64 - 1) / 2); +const OUTER_REMAINING_DEGREE_BOUND: usize = 3; +const DENSE_BIND_PAR_THRESHOLD: usize = 1024; +const OUTER_FIRST_GROUP_ROWS: [usize; 10] = [1, 2, 3, 4, 5, 6, 11, 14, 17, 18]; +const OUTER_SECOND_GROUP_ROWS: [usize; 9] = [0, 7, 8, 9, 10, 12, 13, 15, 16]; +const OUTER_EQ_CONSTRAINT_ROWS: usize = + OUTER_FIRST_GROUP_ROWS.len() + OUTER_SECOND_GROUP_ROWS.len(); +const OUTER_UNISKIP_TARGET_COEFFS: [[i64; OUTER_UNISKIP_DOMAIN_SIZE]; OUTER_UNISKIP_DEGREE] = [ + [10, -45, 120, -210, 252, -210, 120, -45, 10, -1], + [-1, 10, -45, 120, -210, 252, -210, 120, -45, 10], + [55, -330, 990, -1848, 2310, -1980, 1155, -440, 99, -10], + [-10, 99, -440, 1155, -1980, 2310, -1848, 990, -330, 55], + [ + 220, -1485, 4752, -9240, 11880, -10395, 6160, -2376, 540, -55, + ], + [ + -55, 540, -2376, 6160, -10395, 11880, -9240, 4752, -1485, 220, + ], + [ + 715, -5148, 17160, -34320, 45045, -40040, 24024, -9360, 2145, -220, + ], + [ + -220, 2145, -9360, 24024, -40040, 45045, -34320, 17160, -5148, 715, + ], + [ + 2002, -15015, 51480, -105_105, 140_140, -126_126, 76440, -30030, 6930, -715, + ], +]; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage1ExecutionMode { + Prover, + Verifier, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage1CpuProgramPlan { + pub params: Stage1Params, + pub transcript_squeezes: &'static [Stage1TranscriptSqueezePlan], + pub kernels: &'static [Stage1KernelPlan], + pub claims: &'static [Stage1SumcheckClaimPlan], + pub batches: &'static [Stage1SumcheckBatchPlan], + pub drivers: &'static [Stage1SumcheckDriverPlan], + pub instance_results: &'static [Stage1SumcheckInstanceResultPlan], + pub evals: &'static [Stage1SumcheckEvalPlan], + pub opening_claims: &'static [Stage1OpeningClaimPlan], + pub opening_batches: &'static [Stage1OpeningBatchPlan], +} + +impl Stage1CpuProgramPlan { + pub fn evals_for_driver<'a>( + &'a self, + driver: &'a str, + ) -> impl Iterator + 'a { + self.evals.iter().filter(move |eval| eval.source == driver) + } + + pub fn instance_results_for_driver<'a>( + &'a self, + driver: &'a str, + ) -> impl Iterator + 'a { + self.instance_results + .iter() + .filter(move |instance| instance.source == driver) + } +} + +#[derive(Clone, Debug)] +pub struct Stage1NamedEval { + pub name: &'static str, + pub oracle: &'static str, + pub value: F, +} + +#[derive(Clone, Debug)] +pub struct Stage1SumcheckOutput { + pub driver: &'static str, + pub point: Vec, + pub evals: Vec>, + pub proof: SumcheckProof, +} + +#[derive(Clone, Debug)] +pub struct Stage1ChallengeVector { + pub symbol: &'static str, + pub values: Vec, +} + +#[derive(Clone, Debug)] +pub struct Stage1OpeningValue { + pub symbol: &'static str, + pub oracle: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Debug)] +pub struct Stage1ExecutionArtifacts { + pub challenge_vectors: Vec>, + pub sumchecks: Vec>, + pub opening_values: Vec>, + pub opening_batches: Vec<&'static Stage1OpeningBatchPlan>, +} + +impl Default for Stage1ExecutionArtifacts { + fn default() -> Self { + Self { + challenge_vectors: Vec::new(), + sumchecks: Vec::new(), + opening_values: Vec::new(), + opening_batches: Vec::new(), + } + } +} + +impl Stage1ExecutionArtifacts { + pub fn opening_value(&self, symbol: &str) -> Option<&Stage1OpeningValue> { + self.opening_values + .iter() + .find(|opening| opening.symbol == symbol) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage1OracleData<'a, F: Field> { + pub name: &'static str, + pub evaluations: &'a [F], +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage1OuterRemainingContext<'a, F: Field> { + pub tau: &'a [F], + pub r0: F, +} + +pub type Stage1RemainingRoundProof = Result<(Vec, Vec>), Stage1KernelError>; + +pub trait Stage1OuterRemainingEvaluator: Sync { + fn evaluate(&self, context: Stage1OuterRemainingContext<'_, F>, point: &[F]) -> F; + + fn uniskip_extended_evals(&self, _tau: &[F]) -> Option> { + None + } + + fn evaluate_virtual_oracle( + &self, + _context: Stage1OuterRemainingContext<'_, F>, + _oracle: &str, + _point: &[F], + ) -> Option { + None + } + + fn evaluate_virtual_oracles( + &self, + context: Stage1OuterRemainingContext<'_, F>, + oracles: &[&str], + point: &[F], + ) -> Option> { + oracles + .iter() + .map(|oracle| self.evaluate_virtual_oracle(context, oracle, point)) + .collect() + } + + fn prove_remaining_rounds( + &self, + _context: Stage1OuterRemainingContext<'_, F>, + _num_rounds: usize, + _batching_coeff: F, + _initial_claim: F, + _observe_round: &mut dyn FnMut(&UnivariatePoly) -> F, + ) -> Option> { + None + } +} + +#[derive(Clone, Copy)] +pub struct Stage1ProverInputs<'a, F: Field> { + pub trace_num_vars: usize, + pub virtual_oracles: &'a [Stage1OracleData<'a, F>], + pub uniskip_extended_evals: Option<&'a [F]>, + pub outer_remaining_evaluator: Option<&'a dyn Stage1OuterRemainingEvaluator>, +} + +impl<'a, F: Field> Stage1ProverInputs<'a, F> { + pub fn new(trace_num_vars: usize, virtual_oracles: &'a [Stage1OracleData<'a, F>]) -> Self { + Self { + trace_num_vars, + virtual_oracles, + uniskip_extended_evals: None, + outer_remaining_evaluator: None, + } + } + + pub fn empty(trace_num_vars: usize) -> Self { + Self { + trace_num_vars, + virtual_oracles: &[], + uniskip_extended_evals: None, + outer_remaining_evaluator: None, + } + } + + pub fn with_uniskip_extended_evals(mut self, evaluations: &'a [F]) -> Self { + self.uniskip_extended_evals = Some(evaluations); + self + } + + pub fn with_outer_remaining_evaluator( + mut self, + evaluator: &'a dyn Stage1OuterRemainingEvaluator, + ) -> Self { + self.outer_remaining_evaluator = Some(evaluator); + self + } + + pub fn oracle(&self, name: &str) -> Option<&'a [F]> { + self.virtual_oracles + .iter() + .find(|oracle| oracle.name == name) + .map(|oracle| oracle.evaluations) + } +} + +#[derive(Debug)] +pub struct Stage1OuterR1csData<'a, F: Field> { + pub key: &'a R1csKey, + pub witness: &'a [F], + row_dots: R1csRowDotTable, +} + +impl<'a, F: Field> Stage1OuterR1csData<'a, F> { + #[tracing::instrument(skip_all, name = "Stage1OuterR1csData::new")] + pub fn new(key: &'a R1csKey, witness: &'a [F]) -> Result { + let expected = key.num_cycles * key.num_vars_padded; + if witness.len() != expected { + return Err(Stage1KernelError::InvalidInputLength { + input: "r1cs_witness", + expected, + actual: witness.len(), + }); + } + Ok(Self { + key, + witness, + row_dots: R1csRowDotTable::compute_ab_prefix(key, witness, OUTER_EQ_CONSTRAINT_ROWS), + }) + } + + fn witness_evals(&self, cycle_point: &[F]) -> Vec { + assert_eq!( + cycle_point.len(), + self.key.num_cycle_vars(), + "stage1 cycle point dimension mismatch" + ); + if let Some(cycle) = boolean_index(cycle_point) { + let base = cycle * self.key.num_vars_padded; + return self.witness[base..base + self.key.matrices.num_vars].to_vec(); + } + (0..self.key.matrices.num_vars) + .map(|variable| self.witness_eval(variable, cycle_point)) + .collect() + } + + fn witness_eval(&self, variable: usize, cycle_point: &[F]) -> F { + if let Some(cycle) = boolean_index(cycle_point) { + return self.witness[cycle * self.key.num_vars_padded + variable]; + } + let eq = EqPolynomial::new(cycle_point.to_vec()).evaluations(); + eq.iter() + .take(self.key.num_cycles) + .enumerate() + .fold(F::zero(), |acc, (cycle, &weight)| { + acc + weight * self.witness[cycle * self.key.num_vars_padded + variable] + }) + } + + fn witness_evals_for_variables(&self, variables: &[usize], cycle_point: &[F]) -> Vec { + if let Some(cycle) = boolean_index(cycle_point) { + let base = cycle * self.key.num_vars_padded; + return variables + .iter() + .map(|&variable| self.witness[base + variable]) + .collect(); + } + + let eq = EqPolynomial::new(cycle_point.to_vec()).evaluations(); + let accumulators = eq + .par_iter() + .take(self.key.num_cycles) + .enumerate() + .fold( + || vec![F::Accumulator::default(); variables.len()], + |mut local, (cycle, &weight)| { + let base = cycle * self.key.num_vars_padded; + for (accumulator, &variable) in local.iter_mut().zip(variables) { + accumulator.fmadd(weight, self.witness[base + variable]); + } + local + }, + ) + .reduce( + || vec![F::Accumulator::default(); variables.len()], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + accumulators + .into_iter() + .map(FieldAccumulator::reduce) + .collect() + } + + fn inner_sum_product(&self, r_stream: F, r0: F, witness_evals: &[F]) -> F { + let weights = lagrange_evals(OUTER_UNISKIP_BASE_START, OUTER_UNISKIP_DOMAIN_SIZE, r0); + let (az_g0, bz_g0) = self.group_matvecs(&OUTER_FIRST_GROUP_ROWS, &weights, witness_evals); + let (az_g1, bz_g1) = self.group_matvecs(&OUTER_SECOND_GROUP_ROWS, &weights, witness_evals); + let az = az_g0 + r_stream * (az_g1 - az_g0); + let bz = bz_g0 + r_stream * (bz_g1 - bz_g0); + az * bz + } + + fn group_matvecs(&self, rows: &[usize], weights: &[F], witness_evals: &[F]) -> (F, F) { + let mut az = F::zero(); + let mut bz = F::zero(); + for (&row, &weight) in rows.iter().zip(weights.iter()) { + az += weight * Self::row_dot(&self.key.matrices.a[row], witness_evals); + bz += weight * Self::row_dot(&self.key.matrices.b[row], witness_evals); + } + (az, bz) + } + + fn group_matvecs_from_dots( + rows: &[usize], + weights: &[F], + dots: R1csRowDotSlice<'_, F>, + ) -> (F, F) { + let mut az = F::zero(); + let mut bz = F::zero(); + for (&row, &weight) in rows.iter().zip(weights.iter()) { + az += weight * dots.a[row]; + bz += weight * dots.b[row]; + } + (az, bz) + } + + #[cfg(test)] + fn group_matvecs_from_integer_coeffs( + rows: &[usize], + coefficients: &[i64], + coefficient_fields: &[F], + dots: R1csRowDotSlice<'_, F>, + ) -> (F, F) { + let mut az = F::zero(); + let mut bz = F::zero(); + for ((&row, &coefficient), &coefficient_field) in rows + .iter() + .zip(coefficients.iter()) + .zip(coefficient_fields.iter()) + { + if coefficient == 0 { + continue; + } + let a = dots.a[row]; + if a == F::one() { + az += coefficient_field; + } else if a != F::zero() { + az += a.mul_i64(coefficient); + } + + let b = dots.b[row]; + if b != F::zero() { + bz += b.mul_i64(coefficient); + } + } + (az, bz) + } + + fn group_matvecs_all_uniskip_targets( + rows: &[usize], + target_coeff_fields: &[[F; OUTER_UNISKIP_DOMAIN_SIZE]; OUTER_UNISKIP_DEGREE], + dots: R1csRowDotSlice<'_, F>, + ) -> [(F, F); OUTER_UNISKIP_DEGREE] { + let mut az = [F::zero(); OUTER_UNISKIP_DEGREE]; + let mut bz = [F::zero(); OUTER_UNISKIP_DEGREE]; + for (position, &row) in rows.iter().enumerate() { + let a = dots.a[row]; + let b = dots.b[row]; + if a == F::one() { + for target in 0..OUTER_UNISKIP_DEGREE { + az[target] += target_coeff_fields[target][position]; + } + } else if a != F::zero() { + for target in 0..OUTER_UNISKIP_DEGREE { + az[target] += a.mul_i64(OUTER_UNISKIP_TARGET_COEFFS[target][position]); + } + } + if b != F::zero() { + for target in 0..OUTER_UNISKIP_DEGREE { + bz[target] += b.mul_i64(OUTER_UNISKIP_TARGET_COEFFS[target][position]); + } + } + } + core::array::from_fn(|target| (az[target], bz[target])) + } + + fn row_dot(row: &[(usize, F)], witness_evals: &[F]) -> F { + let mut acc = F::zero(); + for &(variable, coefficient) in row { + acc += coefficient * witness_evals[variable]; + } + acc + } + + fn remaining_cycle_point(point: &[F]) -> Vec { + point[1..].iter().rev().copied().collect() + } + + #[tracing::instrument(skip_all, name = "Stage1OuterR1csData::dense_outer_state")] + fn dense_outer_state( + &self, + context: Stage1OuterRemainingContext<'_, F>, + num_rounds: usize, + batching_coeff: F, + ) -> DenseOuterState { + let tau_high = context.tau[context.tau.len() - 1]; + let tau_low = &context.tau[..context.tau.len() - 1]; + let lagrange_tau_r0 = lagrange_kernel_eval( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + tau_high, + context.r0, + ); + let weights = lagrange_evals( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + context.r0, + ); + let len = 1usize << num_rounds; + let scale = lagrange_tau_r0 * batching_coeff; + let eq_evals = EqPolynomial::new(tau_low.to_vec()).evaluations(); + let mut eq = vec![F::zero(); len]; + let mut az = vec![F::zero(); len]; + let mut bz = vec![F::zero(); len]; + eq.par_chunks_mut(2) + .zip(az.par_chunks_mut(2)) + .zip(bz.par_chunks_mut(2)) + .enumerate() + .for_each(|(cycle, ((eq_pair, az_pair), bz_pair))| { + let index = cycle << 1; + let dots = self.row_dots.cycle(cycle); + let (az_g0, bz_g0) = + Self::group_matvecs_from_dots(&OUTER_FIRST_GROUP_ROWS, &weights, dots); + let (az_g1, bz_g1) = + Self::group_matvecs_from_dots(&OUTER_SECOND_GROUP_ROWS, &weights, dots); + eq_pair[0] = eq_evals[index] * scale; + az_pair[0] = az_g0; + bz_pair[0] = bz_g0; + eq_pair[1] = eq_evals[index + 1] * scale; + az_pair[1] = az_g1; + bz_pair[1] = bz_g1; + }); + DenseOuterState { + eq, + az, + bz, + eq_scratch: Vec::with_capacity(len / 2), + az_scratch: Vec::with_capacity(len / 2), + bz_scratch: Vec::with_capacity(len / 2), + } + } +} + +impl Stage1OuterRemainingEvaluator for Stage1OuterR1csData<'_, F> { + fn evaluate(&self, context: Stage1OuterRemainingContext<'_, F>, point: &[F]) -> F { + assert_eq!( + point.len(), + self.key.num_cycle_vars() + 1, + "stage1 outer remaining point dimension mismatch" + ); + assert_eq!( + context.tau.len(), + self.key.num_cycle_vars() + 2, + "stage1 tau dimension mismatch" + ); + let tau_high = context.tau[context.tau.len() - 1]; + let tau_low = &context.tau[..context.tau.len() - 1]; + let mut point_reversed = point.to_vec(); + point_reversed.reverse(); + let tau_weight = EqPolynomial::::mle(tau_low, &point_reversed); + let lagrange_tau_r0 = lagrange_kernel_eval( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + tau_high, + context.r0, + ); + let cycle_point = Self::remaining_cycle_point(point); + let witness_evals = self.witness_evals(&cycle_point); + lagrange_tau_r0 * tau_weight * self.inner_sum_product(point[0], context.r0, &witness_evals) + } + + #[tracing::instrument(skip_all, name = "Stage1OuterR1csData::uniskip_extended_evals")] + fn uniskip_extended_evals(&self, tau: &[F]) -> Option> { + if tau.len() != self.key.num_cycle_vars() + 2 { + return None; + } + let tau_low = &tau[..tau.len() - 1]; + let num_rounds = self.key.num_cycle_vars() + 1; + let eq_evals = EqPolynomial::new(tau_low.to_vec()).evaluations(); + let num_cycles = 1usize << (num_rounds - 1); + assert_eq!(self.row_dots.cycle_count(), num_cycles); + let target_coeff_fields = + OUTER_UNISKIP_TARGET_COEFFS.map(|coefficients| coefficients.map(F::from_i64)); + let accumulators = (0..num_cycles) + .into_par_iter() + .fold( + || [F::Accumulator::default(); OUTER_UNISKIP_DEGREE], + |mut local, cycle| { + let dots = self.row_dots.cycle(cycle); + let index = cycle << 1; + let first_group = Self::group_matvecs_all_uniskip_targets( + &OUTER_FIRST_GROUP_ROWS, + &target_coeff_fields, + dots, + ); + let second_group = Self::group_matvecs_all_uniskip_targets( + &OUTER_SECOND_GROUP_ROWS, + &target_coeff_fields, + dots, + ); + for target in 0..OUTER_UNISKIP_DEGREE { + let (az_g0, bz_g0) = first_group[target]; + let (az_g1, bz_g1) = second_group[target]; + local[target].fmadd(eq_evals[index], az_g0 * bz_g0); + local[target].fmadd(eq_evals[index + 1], az_g1 * bz_g1); + } + local + }, + ) + .reduce( + || [F::Accumulator::default(); OUTER_UNISKIP_DEGREE], + |mut left, right| { + for target in 0..OUTER_UNISKIP_DEGREE { + left[target].merge(right[target]); + } + left + }, + ); + let extended_evals = accumulators.map(FieldAccumulator::reduce).to_vec(); + Some(extended_evals) + } + + fn evaluate_virtual_oracle( + &self, + _context: Stage1OuterRemainingContext<'_, F>, + oracle: &str, + point: &[F], + ) -> Option { + let variable = r1cs_oracle_variable(oracle)?; + let cycle_point = Self::remaining_cycle_point(point); + Some(self.witness_eval(variable, &cycle_point)) + } + + #[tracing::instrument(skip_all, name = "Stage1OuterR1csData::evaluate_virtual_oracles")] + fn evaluate_virtual_oracles( + &self, + _context: Stage1OuterRemainingContext<'_, F>, + oracles: &[&str], + point: &[F], + ) -> Option> { + let variables = oracles + .iter() + .map(|oracle| r1cs_oracle_variable(oracle)) + .collect::>>()?; + let cycle_point = Self::remaining_cycle_point(point); + Some(self.witness_evals_for_variables(&variables, &cycle_point)) + } + + #[tracing::instrument(skip_all, name = "Stage1OuterR1csData::prove_remaining_rounds")] + fn prove_remaining_rounds( + &self, + context: Stage1OuterRemainingContext<'_, F>, + num_rounds: usize, + batching_coeff: F, + initial_claim: F, + observe_round: &mut dyn FnMut(&UnivariatePoly) -> F, + ) -> Option> { + let mut state = self.dense_outer_state(context, num_rounds, batching_coeff); + let mut running_sum = initial_claim * batching_coeff; + let mut point = Vec::with_capacity(num_rounds); + let mut round_polynomials = Vec::with_capacity(num_rounds); + + for _round in 0..num_rounds { + let poly = state.round_poly(); + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != running_sum { + return Some(Err(Stage1KernelError::InvalidProof { + driver: "stage1.outer.remaining", + reason: "dense outer remaining claim mismatch", + })); + } + let challenge = observe_round(&poly); + running_sum = poly.evaluate(challenge); + state.bind(challenge); + point.push(challenge); + round_polynomials.push(poly); + } + Some(Ok((point, round_polynomials))) + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stage1Proof { + pub sumchecks: Vec>, +} + +impl From> for Stage1Proof { + fn from(artifacts: Stage1ExecutionArtifacts) -> Self { + Self { + sumchecks: artifacts.sumchecks, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage1KernelContext<'a> { + pub mode: Stage1ExecutionMode, + pub program: &'a Stage1CpuProgramPlan, + pub kernel: &'a Stage1KernelPlan, + pub batch: &'a Stage1SumcheckBatchPlan, + pub driver: &'a Stage1SumcheckDriverPlan, +} + +pub trait Stage1KernelExecutor { + fn observe_challenge_vector( + &mut self, + _plan: &'static Stage1TranscriptSqueezePlan, + _values: &[F], + ) -> Result<(), Stage1KernelError> { + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + _output: &Stage1SumcheckOutput, + ) -> Result<(), Stage1KernelError> { + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript; + + fn verify_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript; +} + +#[derive(Clone, Debug, Default)] +pub struct UnsupportedStage1KernelExecutor; + +impl Stage1KernelExecutor for UnsupportedStage1KernelExecutor { + fn prove_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + Err(Stage1KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + Err(Stage1KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stage1ShapeKernelExecutor; + +impl Stage1KernelExecutor for Stage1ShapeKernelExecutor { + fn prove_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + run_shape_kernel(context, transcript) + } + + fn verify_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + run_shape_kernel(context, transcript) + } +} + +#[derive(Clone)] +pub struct Stage1ProverKernelExecutor<'a, F: Field> { + pub inputs: Stage1ProverInputs<'a, F>, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage1ProverKernelExecutor<'a, F> { + pub fn new(inputs: Stage1ProverInputs<'a, F>) -> Self { + Self { + inputs, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } +} + +impl Stage1KernelExecutor for Stage1ProverKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage1TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage1KernelError> { + self.challenge_vectors.push(Stage1ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage1SumcheckOutput, + ) -> Result<(), Stage1KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + prove_stage1_kernel( + context, + &self.inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + Err(Stage1KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage1ExecutionMode::Prover, + actual: Stage1ExecutionMode::Verifier, + }) + } +} + +#[derive(Clone, Debug)] +pub struct Stage1VerifierKernelExecutor<'a, F: Field> { + pub proof: &'a Stage1Proof, + pub cursor: usize, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage1VerifierKernelExecutor<'a, F> { + pub fn new(proof: &'a Stage1Proof) -> Self { + Self { + proof, + cursor: 0, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } +} + +impl Stage1KernelExecutor for Stage1VerifierKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage1TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage1KernelError> { + self.challenge_vectors.push(Stage1ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage1SumcheckOutput, + ) -> Result<(), Stage1KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + Err(Stage1KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage1ExecutionMode::Verifier, + actual: Stage1ExecutionMode::Prover, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + let proof = self.proof.sumchecks.get(self.cursor); + self.cursor += usize::from(proof.is_some()); + verify_stage1_kernel( + context, + proof, + &self.challenge_vectors, + &self.completed_sumchecks, + transcript, + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Stage1KernelError { + MissingKernel { + driver: &'static str, + kernel: &'static str, + }, + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + PlanCountMismatch { + artifact: &'static str, + expected: usize, + actual: usize, + }, + KernelNotImplemented { + abi: &'static str, + }, + WrongExecutorMode { + driver: &'static str, + expected: Stage1ExecutionMode, + actual: Stage1ExecutionMode, + }, + MissingProof { + driver: &'static str, + }, + MissingKernelInput { + kernel: &'static str, + input: &'static str, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + UnsupportedPointOrder { + symbol: &'static str, + point_order: &'static str, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, +} + +impl Display for Stage1KernelError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingKernel { driver, kernel } => { + write!( + formatter, + "stage1 driver @{driver} references missing kernel @{kernel}" + ) + } + Self::MissingBatch { driver, batch } => { + write!( + formatter, + "stage1 driver @{driver} references missing batch @{batch}" + ) + } + Self::PlanCountMismatch { + artifact, + expected, + actual, + } => write!( + formatter, + "stage1 plan @{artifact} count mismatch: expected {expected}, got {actual}" + ), + Self::KernelNotImplemented { abi } => { + write!(formatter, "stage1 kernel ABI `{abi}` is not implemented") + } + Self::WrongExecutorMode { + driver, + expected, + actual, + } => write!( + formatter, + "stage1 driver @{driver} ran with {actual:?} executor path, expected {expected:?}" + ), + Self::MissingProof { driver } => { + write!( + formatter, + "stage1 verifier missing proof for driver @{driver}" + ) + } + Self::MissingKernelInput { kernel, input } => { + write!( + formatter, + "stage1 kernel `{kernel}` missing input `{input}`" + ) + } + Self::InvalidInputLength { + input, + expected, + actual, + } => write!( + formatter, + "stage1 input `{input}` length mismatch: expected {expected}, got {actual}" + ), + Self::UnsupportedPointOrder { + symbol, + point_order, + } => write!( + formatter, + "stage1 instance @{symbol} uses unsupported point order `{point_order}`" + ), + Self::InvalidProof { driver, reason } => { + write!( + formatter, + "stage1 proof for driver @{driver} is invalid: {reason}" + ) + } + } + } +} + +impl Error for Stage1KernelError {} + +pub fn execute_stage1_program( + program: &'static Stage1CpuProgramPlan, + mode: Stage1ExecutionMode, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage1KernelError> +where + F: Field, + E: Stage1KernelExecutor, + T: Transcript, +{ + verify_static_program_shape(program)?; + let mut artifacts = Stage1ExecutionArtifacts::default(); + for squeeze in program.transcript_squeezes { + let values = transcript.challenge_vector(squeeze.count); + executor.observe_challenge_vector(squeeze, &values)?; + artifacts.challenge_vectors.push(Stage1ChallengeVector { + symbol: squeeze.symbol, + values, + }); + } + for driver in program.drivers { + let kernel_symbol = driver.kernel.ok_or(Stage1KernelError::MissingKernel { + driver: driver.symbol, + kernel: "", + })?; + let kernel = + find_kernel(program, kernel_symbol).ok_or(Stage1KernelError::MissingKernel { + driver: driver.symbol, + kernel: kernel_symbol, + })?; + let batch = find_batch(program, driver.batch).ok_or(Stage1KernelError::MissingBatch { + driver: driver.symbol, + batch: driver.batch, + })?; + let context = Stage1KernelContext { + mode, + program, + kernel, + batch, + driver, + }; + let output = match mode { + Stage1ExecutionMode::Prover => executor.prove_sumcheck(context, transcript)?, + Stage1ExecutionMode::Verifier => executor.verify_sumcheck(context, transcript)?, + }; + executor.observe_sumcheck_output(&output)?; + artifacts.sumchecks.push(output); + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + artifacts.opening_values = stage1_opening_values(program, &artifacts.sumchecks)?; + Ok(artifacts) +} + +fn stage1_opening_values( + program: &'static Stage1CpuProgramPlan, + sumchecks: &[Stage1SumcheckOutput], +) -> Result>, Stage1KernelError> { + let mut points = Vec::>::new(); + let mut scalars = Vec::>::new(); + for output in sumchecks { + points.push(Stage1PointValue { + symbol: output.driver, + point: output.point.clone(), + }); + for instance in program.instance_results_for_driver(output.driver) { + points.push(Stage1PointValue { + symbol: instance.symbol, + point: stage1_instance_point(instance, &output.point)?, + }); + } + for eval in program.evals_for_driver(output.driver) { + let value = output + .evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| output.evals.get(eval.index)) + .ok_or(Stage1KernelError::MissingKernelInput { + kernel: output.driver, + input: eval.symbol, + })? + .value; + scalars.push(Stage1ScalarValue { + symbol: eval.symbol, + value, + }); + scalars.push(Stage1ScalarValue { + symbol: eval.name, + value, + }); + } + } + program + .opening_claims + .iter() + .map(|claim| { + let point = points + .iter() + .find(|point| point.symbol == claim.point_source) + .ok_or(Stage1KernelError::MissingKernelInput { + kernel: claim.symbol, + input: claim.point_source, + })? + .point + .clone(); + let eval = scalars + .iter() + .find(|scalar| scalar.symbol == claim.eval_source) + .ok_or(Stage1KernelError::MissingKernelInput { + kernel: claim.symbol, + input: claim.eval_source, + })? + .value; + Ok(Stage1OpeningValue { + symbol: claim.symbol, + oracle: claim.oracle, + point, + eval, + }) + }) + .collect() +} + +fn stage1_instance_point( + instance: &Stage1SumcheckInstanceResultPlan, + point: &[F], +) -> Result, Stage1KernelError> { + let end = instance.round_offset + instance.point_arity; + let mut point = point + .get(instance.round_offset..end) + .ok_or(Stage1KernelError::InvalidInputLength { + input: instance.symbol, + expected: end, + actual: point.len(), + })? + .to_vec(); + match instance.point_order { + "as_is" => Ok(point), + "reverse" => { + point.reverse(); + Ok(point) + } + point_order => Err(Stage1KernelError::UnsupportedPointOrder { + symbol: instance.symbol, + point_order, + }), + } +} + +#[derive(Clone, Debug)] +struct Stage1PointValue { + symbol: &'static str, + point: Vec, +} + +#[derive(Clone, Copy, Debug)] +struct Stage1ScalarValue { + symbol: &'static str, + value: F, +} + +fn verify_static_program_shape( + program: &'static Stage1CpuProgramPlan, +) -> Result<(), Stage1KernelError> { + for batch in program.batches { + verify_count(batch.symbol, batch.count, batch.ordered_claims.len())?; + verify_count(batch.symbol, batch.count, batch.claim_operands.len())?; + } + for batch in program.opening_batches { + verify_count(batch.symbol, batch.count, batch.ordered_claims.len())?; + verify_count(batch.symbol, batch.count, batch.claim_operands.len())?; + } + Ok(()) +} + +fn verify_count( + artifact: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage1KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage1KernelError::PlanCountMismatch { + artifact, + expected, + actual, + }) + } +} + +fn find_kernel( + program: &'static Stage1CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage1KernelPlan> { + program + .kernels + .iter() + .find(|kernel| kernel.symbol == symbol) +} + +fn find_batch( + program: &'static Stage1CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage1SumcheckBatchPlan> { + program.batches.iter().find(|batch| batch.symbol == symbol) +} + +fn prove_stage1_kernel( + context: Stage1KernelContext<'_>, + inputs: &Stage1ProverInputs<'_, F>, + challenge_vectors: &[Stage1ChallengeVector], + completed_sumchecks: &[Stage1SumcheckOutput], + transcript: &mut T, +) -> Result, Stage1KernelError> +where + F: Field, + T: Transcript, +{ + match context.kernel.abi { + "jolt_stage1_outer_uniskip" => { + prove_outer_uniskip(context, inputs, challenge_vectors, transcript) + } + "jolt_stage1_outer_remaining" => prove_outer_remaining( + context, + inputs, + challenge_vectors, + completed_sumchecks, + transcript, + ), + abi => Err(Stage1KernelError::KernelNotImplemented { abi }), + } +} + +fn verify_stage1_kernel( + context: Stage1KernelContext<'_>, + proof: Option<&Stage1SumcheckOutput>, + challenge_vectors: &[Stage1ChallengeVector], + completed_sumchecks: &[Stage1SumcheckOutput], + transcript: &mut T, +) -> Result, Stage1KernelError> +where + F: Field, + T: Transcript, +{ + match context.kernel.abi { + "jolt_stage1_outer_uniskip" => { + verify_outer_uniskip(context, proof, challenge_vectors, transcript) + } + "jolt_stage1_outer_remaining" => { + verify_outer_remaining(context, proof, completed_sumchecks, transcript) + } + abi => Err(Stage1KernelError::KernelNotImplemented { abi }), + } +} + +#[tracing::instrument(skip_all, name = "prove_outer_uniskip")] +fn prove_outer_uniskip( + context: Stage1KernelContext<'_>, + inputs: &Stage1ProverInputs<'_, F>, + challenge_vectors: &[Stage1ChallengeVector], + transcript: &mut T, +) -> Result, Stage1KernelError> +where + F: Field, + T: Transcript, +{ + let tau = find_challenge_vector(challenge_vectors, "stage1.tau").ok_or( + Stage1KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "stage1.tau", + }, + )?; + let tau_high = tau + .last() + .copied() + .ok_or(Stage1KernelError::InvalidInputLength { + input: "stage1.tau", + expected: 1, + actual: 0, + })?; + let owned_extended_evals; + let extended_evals = if let Some(extended_evals) = inputs.uniskip_extended_evals { + extended_evals + } else { + let evaluator = + inputs + .outer_remaining_evaluator + .ok_or(Stage1KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "uniskip_extended_evals", + })?; + owned_extended_evals = + evaluator + .uniskip_extended_evals(tau) + .ok_or(Stage1KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "uniskip_extended_evals", + })?; + owned_extended_evals.as_slice() + }; + let poly = build_outer_uniskip_poly(extended_evals, tau_high)?; + append_univariate_poly(transcript, context.driver.round_label, &poly); + let r0 = transcript.challenge(); + let eval = poly.evaluate(r0); + append_labeled_scalar(transcript, "opening_claim", &eval); + Ok(Stage1SumcheckOutput { + driver: context.driver.symbol, + point: vec![r0], + evals: driver_evals(context, eval), + proof: SumcheckProof { + round_polynomials: vec![poly], + }, + }) +} + +#[tracing::instrument(skip_all, name = "prove_outer_remaining")] +fn prove_outer_remaining( + context: Stage1KernelContext<'_>, + inputs: &Stage1ProverInputs<'_, F>, + challenge_vectors: &[Stage1ChallengeVector], + completed_sumchecks: &[Stage1SumcheckOutput], + transcript: &mut T, +) -> Result, Stage1KernelError> +where + F: Field, + T: Transcript, +{ + let evaluator = + inputs + .outer_remaining_evaluator + .ok_or(Stage1KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "outer_remaining_evaluator", + })?; + let tau = find_challenge_vector(challenge_vectors, "stage1.tau").ok_or( + Stage1KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "stage1.tau", + }, + )?; + let (r0, input_claim) = uniskip_point_and_claim(completed_sumchecks).ok_or( + Stage1KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "stage1.uniskip.eval", + }, + )?; + let remaining_context = Stage1OuterRemainingContext { tau, r0 }; + append_labeled_scalar(transcript, context.driver.claim_label, &input_claim); + let batching_coeff = transcript.challenge(); + let fast_path = evaluator.prove_remaining_rounds( + remaining_context, + context.driver.num_rounds, + batching_coeff, + input_claim, + &mut |poly| { + append_compressed_univariate_poly(transcript, context.driver.round_label, poly); + transcript.challenge() + }, + ); + let (point, round_polynomials) = if let Some(result) = fast_path { + result? + } else { + prove_outer_remaining_fallback( + context, + evaluator, + remaining_context, + batching_coeff, + input_claim, + transcript, + )? + }; + + let evals = remaining_driver_evals(context, evaluator, remaining_context, &point)?; + append_opening_claims(transcript, &evals); + Ok(Stage1SumcheckOutput { + driver: context.driver.symbol, + point, + evals, + proof: SumcheckProof { round_polynomials }, + }) +} + +fn verify_outer_uniskip( + context: Stage1KernelContext<'_>, + proof: Option<&Stage1SumcheckOutput>, + _challenge_vectors: &[Stage1ChallengeVector], + transcript: &mut T, +) -> Result, Stage1KernelError> +where + F: Field, + T: Transcript, +{ + let Some(proof) = proof else { + return Err(Stage1KernelError::MissingProof { + driver: context.driver.symbol, + }); + }; + if proof.driver != context.driver.symbol { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + let Some(poly) = proof.proof.round_polynomials.first() else { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "missing uniskip round polynomial", + }); + }; + if proof.proof.round_polynomials.len() != 1 { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected uniskip round count", + }); + } + if polynomial_degree(poly) > OUTER_UNISKIP_DEGREE_BOUND { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "uniskip polynomial exceeds degree bound", + }); + } + append_univariate_poly(transcript, context.driver.round_label, poly); + let r0 = transcript.challenge(); + if !uniskip_sum_matches(poly, F::zero()) { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "uniskip polynomial sum check failed", + }); + } + let eval = poly.evaluate(r0); + if !proof.point.is_empty() && (proof.point.len() != 1 || proof.point[0] != r0) { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "uniskip point mismatch", + }); + } + validate_eval_shape(context, &proof.evals, Some(eval))?; + append_labeled_scalar(transcript, "opening_claim", &eval); + Ok(Stage1SumcheckOutput { + driver: context.driver.symbol, + point: vec![r0], + evals: driver_evals(context, eval), + proof: proof.proof.clone(), + }) +} + +fn verify_outer_remaining( + context: Stage1KernelContext<'_>, + proof: Option<&Stage1SumcheckOutput>, + completed_sumchecks: &[Stage1SumcheckOutput], + transcript: &mut T, +) -> Result, Stage1KernelError> +where + F: Field, + T: Transcript, +{ + let Some(proof) = proof else { + return Err(Stage1KernelError::MissingProof { + driver: context.driver.symbol, + }); + }; + if proof.driver != context.driver.symbol { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + if proof.proof.round_polynomials.len() != context.driver.num_rounds { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected outer remaining round count", + }); + } + let input_claim = + uniskip_output_claim(completed_sumchecks).ok_or(Stage1KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "stage1.uniskip.eval", + })?; + append_labeled_scalar(transcript, context.driver.claim_label, &input_claim); + let batching_coeff = transcript.challenge(); + let mut running_sum = input_claim * batching_coeff; + let mut point = Vec::with_capacity(context.driver.num_rounds); + + for poly in &proof.proof.round_polynomials { + if polynomial_degree(poly) > OUTER_REMAINING_DEGREE_BOUND { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "outer remaining polynomial exceeds degree bound", + }); + } + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != running_sum { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "outer remaining round check failed", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, poly); + let challenge = transcript.challenge(); + running_sum = poly.evaluate(challenge); + point.push(challenge); + } + if !proof.point.is_empty() && proof.point != point { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "outer remaining point mismatch", + }); + } + validate_eval_shape(context, &proof.evals, None)?; + append_opening_claims(transcript, &proof.evals); + Ok(Stage1SumcheckOutput { + driver: context.driver.symbol, + point, + evals: proof.evals.clone(), + proof: proof.proof.clone(), + }) +} + +fn prove_outer_remaining_fallback( + context: Stage1KernelContext<'_>, + evaluator: &dyn Stage1OuterRemainingEvaluator, + remaining_context: Stage1OuterRemainingContext<'_, F>, + batching_coeff: F, + input_claim: F, + transcript: &mut T, +) -> Result<(Vec, Vec>), Stage1KernelError> +where + F: Field, + T: Transcript, +{ + let mut running_sum = input_claim * batching_coeff; + let mut point = Vec::with_capacity(context.driver.num_rounds); + let mut round_polynomials = Vec::with_capacity(context.driver.num_rounds); + for _round in 0..context.driver.num_rounds { + let poly = outer_remaining_round_poly( + evaluator, + remaining_context, + context.driver.num_rounds, + &point, + ); + let scaled_poly = scale_poly(&poly, batching_coeff); + if scaled_poly.evaluate(F::zero()) + scaled_poly.evaluate(F::one()) != running_sum { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "outer remaining prover claim mismatch", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, &scaled_poly); + let challenge = transcript.challenge(); + running_sum = scaled_poly.evaluate(challenge); + point.push(challenge); + round_polynomials.push(scaled_poly); + } + Ok((point, round_polynomials)) +} + +fn find_challenge_vector<'a, F: Field>( + challenges: &'a [Stage1ChallengeVector], + symbol: &str, +) -> Option<&'a [F]> { + challenges + .iter() + .find(|challenge| challenge.symbol == symbol) + .map(|challenge| challenge.values.as_slice()) +} + +fn build_outer_uniskip_poly( + extended_evals: &[F], + tau_high: F, +) -> Result, Stage1KernelError> { + if extended_evals.len() != OUTER_UNISKIP_DEGREE { + return Err(Stage1KernelError::InvalidInputLength { + input: "uniskip_extended_evals", + expected: OUTER_UNISKIP_DEGREE, + actual: extended_evals.len(), + }); + } + + let mut t1_values = vec![F::zero(); OUTER_UNISKIP_EXTENDED_SIZE]; + for (value, target) in extended_evals.iter().zip(outer_uniskip_targets()) { + let index = (target - OUTER_UNISKIP_EXTENDED_START) as usize; + t1_values[index] = *value; + } + + let t1_coeffs = interpolate_to_coeffs(OUTER_UNISKIP_EXTENDED_START, &t1_values); + let lagrange_values = lagrange_evals( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + tau_high, + ); + let lagrange_coeffs = interpolate_to_coeffs(OUTER_UNISKIP_BASE_START, &lagrange_values); + + let mut coefficients = vec![F::zero(); OUTER_UNISKIP_NUM_COEFFS]; + for (i, &lagrange_coeff) in lagrange_coeffs.iter().enumerate() { + for (j, &t1_coeff) in t1_coeffs.iter().enumerate() { + coefficients[i + j] += lagrange_coeff * t1_coeff; + } + } + Ok(UnivariatePoly::new(coefficients)) +} + +/// Returns the off-domain targets used by the Stage 1 outer univariate-skip +/// round polynomial. +pub fn outer_uniskip_targets() -> [i64; OUTER_UNISKIP_DEGREE] { + let ext_left = OUTER_UNISKIP_EXTENDED_START; + let ext_right = OUTER_UNISKIP_DEGREE as i64; + let base_left = OUTER_UNISKIP_BASE_START; + let base_right = base_left + OUTER_UNISKIP_DOMAIN_SIZE as i64 - 1; + let mut targets = [0i64; OUTER_UNISKIP_DEGREE]; + let mut index = 0; + let mut negative = base_left - 1; + let mut positive = base_right + 1; + while negative >= ext_left && positive <= ext_right && index < OUTER_UNISKIP_DEGREE { + targets[index] = negative; + index += 1; + if index >= OUTER_UNISKIP_DEGREE { + break; + } + targets[index] = positive; + index += 1; + negative -= 1; + positive += 1; + } + while index < OUTER_UNISKIP_DEGREE && negative >= ext_left { + targets[index] = negative; + index += 1; + negative -= 1; + } + while index < OUTER_UNISKIP_DEGREE && positive <= ext_right { + targets[index] = positive; + index += 1; + positive += 1; + } + targets +} + +/// Recovers the Stage 1 outer univariate-skip extended evaluations from a +/// first-round proof polynomial. +pub fn outer_uniskip_extended_evals_from_round_poly( + round_poly: &UnivariatePoly, + tau_high: F, +) -> Vec { + outer_uniskip_targets() + .into_iter() + .map(|target| { + let y = F::from_i64(target); + let kernel = lagrange_kernel_eval( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + tau_high, + y, + ); + round_poly.evaluate(y) / kernel + }) + .collect() +} + +fn boolean_index(point: &[F]) -> Option { + let mut index = 0usize; + for value in point { + index <<= 1; + if *value == F::one() { + index |= 1; + } else if *value != F::zero() { + return None; + } + } + Some(index) +} + +fn r1cs_oracle_variable(oracle: &str) -> Option { + match oracle { + "LeftInstructionInput" => Some(rv64::V_LEFT_INSTRUCTION_INPUT), + "RightInstructionInput" => Some(rv64::V_RIGHT_INSTRUCTION_INPUT), + "Product" => Some(rv64::V_PRODUCT), + "ShouldBranch" => Some(rv64::V_SHOULD_BRANCH), + "PC" => Some(rv64::V_PC), + "UnexpandedPC" => Some(rv64::V_UNEXPANDED_PC), + "Imm" => Some(rv64::V_IMM), + "RamAddress" => Some(rv64::V_RAM_ADDRESS), + "Rs1Value" => Some(rv64::V_RS1_VALUE), + "Rs2Value" => Some(rv64::V_RS2_VALUE), + "RdWriteValue" => Some(rv64::V_RD_WRITE_VALUE), + "RamReadValue" => Some(rv64::V_RAM_READ_VALUE), + "RamWriteValue" => Some(rv64::V_RAM_WRITE_VALUE), + "LeftLookupOperand" => Some(rv64::V_LEFT_LOOKUP_OPERAND), + "RightLookupOperand" => Some(rv64::V_RIGHT_LOOKUP_OPERAND), + "NextUnexpandedPC" => Some(rv64::V_NEXT_UNEXPANDED_PC), + "NextPC" => Some(rv64::V_NEXT_PC), + "NextIsVirtual" => Some(rv64::V_NEXT_IS_VIRTUAL), + "NextIsFirstInSequence" => Some(rv64::V_NEXT_IS_FIRST_IN_SEQUENCE), + "LookupOutput" => Some(rv64::V_LOOKUP_OUTPUT), + "ShouldJump" => Some(rv64::V_SHOULD_JUMP), + "OpFlagAddOperands" => Some(rv64::V_FLAG_ADD_OPERANDS), + "OpFlagSubtractOperands" => Some(rv64::V_FLAG_SUBTRACT_OPERANDS), + "OpFlagMultiplyOperands" => Some(rv64::V_FLAG_MULTIPLY_OPERANDS), + "OpFlagLoad" => Some(rv64::V_FLAG_LOAD), + "OpFlagStore" => Some(rv64::V_FLAG_STORE), + "OpFlagJump" => Some(rv64::V_FLAG_JUMP), + "OpFlagWriteLookupOutputToRD" => Some(rv64::V_FLAG_WRITE_LOOKUP_OUTPUT_TO_RD), + "OpFlagVirtualInstruction" => Some(rv64::V_FLAG_VIRTUAL_INSTRUCTION), + "OpFlagAssert" => Some(rv64::V_FLAG_ASSERT), + "OpFlagDoNotUpdateUnexpandedPC" => Some(rv64::V_FLAG_DO_NOT_UPDATE_UNEXPANDED_PC), + "OpFlagAdvice" => Some(rv64::V_FLAG_ADVICE), + "OpFlagIsCompressed" => Some(rv64::V_FLAG_IS_COMPRESSED), + "OpFlagIsFirstInSequence" => Some(rv64::V_FLAG_IS_FIRST_IN_SEQUENCE), + "OpFlagIsLastInSequence" => Some(rv64::V_FLAG_IS_LAST_IN_SEQUENCE), + _ => None, + } +} + +fn append_univariate_poly(transcript: &mut T, label: &'static str, poly: &UnivariatePoly) +where + F: Field, + T: Transcript, +{ + transcript.append(&LabelWithCount( + label.as_bytes(), + poly.coefficients().len() as u64, + )); + for coefficient in poly.coefficients() { + transcript.append(coefficient); + } +} + +fn append_compressed_univariate_poly( + transcript: &mut T, + label: &'static str, + poly: &UnivariatePoly, +) where + F: Field, + T: Transcript, +{ + let compressed = poly.compress(); + transcript.append(&LabelWithCount( + label.as_bytes(), + compressed.coeffs_except_linear_term().len() as u64, + )); + for coefficient in compressed.coeffs_except_linear_term() { + transcript.append(coefficient); + } +} + +fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &F) +where + F: Field, + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +fn append_opening_claims(transcript: &mut T, evals: &[Stage1NamedEval]) +where + F: Field, + T: Transcript, +{ + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } +} + +struct DenseOuterState { + eq: Vec, + az: Vec, + bz: Vec, + eq_scratch: Vec, + az_scratch: Vec, + bz_scratch: Vec, +} + +impl DenseOuterState { + #[tracing::instrument(skip_all, name = "DenseOuterState::round_poly")] + fn round_poly(&self) -> UnivariatePoly { + let pair_count = self.eq.len() / 2; + let accumulators = if pair_count >= DENSE_BIND_PAR_THRESHOLD { + self.eq + .par_chunks_exact(2) + .zip(self.az.par_chunks_exact(2)) + .zip(self.bz.par_chunks_exact(2)) + .map(|((eq_pair, az_pair), bz_pair)| { + let mut local = [F::Accumulator::default(); OUTER_REMAINING_DEGREE_BOUND + 1]; + let eq0 = eq_pair[0]; + let eq_delta = eq_pair[1] - eq_pair[0]; + let az0 = az_pair[0]; + let az_delta = az_pair[1] - az_pair[0]; + let bz0 = bz_pair[0]; + let bz_delta = bz_pair[1] - bz_pair[0]; + accumulate_cubic_product_coefficients( + &mut local, eq0, eq_delta, az0, az_delta, bz0, bz_delta, + ); + local + }) + .reduce( + || [F::Accumulator::default(); OUTER_REMAINING_DEGREE_BOUND + 1], + |mut left, right| { + for i in 0..left.len() { + left[i].merge(right[i]); + } + left + }, + ) + } else { + self.eq + .chunks_exact(2) + .zip(self.az.chunks_exact(2)) + .zip(self.bz.chunks_exact(2)) + .fold( + [F::Accumulator::default(); OUTER_REMAINING_DEGREE_BOUND + 1], + |mut local, ((eq_pair, az_pair), bz_pair)| { + let eq0 = eq_pair[0]; + let eq_delta = eq_pair[1] - eq_pair[0]; + let az0 = az_pair[0]; + let az_delta = az_pair[1] - az_pair[0]; + let bz0 = bz_pair[0]; + let bz_delta = bz_pair[1] - bz_pair[0]; + accumulate_cubic_product_coefficients( + &mut local, eq0, eq_delta, az0, az_delta, bz0, bz_delta, + ); + local + }, + ) + }; + UnivariatePoly::new(accumulators.map(FieldAccumulator::reduce).to_vec()) + } + + #[tracing::instrument(skip_all, name = "DenseOuterState::bind")] + fn bind(&mut self, challenge: F) { + bind_dense_evals_reuse(&mut self.eq, &mut self.eq_scratch, challenge); + bind_dense_evals_reuse(&mut self.az, &mut self.az_scratch, challenge); + bind_dense_evals_reuse(&mut self.bz, &mut self.bz_scratch, challenge); + } +} + +#[inline] +fn accumulate_cubic_product_coefficients( + coefficients: &mut [F::Accumulator; OUTER_REMAINING_DEGREE_BOUND + 1], + eq0: F, + eq_delta: F, + az0: F, + az_delta: F, + bz0: F, + bz_delta: F, +) { + let az0_bz0 = az0 * bz0; + let az_delta_bz0 = az_delta * bz0; + let az0_bz_delta = az0 * bz_delta; + let az_delta_bz_delta = az_delta * bz_delta; + + coefficients[0].fmadd(eq0, az0_bz0); + coefficients[1].fmadd(eq_delta, az0_bz0); + coefficients[1].fmadd(eq0, az_delta_bz0); + coefficients[1].fmadd(eq0, az0_bz_delta); + coefficients[2].fmadd(eq_delta, az_delta_bz0); + coefficients[2].fmadd(eq_delta, az0_bz_delta); + coefficients[2].fmadd(eq0, az_delta_bz_delta); + coefficients[3].fmadd(eq_delta, az_delta_bz_delta); +} + +fn bind_dense_evals_reuse(values: &mut Vec, scratch: &mut Vec, challenge: F) { + let half = values.len() / 2; + scratch.resize(half, F::zero()); + if half >= DENSE_BIND_PAR_THRESHOLD { + scratch + .par_iter_mut() + .enumerate() + .for_each(|(index, output)| { + let low = values[index << 1]; + let high = values[(index << 1) + 1]; + *output = low + (high - low) * challenge; + }); + } else { + for (index, output) in scratch.iter_mut().enumerate() { + let low = values[index << 1]; + let high = values[(index << 1) + 1]; + *output = low + (high - low) * challenge; + } + } + std::mem::swap(values, scratch); + scratch.clear(); +} + +fn outer_remaining_round_poly( + evaluator: &dyn Stage1OuterRemainingEvaluator, + context: Stage1OuterRemainingContext<'_, F>, + num_rounds: usize, + prefix: &[F], +) -> UnivariatePoly { + let suffix_rounds = num_rounds - prefix.len() - 1; + let mut evals = Vec::with_capacity(OUTER_REMAINING_DEGREE_BOUND + 1); + for x in 0..=OUTER_REMAINING_DEGREE_BOUND { + let mut sum = F::zero(); + for suffix in 0..(1usize << suffix_rounds) { + let mut point = Vec::with_capacity(num_rounds); + point.extend_from_slice(prefix); + point.push(F::from_u64(x as u64)); + for bit in 0..suffix_rounds { + point.push(F::from_u64(((suffix >> bit) & 1) as u64)); + } + sum += evaluator.evaluate(context, &point); + } + evals.push(sum); + } + UnivariatePoly::new(interpolate_to_coeffs(0, &evals)) +} + +fn scale_poly(poly: &UnivariatePoly, scalar: F) -> UnivariatePoly { + UnivariatePoly::new( + poly.coefficients() + .iter() + .map(|coefficient| *coefficient * scalar) + .collect(), + ) +} + +fn remaining_driver_evals( + context: Stage1KernelContext<'_>, + evaluator: &dyn Stage1OuterRemainingEvaluator, + remaining_context: Stage1OuterRemainingContext<'_, F>, + point: &[F], +) -> Result>, Stage1KernelError> { + let plans = context + .program + .evals + .iter() + .filter(|eval| eval.source == context.driver.symbol) + .collect::>(); + let oracles = plans.iter().map(|eval| eval.oracle).collect::>(); + let values = evaluator + .evaluate_virtual_oracles(remaining_context, &oracles, point) + .ok_or(Stage1KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "remaining_driver_evals", + })?; + if values.len() != plans.len() { + return Err(Stage1KernelError::InvalidInputLength { + input: "remaining_driver_evals", + expected: plans.len(), + actual: values.len(), + }); + } + Ok(plans + .into_iter() + .zip(values) + .map(|(eval, value)| Stage1NamedEval { + name: eval.name, + oracle: eval.oracle, + value, + }) + .collect()) +} + +fn uniskip_output_claim(sumchecks: &[Stage1SumcheckOutput]) -> Option { + uniskip_point_and_claim(sumchecks).map(|(_, claim)| claim) +} + +fn uniskip_point_and_claim(sumchecks: &[Stage1SumcheckOutput]) -> Option<(F, F)> { + sumchecks.iter().find_map(|sumcheck| { + let point = *sumcheck.point.first()?; + let claim = sumcheck + .evals + .iter() + .find(|eval| eval.oracle == "UnivariateSkip") + .map(|eval| eval.value)?; + Some((point, claim)) + }) +} + +fn uniskip_sum_matches(poly: &UnivariatePoly, claim: F) -> bool { + let power_sums = integer_domain_power_sums( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + poly.coefficients().len(), + ); + let sum = poly + .coefficients() + .iter() + .zip(power_sums) + .fold(F::zero(), |acc, (coefficient, power_sum)| { + acc + coefficient.mul_i128(power_sum) + }); + sum == claim +} + +fn integer_domain_power_sums(domain_start: i64, domain_size: usize, count: usize) -> Vec { + let mut sums = vec![0i128; count]; + for offset in 0..domain_size { + let point = i128::from(domain_start + offset as i64); + let mut power = 1i128; + for sum in &mut sums { + *sum += power; + power *= point; + } + } + sums +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients().len().saturating_sub(1) +} + +fn driver_evals(context: Stage1KernelContext<'_>, value: F) -> Vec> { + context + .program + .evals + .iter() + .filter(|eval| eval.source == context.driver.symbol) + .map(|eval| Stage1NamedEval { + name: eval.name, + oracle: eval.oracle, + value, + }) + .collect() +} + +fn validate_eval_shape( + context: Stage1KernelContext<'_>, + actual: &[Stage1NamedEval], + expected_value: Option, +) -> Result<(), Stage1KernelError> { + let expected = context + .program + .evals + .iter() + .filter(|eval| eval.source == context.driver.symbol) + .collect::>(); + if actual.len() != expected.len() { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "eval count mismatch", + }); + } + for (actual, expected) in actual.iter().zip(expected) { + if actual.name != expected.name { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "eval name mismatch", + }); + } + if actual.oracle != expected.oracle { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "eval oracle mismatch", + }); + } + if expected_value + .as_ref() + .is_some_and(|expected_value| actual.value != *expected_value) + { + return Err(Stage1KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "eval value mismatch", + }); + } + } + Ok(()) +} + +fn run_shape_kernel( + context: Stage1KernelContext<'_>, + transcript: &mut T, +) -> Result, Stage1KernelError> +where + F: Field, + T: Transcript, +{ + match context.kernel.abi { + "jolt_stage1_outer_uniskip" | "jolt_stage1_outer_remaining" => { + Ok(shape_sumcheck_output(context, transcript)) + } + abi => Err(Stage1KernelError::KernelNotImplemented { abi }), + } +} + +fn shape_sumcheck_output( + context: Stage1KernelContext<'_>, + transcript: &mut T, +) -> Stage1SumcheckOutput +where + F: Field, + T: Transcript, +{ + let point = (0..context.driver.num_rounds) + .map(|_| transcript.challenge()) + .collect(); + let evals = context + .program + .evals + .iter() + .filter(|eval| eval.source == context.driver.symbol) + .map(|eval| Stage1NamedEval { + name: eval.name, + oracle: eval.oracle, + value: F::from_u64(eval.index as u64), + }) + .collect(); + Stage1SumcheckOutput { + driver: context.driver.symbol, + point, + evals, + proof: shape_sumcheck_proof(context.driver), + } +} + +fn shape_sumcheck_proof(driver: &Stage1SumcheckDriverPlan) -> SumcheckProof { + let coefficients = vec![F::zero(); driver.degree + 1]; + SumcheckProof { + round_polynomials: (0..driver.num_rounds) + .map(|_| UnivariatePoly::new(coefficients.clone())) + .collect(), + } +} + +#[cfg(test)] +#[expect(clippy::expect_used, reason = "tests use explicit panic messages")] +mod tests { + use jolt_field::{Field, Fr}; + use jolt_transcript::{MockTranscript, Transcript}; + + use super::*; + + static KERNELS: &[Stage1KernelPlan] = &[Stage1KernelPlan { + symbol: "kernel", + relation: "relation", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage1_outer_uniskip", + }]; + static FULL_KERNELS: &[Stage1KernelPlan] = &[ + Stage1KernelPlan { + symbol: "uniskip_kernel", + relation: "jolt.stage1.outer.uniskip", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage1_outer_uniskip", + }, + Stage1KernelPlan { + symbol: "remaining_kernel", + relation: "jolt.stage1.outer.remaining", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage1_outer_remaining", + }, + ]; + static CLAIMS: &[Stage1SumcheckClaimPlan] = &[Stage1SumcheckClaimPlan { + symbol: "claim", + stage: "stage", + domain: "domain", + num_rounds: 1, + degree: OUTER_UNISKIP_DEGREE_BOUND, + claim: "zero", + kernel: Some("kernel"), + relation: None, + claim_value: "zero", + input_openings: &[], + }]; + static BATCHES: &[Stage1SumcheckBatchPlan] = &[Stage1SumcheckBatchPlan { + symbol: "batch", + stage: "stage", + proof_slot: "proof", + policy: "single", + count: 1, + ordered_claims: &["claim"], + claim_operands: &["claim"], + claim_label: "claim_label", + round_label: "round_label", + round_schedule: &[1], + }]; + static BAD_BATCHES: &[Stage1SumcheckBatchPlan] = &[Stage1SumcheckBatchPlan { + symbol: "batch", + stage: "stage", + proof_slot: "proof", + policy: "single", + count: 2, + ordered_claims: &["claim"], + claim_operands: &["claim"], + claim_label: "claim_label", + round_label: "round_label", + round_schedule: &[1], + }]; + + #[test] + fn cubic_product_coefficients_match_interpolation() { + let eq0 = Fr::from_u64(3); + let eq_delta = Fr::from_u64(5); + let az0 = Fr::from_u64(7); + let az_delta = Fr::from_u64(11); + let bz0 = Fr::from_u64(13); + let bz_delta = Fr::from_u64(17); + + let mut accumulators = + [::Accumulator::default(); OUTER_REMAINING_DEGREE_BOUND + 1]; + accumulate_cubic_product_coefficients( + &mut accumulators, + eq0, + eq_delta, + az0, + az_delta, + bz0, + bz_delta, + ); + let direct = accumulators.map(FieldAccumulator::reduce).to_vec(); + + let evals = (0..=OUTER_REMAINING_DEGREE_BOUND) + .map(|x| { + let point = Fr::from_u64(x as u64); + (eq0 + eq_delta * point) * (az0 + az_delta * point) * (bz0 + bz_delta * point) + }) + .collect::>(); + assert_eq!(direct, interpolate_to_coeffs(0, &evals)); + } + + #[test] + fn uniskip_integer_coefficients_match_lagrange_weights() { + for (target, coefficients) in outer_uniskip_targets() + .into_iter() + .zip(OUTER_UNISKIP_TARGET_COEFFS) + { + let weights = lagrange_evals( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + Fr::from_i64(target), + ); + let expected = coefficients.map(Fr::from_i64).to_vec(); + assert_eq!(weights, expected); + } + } + + #[test] + fn integer_coeff_group_matvec_matches_field_weights() { + let mut a_values = [Fr::from_u64(0); OUTER_EQ_CONSTRAINT_ROWS]; + let mut b_values = [Fr::from_u64(0); OUTER_EQ_CONSTRAINT_ROWS]; + for i in 0..OUTER_EQ_CONSTRAINT_ROWS { + a_values[i] = match i % 3 { + 0 => Fr::from_u64(0), + 1 => Fr::from_u64(1), + _ => Fr::from_u64((i + 2) as u64), + }; + b_values[i] = Fr::from_i64(i as i64 - 7); + } + let dots = R1csRowDotSlice { + a: &a_values, + b: &b_values, + }; + let coefficients = &OUTER_UNISKIP_TARGET_COEFFS[3]; + let coefficient_fields = coefficients.map(Fr::from_i64); + let weights = coefficient_fields.to_vec(); + + let integer = Stage1OuterR1csData::group_matvecs_from_integer_coeffs( + &OUTER_FIRST_GROUP_ROWS, + coefficients, + &coefficient_fields, + dots, + ); + let field = + Stage1OuterR1csData::group_matvecs_from_dots(&OUTER_FIRST_GROUP_ROWS, &weights, dots); + assert_eq!(integer, field); + } + + static FULL_CLAIMS: &[Stage1SumcheckClaimPlan] = &[ + Stage1SumcheckClaimPlan { + symbol: "uniskip_claim", + stage: "stage", + domain: "uniskip_domain", + num_rounds: 1, + degree: OUTER_UNISKIP_DEGREE_BOUND, + claim: "zero", + kernel: Some("uniskip_kernel"), + relation: None, + claim_value: "zero", + input_openings: &[], + }, + Stage1SumcheckClaimPlan { + symbol: "remaining_claim", + stage: "stage", + domain: "trace_domain", + num_rounds: 2, + degree: OUTER_REMAINING_DEGREE_BOUND, + claim: "stage1.uniskip.opening", + kernel: Some("remaining_kernel"), + relation: None, + claim_value: "stage1.uniskip.eval", + input_openings: &["stage1.uniskip.opening"], + }, + ]; + static FULL_BATCHES: &[Stage1SumcheckBatchPlan] = &[ + Stage1SumcheckBatchPlan { + symbol: "uniskip_batch", + stage: "stage", + proof_slot: "uniskip_proof", + policy: "single", + count: 1, + ordered_claims: &["uniskip_claim"], + claim_operands: &["uniskip_claim"], + claim_label: "uniskip_claim", + round_label: "uniskip_poly", + round_schedule: &[1], + }, + Stage1SumcheckBatchPlan { + symbol: "remaining_batch", + stage: "stage", + proof_slot: "remaining_proof", + policy: "jolt_core_front_loaded", + count: 1, + ordered_claims: &["remaining_claim"], + claim_operands: &["remaining_claim"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[2], + }, + ]; + static DRIVERS: &[Stage1SumcheckDriverPlan] = &[Stage1SumcheckDriverPlan { + symbol: "driver", + stage: "stage", + proof_slot: "proof", + kernel: Some("kernel"), + relation: None, + batch: "batch", + policy: "single", + round_schedule: &[1], + claim_label: "claim_label", + round_label: "round_label", + num_rounds: 1, + degree: OUTER_UNISKIP_DEGREE_BOUND, + }]; + static FULL_DRIVERS: &[Stage1SumcheckDriverPlan] = &[ + Stage1SumcheckDriverPlan { + symbol: "stage1.uniskip.sumcheck", + stage: "stage", + proof_slot: "uniskip_proof", + kernel: Some("uniskip_kernel"), + relation: None, + batch: "uniskip_batch", + policy: "univariate_skip", + round_schedule: &[1], + claim_label: "uniskip_claim", + round_label: "uniskip_poly", + num_rounds: 1, + degree: OUTER_UNISKIP_DEGREE_BOUND, + }, + Stage1SumcheckDriverPlan { + symbol: "stage1.outer_remaining.sumcheck", + stage: "stage", + proof_slot: "remaining_proof", + kernel: Some("remaining_kernel"), + relation: None, + batch: "remaining_batch", + policy: "jolt_core_front_loaded", + round_schedule: &[2], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 2, + degree: OUTER_REMAINING_DEGREE_BOUND, + }, + ]; + static FULL_EVALS: &[Stage1SumcheckEvalPlan] = &[ + Stage1SumcheckEvalPlan { + symbol: "uniskip_eval", + source: "stage1.uniskip.sumcheck", + name: "stage1.uniskip.eval", + index: 0, + oracle: "UnivariateSkip", + }, + Stage1SumcheckEvalPlan { + symbol: "remaining_eval", + source: "stage1.outer_remaining.sumcheck", + name: "stage1.outer_remaining.eval.Synthetic", + index: 0, + oracle: "Synthetic", + }, + ]; + static SQUEEZES: &[Stage1TranscriptSqueezePlan] = &[Stage1TranscriptSqueezePlan { + symbol: "stage1.tau", + label: "outer_tau", + kind: "challenge_vector", + count: 2, + }]; + static REAL_SQUEEZES: &[Stage1TranscriptSqueezePlan] = &[Stage1TranscriptSqueezePlan { + symbol: "stage1.tau", + label: "outer_tau", + kind: "challenge_vector", + count: 3, + }]; + static PROGRAM: Stage1CpuProgramPlan = Stage1CpuProgramPlan { + params: Stage1Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }, + transcript_squeezes: SQUEEZES, + kernels: KERNELS, + claims: CLAIMS, + batches: BATCHES, + drivers: DRIVERS, + instance_results: &[], + evals: &[], + opening_claims: &[], + opening_batches: &[], + }; + static BAD_PROGRAM: Stage1CpuProgramPlan = Stage1CpuProgramPlan { + params: Stage1Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }, + transcript_squeezes: SQUEEZES, + kernels: KERNELS, + claims: CLAIMS, + batches: BAD_BATCHES, + drivers: DRIVERS, + instance_results: &[], + evals: &[], + opening_claims: &[], + opening_batches: &[], + }; + static FULL_PROGRAM: Stage1CpuProgramPlan = Stage1CpuProgramPlan { + params: Stage1Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }, + transcript_squeezes: SQUEEZES, + kernels: FULL_KERNELS, + claims: FULL_CLAIMS, + batches: FULL_BATCHES, + drivers: FULL_DRIVERS, + instance_results: &[], + evals: FULL_EVALS, + opening_claims: &[], + opening_batches: &[], + }; + static REAL_EVALS: &[Stage1SumcheckEvalPlan] = &[ + Stage1SumcheckEvalPlan { + symbol: "uniskip_eval", + source: "stage1.uniskip.sumcheck", + name: "stage1.uniskip.eval", + index: 0, + oracle: "UnivariateSkip", + }, + Stage1SumcheckEvalPlan { + symbol: "remaining_eval", + source: "stage1.outer_remaining.sumcheck", + name: "stage1.outer_remaining.eval.PC", + index: 0, + oracle: "PC", + }, + ]; + static REAL_PROGRAM: Stage1CpuProgramPlan = Stage1CpuProgramPlan { + params: Stage1Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }, + transcript_squeezes: REAL_SQUEEZES, + kernels: FULL_KERNELS, + claims: FULL_CLAIMS, + batches: FULL_BATCHES, + drivers: FULL_DRIVERS, + instance_results: &[], + evals: REAL_EVALS, + opening_claims: &[], + opening_batches: &[], + }; + + struct SumZeroRemainingEvaluator; + + impl Stage1OuterRemainingEvaluator for SumZeroRemainingEvaluator { + fn evaluate(&self, _context: Stage1OuterRemainingContext<'_, Fr>, point: &[Fr]) -> Fr { + point[0] + point[0] - Fr::from_u64(1) + } + + fn evaluate_virtual_oracle( + &self, + _context: Stage1OuterRemainingContext<'_, Fr>, + oracle: &str, + point: &[Fr], + ) -> Option { + (oracle == "Synthetic").then(|| point.iter().copied().sum()) + } + } + + #[derive(Default)] + struct RecordingExecutor { + modes: Vec, + drivers: Vec<&'static str>, + } + + impl RecordingExecutor { + fn output( + &mut self, + context: Stage1KernelContext<'_>, + point: F, + ) -> Stage1SumcheckOutput { + self.modes.push(context.mode); + self.drivers.push(context.driver.symbol); + Stage1SumcheckOutput { + driver: context.driver.symbol, + point: vec![point], + evals: Vec::new(), + proof: SumcheckProof::default(), + } + } + } + + impl Stage1KernelExecutor for RecordingExecutor { + fn prove_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + Ok(self.output(context, transcript.challenge())) + } + + fn verify_sumcheck( + &mut self, + context: Stage1KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage1KernelError> + where + T: Transcript, + { + Ok(self.output(context, transcript.challenge())) + } + } + + fn noop_r1cs_key_and_witness(num_cycles: usize) -> (R1csKey, Vec) { + let matrices = rv64::rv64_constraints::(); + let key = R1csKey::new(matrices, num_cycles); + let mut witness = vec![Fr::from_u64(0); key.num_cycles * key.num_vars_padded]; + for cycle in 0..key.num_cycles { + let base = cycle * key.num_vars_padded; + witness[base + rv64::V_CONST] = Fr::from_u64(1); + witness[base + rv64::V_FLAG_DO_NOT_UPDATE_UNEXPANDED_PC] = Fr::from_u64(1); + key.matrices + .check_witness(&witness[base..base + rv64::NUM_VARS_PER_CYCLE]) + .expect("noop cycle satisfies RV64 constraints"); + } + (key, witness) + } + + #[test] + fn execute_stage1_program_dispatches_driver_to_executor() { + let mut executor = RecordingExecutor::default(); + let mut transcript = MockTranscript::::new(b"stage1"); + let artifacts = execute_stage1_program( + &PROGRAM, + Stage1ExecutionMode::Prover, + &mut executor, + &mut transcript, + ) + .expect("dispatch succeeds"); + + assert_eq!(executor.modes, vec![Stage1ExecutionMode::Prover]); + assert_eq!(executor.drivers, vec!["driver"]); + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.challenge_vectors.len(), 1); + assert_eq!(artifacts.sumchecks[0].driver, "driver"); + assert_eq!(artifacts.sumchecks[0].point.len(), 1); + } + + #[test] + fn execute_stage1_program_rejects_static_count_mismatch() { + let mut executor = RecordingExecutor::default(); + let mut transcript = MockTranscript::::new(b"stage1"); + let error = execute_stage1_program( + &BAD_PROGRAM, + Stage1ExecutionMode::Verifier, + &mut executor, + &mut transcript, + ) + .expect_err("bad static plan rejected"); + + assert_eq!( + error, + Stage1KernelError::PlanCountMismatch { + artifact: "batch", + expected: 2, + actual: 1, + } + ); + assert!(executor.drivers.is_empty()); + } + + #[test] + fn shape_kernel_executor_runs_known_stage1_kernel_abis() { + let mut executor = Stage1ShapeKernelExecutor; + let mut transcript = MockTranscript::::new(b"stage1"); + let artifacts = execute_stage1_program( + &PROGRAM, + Stage1ExecutionMode::Verifier, + &mut executor, + &mut transcript, + ) + .expect("shape kernel dispatch succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].driver, "driver"); + assert_eq!(artifacts.sumchecks[0].point.len(), 1); + assert_eq!(artifacts.sumchecks[0].proof.round_polynomials.len(), 1); + assert_eq!( + artifacts.sumchecks[0].proof.round_polynomials[0] + .coefficients() + .len(), + OUTER_UNISKIP_DEGREE_BOUND + 1 + ); + } + + #[test] + fn prover_kernel_executor_requires_uniskip_extended_evals() { + let inputs = Stage1ProverInputs::::empty(1); + let mut executor = Stage1ProverKernelExecutor::new(inputs); + let mut transcript = MockTranscript::::new(b"stage1"); + let error = execute_stage1_program( + &PROGRAM, + Stage1ExecutionMode::Prover, + &mut executor, + &mut transcript, + ) + .expect_err("real prover requires extended evaluations"); + + assert_eq!( + error, + Stage1KernelError::MissingKernelInput { + kernel: "jolt_stage1_outer_uniskip", + input: "uniskip_extended_evals", + } + ); + } + + #[test] + fn uniskip_kernel_prover_verifier_self_parity() { + let extended_evals = (1..=OUTER_UNISKIP_DEGREE) + .map(|index| Fr::from_u64(index as u64)) + .collect::>(); + let inputs = Stage1ProverInputs::empty(1).with_uniskip_extended_evals(&extended_evals); + let mut prover_executor = Stage1ProverKernelExecutor::new(inputs); + let mut prover_transcript = MockTranscript::::new(b"stage1"); + let prover_artifacts = execute_stage1_program( + &PROGRAM, + Stage1ExecutionMode::Prover, + &mut prover_executor, + &mut prover_transcript, + ) + .expect("uniskip prover succeeds"); + + let proof = Stage1Proof::from(prover_artifacts.clone()); + let mut verifier_executor = Stage1VerifierKernelExecutor::new(&proof); + let mut verifier_transcript = MockTranscript::::new(b"stage1"); + let verifier_artifacts = execute_stage1_program( + &PROGRAM, + Stage1ExecutionMode::Verifier, + &mut verifier_executor, + &mut verifier_transcript, + ) + .expect("uniskip verifier accepts prover proof"); + + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!(prover_artifacts.sumchecks.len(), 1); + assert_eq!(verifier_artifacts.sumchecks.len(), 1); + assert_eq!( + prover_artifacts.sumchecks[0].point, + verifier_artifacts.sumchecks[0].point + ); + assert_eq!( + prover_artifacts.sumchecks[0].proof.round_polynomials[0].coefficients(), + verifier_artifacts.sumchecks[0].proof.round_polynomials[0].coefficients() + ); + } + + #[test] + fn full_stage1_uniskip_and_remaining_self_parity() { + let extended_evals = vec![Fr::from_u64(0); OUTER_UNISKIP_DEGREE]; + let evaluator = SumZeroRemainingEvaluator; + let inputs = Stage1ProverInputs::empty(1) + .with_uniskip_extended_evals(&extended_evals) + .with_outer_remaining_evaluator(&evaluator); + let mut prover_executor = Stage1ProverKernelExecutor::new(inputs); + let mut prover_transcript = MockTranscript::::new(b"stage1"); + let prover_artifacts = execute_stage1_program( + &FULL_PROGRAM, + Stage1ExecutionMode::Prover, + &mut prover_executor, + &mut prover_transcript, + ) + .expect("full stage1 prover succeeds"); + + let proof = Stage1Proof::from(prover_artifacts.clone()); + let mut verifier_executor = Stage1VerifierKernelExecutor::new(&proof); + let mut verifier_transcript = MockTranscript::::new(b"stage1"); + let verifier_artifacts = execute_stage1_program( + &FULL_PROGRAM, + Stage1ExecutionMode::Verifier, + &mut verifier_executor, + &mut verifier_transcript, + ) + .expect("full stage1 verifier accepts prover proof"); + + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!(prover_artifacts.sumchecks.len(), 2); + assert_eq!(verifier_artifacts.sumchecks.len(), 2); + assert_eq!( + prover_artifacts.sumchecks[1].point, + verifier_artifacts.sumchecks[1].point + ); + assert_eq!(prover_artifacts.sumchecks[1].evals.len(), 1); + assert_eq!( + prover_artifacts.sumchecks[1].evals[0].value, + verifier_artifacts.sumchecks[1].evals[0].value + ); + } + + #[test] + fn full_stage1_r1cs_data_self_parity() { + let (key, witness) = noop_r1cs_key_and_witness(2); + let data = Stage1OuterR1csData::new(&key, &witness).expect("valid R1CS witness shape"); + let inputs = + Stage1ProverInputs::empty(key.num_cycle_vars()).with_outer_remaining_evaluator(&data); + let mut prover_executor = Stage1ProverKernelExecutor::new(inputs); + let mut prover_transcript = MockTranscript::::new(b"stage1"); + let prover_artifacts = execute_stage1_program( + &REAL_PROGRAM, + Stage1ExecutionMode::Prover, + &mut prover_executor, + &mut prover_transcript, + ) + .expect("real R1CS-backed stage1 prover succeeds"); + + let proof = Stage1Proof::from(prover_artifacts.clone()); + let mut verifier_executor = Stage1VerifierKernelExecutor::new(&proof); + let mut verifier_transcript = MockTranscript::::new(b"stage1"); + let verifier_artifacts = execute_stage1_program( + &REAL_PROGRAM, + Stage1ExecutionMode::Verifier, + &mut verifier_executor, + &mut verifier_transcript, + ) + .expect("real R1CS-backed stage1 verifier accepts prover proof"); + + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!(prover_artifacts.sumchecks.len(), 2); + assert_eq!(verifier_artifacts.sumchecks.len(), 2); + assert_eq!( + prover_artifacts.sumchecks[1].evals[0].value, + verifier_artifacts.sumchecks[1].evals[0].value + ); + } + + #[test] + fn full_stage1_r1cs_data_verifier_rejects_tampered_remaining_round() { + let (key, witness) = noop_r1cs_key_and_witness(2); + let data = Stage1OuterR1csData::new(&key, &witness).expect("valid R1CS witness shape"); + let inputs = + Stage1ProverInputs::empty(key.num_cycle_vars()).with_outer_remaining_evaluator(&data); + let mut prover_executor = Stage1ProverKernelExecutor::new(inputs); + let mut prover_transcript = MockTranscript::::new(b"stage1"); + let prover_artifacts = execute_stage1_program( + &REAL_PROGRAM, + Stage1ExecutionMode::Prover, + &mut prover_executor, + &mut prover_transcript, + ) + .expect("real R1CS-backed stage1 prover succeeds"); + + let mut proof = Stage1Proof::from(prover_artifacts); + let remaining = &mut proof.sumchecks[1].proof.round_polynomials[0]; + let mut coefficients = remaining.clone().into_coefficients(); + coefficients[0] += Fr::from_u64(1); + *remaining = UnivariatePoly::new(coefficients); + + let mut verifier_executor = Stage1VerifierKernelExecutor::new(&proof); + let mut verifier_transcript = MockTranscript::::new(b"stage1"); + let error = execute_stage1_program( + &REAL_PROGRAM, + Stage1ExecutionMode::Verifier, + &mut verifier_executor, + &mut verifier_transcript, + ) + .expect_err("tampered remaining round is rejected"); + + assert_eq!( + error, + Stage1KernelError::InvalidProof { + driver: "stage1.outer_remaining.sumcheck", + reason: "outer remaining round check failed", + } + ); + } + + #[test] + fn verifier_kernel_executor_rejects_invalid_uniskip_proof() { + let proof = Stage1Proof { + sumchecks: vec![Stage1SumcheckOutput { + driver: "driver", + point: Vec::new(), + evals: Vec::new(), + proof: SumcheckProof::default(), + }], + }; + let mut executor = Stage1VerifierKernelExecutor::new(&proof); + let mut transcript = MockTranscript::::new(b"stage1"); + let error = execute_stage1_program( + &PROGRAM, + Stage1ExecutionMode::Verifier, + &mut executor, + &mut transcript, + ) + .expect_err("empty verifier proof is invalid"); + + assert_eq!( + error, + Stage1KernelError::InvalidProof { + driver: "driver", + reason: "missing uniskip round polynomial", + } + ); + } +} diff --git a/crates/jolt-kernels/src/stage1/README.md b/crates/jolt-kernels/src/stage1/README.md new file mode 100644 index 0000000000..1e9fa3a517 --- /dev/null +++ b/crates/jolt-kernels/src/stage1/README.md @@ -0,0 +1,17 @@ +Stage 1 kernel modules +====================== + +`stage1.rs` owns the generated-code ABI: static plans, sumcheck execution, +proof verification, and the generic R1CS-backed fallback evaluator. + +`rv64_typed.rs` owns the RV64 coarse CPU specialization. It must remain a +semantic refinement of the generic R1CS evaluator: typed oracle evaluations and +sumcheck products are tested against the R1CS column path before equivalence +tests compare Bolt against jolt-core. + +Future Stage 1 kernels should follow the same shape: + +- Keep protocol scheduling in `stage1.rs`. +- Put workload-specific arithmetic in a focused typed module. +- Preserve a generic fallback or parity oracle for new specializations. +- Add a local typed-vs-generic test before wiring core equivalence. diff --git a/crates/jolt-kernels/src/stage1/rv64_typed.rs b/crates/jolt-kernels/src/stage1/rv64_typed.rs new file mode 100644 index 0000000000..1d52ebe8c2 --- /dev/null +++ b/crates/jolt-kernels/src/stage1/rv64_typed.rs @@ -0,0 +1,1207 @@ +use std::cmp::Ordering; + +use jolt_field::signed::{S128, S160, S192, S64}; +use jolt_field::{Field, Fr, Limbs}; +use jolt_poly::lagrange::{lagrange_evals, lagrange_kernel_eval}; +use jolt_poly::{EqPolynomial, UnivariatePoly}; +use jolt_r1cs::R1csKey; +use rayon::prelude::*; + +use super::{ + boolean_index, DenseOuterState, Stage1KernelError, Stage1OuterR1csData, + Stage1OuterRemainingContext, Stage1OuterRemainingEvaluator, Stage1RemainingRoundProof, + OUTER_SECOND_GROUP_ROWS, OUTER_UNISKIP_BASE_START, OUTER_UNISKIP_DEGREE, + OUTER_UNISKIP_DOMAIN_SIZE, OUTER_UNISKIP_TARGET_COEFFS, +}; + +const RV64_NUM_CIRCUIT_FLAGS: usize = 14; +const FLAG_ADD_OPERANDS: usize = 0; +const FLAG_SUBTRACT_OPERANDS: usize = 1; +const FLAG_MULTIPLY_OPERANDS: usize = 2; +const FLAG_LOAD: usize = 3; +const FLAG_STORE: usize = 4; +const FLAG_JUMP: usize = 5; +const FLAG_WRITE_LOOKUP_OUTPUT_TO_RD: usize = 6; +const FLAG_VIRTUAL_INSTRUCTION: usize = 7; +const FLAG_ASSERT: usize = 8; +const FLAG_DO_NOT_UPDATE_UNEXPANDED_PC: usize = 9; +const FLAG_ADVICE: usize = 10; +const FLAG_IS_COMPRESSED: usize = 11; +const FLAG_IS_FIRST_IN_SEQUENCE: usize = 12; +const FLAG_IS_LAST_IN_SEQUENCE: usize = 13; + +#[derive(Clone, Copy, Debug)] +pub struct Stage1Rv64Cycle { + pub left_input: u64, + pub right_input: S64, + pub product: S128, + pub left_lookup: u64, + pub right_lookup: u128, + pub lookup_output: u64, + pub rs1_read_value: u64, + pub rs2_read_value: u64, + pub rd_write_value: u64, + pub ram_addr: u64, + pub ram_read_value: u64, + pub ram_write_value: u64, + pub pc: u64, + pub next_pc: u64, + pub unexpanded_pc: u64, + pub next_unexpanded_pc: u64, + pub imm: S64, + pub flags: [bool; RV64_NUM_CIRCUIT_FLAGS], + pub should_jump: bool, + pub should_branch: bool, + pub next_is_virtual: bool, + pub next_is_first_in_sequence: bool, +} + +impl Stage1Rv64Cycle { + pub fn padding() -> Self { + let mut flags = [false; RV64_NUM_CIRCUIT_FLAGS]; + flags[FLAG_DO_NOT_UPDATE_UNEXPANDED_PC] = true; + Self { + left_input: 0, + right_input: S64::from_u64(0), + product: S128::from_u64(0), + left_lookup: 0, + right_lookup: 0, + lookup_output: 0, + rs1_read_value: 0, + rs2_read_value: 0, + rd_write_value: 0, + ram_addr: 0, + ram_read_value: 0, + ram_write_value: 0, + pc: 0, + next_pc: 0, + unexpanded_pc: 0, + next_unexpanded_pc: 0, + imm: S64::from_u64(0), + flags, + should_jump: false, + should_branch: false, + next_is_virtual: false, + next_is_first_in_sequence: false, + } + } +} + +#[derive(Debug)] +pub struct Stage1OuterRv64Data<'a> { + field_data: Stage1OuterR1csData<'a, Fr>, + cycles: &'a [Stage1Rv64Cycle], +} + +impl<'a> Stage1OuterRv64Data<'a> { + pub fn new( + key: &'a R1csKey, + witness: &'a [Fr], + cycles: &'a [Stage1Rv64Cycle], + ) -> Result { + if cycles.len() != key.num_cycles { + return Err(Stage1KernelError::InvalidInputLength { + input: "rv64_cycles", + expected: key.num_cycles, + actual: cycles.len(), + }); + } + Ok(Self { + field_data: Stage1OuterR1csData::new(key, witness)?, + cycles, + }) + } + + #[tracing::instrument(skip_all, name = "Stage1OuterRv64Data::dense_outer_state")] + fn dense_outer_state( + &self, + context: Stage1OuterRemainingContext<'_, Fr>, + num_rounds: usize, + batching_coeff: Fr, + ) -> DenseOuterState { + let tau_high = context.tau[context.tau.len() - 1]; + let tau_low = &context.tau[..context.tau.len() - 1]; + let lagrange_tau_r0 = lagrange_kernel_eval( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + tau_high, + context.r0, + ); + let weights = lagrange_evals( + OUTER_UNISKIP_BASE_START, + OUTER_UNISKIP_DOMAIN_SIZE, + context.r0, + ); + let len = 1usize << num_rounds; + let scale = lagrange_tau_r0 * batching_coeff; + let eq_evals = EqPolynomial::new(tau_low.to_vec()).evaluations(); + let mut eq = vec![Fr::from_u64(0); len]; + let mut az = vec![Fr::from_u64(0); len]; + let mut bz = vec![Fr::from_u64(0); len]; + eq.par_chunks_mut(2) + .zip(az.par_chunks_mut(2)) + .zip(bz.par_chunks_mut(2)) + .enumerate() + .for_each(|(cycle, ((eq_pair, az_pair), bz_pair))| { + let index = cycle << 1; + let eval = Stage1Rv64Eval::new(&self.cycles[cycle]); + let (az_g0, bz_g0) = eval.first_group_linear(&weights); + let (az_g1, bz_g1) = eval.second_group_linear(&weights); + eq_pair[0] = eq_evals[index] * scale; + az_pair[0] = az_g0; + bz_pair[0] = bz_g0; + eq_pair[1] = eq_evals[index + 1] * scale; + az_pair[1] = az_g1; + bz_pair[1] = bz_g1; + }); + DenseOuterState { + eq, + az, + bz, + eq_scratch: Vec::with_capacity(len / 2), + az_scratch: Vec::with_capacity(len / 2), + bz_scratch: Vec::with_capacity(len / 2), + } + } +} + +impl Stage1OuterRemainingEvaluator for Stage1OuterRv64Data<'_> { + fn evaluate(&self, context: Stage1OuterRemainingContext<'_, Fr>, point: &[Fr]) -> Fr { + self.field_data.evaluate(context, point) + } + + #[tracing::instrument(skip_all, name = "Stage1OuterRv64Data::uniskip_extended_evals")] + fn uniskip_extended_evals(&self, tau: &[Fr]) -> Option> { + if tau.len() != self.field_data.key.num_cycle_vars() + 2 { + return None; + } + let tau_low = &tau[..tau.len() - 1]; + let num_rounds = self.field_data.key.num_cycle_vars() + 1; + let eq_evals = EqPolynomial::new(tau_low.to_vec()).evaluations(); + let num_cycles = 1usize << (num_rounds - 1); + if self.cycles.len() != num_cycles { + return None; + } + let accumulators = (0..num_cycles) + .into_par_iter() + .fold( + || [FrSignedProductAccumulator::zero(); OUTER_UNISKIP_DEGREE], + |mut local, cycle| { + let eval = Stage1Rv64Eval::new(&self.cycles[cycle]); + let (first_products, second_products) = eval.uniskip_products(); + let index = cycle << 1; + for (target, accumulator) in local.iter_mut().enumerate() { + accumulator.fmadd_s192(eq_evals[index], first_products[target]); + accumulator.fmadd_s192(eq_evals[index + 1], second_products[target]); + } + local + }, + ) + .reduce( + || [FrSignedProductAccumulator::zero(); OUTER_UNISKIP_DEGREE], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + Some( + accumulators + .into_iter() + .map(FrSignedProductAccumulator::reduce) + .collect(), + ) + } + + fn evaluate_virtual_oracle( + &self, + context: Stage1OuterRemainingContext<'_, Fr>, + oracle: &str, + point: &[Fr], + ) -> Option { + self.evaluate_virtual_oracles(context, &[oracle], point) + .and_then(|values| values.into_iter().next()) + } + + #[tracing::instrument(skip_all, name = "Stage1OuterRv64Data::evaluate_virtual_oracles")] + fn evaluate_virtual_oracles( + &self, + _context: Stage1OuterRemainingContext<'_, Fr>, + oracles: &[&str], + point: &[Fr], + ) -> Option> { + if point.len() != self.field_data.key.num_cycle_vars() + 1 { + return None; + } + let rv64_oracles = oracles + .iter() + .map(|oracle| Stage1Rv64Oracle::from_name(oracle)) + .collect::>>()?; + let cycle_point = Stage1OuterR1csData::::remaining_cycle_point(point); + if let Some(cycle) = boolean_index(&cycle_point) { + let row = self.cycles.get(cycle)?; + return Some( + rv64_oracles + .iter() + .map(|oracle| oracle.field_value(row)) + .collect(), + ); + } + + let eq = EqPolynomial::new(cycle_point).evaluations(); + let accumulators = eq + .par_iter() + .take(self.cycles.len()) + .enumerate() + .fold( + || vec![FrSignedProductAccumulator::zero(); rv64_oracles.len()], + |mut local, (cycle, &weight)| { + let row = &self.cycles[cycle]; + for (accumulator, oracle) in local.iter_mut().zip(&rv64_oracles) { + accumulator.fmadd_rv64_scalar(weight, oracle.scalar(row)); + } + local + }, + ) + .reduce( + || vec![FrSignedProductAccumulator::zero(); rv64_oracles.len()], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + Some( + accumulators + .into_iter() + .map(FrSignedProductAccumulator::reduce) + .collect(), + ) + } + + fn prove_remaining_rounds( + &self, + context: Stage1OuterRemainingContext<'_, Fr>, + num_rounds: usize, + batching_coeff: Fr, + initial_claim: Fr, + observe_round: &mut dyn FnMut(&UnivariatePoly) -> Fr, + ) -> Option> { + let mut state = self.dense_outer_state(context, num_rounds, batching_coeff); + let mut running_sum = initial_claim * batching_coeff; + let mut point = Vec::with_capacity(num_rounds); + let mut round_polynomials = Vec::with_capacity(num_rounds); + + for _round in 0..num_rounds { + let poly = state.round_poly(); + if poly.evaluate(Fr::from_u64(0)) + poly.evaluate(Fr::from_u64(1)) != running_sum { + return Some(Err(Stage1KernelError::InvalidProof { + driver: "stage1.outer.remaining", + reason: "dense outer remaining claim mismatch", + })); + } + let challenge = observe_round(&poly); + running_sum = poly.evaluate(challenge); + state.bind(challenge); + point.push(challenge); + round_polynomials.push(poly); + } + Some(Ok((point, round_polynomials))) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Stage1Rv64Oracle { + LeftInstructionInput, + RightInstructionInput, + Product, + ShouldBranch, + Pc, + UnexpandedPc, + Imm, + RamAddress, + Rs1Value, + Rs2Value, + RdWriteValue, + RamReadValue, + RamWriteValue, + LeftLookupOperand, + RightLookupOperand, + NextUnexpandedPc, + NextPc, + NextIsVirtual, + NextIsFirstInSequence, + LookupOutput, + ShouldJump, + OpFlagAddOperands, + OpFlagSubtractOperands, + OpFlagMultiplyOperands, + OpFlagLoad, + OpFlagStore, + OpFlagJump, + OpFlagWriteLookupOutputToRd, + OpFlagVirtualInstruction, + OpFlagAssert, + OpFlagDoNotUpdateUnexpandedPc, + OpFlagAdvice, + OpFlagIsCompressed, + OpFlagIsFirstInSequence, + OpFlagIsLastInSequence, +} + +impl Stage1Rv64Oracle { + fn from_name(name: &str) -> Option { + match name { + "LeftInstructionInput" => Some(Self::LeftInstructionInput), + "RightInstructionInput" => Some(Self::RightInstructionInput), + "Product" => Some(Self::Product), + "ShouldBranch" => Some(Self::ShouldBranch), + "PC" => Some(Self::Pc), + "UnexpandedPC" => Some(Self::UnexpandedPc), + "Imm" => Some(Self::Imm), + "RamAddress" => Some(Self::RamAddress), + "Rs1Value" => Some(Self::Rs1Value), + "Rs2Value" => Some(Self::Rs2Value), + "RdWriteValue" => Some(Self::RdWriteValue), + "RamReadValue" => Some(Self::RamReadValue), + "RamWriteValue" => Some(Self::RamWriteValue), + "LeftLookupOperand" => Some(Self::LeftLookupOperand), + "RightLookupOperand" => Some(Self::RightLookupOperand), + "NextUnexpandedPC" => Some(Self::NextUnexpandedPc), + "NextPC" => Some(Self::NextPc), + "NextIsVirtual" => Some(Self::NextIsVirtual), + "NextIsFirstInSequence" => Some(Self::NextIsFirstInSequence), + "LookupOutput" => Some(Self::LookupOutput), + "ShouldJump" => Some(Self::ShouldJump), + "OpFlagAddOperands" => Some(Self::OpFlagAddOperands), + "OpFlagSubtractOperands" => Some(Self::OpFlagSubtractOperands), + "OpFlagMultiplyOperands" => Some(Self::OpFlagMultiplyOperands), + "OpFlagLoad" => Some(Self::OpFlagLoad), + "OpFlagStore" => Some(Self::OpFlagStore), + "OpFlagJump" => Some(Self::OpFlagJump), + "OpFlagWriteLookupOutputToRD" => Some(Self::OpFlagWriteLookupOutputToRd), + "OpFlagVirtualInstruction" => Some(Self::OpFlagVirtualInstruction), + "OpFlagAssert" => Some(Self::OpFlagAssert), + "OpFlagDoNotUpdateUnexpandedPC" => Some(Self::OpFlagDoNotUpdateUnexpandedPc), + "OpFlagAdvice" => Some(Self::OpFlagAdvice), + "OpFlagIsCompressed" => Some(Self::OpFlagIsCompressed), + "OpFlagIsFirstInSequence" => Some(Self::OpFlagIsFirstInSequence), + "OpFlagIsLastInSequence" => Some(Self::OpFlagIsLastInSequence), + _ => None, + } + } + + #[inline] + fn scalar(self, row: &Stage1Rv64Cycle) -> Stage1Rv64Scalar { + match self { + Self::LeftInstructionInput => Stage1Rv64Scalar::U64(row.left_input), + Self::RightInstructionInput => Stage1Rv64Scalar::S64(row.right_input), + Self::Product => Stage1Rv64Scalar::S128(row.product), + Self::ShouldBranch => Stage1Rv64Scalar::Bool(row.should_branch), + Self::Pc => Stage1Rv64Scalar::U64(row.pc), + Self::UnexpandedPc => Stage1Rv64Scalar::U64(row.unexpanded_pc), + Self::Imm => Stage1Rv64Scalar::S64(row.imm), + Self::RamAddress => Stage1Rv64Scalar::U64(row.ram_addr), + Self::Rs1Value => Stage1Rv64Scalar::U64(row.rs1_read_value), + Self::Rs2Value => Stage1Rv64Scalar::U64(row.rs2_read_value), + Self::RdWriteValue => Stage1Rv64Scalar::U64(row.rd_write_value), + Self::RamReadValue => Stage1Rv64Scalar::U64(row.ram_read_value), + Self::RamWriteValue => Stage1Rv64Scalar::U64(row.ram_write_value), + Self::LeftLookupOperand => Stage1Rv64Scalar::U64(row.left_lookup), + Self::RightLookupOperand => Stage1Rv64Scalar::U128(row.right_lookup), + Self::NextUnexpandedPc => Stage1Rv64Scalar::U64(row.next_unexpanded_pc), + Self::NextPc => Stage1Rv64Scalar::U64(row.next_pc), + Self::NextIsVirtual => Stage1Rv64Scalar::Bool(row.next_is_virtual), + Self::NextIsFirstInSequence => Stage1Rv64Scalar::Bool(row.next_is_first_in_sequence), + Self::LookupOutput => Stage1Rv64Scalar::U64(row.lookup_output), + Self::ShouldJump => Stage1Rv64Scalar::Bool(row.should_jump), + Self::OpFlagAddOperands => Stage1Rv64Scalar::Bool(row.flags[FLAG_ADD_OPERANDS]), + Self::OpFlagSubtractOperands => { + Stage1Rv64Scalar::Bool(row.flags[FLAG_SUBTRACT_OPERANDS]) + } + Self::OpFlagMultiplyOperands => { + Stage1Rv64Scalar::Bool(row.flags[FLAG_MULTIPLY_OPERANDS]) + } + Self::OpFlagLoad => Stage1Rv64Scalar::Bool(row.flags[FLAG_LOAD]), + Self::OpFlagStore => Stage1Rv64Scalar::Bool(row.flags[FLAG_STORE]), + Self::OpFlagJump => Stage1Rv64Scalar::Bool(row.flags[FLAG_JUMP]), + Self::OpFlagWriteLookupOutputToRd => { + Stage1Rv64Scalar::Bool(row.flags[FLAG_WRITE_LOOKUP_OUTPUT_TO_RD]) + } + Self::OpFlagVirtualInstruction => { + Stage1Rv64Scalar::Bool(row.flags[FLAG_VIRTUAL_INSTRUCTION]) + } + Self::OpFlagAssert => Stage1Rv64Scalar::Bool(row.flags[FLAG_ASSERT]), + Self::OpFlagDoNotUpdateUnexpandedPc => { + Stage1Rv64Scalar::Bool(row.flags[FLAG_DO_NOT_UPDATE_UNEXPANDED_PC]) + } + Self::OpFlagAdvice => Stage1Rv64Scalar::Bool(row.flags[FLAG_ADVICE]), + Self::OpFlagIsCompressed => Stage1Rv64Scalar::Bool(row.flags[FLAG_IS_COMPRESSED]), + Self::OpFlagIsFirstInSequence => { + Stage1Rv64Scalar::Bool(row.flags[FLAG_IS_FIRST_IN_SEQUENCE]) + } + Self::OpFlagIsLastInSequence => { + Stage1Rv64Scalar::Bool(row.flags[FLAG_IS_LAST_IN_SEQUENCE]) + } + } + } + + #[inline] + fn field_value(self, row: &Stage1Rv64Cycle) -> Fr { + let mut accumulator = FrSignedProductAccumulator::zero(); + accumulator.fmadd_rv64_scalar(Fr::from_u64(1), self.scalar(row)); + accumulator.reduce() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Stage1Rv64Scalar { + Bool(bool), + U64(u64), + U128(u128), + S64(S64), + S128(S128), +} + +struct Stage1Rv64Eval<'a> { + row: &'a Stage1Rv64Cycle, +} + +impl<'a> Stage1Rv64Eval<'a> { + fn new(row: &'a Stage1Rv64Cycle) -> Self { + Self { row } + } + + #[inline] + fn first_group_linear(&self, weights: &[Fr]) -> (Fr, Fr) { + let mut az = Fr::from_u64(0); + let mut bz = FrSignedProductAccumulator::zero(); + + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[0], + self.not_load_store(), + S128::from_u64(self.row.ram_addr), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[1], + self.row.flags[FLAG_LOAD], + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.ram_read_value, + self.row.ram_write_value, + )), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[2], + self.row.flags[FLAG_LOAD], + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.ram_read_value, + self.row.rd_write_value, + )), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[3], + self.row.flags[FLAG_STORE], + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.rs2_read_value, + self.row.ram_write_value, + )), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[4], + self.add_sub_mul(), + S128::from_u64(self.row.left_lookup), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[5], + !self.add_sub_mul(), + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.left_lookup, + self.row.left_input, + )), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[6], + self.row.flags[FLAG_ASSERT], + S128::zero_extend_from(&S64::from_diff_u64s(self.row.lookup_output, 1)), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[7], + self.row.should_jump, + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.next_unexpanded_pc, + self.row.lookup_output, + )), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[8], + self.row.flags[FLAG_VIRTUAL_INSTRUCTION] && !self.row.flags[FLAG_IS_LAST_IN_SEQUENCE], + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.next_pc, + self.row.pc.wrapping_add(1), + )), + ); + Self::accumulate_first_linear( + &mut az, + &mut bz, + weights[9], + self.row.next_is_virtual && !self.row.next_is_first_in_sequence, + S128::from_u64(u64::from(!self.row.flags[FLAG_DO_NOT_UPDATE_UNEXPANDED_PC])), + ); + + (az, bz.reduce()) + } + + #[inline] + fn second_group_linear(&self, weights: &[Fr]) -> (Fr, Fr) { + let mut az = Fr::from_u64(0); + let mut bz = FrSignedProductAccumulator::zero(); + + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[0], + self.load_or_store(), + S192::from_i128(self.ram_addr_minus_rs1_plus_imm()), + ); + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[1], + self.row.flags[FLAG_ADD_OPERANDS], + (S160::from(self.row.right_lookup) - S160::from(self.right_add_expected())) + .to_signed_bigint_nplus1::<3>(), + ); + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[2], + self.row.flags[FLAG_SUBTRACT_OPERANDS], + (S160::from(self.row.right_lookup) - S160::from(self.right_sub_expected())) + .to_signed_bigint_nplus1::<3>(), + ); + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[3], + self.row.flags[FLAG_MULTIPLY_OPERANDS], + (S160::from(self.row.right_lookup) - S160::from(self.row.product)) + .to_signed_bigint_nplus1::<3>(), + ); + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[4], + !self.add_sub_mul_advice(), + (S160::from(self.row.right_lookup) + - S160::from(S128::zero_extend_from(&self.row.right_input))) + .to_signed_bigint_nplus1::<3>(), + ); + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[5], + self.row.flags[FLAG_WRITE_LOOKUP_OUTPUT_TO_RD], + S192::zero_extend_from(&S64::from_diff_u64s( + self.row.rd_write_value, + self.row.lookup_output, + )), + ); + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[6], + self.row.flags[FLAG_JUMP], + S192::zero_extend_from(&S64::from_diff_u64s( + self.row.rd_write_value, + self.expected_pc_plus_const(), + )), + ); + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[7], + self.row.should_branch, + S192::from_i128(self.next_unexpanded_pc_minus_pc_plus_imm()), + ); + Self::accumulate_second_linear( + &mut az, + &mut bz, + weights[8], + !self.row.flags[FLAG_JUMP] && !self.row.should_branch, + S192::zero_extend_from(&S64::from_diff_u64s( + self.row.next_unexpanded_pc, + self.expected_next_unexpanded_pc(), + )), + ); + + (az, bz.reduce()) + } + + #[inline] + fn uniskip_products(&self) -> ([S192; OUTER_UNISKIP_DEGREE], [S192; OUTER_UNISKIP_DEGREE]) { + let (first_guards, first_terms) = self.first_group_terms(); + let (second_guards, second_terms) = self.second_group_terms(); + ( + core::array::from_fn(|target| { + Self::first_group_product_from_terms(target, &first_guards, &first_terms) + }), + core::array::from_fn(|target| { + Self::second_group_product_from_terms(target, &second_guards, &second_terms) + }), + ) + } + + #[inline] + fn first_group_terms( + &self, + ) -> ( + [bool; OUTER_UNISKIP_DOMAIN_SIZE], + [S128; OUTER_UNISKIP_DOMAIN_SIZE], + ) { + ( + [ + self.not_load_store(), + self.row.flags[FLAG_LOAD], + self.row.flags[FLAG_LOAD], + self.row.flags[FLAG_STORE], + self.add_sub_mul(), + !self.add_sub_mul(), + self.row.flags[FLAG_ASSERT], + self.row.should_jump, + self.row.flags[FLAG_VIRTUAL_INSTRUCTION] + && !self.row.flags[FLAG_IS_LAST_IN_SEQUENCE], + self.row.next_is_virtual && !self.row.next_is_first_in_sequence, + ], + [ + S128::from_u64(self.row.ram_addr), + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.ram_read_value, + self.row.ram_write_value, + )), + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.ram_read_value, + self.row.rd_write_value, + )), + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.rs2_read_value, + self.row.ram_write_value, + )), + S128::from_u64(self.row.left_lookup), + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.left_lookup, + self.row.left_input, + )), + S128::zero_extend_from(&S64::from_diff_u64s(self.row.lookup_output, 1)), + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.next_unexpanded_pc, + self.row.lookup_output, + )), + S128::zero_extend_from(&S64::from_diff_u64s( + self.row.next_pc, + self.row.pc.wrapping_add(1), + )), + S128::from_u64(u64::from(!self.row.flags[FLAG_DO_NOT_UPDATE_UNEXPANDED_PC])), + ], + ) + } + + #[inline] + fn second_group_terms( + &self, + ) -> ( + [bool; OUTER_SECOND_GROUP_ROWS.len()], + [S192; OUTER_SECOND_GROUP_ROWS.len()], + ) { + ( + [ + self.load_or_store(), + self.row.flags[FLAG_ADD_OPERANDS], + self.row.flags[FLAG_SUBTRACT_OPERANDS], + self.row.flags[FLAG_MULTIPLY_OPERANDS], + !self.add_sub_mul_advice(), + self.row.flags[FLAG_WRITE_LOOKUP_OUTPUT_TO_RD], + self.row.flags[FLAG_JUMP], + self.row.should_branch, + !self.row.flags[FLAG_JUMP] && !self.row.should_branch, + ], + [ + S192::from_i128(self.ram_addr_minus_rs1_plus_imm()), + (S160::from(self.row.right_lookup) - S160::from(self.right_add_expected())) + .to_signed_bigint_nplus1::<3>(), + (S160::from(self.row.right_lookup) - S160::from(self.right_sub_expected())) + .to_signed_bigint_nplus1::<3>(), + (S160::from(self.row.right_lookup) - S160::from(self.row.product)) + .to_signed_bigint_nplus1::<3>(), + (S160::from(self.row.right_lookup) + - S160::from(S128::zero_extend_from(&self.row.right_input))) + .to_signed_bigint_nplus1::<3>(), + S192::zero_extend_from(&S64::from_diff_u64s( + self.row.rd_write_value, + self.row.lookup_output, + )), + S192::zero_extend_from(&S64::from_diff_u64s( + self.row.rd_write_value, + self.expected_pc_plus_const(), + )), + S192::from_i128(self.next_unexpanded_pc_minus_pc_plus_imm()), + S192::zero_extend_from(&S64::from_diff_u64s( + self.row.next_unexpanded_pc, + self.expected_next_unexpanded_pc(), + )), + ], + ) + } + + #[inline] + fn first_group_product_from_terms( + target: usize, + guards: &[bool; OUTER_UNISKIP_DOMAIN_SIZE], + terms: &[S128; OUTER_UNISKIP_DOMAIN_SIZE], + ) -> S192 { + let coefficients = OUTER_UNISKIP_TARGET_COEFFS[target]; + let mut az = 0i32; + let mut bz = S128::zero(); + for ((&guard, &term), &coefficient) in guards.iter().zip(terms).zip(&coefficients) { + Self::accumulate_first(&mut az, &mut bz, coefficient as i32, guard, term); + } + S64::from_i64(az as i64).mul_trunc::<2, 3>(&bz) + } + + #[inline] + fn second_group_product_from_terms( + target: usize, + guards: &[bool; OUTER_SECOND_GROUP_ROWS.len()], + terms: &[S192; OUTER_SECOND_GROUP_ROWS.len()], + ) -> S192 { + let coefficients = OUTER_UNISKIP_TARGET_COEFFS[target]; + let mut az = 0i32; + let mut bz = S192::zero(); + for ((&guard, &term), &coefficient) in guards.iter().zip(terms).zip(&coefficients) { + Self::accumulate_second(&mut az, &mut bz, coefficient as i32, guard, term); + } + S64::from_i64(az as i64).mul_trunc::<3, 3>(&bz) + } + + #[inline] + fn accumulate_first_linear( + az: &mut Fr, + bz: &mut FrSignedProductAccumulator, + weight: Fr, + guard: bool, + term: S128, + ) { + if guard { + *az += weight; + } else { + bz.fmadd_s128(weight, term); + } + } + + #[inline] + fn accumulate_second_linear( + az: &mut Fr, + bz: &mut FrSignedProductAccumulator, + weight: Fr, + guard: bool, + term: S192, + ) { + if guard { + *az += weight; + } else { + bz.fmadd_s192(weight, term); + } + } + + #[inline] + fn accumulate_first(az: &mut i32, bz: &mut S128, coefficient: i32, guard: bool, term: S128) { + if guard { + *az += coefficient; + } else { + fmadd_i32_s128(bz, coefficient, term); + } + } + + #[inline] + fn accumulate_second(az: &mut i32, bz: &mut S192, coefficient: i32, guard: bool, term: S192) { + if guard { + *az += coefficient; + } else { + fmadd_i32_s192(bz, coefficient, term); + } + } + + #[inline] + fn not_load_store(&self) -> bool { + !self.load_or_store() + } + + #[inline] + fn load_or_store(&self) -> bool { + self.row.flags[FLAG_LOAD] || self.row.flags[FLAG_STORE] + } + + #[inline] + fn add_sub_mul(&self) -> bool { + self.row.flags[FLAG_ADD_OPERANDS] + || self.row.flags[FLAG_SUBTRACT_OPERANDS] + || self.row.flags[FLAG_MULTIPLY_OPERANDS] + } + + #[inline] + fn add_sub_mul_advice(&self) -> bool { + self.add_sub_mul() || self.row.flags[FLAG_ADVICE] + } + + #[inline] + fn ram_addr_minus_rs1_plus_imm(&self) -> i128 { + let expected = if self.row.imm.is_positive { + self.row.rs1_read_value as i128 + self.row.imm.magnitude_as_u64() as i128 + } else { + self.row.rs1_read_value as i128 - self.row.imm.magnitude_as_u64() as i128 + }; + self.row.ram_addr as i128 - expected + } + + #[inline] + fn right_add_expected(&self) -> i128 { + self.row.left_input as i128 + self.row.right_input.to_i128() + } + + #[inline] + fn right_sub_expected(&self) -> i128 { + self.row.left_input as i128 - self.row.right_input.to_i128() + (1i128 << 64) + } + + #[inline] + fn expected_pc_plus_const(&self) -> u64 { + let const_term = 4 - if self.row.flags[FLAG_IS_COMPRESSED] { + 2 + } else { + 0 + }; + self.row.unexpanded_pc.wrapping_add(const_term) + } + + #[inline] + fn next_unexpanded_pc_minus_pc_plus_imm(&self) -> i128 { + self.row.next_unexpanded_pc as i128 + - (self.row.unexpanded_pc as i128 + self.row.imm.to_i128()) + } + + #[inline] + fn expected_next_unexpanded_pc(&self) -> u64 { + let const_term = + 4 - if self.row.flags[FLAG_DO_NOT_UPDATE_UNEXPANDED_PC] { + 4 + } else { + 0 + } - if self.row.flags[FLAG_IS_COMPRESSED] { + 2 + } else { + 0 + }; + self.row.unexpanded_pc.wrapping_add(const_term) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct FrSignedProductAccumulator { + positive: Limbs<9>, + negative: Limbs<9>, +} + +impl FrSignedProductAccumulator { + #[inline] + fn zero() -> Self { + Self { + positive: Limbs::zero(), + negative: Limbs::zero(), + } + } + + #[inline] + fn fmadd_s192(&mut self, field: Fr, scalar: S192) { + if scalar.magnitude_limbs() == [0u64; 3] { + return; + } + self.fmadd_limbs(field, &scalar.magnitude, scalar.is_positive); + } + + #[inline] + fn fmadd_rv64_scalar(&mut self, field: Fr, scalar: Stage1Rv64Scalar) { + match scalar { + Stage1Rv64Scalar::Bool(value) => { + if value { + self.add_positive_field(field); + } + } + Stage1Rv64Scalar::U64(value) => self.fmadd_u64(field, value), + Stage1Rv64Scalar::U128(value) => self.fmadd_u128(field, value), + Stage1Rv64Scalar::S64(value) => self.fmadd_s64(field, value), + Stage1Rv64Scalar::S128(value) => self.fmadd_s128(field, value), + } + } + + #[inline] + fn fmadd_u64(&mut self, field: Fr, scalar: u64) { + if scalar == 0 { + return; + } + if scalar == 1 { + self.add_positive_field(field); + return; + } + self.fmadd_limbs(field, &Limbs::<1>::from_u64(scalar), true); + } + + #[inline] + fn fmadd_u128(&mut self, field: Fr, scalar: u128) { + if scalar == 0 { + return; + } + if scalar <= u64::MAX as u128 { + self.fmadd_u64(field, scalar as u64); + return; + } + self.fmadd_limbs( + field, + &Limbs::<2>::new([scalar as u64, (scalar >> 64) as u64]), + true, + ); + } + + #[inline] + fn fmadd_s64(&mut self, field: Fr, scalar: S64) { + if scalar.magnitude_limbs() == [0u64; 1] { + return; + } + if scalar.is_positive { + self.fmadd_u64(field, scalar.magnitude_as_u64()); + return; + } + self.fmadd_limbs(field, scalar.as_magnitude(), false); + } + + #[inline] + fn fmadd_s128(&mut self, field: Fr, scalar: S128) { + if scalar.magnitude_limbs() == [0u64; 2] { + return; + } + self.fmadd_limbs(field, scalar.as_magnitude(), scalar.is_positive); + } + + #[inline] + fn add_positive_field(&mut self, field: Fr) { + self.positive + .add_assign_trunc::<9>(&Limbs::<9>::zero_extend_from::<4>(&field.inner_limbs())); + } + + #[inline] + fn fmadd_limbs(&mut self, field: Fr, scalar: &Limbs, is_positive: bool) { + let mut product = Limbs::<9>::zero(); + product.fmadd::<4, L>(&field.inner_limbs(), scalar); + if is_positive { + self.positive.add_assign_trunc::<9>(&product); + } else { + self.negative.add_assign_trunc::<9>(&product); + } + } + + #[inline] + fn merge(&mut self, other: Self) { + self.positive.add_assign_trunc::<9>(&other.positive); + self.negative.add_assign_trunc::<9>(&other.negative); + } + + #[inline] + fn reduce(self) -> Fr { + match self.positive.cmp(&self.negative) { + Ordering::Greater | Ordering::Equal => { + let difference = self.positive.sub_trunc::<9, 9>(&self.negative); + Fr::from_barrett_reduced_limbs(difference) + } + Ordering::Less => { + let difference = self.negative.sub_trunc::<9, 9>(&self.positive); + -Fr::from_barrett_reduced_limbs(difference) + } + } + } +} + +#[inline] +fn fmadd_i32_s128(sum: &mut S128, coefficient: i32, term: S128) { + if coefficient == 0 || term.magnitude_as_u128() == 0 { + return; + } + let coefficient_s64 = S64::from_i64(coefficient as i64); + *sum += coefficient_s64.mul_trunc::<2, 2>(&term); +} + +#[inline] +fn fmadd_i32_s192(sum: &mut S192, coefficient: i32, term: S192) { + if coefficient == 0 || term.magnitude_limbs() == [0u64; 3] { + return; + } + let coefficient_s64 = S64::from_i64(coefficient as i64); + *sum += coefficient_s64.mul_trunc::<3, 3>(&term); +} + +#[cfg(test)] +#[expect(clippy::expect_used, reason = "tests use explicit panic messages")] +mod tests { + use jolt_field::{Field, Fr}; + use jolt_r1cs::{constraints::rv64, R1csKey}; + + use super::*; + + static RV64_ORACLE_NAMES: &[&str] = &[ + "LeftInstructionInput", + "RightInstructionInput", + "Product", + "ShouldBranch", + "PC", + "UnexpandedPC", + "Imm", + "RamAddress", + "Rs1Value", + "Rs2Value", + "RdWriteValue", + "RamReadValue", + "RamWriteValue", + "LeftLookupOperand", + "RightLookupOperand", + "NextUnexpandedPC", + "NextPC", + "NextIsVirtual", + "NextIsFirstInSequence", + "LookupOutput", + "ShouldJump", + "OpFlagAddOperands", + "OpFlagSubtractOperands", + "OpFlagMultiplyOperands", + "OpFlagLoad", + "OpFlagStore", + "OpFlagJump", + "OpFlagWriteLookupOutputToRD", + "OpFlagVirtualInstruction", + "OpFlagAssert", + "OpFlagDoNotUpdateUnexpandedPC", + "OpFlagAdvice", + "OpFlagIsCompressed", + "OpFlagIsFirstInSequence", + "OpFlagIsLastInSequence", + ]; + + fn rv64_eval_test_cycles() -> Vec { + let mut first = Stage1Rv64Cycle::padding(); + first.left_input = 7; + first.right_input = S64::from_i64(-5); + first.product = S128::from_i128(-35); + first.left_lookup = 11; + first.right_lookup = u128::MAX - 4; + first.lookup_output = 1; + first.rs1_read_value = 13; + first.rs2_read_value = 17; + first.rd_write_value = 19; + first.ram_addr = 23; + first.ram_read_value = 29; + first.ram_write_value = 31; + first.pc = 37; + first.next_pc = 41; + first.unexpanded_pc = 43; + first.next_unexpanded_pc = 47; + first.imm = S64::from_i64(-53); + first.flags[FLAG_ADD_OPERANDS] = true; + first.flags[FLAG_LOAD] = true; + first.flags[FLAG_IS_FIRST_IN_SEQUENCE] = true; + first.should_branch = true; + first.next_is_virtual = true; + + let mut second = Stage1Rv64Cycle::padding(); + second.left_input = 59; + second.right_input = S64::from_u64(61); + second.product = S128::from_u128(3599); + second.left_lookup = 67; + second.right_lookup = 71; + second.lookup_output = 73; + second.rs1_read_value = 79; + second.rs2_read_value = 83; + second.rd_write_value = 89; + second.ram_addr = 97; + second.ram_read_value = 101; + second.ram_write_value = 103; + second.pc = 107; + second.next_pc = 109; + second.unexpanded_pc = 113; + second.next_unexpanded_pc = 127; + second.imm = S64::from_u64(131); + second.flags[FLAG_MULTIPLY_OPERANDS] = true; + second.flags[FLAG_STORE] = true; + second.flags[FLAG_JUMP] = true; + second.flags[FLAG_IS_LAST_IN_SEQUENCE] = true; + second.should_jump = true; + second.next_is_first_in_sequence = true; + + vec![first, second] + } + + fn rv64_eval_test_witness(key: &R1csKey, cycles: &[Stage1Rv64Cycle]) -> Vec { + let mut witness = vec![Fr::from_u64(0); key.num_cycles * key.num_vars_padded]; + for (cycle, row) in cycles.iter().enumerate() { + let base = cycle * key.num_vars_padded; + witness[base + rv64::V_CONST] = Fr::from_u64(1); + for name in RV64_ORACLE_NAMES { + let oracle = Stage1Rv64Oracle::from_name(name).expect("known RV64 oracle"); + let variable = + super::super::r1cs_oracle_variable(name).expect("known R1CS variable"); + witness[base + variable] = oracle.field_value(row); + } + } + witness + } + + #[test] + fn typed_virtual_oracle_evals_match_r1cs_columns() { + let cycles = rv64_eval_test_cycles(); + let key = R1csKey::new(rv64::rv64_constraints::(), cycles.len()); + let witness = rv64_eval_test_witness(&key, &cycles); + let r1cs_data = Stage1OuterR1csData::new(&key, &witness).expect("valid witness shape"); + let rv64_data = + Stage1OuterRv64Data::new(&key, &witness, &cycles).expect("valid RV64 shape"); + let tau = [Fr::from_u64(0); 3]; + let context = Stage1OuterRemainingContext { + tau: &tau, + r0: Fr::from_u64(0), + }; + + for point in [ + vec![Fr::from_u64(3), Fr::from_u64(5)], + vec![Fr::from_u64(3), Fr::from_u64(1)], + ] { + assert_eq!( + rv64_data.evaluate_virtual_oracles(context, RV64_ORACLE_NAMES, &point), + r1cs_data.evaluate_virtual_oracles(context, RV64_ORACLE_NAMES, &point) + ); + } + } +} diff --git a/crates/jolt-kernels/src/stage2.rs b/crates/jolt-kernels/src/stage2.rs new file mode 100644 index 0000000000..d62df82e0f --- /dev/null +++ b/crates/jolt-kernels/src/stage2.rs @@ -0,0 +1,5201 @@ +use std::borrow::Cow; +use std::cmp::Ordering; +use std::error::Error; +use std::fmt::{self, Display, Formatter}; +use std::mem::MaybeUninit; + +use crate::dense::{bind_dense_evals_reuse, DENSE_BIND_PAR_THRESHOLD}; +use crate::split_eq::SplitEqState; +use jolt_field::signed::{S128, S256}; +use jolt_field::{Field, FieldAccumulator, Fr, Limbs}; +use jolt_poly::lagrange::{interpolate_to_coeffs, lagrange_evals, lagrange_kernel_eval}; +use jolt_poly::EqPolynomial; +use jolt_poly::UnivariatePoly; +use jolt_sumcheck::SumcheckProof; +use jolt_transcript::{Label, LabelWithCount, Transcript}; +use rayon::prelude::*; + +const PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START: i64 = -1; +const PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE: usize = 3; +const PRODUCT_VIRTUAL_UNISKIP_DEGREE: usize = 2; +const PRODUCT_VIRTUAL_UNISKIP_EXTENDED_START: i64 = -(PRODUCT_VIRTUAL_UNISKIP_DEGREE as i64); +const PRODUCT_VIRTUAL_UNISKIP_EXTENDED_SIZE: usize = 2 * PRODUCT_VIRTUAL_UNISKIP_DEGREE + 1; +const PRODUCT_VIRTUAL_UNISKIP_NUM_COEFFS: usize = 3 * PRODUCT_VIRTUAL_UNISKIP_DEGREE + 1; +const PRODUCT_VIRTUAL_UNISKIP_TARGET_COEFFS: [[i32; PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE]; + PRODUCT_VIRTUAL_UNISKIP_DEGREE] = [[3, -3, 1], [1, -3, 3]]; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage2ExecutionMode { + Prover, + Verifier, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage2Relation { + ProductVirtualUniskip, + RamReadWrite, + ProductVirtualRemainder, + InstructionLookupClaimReduction, + RamRafEvaluation, + RamOutputCheck, + Batched, +} + +impl Stage2Relation { + pub fn from_symbol(symbol: &str) -> Option { + match symbol { + "jolt.stage2.product_virtual.uniskip" => Some(Self::ProductVirtualUniskip), + "jolt.stage2.ram.read_write" => Some(Self::RamReadWrite), + "jolt.stage2.product_virtual.remainder" => Some(Self::ProductVirtualRemainder), + "jolt.stage2.instruction_lookup.claim_reduction" => { + Some(Self::InstructionLookupClaimReduction) + } + "jolt.stage2.ram.raf_evaluation" => Some(Self::RamRafEvaluation), + "jolt.stage2.ram.output_check" => Some(Self::RamOutputCheck), + "jolt.stage2.batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn symbol(self) -> &'static str { + match self { + Self::ProductVirtualUniskip => "jolt.stage2.product_virtual.uniskip", + Self::RamReadWrite => "jolt.stage2.ram.read_write", + Self::ProductVirtualRemainder => "jolt.stage2.product_virtual.remainder", + Self::InstructionLookupClaimReduction => { + "jolt.stage2.instruction_lookup.claim_reduction" + } + Self::RamRafEvaluation => "jolt.stage2.ram.raf_evaluation", + Self::RamOutputCheck => "jolt.stage2.ram.output_check", + Self::Batched => "jolt.stage2.batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage2KernelAbi { + ProductVirtualUniskip, + RamReadWrite, + ProductVirtualRemainder, + InstructionLookupClaimReduction, + RamRafEvaluation, + RamOutputCheck, + Batched, +} + +impl Stage2KernelAbi { + pub fn from_name(name: &str) -> Option { + match name { + "jolt_stage2_product_virtual_uniskip" => Some(Self::ProductVirtualUniskip), + "jolt_stage2_ram_read_write" => Some(Self::RamReadWrite), + "jolt_stage2_product_virtual_remainder" => Some(Self::ProductVirtualRemainder), + "jolt_stage2_instruction_lookup_claim_reduction" => { + Some(Self::InstructionLookupClaimReduction) + } + "jolt_stage2_ram_raf_evaluation" => Some(Self::RamRafEvaluation), + "jolt_stage2_ram_output_check" => Some(Self::RamOutputCheck), + "jolt_stage2_batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn name(self) -> &'static str { + match self { + Self::ProductVirtualUniskip => "jolt_stage2_product_virtual_uniskip", + Self::RamReadWrite => "jolt_stage2_ram_read_write", + Self::ProductVirtualRemainder => "jolt_stage2_product_virtual_remainder", + Self::InstructionLookupClaimReduction => { + "jolt_stage2_instruction_lookup_claim_reduction" + } + Self::RamRafEvaluation => "jolt_stage2_ram_raf_evaluation", + Self::RamOutputCheck => "jolt_stage2_ram_output_check", + Self::Batched => "jolt_stage2_batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +impl Stage2KernelPlan { + pub fn relation_kind(&self) -> Result { + Stage2Relation::from_symbol(self.relation).ok_or(Stage2KernelError::UnknownRelation { + relation: self.relation, + }) + } + + pub fn abi_kind(&self) -> Result { + Stage2KernelAbi::from_name(self.abi) + .ok_or(Stage2KernelError::UnknownKernelAbi { abi: self.abi }) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operand_names: &'static [&'static str], + pub operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2CpuProgramPlan { + pub params: Stage2Params, + pub steps: &'static [Stage2ProgramStepPlan], + pub transcript_squeezes: &'static [Stage2TranscriptSqueezePlan], + pub opening_inputs: &'static [Stage2OpeningInputPlan], + pub field_constants: &'static [Stage2FieldConstantPlan], + pub field_exprs: &'static [Stage2FieldExprPlan], + pub kernels: &'static [Stage2KernelPlan], + pub claims: &'static [Stage2SumcheckClaimPlan], + pub batches: &'static [Stage2SumcheckBatchPlan], + pub drivers: &'static [Stage2SumcheckDriverPlan], + pub instance_results: &'static [Stage2SumcheckInstanceResultPlan], + pub evals: &'static [Stage2SumcheckEvalPlan], + pub point_slices: &'static [Stage2PointSlicePlan], + pub point_concats: &'static [Stage2PointConcatPlan], + pub opening_claims: &'static [Stage2OpeningClaimPlan], + pub opening_batches: &'static [Stage2OpeningBatchPlan], +} + +impl Stage2CpuProgramPlan { + pub fn kernel(&self, symbol: &str) -> Option<&Stage2KernelPlan> { + find_kernel(self, symbol) + } + + pub fn batch(&self, symbol: &str) -> Option<&Stage2SumcheckBatchPlan> { + find_batch(self, symbol) + } + + pub fn claim(&self, symbol: &str) -> Option<&Stage2SumcheckClaimPlan> { + self.claims.iter().find(|claim| claim.symbol == symbol) + } + + pub fn evals_for_driver<'a>( + &'a self, + driver: &'a str, + ) -> impl Iterator + 'a { + self.evals.iter().filter(move |eval| eval.source == driver) + } + + pub fn instance_results_for_driver<'a>( + &'a self, + driver: &'a str, + ) -> impl Iterator + 'a { + self.instance_results + .iter() + .filter(move |instance| instance.source == driver) + } +} + +#[derive(Clone, Debug)] +pub struct Stage2NamedEval { + pub name: &'static str, + pub oracle: &'static str, + pub value: F, +} + +#[derive(Clone, Debug)] +pub struct Stage2SumcheckOutput { + pub driver: &'static str, + pub point: Vec, + pub evals: Vec>, + pub opening_claims: Vec>, + pub proof: SumcheckProof, +} + +#[derive(Clone, Debug)] +pub struct Stage2ChallengeVector { + pub symbol: &'static str, + pub values: Vec, +} + +#[derive(Clone, Debug)] +pub struct Stage2OpeningClaimValue { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub claim_kind: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Debug)] +pub struct Stage2ExecutionArtifacts { + pub challenge_vectors: Vec>, + pub sumchecks: Vec>, + pub opening_claims: Vec>, + pub opening_batches: Vec<&'static Stage2OpeningBatchPlan>, +} + +impl Default for Stage2ExecutionArtifacts { + fn default() -> Self { + Self { + challenge_vectors: Vec::new(), + sumchecks: Vec::new(), + opening_claims: Vec::new(), + opening_batches: Vec::new(), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stage2Proof { + pub sumchecks: Vec>, +} + +impl From> for Stage2Proof { + fn from(artifacts: Stage2ExecutionArtifacts) -> Self { + Self { + sumchecks: artifacts.sumchecks, + } + } +} + +#[derive(Clone, Debug)] +pub struct Stage2ScalarValue { + pub symbol: &'static str, + pub value: F, +} + +#[derive(Clone, Debug)] +pub struct Stage2PointValue { + pub symbol: &'static str, + pub point: Vec, +} + +#[derive(Clone, Debug)] +pub struct Stage2OpeningInputValue { + pub symbol: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2ProductVirtualCycle { + pub instruction_left_input: u64, + pub instruction_right_input: i128, + pub should_branch_lookup_output: u64, + pub write_lookup_output_to_rd_flag: bool, + pub jump_flag: bool, + pub should_branch_flag: bool, + pub not_next_noop: bool, + pub virtual_instruction_flag: bool, +} + +impl Stage2ProductVirtualCycle { + pub fn padding() -> Self { + Self { + instruction_left_input: 0, + instruction_right_input: 0, + should_branch_lookup_output: 0, + write_lookup_output_to_rd_flag: false, + jump_flag: false, + should_branch_flag: false, + not_next_noop: false, + virtual_instruction_flag: false, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2InstructionLookupCycle { + pub lookup_output: u64, + pub left_lookup_operand: u64, + pub right_lookup_operand: u128, + pub left_instruction_input: u64, + pub right_instruction_input: i128, +} + +impl Stage2InstructionLookupCycle { + pub fn padding() -> Self { + Self { + lookup_output: 0, + left_lookup_operand: 0, + right_lookup_operand: 0, + left_instruction_input: 0, + right_instruction_input: 0, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage2RamAccess { + pub remapped_address: Option, + pub read_value: u64, + pub write_value: u64, +} + +impl Stage2RamAccess { + pub fn noop() -> Self { + Self { + remapped_address: None, + read_value: 0, + write_value: 0, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage2RamOutputLayout { + pub io_start: usize, + pub io_end: usize, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage2RamData<'a> { + pub log_k: usize, + pub start_address: u64, + pub initial_ram: &'a [u64], + pub final_ram: &'a [u64], + pub accesses: &'a [Stage2RamAccess], + pub output_layout: Option, +} + +#[derive(Clone, Debug, Default)] +pub struct Stage2ValueStore { + scalars: Vec>, + points: Vec>, +} + +impl Stage2ValueStore { + pub fn new() -> Self { + Self::default() + } + + pub fn with_opening_inputs(inputs: &[Stage2OpeningInputValue]) -> Self { + let mut store = Self::new(); + store.insert_opening_inputs(inputs); + store + } + + pub fn insert_opening_inputs(&mut self, inputs: &[Stage2OpeningInputValue]) { + for input in inputs { + self.insert_scalar(input.symbol, input.eval); + self.insert_point(input.symbol, input.point.clone()); + } + } + + pub fn insert_scalar(&mut self, symbol: &'static str, value: F) { + if let Some(existing) = self + .scalars + .iter_mut() + .find(|existing| existing.symbol == symbol) + { + existing.value = value; + } else { + self.scalars.push(Stage2ScalarValue { symbol, value }); + } + } + + pub fn insert_point(&mut self, symbol: &'static str, point: Vec) { + if let Some(existing) = self + .points + .iter_mut() + .find(|existing| existing.symbol == symbol) + { + existing.point = point; + } else { + self.points.push(Stage2PointValue { symbol, point }); + } + } + + pub fn try_scalar(&self, symbol: &str) -> Option { + self.scalars + .iter() + .find(|value| value.symbol == symbol) + .map(|value| value.value) + } + + pub fn scalar(&self, symbol: &'static str) -> Result { + self.try_scalar(symbol) + .ok_or(Stage2KernelError::MissingValue { symbol }) + } + + pub fn point(&self, symbol: &'static str) -> Result<&[F], Stage2KernelError> { + self.try_point(symbol) + .ok_or(Stage2KernelError::MissingValue { symbol }) + } + + pub fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.points + .iter() + .find(|value| value.symbol == symbol) + .map(|value| value.point.as_slice()) + } + + pub fn seed_constants( + &mut self, + program: &'static Stage2CpuProgramPlan, + ) -> Result<(), Stage2KernelError> { + for constant in program.field_constants { + self.insert_scalar(constant.symbol, F::from_u64(constant.value as u64)); + } + Ok(()) + } + + pub fn observe_challenge_vector( + &mut self, + _program: &'static Stage2CpuProgramPlan, + plan: &'static Stage2TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage2KernelError> { + if matches!(plan.kind, "challenge_scalar" | "scalar") { + if values.len() != 1 { + return Err(Stage2KernelError::InvalidInputLength { + input: plan.symbol, + expected: 1, + actual: values.len(), + }); + } + self.insert_scalar(plan.symbol, values[0]); + } + Ok(()) + } + + pub fn observe_sumcheck_output( + &mut self, + program: &'static Stage2CpuProgramPlan, + output: &Stage2SumcheckOutput, + ) -> Result<(), Stage2KernelError> { + self.observe_sumcheck_values(program, output.driver, &output.point, &output.evals) + } + + pub fn observe_sumcheck_values( + &mut self, + program: &'static Stage2CpuProgramPlan, + driver: &'static str, + point: &[F], + evals: &[Stage2NamedEval], + ) -> Result<(), Stage2KernelError> { + self.insert_point(driver, point.to_vec()); + for instance in program.instance_results_for_driver(driver) { + let end = instance.round_offset + instance.point_arity; + let mut point = point + .get(instance.round_offset..end) + .ok_or(Stage2KernelError::InvalidInputLength { + input: instance.symbol, + expected: end, + actual: point.len(), + })? + .to_vec(); + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + _ => { + return Err(Stage2KernelError::InvalidProof { + driver, + reason: "unsupported point order", + }); + } + } + self.insert_point(instance.symbol, point); + } + for eval in program.evals_for_driver(driver) { + let value = evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| evals.get(eval.index)) + .ok_or(Stage2KernelError::MissingValue { + symbol: eval.symbol, + })? + .value; + self.insert_scalar(eval.symbol, value); + self.insert_scalar(eval.name, value); + } + Ok(()) + } + + pub fn evaluate_available_points( + &mut self, + program: &'static Stage2CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for slice in program.point_slices { + if self.try_point(slice.symbol).is_some() { + continue; + } + let Some(input) = self.try_point(slice.input) else { + continue; + }; + let end = slice.offset + slice.length; + let point = input + .get(slice.offset..end) + .ok_or(Stage2KernelError::InvalidInputLength { + input: slice.symbol, + expected: end, + actual: input.len(), + })? + .to_vec(); + self.insert_point(slice.symbol, point); + progress += 1; + } + for concat in program.point_concats { + if self.try_point(concat.symbol).is_some() { + continue; + } + let Some(point) = self.try_concat_point(concat) else { + continue; + }; + verify_count(concat.symbol, concat.arity, point.len())?; + self.insert_point(concat.symbol, point); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + pub fn evaluate_available_field_exprs( + &mut self, + program: &'static Stage2CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for expr in program.field_exprs { + if self.try_scalar(expr.symbol).is_some() { + continue; + } + let Some(operands) = self.try_expr_operands(expr) else { + continue; + }; + let value = evaluate_stage2_field_expr(expr, &operands)?; + self.insert_scalar(expr.symbol, value); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + pub fn claim_value( + &mut self, + program: &'static Stage2CpuProgramPlan, + claim: &Stage2SumcheckClaimPlan, + ) -> Result { + let _ = self.evaluate_available_field_exprs(program)?; + self.scalar(claim.claim_value) + } + + pub fn batch_claim_values( + &mut self, + program: &'static Stage2CpuProgramPlan, + batch: &Stage2SumcheckBatchPlan, + ) -> Result, Stage2KernelError> { + batch + .claim_operands + .iter() + .map(|symbol| { + let claim = program + .claim(symbol) + .ok_or(Stage2KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + self.claim_value(program, claim) + }) + .collect() + } + + fn try_expr_operands(&self, expr: &Stage2FieldExprPlan) -> Option> { + expr.operands + .iter() + .map(|operand| self.try_scalar(operand)) + .collect() + } + + fn try_concat_point(&self, concat: &Stage2PointConcatPlan) -> Option> { + let mut point = Vec::with_capacity(concat.arity); + for input in concat.inputs { + point.extend_from_slice(self.try_point(input)?); + } + Some(point) + } +} + +pub fn evaluate_stage2_field_expr( + expr: &Stage2FieldExprPlan, + operands: &[F], +) -> Result { + if let Some(value) = evaluate_stage2_field_op(expr, operands)? { + return Ok(value); + } + match expr.formula { + "opening_eval" => single_operand(expr.symbol, operands), + "jolt_stage2_product_virtual_uniskip_input" => { + require_operand_count(expr.symbol, 4, operands.len())?; + let weights = lagrange_evals( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + operands[0], + ); + Ok(weights[0] * operands[1] + weights[1] * operands[2] + weights[2] * operands[3]) + } + "jolt_stage2_ram_read_write_input" => { + require_operand_count(expr.symbol, 3, operands.len())?; + Ok(operands[1] + operands[0] * operands[2]) + } + "jolt_stage2_instruction_lookup_input" => { + require_operand_count(expr.symbol, 6, operands.len())?; + let gamma = operands[0]; + let gamma_sqr = gamma.square(); + let gamma_cub = gamma_sqr * gamma; + let gamma_quart = gamma_sqr.square(); + Ok(operands[1] + + gamma * operands[2] + + gamma_sqr * operands[3] + + gamma_cub * operands[4] + + gamma_quart * operands[5]) + } + formula => Err(Stage2KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }), + } +} + +fn evaluate_stage2_field_op( + expr: &Stage2FieldExprPlan, + operands: &[F], +) -> Result, Stage2KernelError> { + match expr.formula { + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(Some(operands[0] + operands[1])) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(Some(operands[0] - operands[1])) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(Some(operands[0] * operands[1])) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(Some(-operands[0])) + } + _ => { + if let Some(exponent) = expr.formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + Stage2KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + } + })?; + return Ok(Some(pow_field(operands[0], exponent))); + } + if let Some(spec) = expr.formula.strip_prefix("poly.lagrange_basis_eval:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let (domain_start, domain_size, index) = parse_lagrange_basis_spec(expr, spec)?; + let weights = lagrange_evals(domain_start, domain_size, operands[0]); + let value = + weights + .get(index) + .copied() + .ok_or(Stage2KernelError::InvalidInputLength { + input: expr.symbol, + expected: index + 1, + actual: weights.len(), + })?; + return Ok(Some(value)); + } + Ok(None) + } + } +} + +fn parse_lagrange_basis_spec( + expr: &Stage2FieldExprPlan, + spec: &str, +) -> Result<(i64, usize, usize), Stage2KernelError> { + let parts = spec.split(':').collect::>(); + if parts.len() != 3 { + return Err(Stage2KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + }); + } + let domain_start = + parts[0] + .parse::() + .map_err(|_| Stage2KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + })?; + let domain_size = + parts[1] + .parse::() + .map_err(|_| Stage2KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + })?; + let index = parts[2] + .parse::() + .map_err(|_| Stage2KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + })?; + Ok((domain_start, domain_size, index)) +} + +fn pow_field(base: F, mut exponent: usize) -> F { + let mut result = F::one(); + let mut power = base; + while exponent != 0 { + if exponent & 1 == 1 { + result *= power; + } + power = power.square(); + exponent >>= 1; + } + result +} + +fn single_operand(symbol: &'static str, operands: &[F]) -> Result { + require_operand_count(symbol, 1, operands.len())?; + Ok(operands[0]) +} + +fn require_operand_count( + input: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage2KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage2KernelError::InvalidInputLength { + input, + expected, + actual, + }) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage2KernelContext<'a> { + pub mode: Stage2ExecutionMode, + pub program: &'static Stage2CpuProgramPlan, + pub kernel: &'a Stage2KernelPlan, + pub batch: &'a Stage2SumcheckBatchPlan, + pub driver: &'a Stage2SumcheckDriverPlan, +} + +impl Stage2KernelContext<'_> { + pub fn relation_kind(&self) -> Result { + self.kernel.relation_kind() + } + + pub fn abi_kind(&self) -> Result { + self.kernel.abi_kind() + } + + pub fn batch_claims(&self) -> Result, Stage2KernelError> { + self.batch + .claim_operands + .iter() + .map(|symbol| { + self.program + .claim(symbol) + .ok_or(Stage2KernelError::MissingClaim { + batch: self.batch.symbol, + claim: symbol, + }) + }) + .collect() + } +} + +pub trait Stage2KernelExecutor { + fn observe_challenge_vector( + &mut self, + _plan: &'static Stage2TranscriptSqueezePlan, + _values: &[F], + ) -> Result<(), Stage2KernelError> { + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + _output: &Stage2SumcheckOutput, + ) -> Result<(), Stage2KernelError> { + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage2KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage2KernelError> + where + T: Transcript; + + fn verify_sumcheck( + &mut self, + context: Stage2KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage2KernelError> + where + T: Transcript; +} + +#[derive(Clone, Debug, Default)] +pub struct UnsupportedStage2KernelExecutor; + +impl Stage2KernelExecutor for UnsupportedStage2KernelExecutor { + fn prove_sumcheck( + &mut self, + context: Stage2KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage2KernelError> + where + T: Transcript, + { + Err(Stage2KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage2KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage2KernelError> + where + T: Transcript, + { + Err(Stage2KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } +} + +#[derive(Clone)] +pub struct Stage2ProverInputs<'a, F: Field> { + pub opening_inputs: &'a [Stage2OpeningInputValue], + pub product_uniskip_extended_evals: Option>, + pub product_virtual_cycles: Option<&'a [Stage2ProductVirtualCycle]>, + pub instruction_lookup_cycles: Option<&'a [Stage2InstructionLookupCycle]>, + pub ram: Option<&'a Stage2RamData<'a>>, +} + +impl<'a, F: Field> Stage2ProverInputs<'a, F> { + pub fn new(opening_inputs: &'a [Stage2OpeningInputValue]) -> Self { + Self { + opening_inputs, + product_uniskip_extended_evals: None, + product_virtual_cycles: None, + instruction_lookup_cycles: None, + ram: None, + } + } + + pub fn empty() -> Self { + Self { + opening_inputs: &[], + product_uniskip_extended_evals: None, + product_virtual_cycles: None, + instruction_lookup_cycles: None, + ram: None, + } + } + + pub fn with_product_uniskip_extended_evals(mut self, evaluations: &'a [F]) -> Self { + self.product_uniskip_extended_evals = Some(Cow::Borrowed(evaluations)); + self + } + + pub fn with_product_virtual_cycles(mut self, cycles: &'a [Stage2ProductVirtualCycle]) -> Self { + self.product_virtual_cycles = Some(cycles); + self + } + + pub fn with_instruction_lookup_cycles( + mut self, + cycles: &'a [Stage2InstructionLookupCycle], + ) -> Self { + self.instruction_lookup_cycles = Some(cycles); + self + } + + pub fn with_ram_data(mut self, ram: &'a Stage2RamData<'a>) -> Self { + self.ram = Some(ram); + self + } +} + +impl<'a> Stage2ProverInputs<'a, Fr> { + pub fn with_product_virtual_witness( + mut self, + cycles: &'a [Stage2ProductVirtualCycle], + ) -> Result { + let tau_low = self + .opening_inputs + .iter() + .find(|input| input.symbol == "stage2.input.stage1.Product") + .map(|input| input.point.as_slice()) + .ok_or(Stage2KernelError::MissingValue { + symbol: "stage2.input.stage1.Product", + })?; + let extended_evals = product_virtual_uniskip_extended_evals(cycles, tau_low)?; + self.product_uniskip_extended_evals = Some(Cow::Owned(extended_evals.to_vec())); + self.product_virtual_cycles = Some(cycles); + Ok(self) + } +} + +#[derive(Clone)] +pub struct Stage2ProverKernelExecutor<'a, F: Field> { + pub inputs: Stage2ProverInputs<'a, F>, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage2ProverKernelExecutor<'a, F> { + pub fn new(inputs: Stage2ProverInputs<'a, F>) -> Self { + Self { + inputs, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage2CpuProgramPlan, + ) -> Result, Stage2KernelError> { + let mut store = Stage2ValueStore::with_opening_inputs(self.inputs.opening_inputs); + store.seed_constants(program)?; + for challenge in &self.challenge_vectors { + store.insert_point(challenge.symbol, challenge.values.clone()); + if let Some(plan) = program + .transcript_squeezes + .iter() + .find(|plan| plan.symbol == challenge.symbol) + .filter(|plan| matches!(plan.kind, "challenge_scalar" | "scalar")) + { + if challenge.values.len() != 1 { + return Err(Stage2KernelError::InvalidInputLength { + input: plan.symbol, + expected: 1, + actual: challenge.values.len(), + }); + } + store.insert_scalar(plan.symbol, challenge.values[0]); + } + } + for output in &self.completed_sumchecks { + store.observe_sumcheck_output(program, output)?; + } + let _ = store.evaluate_available_points(program)?; + let _ = store.evaluate_available_field_exprs(program)?; + Ok(store) + } +} + +impl Stage2KernelExecutor for Stage2ProverKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage2TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage2KernelError> { + self.challenge_vectors.push(Stage2ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage2SumcheckOutput, + ) -> Result<(), Stage2KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage2KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage2KernelError> + where + T: Transcript, + { + prove_stage2_kernel( + context, + &self.inputs, + self.value_store(context.program)?, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage2KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage2KernelError> + where + T: Transcript, + { + Err(Stage2KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage2ExecutionMode::Prover, + actual: Stage2ExecutionMode::Verifier, + }) + } +} + +#[derive(Clone)] +pub struct Stage2VerifierKernelExecutor<'a, F: Field> { + pub proof: &'a Stage2Proof, + pub opening_inputs: &'a [Stage2OpeningInputValue], + pub ram: Option<&'a Stage2RamData<'a>>, + pub cursor: usize, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage2VerifierKernelExecutor<'a, F> { + pub fn new( + proof: &'a Stage2Proof, + opening_inputs: &'a [Stage2OpeningInputValue], + ) -> Self { + Self { + proof, + opening_inputs, + ram: None, + cursor: 0, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + pub fn with_ram_data(mut self, ram: &'a Stage2RamData<'a>) -> Self { + self.ram = Some(ram); + self + } + + fn value_store( + &self, + program: &'static Stage2CpuProgramPlan, + ) -> Result, Stage2KernelError> { + let mut store = Stage2ValueStore::with_opening_inputs(self.opening_inputs); + store.seed_constants(program)?; + for challenge in &self.challenge_vectors { + store.insert_point(challenge.symbol, challenge.values.clone()); + if let Some(plan) = program + .transcript_squeezes + .iter() + .find(|plan| plan.symbol == challenge.symbol) + .filter(|plan| matches!(plan.kind, "challenge_scalar" | "scalar")) + { + if challenge.values.len() != 1 { + return Err(Stage2KernelError::InvalidInputLength { + input: plan.symbol, + expected: 1, + actual: challenge.values.len(), + }); + } + store.insert_scalar(plan.symbol, challenge.values[0]); + } + } + for output in &self.completed_sumchecks { + store.observe_sumcheck_output(program, output)?; + } + let _ = store.evaluate_available_points(program)?; + let _ = store.evaluate_available_field_exprs(program)?; + Ok(store) + } +} + +impl Stage2KernelExecutor for Stage2VerifierKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage2TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage2KernelError> { + self.challenge_vectors.push(Stage2ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage2SumcheckOutput, + ) -> Result<(), Stage2KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage2KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage2KernelError> + where + T: Transcript, + { + Err(Stage2KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage2ExecutionMode::Verifier, + actual: Stage2ExecutionMode::Prover, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage2KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage2KernelError> + where + T: Transcript, + { + let proof = + self.proof + .sumchecks + .get(self.cursor) + .ok_or(Stage2KernelError::MissingProof { + driver: context.driver.symbol, + })?; + self.cursor += 1; + verify_stage2_kernel( + context, + self.value_store(context.program)?, + proof, + self.ram, + transcript, + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Stage2KernelError { + MissingKernel { + driver: &'static str, + kernel: &'static str, + }, + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + MissingClaim { + batch: &'static str, + claim: &'static str, + }, + MissingValue { + symbol: &'static str, + }, + PlanCountMismatch { + artifact: &'static str, + expected: usize, + actual: usize, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + UnsupportedFieldExpr { + symbol: &'static str, + formula: &'static str, + }, + UnknownRelation { + relation: &'static str, + }, + UnknownKernelAbi { + abi: &'static str, + }, + KernelNotImplemented { + abi: &'static str, + }, + WrongExecutorMode { + driver: &'static str, + expected: Stage2ExecutionMode, + actual: Stage2ExecutionMode, + }, + MissingProof { + driver: &'static str, + }, + MissingKernelInput { + kernel: &'static str, + input: &'static str, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, +} + +impl Display for Stage2KernelError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingKernel { driver, kernel } => { + write!( + formatter, + "stage2 driver @{driver} references missing kernel @{kernel}" + ) + } + Self::MissingBatch { driver, batch } => { + write!( + formatter, + "stage2 driver @{driver} references missing batch @{batch}" + ) + } + Self::MissingClaim { batch, claim } => { + write!( + formatter, + "stage2 batch @{batch} references missing claim @{claim}" + ) + } + Self::MissingValue { symbol } => { + write!(formatter, "stage2 value @{symbol} is not available") + } + Self::PlanCountMismatch { + artifact, + expected, + actual, + } => write!( + formatter, + "stage2 plan @{artifact} count mismatch: expected {expected}, got {actual}" + ), + Self::InvalidInputLength { + input, + expected, + actual, + } => write!( + formatter, + "stage2 input `{input}` length mismatch: expected {expected}, got {actual}" + ), + Self::UnsupportedFieldExpr { symbol, formula } => write!( + formatter, + "stage2 field expr @{symbol} uses unsupported formula `{formula}`" + ), + Self::UnknownRelation { relation } => { + write!(formatter, "stage2 relation @{relation} is not registered") + } + Self::UnknownKernelAbi { abi } => { + write!(formatter, "stage2 kernel ABI `{abi}` is not registered") + } + Self::KernelNotImplemented { abi } => { + write!(formatter, "stage2 kernel ABI `{abi}` is not implemented") + } + Self::WrongExecutorMode { + driver, + expected, + actual, + } => write!( + formatter, + "stage2 driver @{driver} ran with {actual:?} executor path, expected {expected:?}" + ), + Self::MissingProof { driver } => { + write!( + formatter, + "stage2 verifier missing proof for driver @{driver}" + ) + } + Self::MissingKernelInput { kernel, input } => { + write!( + formatter, + "stage2 kernel `{kernel}` missing input `{input}`" + ) + } + Self::InvalidProof { driver, reason } => { + write!( + formatter, + "stage2 proof for driver @{driver} is invalid: {reason}" + ) + } + } + } +} + +impl Error for Stage2KernelError {} + +fn prove_stage2_kernel( + context: Stage2KernelContext<'_>, + inputs: &Stage2ProverInputs<'_, F>, + store: Stage2ValueStore, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage2KernelAbi::ProductVirtualUniskip => { + prove_product_virtual_uniskip(context, inputs, store, transcript) + } + Stage2KernelAbi::Batched => prove_batched_stage2(context, inputs, store, transcript), + abi => Err(Stage2KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +fn verify_stage2_kernel( + context: Stage2KernelContext<'_>, + store: Stage2ValueStore, + proof: &Stage2SumcheckOutput, + ram: Option<&Stage2RamData<'_>>, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage2KernelAbi::ProductVirtualUniskip => { + verify_product_virtual_uniskip(context, store, proof, transcript) + } + Stage2KernelAbi::Batched => verify_batched_stage2(context, store, proof, ram, transcript), + abi => Err(Stage2KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +#[tracing::instrument(skip_all, name = "Stage2::prove_product_virtual_uniskip")] +fn prove_product_virtual_uniskip( + context: Stage2KernelContext<'_>, + inputs: &Stage2ProverInputs<'_, F>, + mut store: Stage2ValueStore, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + F: Field, + T: Transcript, +{ + let claim = + context + .batch_claims()? + .into_iter() + .next() + .ok_or(Stage2KernelError::MissingClaim { + batch: context.batch.symbol, + claim: "stage2.product_virtual.uniskip.input", + })?; + let input_claim = store.claim_value(context.program, claim)?; + let base_evals = product_uniskip_base_evals(&store)?; + let extended_evals = inputs.product_uniskip_extended_evals.as_deref().ok_or( + Stage2KernelError::MissingKernelInput { + kernel: context.kernel.abi, + input: "product_uniskip_extended_evals", + }, + )?; + let poly = build_product_uniskip_poly( + &base_evals, + extended_evals, + store.scalar("stage2.product_virtual.tau_high")?, + )?; + if !product_uniskip_sum_matches(&poly, input_claim) { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "product uniskip input claim mismatch", + }); + } + append_univariate_poly(transcript, context.driver.round_label, &poly); + let r0 = transcript.challenge(); + let eval = poly.evaluate(r0); + append_labeled_scalar(transcript, "opening_claim", &eval); + Ok(Stage2SumcheckOutput { + driver: context.driver.symbol, + point: vec![r0], + evals: driver_evals(context, eval), + opening_claims: Vec::new(), + proof: SumcheckProof { + round_polynomials: vec![poly], + }, + }) +} + +fn verify_product_virtual_uniskip( + context: Stage2KernelContext<'_>, + mut store: Stage2ValueStore, + proof: &Stage2SumcheckOutput, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + F: Field, + T: Transcript, +{ + if proof.driver != context.driver.symbol { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + let [poly] = proof.proof.round_polynomials.as_slice() else { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected product uniskip round count", + }); + }; + if polynomial_degree(poly) > context.driver.degree { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "product uniskip polynomial exceeds degree bound", + }); + } + let claim = + context + .batch_claims()? + .into_iter() + .next() + .ok_or(Stage2KernelError::MissingClaim { + batch: context.batch.symbol, + claim: "stage2.product_virtual.uniskip.input", + })?; + let input_claim = store.claim_value(context.program, claim)?; + if !product_uniskip_sum_matches(poly, input_claim) { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "product uniskip input claim mismatch", + }); + } + append_univariate_poly(transcript, context.driver.round_label, poly); + let r0 = transcript.challenge(); + if !proof.point.is_empty() && proof.point != [r0] { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "product uniskip point mismatch", + }); + } + let eval = poly.evaluate(r0); + append_labeled_scalar(transcript, "opening_claim", &eval); + let evals = driver_evals(context, eval); + verify_driver_evals(context.driver.symbol, &evals, &proof.evals)?; + Ok(Stage2SumcheckOutput { + driver: context.driver.symbol, + point: vec![r0], + evals, + opening_claims: Vec::new(), + proof: proof.proof.clone(), + }) +} + +#[tracing::instrument(skip_all, name = "Stage2::prove_batched")] +fn prove_batched_stage2( + context: Stage2KernelContext<'_>, + inputs: &Stage2ProverInputs<'_, F>, + mut store: Stage2ValueStore, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + F: Field, + T: Transcript, +{ + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let mut instances = Vec::with_capacity(claims.len()); + for (index, claim) in claims.iter().enumerate() { + instances.push(Stage2BatchedInstance { + claim, + relation: claim_relation(context.program, claim)?, + offset: instance_round_offset(context.program, context.driver.symbol, claim.symbol)?, + previous_claim: input_claims[index].mul_pow_2(max_rounds - claim.num_rounds), + state: Stage2ProverInstanceState::new(context.program, claim, inputs, &store)?, + }); + } + + let mut point = Vec::with_capacity(max_rounds); + let mut round_polynomials = Vec::with_capacity(max_rounds); + let mut batched_claim = instances + .iter() + .zip(&batching_coeffs) + .map(|(instance, &coefficient)| instance.previous_claim * coefficient) + .sum::(); + let two_inv = F::from_u64(2) + .inverse() + .ok_or(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "field element 2 is not invertible", + })?; + + for round in 0..max_rounds { + let mut individual_polys = Vec::with_capacity(instances.len()); + for instance in &mut instances { + let poly = if instance.is_active(round) { + instance + .state + .round_poly(round - instance.offset, instance.previous_claim)? + } else { + UnivariatePoly::new(vec![instance.previous_claim * two_inv]) + }; + #[cfg(debug_assertions)] + { + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != instance.previous_claim { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched instance round claim mismatch", + }); + } + } + individual_polys.push(poly); + } + let batched_poly = combine_univariate_polys(&individual_polys, &batching_coeffs); + #[cfg(debug_assertions)] + { + if batched_poly.evaluate(F::zero()) + batched_poly.evaluate(F::one()) != batched_claim { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round claim mismatch", + }); + } + } + append_compressed_univariate_poly(transcript, context.driver.round_label, &batched_poly); + let challenge = transcript.challenge(); + point.push(challenge); + batched_claim = batched_poly.evaluate(challenge); + + for (instance, poly) in instances.iter_mut().zip(individual_polys) { + instance.previous_claim = poly.evaluate(challenge); + if instance.is_active(round) { + instance.state.ingest_challenge(challenge)?; + } + } + round_polynomials.push(batched_poly); + } + + let mut evals = Vec::new(); + for instance in &instances { + evals.extend(instance.state.final_evals(instance.relation)?); + } + let expected = expected_batched_output_claim( + context, + &store, + &evals, + &point, + &batching_coeffs, + inputs.ram, + )?; + if batched_claim != expected { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + store.observe_sumcheck_values(context.program, context.driver.symbol, &point, &evals)?; + let opening_claims = append_opening_claims(context.program, &mut store, transcript, &evals)?; + Ok(Stage2SumcheckOutput { + driver: context.driver.symbol, + point, + evals, + opening_claims, + proof: SumcheckProof { round_polynomials }, + }) +} + +fn verify_batched_stage2( + context: Stage2KernelContext<'_>, + mut store: Stage2ValueStore, + proof: &Stage2SumcheckOutput, + ram: Option<&Stage2RamData<'_>>, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + F: Field, + T: Transcript, +{ + if proof.driver != context.driver.symbol { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + if proof.proof.round_polynomials.len() != context.driver.num_rounds { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected batched round count", + }); + } + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let mut running_claim = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), &coefficient)| { + claim.mul_pow_2(max_rounds - plan.num_rounds) * coefficient + }) + .sum::(); + let mut point = Vec::with_capacity(max_rounds); + + for poly in &proof.proof.round_polynomials { + if polynomial_degree(poly) > context.driver.degree { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched polynomial exceeds degree bound", + }); + } + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != running_claim { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round check failed", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, poly); + let challenge = transcript.challenge(); + running_claim = poly.evaluate(challenge); + point.push(challenge); + } + if !proof.point.is_empty() && proof.point != point { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched point mismatch", + }); + } + let expected = expected_batched_output_claim( + context, + &store, + &proof.evals, + &point, + &batching_coeffs, + ram, + )?; + if running_claim != expected { + return Err(Stage2KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + store.observe_sumcheck_values(context.program, context.driver.symbol, &point, &proof.evals)?; + let opening_claims = + append_opening_claims(context.program, &mut store, transcript, &proof.evals)?; + Ok(Stage2SumcheckOutput { + driver: context.driver.symbol, + point, + evals: proof.evals.clone(), + opening_claims, + proof: proof.proof.clone(), + }) +} + +struct Stage2BatchedInstance<'a, F: Field> { + claim: &'a Stage2SumcheckClaimPlan, + relation: Stage2Relation, + offset: usize, + previous_claim: F, + state: Stage2ProverInstanceState<'a, F>, +} + +impl Stage2BatchedInstance<'_, F> { + fn is_active(&self, round: usize) -> bool { + round >= self.offset && round < self.offset + self.claim.num_rounds + } +} + +enum Stage2ProverInstanceState<'a, F: Field> { + RamReadWrite(RamReadWriteState), + ProductVirtualRemainder(ProductRemainderState<'a, F>), + InstructionLookupClaimReduction(InstructionLookupState<'a, F>), + RamRafEvaluation(DenseInstanceState), + RamOutputCheck(RamOutputState<'a, F>), +} + +impl<'a, F: Field> Stage2ProverInstanceState<'a, F> { + fn new( + program: &'static Stage2CpuProgramPlan, + claim: &Stage2SumcheckClaimPlan, + inputs: &Stage2ProverInputs<'a, F>, + store: &Stage2ValueStore, + ) -> Result { + match claim_relation(program, claim)? { + Stage2Relation::RamReadWrite => Ok(Self::RamReadWrite(RamReadWriteState::new( + claim, inputs, store, + )?)), + Stage2Relation::ProductVirtualRemainder => Ok(Self::ProductVirtualRemainder( + product_remainder_state(claim, inputs, store)?, + )), + Stage2Relation::InstructionLookupClaimReduction => { + Ok(Self::InstructionLookupClaimReduction( + instruction_lookup_state(claim, inputs, store)?, + )) + } + Stage2Relation::RamRafEvaluation => { + Ok(Self::RamRafEvaluation(ram_raf_state(claim, inputs, store)?)) + } + Stage2Relation::RamOutputCheck => Ok(Self::RamOutputCheck(ram_output_state( + claim, inputs, store, + )?)), + relation => Err(Stage2KernelError::KernelNotImplemented { + abi: relation.symbol(), + }), + } + } + + fn round_poly( + &mut self, + round: usize, + previous_claim: F, + ) -> Result, Stage2KernelError> { + match self { + Self::RamReadWrite(state) => state.round_poly(round, previous_claim), + Self::ProductVirtualRemainder(state) => Ok(state.round_poly(previous_claim)), + Self::InstructionLookupClaimReduction(state) => Ok(state.round_poly(previous_claim)), + Self::RamRafEvaluation(state) => Ok(state.round_poly(previous_claim)), + Self::RamOutputCheck(state) => Ok(state.round_poly(previous_claim)), + } + } + + fn ingest_challenge(&mut self, challenge: F) -> Result<(), Stage2KernelError> { + match self { + Self::RamReadWrite(state) => state.ingest_challenge(challenge), + Self::ProductVirtualRemainder(state) => { + state.bind(challenge); + Ok(()) + } + Self::InstructionLookupClaimReduction(state) => { + state.bind(challenge); + Ok(()) + } + Self::RamRafEvaluation(state) => { + state.bind(challenge); + Ok(()) + } + Self::RamOutputCheck(state) => { + state.bind(challenge); + Ok(()) + } + } + } + + fn final_evals( + &self, + relation: Stage2Relation, + ) -> Result>, Stage2KernelError> { + match self { + Self::RamReadWrite(state) => Ok(vec![ + named_eval( + "stage2.ram_read_write.eval.RamVal", + "RamVal", + state.val_eval()?, + ), + named_eval( + "stage2.ram_read_write.eval.RamRa", + "RamRa", + state.ra_eval()?, + ), + named_eval( + "stage2.ram_read_write.eval.RamInc", + "RamInc", + state.inc_eval()?, + ), + ]), + Self::ProductVirtualRemainder(state) => state.final_evals(relation), + Self::InstructionLookupClaimReduction(state) => state.final_evals(relation), + Self::RamOutputCheck(state) => state.final_evals(relation), + Self::RamRafEvaluation(state) => Ok(vec![named_eval( + "stage2.ram_raf.eval.RamRa", + "RamRa", + state.factor_eval(0, relation)?, + )]), + } + } +} + +struct ProductRemainderState<'a, F: Field> { + cycles: &'a [Stage2ProductVirtualCycle], + left: Vec, + right: Vec, + left_scratch: Vec, + right_scratch: Vec, + split_eq: SplitEqState, + point: Vec, +} + +impl ProductRemainderState<'_, F> { + fn round_poly(&self, previous_claim: F) -> UnivariatePoly { + product_remainder_split_round_poly(&self.left, &self.right, &self.split_eq, previous_claim) + } + + #[tracing::instrument(skip_all, name = "ProductRemainderState::bind")] + fn bind(&mut self, challenge: F) { + let left = &mut self.left; + let left_scratch = &mut self.left_scratch; + let right = &mut self.right; + let right_scratch = &mut self.right_scratch; + rayon::join( + || bind_dense_evals_reuse(left, left_scratch, challenge), + || bind_dense_evals_reuse(right, right_scratch, challenge), + ); + self.split_eq.bind(challenge); + self.point.push(challenge); + } + + fn final_evals( + &self, + relation: Stage2Relation, + ) -> Result>, Stage2KernelError> { + product_remainder_final_evals(self.cycles, &self.point, relation) + } +} + +#[tracing::instrument(skip_all, name = "ProductRemainderState::round_poly")] +fn product_remainder_split_round_poly( + left: &[F], + right: &[F], + split_eq: &SplitEqState, + previous_claim: F, +) -> UnivariatePoly { + let e_in = split_eq.e_in(); + let e_out = split_eq.e_out(); + let (q_constant, q_quadratic) = if e_in.len() > 1 { + product_remainder_low_round_coefficients(left, right, e_in, e_out) + } else { + product_remainder_high_round_coefficients(left, right, e_in[0], e_out) + }; + gruen_cubic_poly( + split_eq.current_target(), + q_constant, + q_quadratic, + previous_claim, + ) +} + +fn product_remainder_low_round_coefficients( + left: &[F], + right: &[F], + e_in: &[F], + e_out: &[F], +) -> (F, F) { + let in_len = e_in.len(); + let in_pairs = in_len / 2; + if left.len() / 2 >= DENSE_BIND_PAR_THRESHOLD { + let accumulators = (0..e_out.len()) + .into_par_iter() + .map(|x_out| { + let mut local = [F::Accumulator::default(); 2]; + let base = x_out * in_len; + let out_weight = e_out[x_out]; + for pair in 0..in_pairs { + accumulate_product_remainder_quadratic_pair( + &mut local, + out_weight * (e_in[2 * pair] + e_in[2 * pair + 1]), + left[base + 2 * pair], + left[base + 2 * pair + 1], + right[base + 2 * pair], + right[base + 2 * pair + 1], + ); + } + local + }) + .reduce( + || [F::Accumulator::default(); 2], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + (accumulators[0].reduce(), accumulators[1].reduce()) + } else { + let mut total = [F::Accumulator::default(); 2]; + for (x_out, &out_weight) in e_out.iter().enumerate() { + let base = x_out * in_len; + for pair in 0..in_pairs { + accumulate_product_remainder_quadratic_pair( + &mut total, + out_weight * (e_in[2 * pair] + e_in[2 * pair + 1]), + left[base + 2 * pair], + left[base + 2 * pair + 1], + right[base + 2 * pair], + right[base + 2 * pair + 1], + ); + } + } + (total[0].reduce(), total[1].reduce()) + } +} + +fn product_remainder_high_round_coefficients( + left: &[F], + right: &[F], + in_weight: F, + e_out: &[F], +) -> (F, F) { + let pairs = e_out.len() / 2; + if pairs >= DENSE_BIND_PAR_THRESHOLD { + let accumulators = (0..pairs) + .into_par_iter() + .map(|pair| { + let mut local = [F::Accumulator::default(); 2]; + accumulate_product_remainder_quadratic_pair( + &mut local, + in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]), + left[2 * pair], + left[2 * pair + 1], + right[2 * pair], + right[2 * pair + 1], + ); + local + }) + .reduce( + || [F::Accumulator::default(); 2], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + (accumulators[0].reduce(), accumulators[1].reduce()) + } else { + let mut total = [F::Accumulator::default(); 2]; + for pair in 0..pairs { + accumulate_product_remainder_quadratic_pair( + &mut total, + in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]), + left[2 * pair], + left[2 * pair + 1], + right[2 * pair], + right[2 * pair + 1], + ); + } + (total[0].reduce(), total[1].reduce()) + } +} + +fn accumulate_product_remainder_quadratic_pair( + accumulators: &mut [F::Accumulator; 2], + weight: F, + left0: F, + left1: F, + right0: F, + right1: F, +) { + accumulators[0].fmadd(weight * left0, right0); + accumulators[1].fmadd(weight * (left1 - left0), right1 - right0); +} + +fn gruen_cubic_poly( + target: F, + q_constant: F, + q_quadratic_coeff: F, + previous_claim: F, +) -> UnivariatePoly { + let eq_eval_1 = target; + let eq_eval_0 = F::one() - target; + let eq_delta = eq_eval_1 - eq_eval_0; + let eq_eval_2 = eq_eval_1 + eq_delta; + let eq_eval_3 = eq_eval_2 + eq_delta; + let cubic_eval_0 = eq_eval_0 * q_constant; + let cubic_eval_1 = previous_claim - cubic_eval_0; + let quadratic_eval_1 = cubic_eval_1 / eq_eval_1; + let e_times_2 = q_quadratic_coeff + q_quadratic_coeff; + let quadratic_eval_2 = quadratic_eval_1 + quadratic_eval_1 - q_constant + e_times_2; + let quadratic_eval_3 = quadratic_eval_2 + quadratic_eval_1 - q_constant + e_times_2 + e_times_2; + UnivariatePoly::from_evals(&[ + cubic_eval_0, + cubic_eval_1, + eq_eval_2 * quadratic_eval_2, + eq_eval_3 * quadratic_eval_3, + ]) +} + +struct InstructionLookupState<'a, F: Field> { + cycles: &'a [Stage2InstructionLookupCycle], + r_spartan: Vec, + gamma: F, + gamma_sqr: F, + gamma_cub: F, + gamma_quart: F, + phase: InstructionLookupPhase, +} + +impl InstructionLookupState<'_, F> { + fn round_poly(&self, previous_claim: F) -> UnivariatePoly { + self.phase.round_poly(previous_claim) + } + + fn bind(&mut self, challenge: F) { + if let InstructionLookupPhase::Phase1(phase1) = &self.phase { + if phase1.should_transition_to_phase2() { + let mut challenges = phase1.challenges.clone(); + challenges.push(challenge); + self.phase = InstructionLookupPhase::Phase2(InstructionLookupPhase2::new( + self.cycles, + &self.r_spartan, + &challenges, + [self.gamma, self.gamma_sqr, self.gamma_cub, self.gamma_quart], + )); + return; + } + } + self.phase.bind(challenge); + } + + fn final_evals( + &self, + _relation: Stage2Relation, + ) -> Result>, Stage2KernelError> { + let InstructionLookupPhase::Phase2(phase2) = &self.phase else { + return Err(Stage2KernelError::InvalidProof { + driver: Stage2Relation::InstructionLookupClaimReduction.symbol(), + reason: "instruction lookup did not reach phase 2", + }); + }; + phase2.final_evals() + } +} + +enum InstructionLookupPhase { + Phase1(InstructionLookupPhase1), + Phase2(InstructionLookupPhase2), +} + +impl InstructionLookupPhase { + fn round_poly(&self, previous_claim: F) -> UnivariatePoly { + match self { + Self::Phase1(state) => state.round_poly(previous_claim), + Self::Phase2(state) => state.round_poly(previous_claim), + } + } + + fn bind(&mut self, challenge: F) { + match self { + Self::Phase1(state) => state.bind(challenge), + Self::Phase2(state) => state.bind(challenge), + } + } +} + +struct InstructionLookupPhase1 { + p: Vec, + q: Vec, + challenges: Vec, +} + +impl InstructionLookupPhase1 { + fn new(cycles: &[Stage2InstructionLookupCycle], r_spartan: &[F], gamma_powers: [F; 4]) -> Self { + let (r_hi, r_lo) = r_spartan.split_at(r_spartan.len() / 2); + let p = EqPolynomial::::evals(r_lo, None); + let eq_suffix = EqPolynomial::::evals(r_hi, None); + let prefix_len = p.len(); + let q = (0..prefix_len) + .into_par_iter() + .map(|x_lo| { + let mut accumulators = [F::Accumulator::default(); 5]; + for (x_hi, &weight) in eq_suffix.iter().enumerate() { + let index = x_lo + (x_hi * prefix_len); + accumulate_instruction_lookup_outputs( + &mut accumulators, + &cycles[index], + weight, + ); + } + combine_instruction_lookup_values( + accumulators.map(FieldAccumulator::reduce), + gamma_powers, + ) + }) + .collect(); + Self { + p, + q, + challenges: Vec::new(), + } + } + + #[tracing::instrument(skip_all, name = "InstructionLookupPhase1::round_poly")] + fn round_poly(&self, _previous_claim: F) -> UnivariatePoly { + round_poly_from_factor_slices(&[&self.p, &self.q], 2) + } + + #[tracing::instrument(skip_all, name = "InstructionLookupPhase1::bind")] + fn bind(&mut self, challenge: F) { + self.challenges.push(challenge); + let mut scratch = Vec::new(); + bind_dense_evals_reuse(&mut self.p, &mut scratch, challenge); + bind_dense_evals_reuse(&mut self.q, &mut scratch, challenge); + } + + fn should_transition_to_phase2(&self) -> bool { + self.p.len() == 2 + } +} + +struct InstructionLookupPhase2 { + eq: Vec, + combined: Vec, + outputs: [Vec; 5], +} + +impl InstructionLookupPhase2 { + #[tracing::instrument(skip_all, name = "InstructionLookupPhase2::new")] + fn new( + cycles: &[Stage2InstructionLookupCycle], + r_spartan: &[F], + challenges: &[F], + gamma_powers: [F; 4], + ) -> Self { + let n_remaining_rounds = r_spartan.len() - challenges.len(); + let remaining_len = 1usize << n_remaining_rounds; + let prefix_point = reverse_slice(challenges); + let (r_hi, r_lo) = r_spartan.split_at(r_spartan.len() / 2); + let eq_prefix = EqPolynomial::::mle(&prefix_point, r_lo); + let prefix_eq_evals = EqPolynomial::::evals(&prefix_point, None); + let rows = (0..remaining_len) + .into_par_iter() + .map(|x_hi| { + let start = x_hi * prefix_eq_evals.len(); + let mut accumulators = [F::Accumulator::default(); 5]; + for (x_lo, &weight) in prefix_eq_evals.iter().enumerate() { + accumulate_instruction_lookup_outputs( + &mut accumulators, + &cycles[start + x_lo], + weight, + ); + } + let outputs = accumulators.map(FieldAccumulator::reduce); + let combined = combine_instruction_lookup_values(outputs, gamma_powers); + (outputs, combined) + }) + .collect::>(); + let mut outputs = core::array::from_fn(|_| Vec::with_capacity(remaining_len)); + let mut combined = Vec::with_capacity(remaining_len); + for (row_outputs, row_combined) in rows { + for (output, value) in outputs.iter_mut().zip(row_outputs) { + output.push(value); + } + combined.push(row_combined); + } + Self { + eq: EqPolynomial::::evals(r_hi, Some(eq_prefix)), + combined, + outputs, + } + } + + #[tracing::instrument(skip_all, name = "InstructionLookupPhase2::round_poly")] + fn round_poly(&self, _previous_claim: F) -> UnivariatePoly { + round_poly_from_factor_slices(&[&self.eq, &self.combined], 2) + } + + #[tracing::instrument(skip_all, name = "InstructionLookupPhase2::bind")] + fn bind(&mut self, challenge: F) { + let mut scratch = Vec::new(); + bind_dense_evals_reuse(&mut self.eq, &mut scratch, challenge); + bind_dense_evals_reuse(&mut self.combined, &mut scratch, challenge); + for output in &mut self.outputs { + bind_dense_evals_reuse(output, &mut scratch, challenge); + } + } + + fn final_evals(&self) -> Result>, Stage2KernelError> { + INSTRUCTION_LOOKUP_EVAL_NAMES + .iter() + .zip(&self.outputs) + .map(|(&(name, oracle), output)| { + output + .first() + .copied() + .map(|value| named_eval(name, oracle, value)) + .ok_or(Stage2KernelError::InvalidProof { + driver: Stage2Relation::InstructionLookupClaimReduction.symbol(), + reason: "empty instruction lookup output", + }) + }) + .collect() + } +} + +fn combine_instruction_lookup_values(values: [F; 5], gamma_powers: [F; 4]) -> F { + values[0] + + gamma_powers[0] * values[1] + + gamma_powers[1] * values[2] + + gamma_powers[2] * values[3] + + gamma_powers[3] * values[4] +} + +#[derive(Clone)] +struct DenseInstanceState { + factors: Vec>, + factor_scratch: Vec>, + point: Vec, +} + +impl DenseInstanceState { + fn new(factors: Vec>) -> Self { + let factor_scratch = (0..factors.len()).map(|_| Vec::new()).collect(); + Self { + factors, + factor_scratch, + point: Vec::new(), + } + } + + #[tracing::instrument(skip_all, name = "Stage2DenseState::round_poly")] + fn round_poly(&self, _previous_claim: F) -> UnivariatePoly { + round_poly_from_factors(&self.factors, self.degree()) + } + + fn degree(&self) -> usize { + self.factors.len() + } + + #[tracing::instrument(skip_all, name = "Stage2DenseState::bind")] + fn bind(&mut self, challenge: F) { + for (factor, scratch) in self.factors.iter_mut().zip(&mut self.factor_scratch) { + bind_dense_evals_reuse(factor, scratch, challenge); + } + self.point.push(challenge); + } + + fn factor_eval(&self, index: usize, relation: Stage2Relation) -> Result { + self.factors + .get(index) + .and_then(|values| values.first()) + .copied() + .ok_or(Stage2KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty dense factor", + }) + } +} + +#[derive(Clone)] +struct RamOutputState<'a, F: Field> { + dense: DenseInstanceState, + final_ram: &'a [u64], + nonzero_final_ram: Vec<(usize, u64)>, +} + +impl RamOutputState<'_, F> { + fn round_poly(&self, previous_claim: F) -> UnivariatePoly { + self.dense.round_poly(previous_claim) + } + + fn bind(&mut self, challenge: F) { + self.dense.bind(challenge); + } + + fn final_evals( + &self, + _relation: Stage2Relation, + ) -> Result>, Stage2KernelError> { + Ok(vec![named_eval( + "stage2.ram_output.eval.RamValFinal", + "RamValFinal", + ram_eval_reversed(self.final_ram, &self.nonzero_final_ram, &self.dense.point), + )]) + } +} + +#[tracing::instrument(skip_all, name = "Stage2::product_remainder_state")] +fn product_remainder_state<'a, F: Field>( + _claim: &Stage2SumcheckClaimPlan, + inputs: &Stage2ProverInputs<'a, F>, + store: &Stage2ValueStore, +) -> Result, Stage2KernelError> { + let cycles = inputs + .product_virtual_cycles + .ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_product_virtual_remainder", + input: "product_virtual_cycles", + })?; + let tau_low = store.point("stage2.input.stage1.Product")?; + let expected = + 1usize + .checked_shl(tau_low.len() as u32) + .ok_or(Stage2KernelError::InvalidInputLength { + input: "stage2.product_virtual.cycles", + expected: usize::BITS as usize, + actual: tau_low.len(), + })?; + if cycles.len() != expected { + return Err(Stage2KernelError::InvalidInputLength { + input: "stage2.product_virtual.cycles", + expected, + actual: cycles.len(), + }); + } + let tau_high = store.scalar("stage2.product_virtual.tau_high")?; + let r0 = *store + .point("stage2.product_virtual.uniskip.sumcheck")? + .first() + .ok_or(Stage2KernelError::MissingValue { + symbol: "stage2.product_virtual.uniskip.sumcheck", + })?; + let lagrange_tau_r0 = lagrange_kernel_eval( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + tau_high, + r0, + ); + let weights = lagrange_evals( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + r0, + ); + let mut left = vec![F::zero(); cycles.len()]; + let mut right = vec![F::zero(); cycles.len()]; + left.par_iter_mut() + .zip(right.par_iter_mut()) + .zip(cycles.par_iter()) + .for_each(|((left, right), cycle)| { + *left = weights[0].mul_u64(cycle.instruction_left_input) + + weights[1].mul_u64(cycle.should_branch_lookup_output) + + if cycle.jump_flag { + weights[2] + } else { + F::zero() + }; + *right = weights[0].mul_i128(cycle.instruction_right_input) + + if cycle.should_branch_flag { + weights[1] + } else { + F::zero() + } + + if cycle.not_next_noop { + weights[2] + } else { + F::zero() + }; + }); + Ok(ProductRemainderState { + cycles, + left, + right, + left_scratch: Vec::new(), + right_scratch: Vec::new(), + split_eq: SplitEqState::new_low_to_high(tau_low, Some(lagrange_tau_r0)), + point: Vec::new(), + }) +} + +#[tracing::instrument(skip_all, name = "Stage2::instruction_lookup_state")] +fn instruction_lookup_state<'a, F: Field>( + _claim: &Stage2SumcheckClaimPlan, + inputs: &Stage2ProverInputs<'a, F>, + store: &Stage2ValueStore, +) -> Result, Stage2KernelError> { + let cycles = inputs + .instruction_lookup_cycles + .ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_instruction_lookup_claim_reduction", + input: "instruction_lookup_cycles", + })?; + let r_spartan = store.point("stage2.input.stage1.LookupOutput")?; + let expected = 1usize.checked_shl(r_spartan.len() as u32).ok_or( + Stage2KernelError::InvalidInputLength { + input: "stage2.instruction_lookup.cycles", + expected: usize::BITS as usize, + actual: r_spartan.len(), + }, + )?; + if cycles.len() != expected { + return Err(Stage2KernelError::InvalidInputLength { + input: "stage2.instruction_lookup.cycles", + expected, + actual: cycles.len(), + }); + } + let gamma = store.scalar("stage2.instruction_lookup.gamma")?; + let gamma_sqr = gamma.square(); + let gamma_cub = gamma_sqr * gamma; + let gamma_quart = gamma_sqr.square(); + Ok(InstructionLookupState { + cycles, + r_spartan: r_spartan.to_vec(), + gamma, + gamma_sqr, + gamma_cub, + gamma_quart, + phase: InstructionLookupPhase::Phase1(InstructionLookupPhase1::new( + cycles, + r_spartan, + [gamma, gamma_sqr, gamma_cub, gamma_quart], + )), + }) +} + +#[tracing::instrument(skip_all, name = "Stage2::ram_raf_state")] +fn ram_raf_state( + claim: &Stage2SumcheckClaimPlan, + inputs: &Stage2ProverInputs<'_, F>, + store: &Stage2ValueStore, +) -> Result, Stage2KernelError> { + let ram = inputs.ram.ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_ram_raf_evaluation", + input: "ram", + })?; + require_operand_count("stage2.ram_raf.num_rounds", ram.log_k, claim.num_rounds)?; + let r_cycle = store.point("stage2.input.stage1.RamAddress")?; + let eq_cycle = EqPolynomial::::evals(r_cycle, None); + if ram.accesses.len() != eq_cycle.len() { + return Err(Stage2KernelError::InvalidInputLength { + input: "stage2.ram.accesses", + expected: eq_cycle.len(), + actual: ram.accesses.len(), + }); + } + let k = 1usize << ram.log_k; + let mut ra = vec![F::zero(); k]; + for (access, weight) in ram.accesses.iter().zip(eq_cycle) { + if let Some(address) = access.remapped_address { + ra[address] += weight; + } + } + let mut next_address = F::from_u64(ram.start_address); + let address_step = F::from_u64(8); + let mut unmap = Vec::with_capacity(k); + for _ in 0..k { + unmap.push(next_address); + next_address += address_step; + } + Ok(DenseInstanceState::new(vec![ra, unmap])) +} + +#[tracing::instrument(skip_all, name = "Stage2::ram_output_state")] +fn ram_output_state<'a, F: Field>( + claim: &Stage2SumcheckClaimPlan, + inputs: &Stage2ProverInputs<'a, F>, + store: &Stage2ValueStore, +) -> Result, Stage2KernelError> { + let ram = inputs.ram.ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_ram_output_check", + input: "ram", + })?; + require_operand_count("stage2.ram_output.num_rounds", ram.log_k, claim.num_rounds)?; + let layout = ram + .output_layout + .ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_ram_output_check", + input: "ram.output_layout", + })?; + let k = 1usize << ram.log_k; + require_operand_count("stage2.ram.final_ram", k, ram.final_ram.len())?; + let r_address = store.point("stage2.ram_output.r_address")?; + let eq = EqPolynomial::::evals(r_address, None); + let mut io_mask = vec![F::zero(); k]; + let mut diff = vec![F::zero(); k]; + let mut nonzero_final_ram = Vec::new(); + for index in 0..k { + let final_value = ram.final_ram[index]; + if final_value != 0 { + nonzero_final_ram.push((index, final_value)); + } + if index >= layout.io_start && index < layout.io_end { + io_mask[index] = F::one(); + } else if final_value != 0 { + diff[index] = F::from_u64(final_value); + } + } + Ok(RamOutputState { + dense: DenseInstanceState::new(vec![eq, io_mask, diff]), + final_ram: ram.final_ram, + nonzero_final_ram, + }) +} + +#[derive(Clone, Debug)] +struct RamCycleEntry { + row: usize, + col: usize, + prev_val: u64, + next_val: u64, + val_coeff: F, + ra_coeff: F, +} + +#[derive(Clone, Debug)] +struct RamAddressEntry { + row: usize, + col: usize, + prev_val: F, + next_val: F, + val_coeff: F, + ra_coeff: F, +} + +#[derive(Clone)] +struct RamReadWriteState { + gamma: F, + log_t: usize, + round: usize, + cycle_eq: SplitEqState, + cycle_entries: Vec>, + address_entries: Vec>, + address_scratch: Vec>, + inc: Vec, + inc_scratch: Vec, + val_init: Vec, + val_init_scratch: Vec, +} + +impl RamReadWriteState { + #[tracing::instrument(skip_all, name = "RamReadWriteState::new")] + fn new( + _claim: &Stage2SumcheckClaimPlan, + inputs: &Stage2ProverInputs<'_, F>, + store: &Stage2ValueStore, + ) -> Result { + let ram = inputs.ram.ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_ram_read_write", + input: "ram", + })?; + let r_cycle = store.point("stage2.input.stage1.RamReadValue")?; + let log_t = r_cycle.len(); + let t = 1usize << log_t; + let k = 1usize << ram.log_k; + require_operand_count("stage2.ram.accesses", t, ram.accesses.len())?; + require_operand_count("stage2.ram.initial_ram", k, ram.initial_ram.len())?; + let gamma = store.scalar("stage2.ram_read_write.gamma")?; + let mut cycle_entries = Vec::with_capacity(ram.accesses.len()); + let mut inc = Vec::with_capacity(t); + for (row, access) in ram.accesses.iter().enumerate() { + inc.push(if access.write_value == access.read_value { + F::zero() + } else { + F::from_u64(access.write_value) - F::from_u64(access.read_value) + }); + if let Some(col) = access.remapped_address { + cycle_entries.push(RamCycleEntry { + row, + col, + prev_val: access.read_value, + next_val: access.write_value, + val_coeff: F::from_u64(access.read_value), + ra_coeff: F::one(), + }); + } + } + Ok(Self { + gamma, + log_t, + round: 0, + cycle_eq: SplitEqState::new_low_to_high(r_cycle, None), + cycle_entries, + address_entries: Vec::new(), + address_scratch: Vec::new(), + inc, + inc_scratch: Vec::new(), + val_init: ram + .initial_ram + .iter() + .map(|&value| { + if value == 0 { + F::zero() + } else { + F::from_u64(value) + } + }) + .collect(), + val_init_scratch: Vec::new(), + }) + } + + fn round_poly( + &mut self, + _round: usize, + previous_claim: F, + ) -> Result, Stage2KernelError> { + if self.round < self.log_t { + Ok(self.cycle_round_poly(previous_claim)) + } else { + Ok(self.address_round_poly(previous_claim)) + } + } + + fn ingest_challenge(&mut self, challenge: F) -> Result<(), Stage2KernelError> { + if self.round < self.log_t { + self.bind_cycle(challenge); + if self.round + 1 == self.log_t { + self.address_entries = self + .cycle_entries + .drain(..) + .map(|entry| RamAddressEntry { + row: entry.row, + col: entry.col, + prev_val: F::from_u64(entry.prev_val), + next_val: F::from_u64(entry.next_val), + val_coeff: entry.val_coeff, + ra_coeff: entry.ra_coeff, + }) + .collect(); + self.address_entries + .sort_by_key(|entry| (entry.col, entry.row)); + } + } else { + self.bind_address(challenge); + } + self.round += 1; + Ok(()) + } + + #[tracing::instrument(skip_all, name = "RamReadWriteState::cycle_round_poly")] + fn cycle_round_poly(&self, previous_claim: F) -> UnivariatePoly { + let e_in = self.cycle_eq.e_in(); + let e_out = self.cycle_eq.e_out(); + let (q_constant, q_quadratic) = if e_in.len() > 1 { + cycle_low_round_coefficients(&self.cycle_entries, &self.inc, e_in, e_out, self.gamma) + } else { + cycle_high_round_coefficients( + &self.cycle_entries, + &self.inc, + e_in[0], + e_out, + self.gamma, + ) + }; + gruen_cubic_poly( + self.cycle_eq.current_target(), + q_constant, + q_quadratic, + previous_claim, + ) + } + + #[tracing::instrument(skip_all, name = "RamReadWriteState::address_round_poly")] + fn address_round_poly(&self, previous_claim: F) -> UnivariatePoly { + let mut evals = [F::zero(); 2]; + let cycle_eq = self.cycle_eq_eval(); + let mut cursor = 0; + while cursor < self.address_entries.len() { + let pair = self.address_entries[cursor].col / 2; + let start = cursor; + while cursor < self.address_entries.len() + && self.address_entries[cursor].col / 2 == pair + { + cursor += 1; + } + let entries = &self.address_entries[start..cursor]; + let odd_start = entries.partition_point(|entry| entry.col % 2 == 0); + let even = &entries[..odd_start]; + let odd = &entries[odd_start..]; + let even_checkpoint = self.val_init[2 * pair]; + let odd_checkpoint = self.val_init[2 * pair + 1]; + for (x, eval_index) in [(0usize, 0usize), (2usize, 1usize)] { + evals[eval_index] += address_pair_eval( + even, + odd, + even_checkpoint, + odd_checkpoint, + RamEvalWeights { + inc: self.inc[0], + eq: cycle_eq, + gamma: self.gamma, + x: F::from_u64(x as u64), + }, + ); + } + } + UnivariatePoly::from_evals_and_hint(previous_claim, &evals) + } + + #[tracing::instrument(skip_all, name = "RamReadWriteState::bind_cycle")] + fn bind_cycle(&mut self, challenge: F) { + self.cycle_entries = bind_cycle_entries_parallel(&self.cycle_entries, challenge); + let inc = &mut self.inc; + let inc_scratch = &mut self.inc_scratch; + bind_dense_evals_reuse(inc, inc_scratch, challenge); + self.cycle_eq.bind(challenge); + } + + #[tracing::instrument(skip_all, name = "RamReadWriteState::bind_address")] + fn bind_address(&mut self, challenge: F) { + let mut bound = std::mem::take(&mut self.address_scratch); + bound.clear(); + bound.reserve(self.address_entries.len()); + let mut cursor = 0; + while cursor < self.address_entries.len() { + let pair = self.address_entries[cursor].col / 2; + let start = cursor; + while cursor < self.address_entries.len() + && self.address_entries[cursor].col / 2 == pair + { + cursor += 1; + } + let entries = &self.address_entries[start..cursor]; + let odd_start = entries.partition_point(|entry| entry.col % 2 == 0); + bind_address_cols( + &entries[..odd_start], + &entries[odd_start..], + self.val_init[2 * pair], + self.val_init[2 * pair + 1], + challenge, + &mut bound, + ); + } + std::mem::swap(&mut self.address_entries, &mut bound); + self.address_scratch = bound; + bind_dense_evals_reuse(&mut self.val_init, &mut self.val_init_scratch, challenge); + } + + fn ra_eval(&self) -> Result { + Ok(self + .address_entries + .first() + .filter(|entry| entry.col == 0 && entry.row == 0) + .map_or(F::zero(), |entry| entry.ra_coeff)) + } + + fn val_eval(&self) -> Result { + Ok(self + .address_entries + .first() + .filter(|entry| entry.col == 0 && entry.row == 0) + .map_or(self.val_init[0], |entry| entry.val_coeff)) + } + + fn inc_eval(&self) -> Result { + Ok(self.inc[0]) + } + + fn cycle_eq_eval(&self) -> F { + self.cycle_eq.eval() + } +} + +fn cycle_low_round_coefficients( + entries: &[RamCycleEntry], + inc: &[F], + e_in: &[F], + e_out: &[F], + gamma: F, +) -> (F, F) { + let in_pairs = e_in.len() / 2; + let accumulators = if entries.len() >= DENSE_BIND_PAR_THRESHOLD { + entries + .par_chunk_by(|left, right| (left.row / 2) / in_pairs == (right.row / 2) / in_pairs) + .map(|entries| { + let mut local = [F::Accumulator::default(); 2]; + accumulate_cycle_outer_chunk( + &mut local, entries, inc, e_in, e_out, in_pairs, gamma, + ); + local + }) + .reduce( + || [F::Accumulator::default(); 2], + |mut left, right| { + for index in 0..left.len() { + left[index].merge(right[index]); + } + left + }, + ) + } else { + entries + .chunk_by(|left, right| (left.row / 2) / in_pairs == (right.row / 2) / in_pairs) + .fold([F::Accumulator::default(); 2], |mut local, entries| { + accumulate_cycle_outer_chunk( + &mut local, entries, inc, e_in, e_out, in_pairs, gamma, + ); + local + }) + }; + (accumulators[0].reduce(), accumulators[1].reduce()) +} + +fn cycle_high_round_coefficients( + entries: &[RamCycleEntry], + inc: &[F], + in_weight: F, + e_out: &[F], + gamma: F, +) -> (F, F) { + let accumulators = if entries.len() >= DENSE_BIND_PAR_THRESHOLD { + entries + .par_chunk_by(|left, right| left.row / 2 == right.row / 2) + .map(|entries| { + let mut local = [F::Accumulator::default(); 2]; + let pair = entries[0].row / 2; + let weight = in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]); + accumulate_cycle_row_pair(&mut local, entries, inc, weight, gamma); + local + }) + .reduce( + || [F::Accumulator::default(); 2], + |mut left, right| { + for index in 0..left.len() { + left[index].merge(right[index]); + } + left + }, + ) + } else { + entries + .chunk_by(|left, right| left.row / 2 == right.row / 2) + .fold([F::Accumulator::default(); 2], |mut local, entries| { + let pair = entries[0].row / 2; + let weight = in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]); + accumulate_cycle_row_pair(&mut local, entries, inc, weight, gamma); + local + }) + }; + (accumulators[0].reduce(), accumulators[1].reduce()) +} + +fn accumulate_cycle_outer_chunk( + accumulators: &mut [F::Accumulator; 2], + entries: &[RamCycleEntry], + inc: &[F], + e_in: &[F], + e_out: &[F], + in_pairs: usize, + gamma: F, +) { + let x_out = (entries[0].row / 2) / in_pairs; + let out_weight = e_out[x_out]; + for entries in entries.chunk_by(|left, right| left.row / 2 == right.row / 2) { + let pair = entries[0].row / 2; + let x_in = pair % in_pairs; + let weight = out_weight * (e_in[2 * x_in] + e_in[2 * x_in + 1]); + accumulate_cycle_row_pair(accumulators, entries, inc, weight, gamma); + } +} + +fn accumulate_cycle_row_pair( + accumulators: &mut [F::Accumulator; 2], + entries: &[RamCycleEntry], + inc: &[F], + weight: F, + gamma: F, +) { + let pair = entries[0].row / 2; + let odd_start = entries.partition_point(|entry| entry.row % 2 == 0); + let even = &entries[..odd_start]; + let odd = &entries[odd_start..]; + let row = pair * 2; + let inc_pair = [inc[row], inc[row + 1]]; + accumulate_cycle_pair_body(accumulators, even, odd, inc_pair, weight, gamma); +} + +fn accumulate_cycle_pair_body( + accumulators: &mut [F::Accumulator; 2], + even: &[RamCycleEntry], + odd: &[RamCycleEntry], + inc_pair: [F; 2], + weight: F, + gamma: F, +) { + let mut i = 0; + let mut j = 0; + while i < even.len() && j < odd.len() { + match even[i].col.cmp(&odd[j].col) { + Ordering::Equal => { + accumulate_cycle_entry_quadratic( + accumulators, + Some(&even[i]), + Some(&odd[j]), + inc_pair, + weight, + gamma, + ); + i += 1; + j += 1; + } + Ordering::Less => { + accumulate_cycle_entry_quadratic( + accumulators, + Some(&even[i]), + None, + inc_pair, + weight, + gamma, + ); + i += 1; + } + Ordering::Greater => { + accumulate_cycle_entry_quadratic( + accumulators, + None, + Some(&odd[j]), + inc_pair, + weight, + gamma, + ); + j += 1; + } + } + } + for entry in &even[i..] { + accumulate_cycle_entry_quadratic(accumulators, Some(entry), None, inc_pair, weight, gamma); + } + for entry in &odd[j..] { + accumulate_cycle_entry_quadratic(accumulators, None, Some(entry), inc_pair, weight, gamma); + } +} + +fn accumulate_cycle_entry_quadratic( + accumulators: &mut [F::Accumulator; 2], + even: Option<&RamCycleEntry>, + odd: Option<&RamCycleEntry>, + inc_pair: [F; 2], + weight: F, + gamma: F, +) { + let (ra0, ra1, val0, val1) = match (even, odd) { + (Some(even), Some(odd)) => (even.ra_coeff, odd.ra_coeff, even.val_coeff, odd.val_coeff), + (Some(even), None) => ( + even.ra_coeff, + F::zero(), + even.val_coeff, + F::from_u64(even.next_val), + ), + (None, Some(odd)) => ( + F::zero(), + odd.ra_coeff, + F::from_u64(odd.prev_val), + odd.val_coeff, + ), + (None, None) => unreachable!(), + }; + let one_plus_gamma = F::one() + gamma; + let inc_delta = inc_pair[1] - inc_pair[0]; + let body0 = one_plus_gamma * val0 + gamma * inc_pair[0]; + let body_delta = one_plus_gamma * (val1 - val0) + gamma * inc_delta; + accumulators[0].fmadd(weight * ra0, body0); + accumulators[1].fmadd(weight * (ra1 - ra0), body_delta); +} + +fn address_pair_eval( + even: &[RamAddressEntry], + odd: &[RamAddressEntry], + mut even_checkpoint: F, + mut odd_checkpoint: F, + weights: RamEvalWeights, +) -> F { + let mut total = F::zero(); + let mut i = 0; + let mut j = 0; + while i < even.len() && j < odd.len() { + match even[i].row.cmp(&odd[j].row) { + Ordering::Equal => { + total += address_entry_eval( + Some(&even[i]), + Some(&odd[j]), + even_checkpoint, + odd_checkpoint, + weights, + ); + even_checkpoint = even[i].next_val; + odd_checkpoint = odd[j].next_val; + i += 1; + j += 1; + } + Ordering::Less => { + total += address_entry_eval( + Some(&even[i]), + None, + even_checkpoint, + odd_checkpoint, + weights, + ); + even_checkpoint = even[i].next_val; + i += 1; + } + Ordering::Greater => { + total += address_entry_eval( + None, + Some(&odd[j]), + even_checkpoint, + odd_checkpoint, + weights, + ); + odd_checkpoint = odd[j].next_val; + j += 1; + } + } + } + for entry in &even[i..] { + total += address_entry_eval(Some(entry), None, even_checkpoint, odd_checkpoint, weights); + even_checkpoint = entry.next_val; + } + for entry in &odd[j..] { + total += address_entry_eval(None, Some(entry), even_checkpoint, odd_checkpoint, weights); + odd_checkpoint = entry.next_val; + } + total +} + +#[derive(Clone, Copy)] +struct RamEvalWeights { + inc: F, + eq: F, + gamma: F, + x: F, +} + +fn address_entry_eval( + even: Option<&RamAddressEntry>, + odd: Option<&RamAddressEntry>, + even_checkpoint: F, + odd_checkpoint: F, + weights: RamEvalWeights, +) -> F { + let (ra0, ra1, val0, val1) = match (even, odd) { + (Some(even), Some(odd)) => (even.ra_coeff, odd.ra_coeff, even.val_coeff, odd.val_coeff), + (Some(even), None) => (even.ra_coeff, F::zero(), even.val_coeff, odd_checkpoint), + (None, Some(odd)) => (F::zero(), odd.ra_coeff, even_checkpoint, odd.val_coeff), + (None, None) => unreachable!(), + }; + let ra = line(ra0, ra1, weights.x); + let val = line(val0, val1, weights.x); + weights.eq * ra * (val + weights.gamma * (val + weights.inc)) +} + +fn bind_cycle_entries_parallel( + entries: &[RamCycleEntry], + challenge: F, +) -> Vec> { + let row_lengths = entries + .par_chunk_by(|left, right| left.row / 2 == right.row / 2) + .map(|entries| { + let odd_start = entries.partition_point(|entry| entry.row % 2 == 0); + let bound_len = bind_cycle_rows_len(&entries[..odd_start], &entries[odd_start..]); + (entries.len(), bound_len) + }) + .collect::>(); + let bound_len = row_lengths.iter().map(|(_, bound_len)| *bound_len).sum(); + let mut bound = Vec::with_capacity(bound_len); + let mut output_remainder = bound.spare_capacity_mut(); + let mut input_remainder = entries; + let mut input_slices = Vec::with_capacity(row_lengths.len()); + let mut output_slices = Vec::with_capacity(row_lengths.len()); + for (input_len, output_len) in row_lengths { + let (output, rest_output) = output_remainder.split_at_mut(output_len); + output_remainder = rest_output; + output_slices.push(output); + let (input, rest_input) = input_remainder.split_at(input_len); + input_remainder = rest_input; + input_slices.push(input); + } + input_slices + .par_iter() + .zip(output_slices.into_par_iter()) + .for_each(|(input, output)| { + let odd_start = input.partition_point(|entry| entry.row % 2 == 0); + let written = + bind_cycle_rows(&input[..odd_start], &input[odd_start..], challenge, output); + debug_assert_eq!(written, output.len()); + }); + // SAFETY: every output slice is disjoint, and `bind_cycle_rows` writes exactly the + // precomputed number of initialized entries into each slice before `set_len`. + unsafe { + bound.set_len(bound_len); + } + bound +} + +fn bind_cycle_rows_len(even: &[RamCycleEntry], odd: &[RamCycleEntry]) -> usize { + let mut i = 0; + let mut j = 0; + let mut len = 0; + while i < even.len() && j < odd.len() { + len += 1; + match even[i].col.cmp(&odd[j].col) { + Ordering::Equal => { + i += 1; + j += 1; + } + Ordering::Less => i += 1, + Ordering::Greater => j += 1, + } + } + len + even.len() - i + odd.len() - j +} + +fn bind_cycle_rows( + even: &[RamCycleEntry], + odd: &[RamCycleEntry], + challenge: F, + out: &mut [MaybeUninit>], +) -> usize { + let mut i = 0; + let mut j = 0; + let mut written = 0; + while i < even.len() && j < odd.len() { + match even[i].col.cmp(&odd[j].col) { + Ordering::Equal => { + let _ = + out[written].write(bind_cycle_entry(Some(&even[i]), Some(&odd[j]), challenge)); + written += 1; + i += 1; + j += 1; + } + Ordering::Less => { + let _ = out[written].write(bind_cycle_entry(Some(&even[i]), None, challenge)); + written += 1; + i += 1; + } + Ordering::Greater => { + let _ = out[written].write(bind_cycle_entry(None, Some(&odd[j]), challenge)); + written += 1; + j += 1; + } + } + } + for entry in &even[i..] { + let _ = out[written].write(bind_cycle_entry(Some(entry), None, challenge)); + written += 1; + } + for entry in &odd[j..] { + let _ = out[written].write(bind_cycle_entry(None, Some(entry), challenge)); + written += 1; + } + written +} + +fn bind_cycle_entry( + even: Option<&RamCycleEntry>, + odd: Option<&RamCycleEntry>, + r: F, +) -> RamCycleEntry { + match (even, odd) { + (Some(even), Some(odd)) => RamCycleEntry { + row: even.row / 2, + col: even.col, + ra_coeff: line(even.ra_coeff, odd.ra_coeff, r), + val_coeff: line(even.val_coeff, odd.val_coeff, r), + prev_val: even.prev_val, + next_val: odd.next_val, + }, + (Some(even), None) => RamCycleEntry { + row: even.row / 2, + col: even.col, + ra_coeff: line(even.ra_coeff, F::zero(), r), + val_coeff: line(even.val_coeff, F::from_u64(even.next_val), r), + prev_val: even.prev_val, + next_val: even.next_val, + }, + (None, Some(odd)) => RamCycleEntry { + row: odd.row / 2, + col: odd.col, + ra_coeff: line(F::zero(), odd.ra_coeff, r), + val_coeff: line(F::from_u64(odd.prev_val), odd.val_coeff, r), + prev_val: odd.prev_val, + next_val: odd.next_val, + }, + (None, None) => unreachable!(), + } +} + +fn bind_address_cols( + even: &[RamAddressEntry], + odd: &[RamAddressEntry], + mut even_checkpoint: F, + mut odd_checkpoint: F, + challenge: F, + out: &mut Vec>, +) { + let mut i = 0; + let mut j = 0; + while i < even.len() && j < odd.len() { + match even[i].row.cmp(&odd[j].row) { + Ordering::Equal => { + out.push(bind_address_entry( + Some(&even[i]), + Some(&odd[j]), + even_checkpoint, + odd_checkpoint, + challenge, + )); + even_checkpoint = even[i].next_val; + odd_checkpoint = odd[j].next_val; + i += 1; + j += 1; + } + Ordering::Less => { + out.push(bind_address_entry( + Some(&even[i]), + None, + even_checkpoint, + odd_checkpoint, + challenge, + )); + even_checkpoint = even[i].next_val; + i += 1; + } + Ordering::Greater => { + out.push(bind_address_entry( + None, + Some(&odd[j]), + even_checkpoint, + odd_checkpoint, + challenge, + )); + odd_checkpoint = odd[j].next_val; + j += 1; + } + } + } + for entry in &even[i..] { + out.push(bind_address_entry( + Some(entry), + None, + even_checkpoint, + odd_checkpoint, + challenge, + )); + even_checkpoint = entry.next_val; + } + for entry in &odd[j..] { + out.push(bind_address_entry( + None, + Some(entry), + even_checkpoint, + odd_checkpoint, + challenge, + )); + odd_checkpoint = entry.next_val; + } +} + +fn bind_address_entry( + even: Option<&RamAddressEntry>, + odd: Option<&RamAddressEntry>, + even_checkpoint: F, + odd_checkpoint: F, + r: F, +) -> RamAddressEntry { + match (even, odd) { + (Some(even), Some(odd)) => RamAddressEntry { + row: even.row, + col: even.col / 2, + ra_coeff: line(even.ra_coeff, odd.ra_coeff, r), + val_coeff: line(even.val_coeff, odd.val_coeff, r), + prev_val: line(even.prev_val, odd.prev_val, r), + next_val: line(even.next_val, odd.next_val, r), + }, + (Some(even), None) => RamAddressEntry { + row: even.row, + col: even.col / 2, + ra_coeff: line(even.ra_coeff, F::zero(), r), + val_coeff: line(even.val_coeff, odd_checkpoint, r), + prev_val: line(even.prev_val, odd_checkpoint, r), + next_val: line(even.next_val, odd_checkpoint, r), + }, + (None, Some(odd)) => RamAddressEntry { + row: odd.row, + col: odd.col / 2, + ra_coeff: line(F::zero(), odd.ra_coeff, r), + val_coeff: line(even_checkpoint, odd.val_coeff, r), + prev_val: line(even_checkpoint, odd.prev_val, r), + next_val: line(even_checkpoint, odd.next_val, r), + }, + (None, None) => unreachable!(), + } +} + +fn expected_batched_output_claim( + context: Stage2KernelContext<'_>, + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + point: &[F], + batching_coeffs: &[F], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let mut expected = F::zero(); + for (claim, &coefficient) in context.batch_claims()?.iter().zip(batching_coeffs) { + let instance = context + .program + .instance_results + .iter() + .find(|instance| { + instance.claim == claim.symbol && instance.source == context.driver.symbol + }) + .ok_or(Stage2KernelError::MissingClaim { + batch: context.batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(Stage2KernelError::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let claim_value = match Stage2Relation::from_symbol(instance.relation).ok_or( + Stage2KernelError::UnknownRelation { + relation: instance.relation, + }, + )? { + Stage2Relation::RamReadWrite => expected_ram_read_write(store, evals, local_point)?, + Stage2Relation::ProductVirtualRemainder => { + expected_product_remainder(store, evals, local_point)? + } + Stage2Relation::InstructionLookupClaimReduction => { + expected_instruction_lookup(store, evals, local_point)? + } + Stage2Relation::RamRafEvaluation => expected_ram_raf(store, evals, local_point, ram)?, + Stage2Relation::RamOutputCheck => expected_ram_output(store, evals, local_point, ram)?, + relation => { + return Err(Stage2KernelError::KernelNotImplemented { + abi: relation.symbol(), + }) + } + }; + expected += coefficient * claim_value; + } + Ok(expected) +} + +fn expected_ram_read_write( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[F], +) -> Result { + let r_cycle_stage1 = store.point("stage2.input.stage1.RamReadValue")?; + let log_t = r_cycle_stage1.len(); + let r_cycle = reverse_slice(&local_point[..log_t]); + let eq_eval = EqPolynomial::::mle(r_cycle_stage1, &r_cycle); + let gamma = store.scalar("stage2.ram_read_write.gamma")?; + let val = eval_by_name(evals, "stage2.ram_read_write.eval.RamVal")?; + let ra = eval_by_name(evals, "stage2.ram_read_write.eval.RamRa")?; + let inc = eval_by_name(evals, "stage2.ram_read_write.eval.RamInc")?; + Ok(eq_eval * ra * (val + gamma * (val + inc))) +} + +fn expected_product_remainder( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[F], +) -> Result { + let tau_low = store.point("stage2.input.stage1.Product")?; + let tau_high = store.scalar("stage2.product_virtual.tau_high")?; + let r0 = *store + .point("stage2.product_virtual.uniskip.sumcheck")? + .first() + .ok_or(Stage2KernelError::MissingValue { + symbol: "stage2.product_virtual.uniskip.sumcheck", + })?; + let r_tail = reverse_slice(local_point); + let low = EqPolynomial::::mle(tau_low, &r_tail); + let high = lagrange_kernel_eval( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + tau_high, + r0, + ); + let weights = lagrange_evals( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + r0, + ); + let left = weights[0] + * eval_by_name( + evals, + "stage2.product_virtual.remainder.eval.LeftInstructionInput", + )? + + weights[1] * eval_by_name(evals, "stage2.product_virtual.remainder.eval.LookupOutput")? + + weights[2] * eval_by_name(evals, "stage2.product_virtual.remainder.eval.OpFlagJump")?; + let right = weights[0] + * eval_by_name( + evals, + "stage2.product_virtual.remainder.eval.RightInstructionInput", + )? + + weights[1] + * eval_by_name( + evals, + "stage2.product_virtual.remainder.eval.InstructionFlagBranch", + )? + + weights[2] + * (F::one() - eval_by_name(evals, "stage2.product_virtual.remainder.eval.NextIsNoop")?); + Ok(high * low * left * right) +} + +fn expected_instruction_lookup( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[F], +) -> Result { + let opening_point = reverse_slice(local_point); + let r_spartan = store.point("stage2.input.stage1.LookupOutput")?; + let eq_eval = EqPolynomial::::mle(&opening_point, r_spartan); + let gamma = store.scalar("stage2.instruction_lookup.gamma")?; + let gamma_sqr = gamma.square(); + let gamma_cub = gamma_sqr * gamma; + let gamma_quart = gamma_sqr.square(); + let weighted = eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LookupOutput", + )? + gamma + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand", + )? + + gamma_sqr + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand", + )? + + gamma_cub + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput", + )? + + gamma_quart + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput", + )?; + Ok(eq_eval * weighted) +} + +fn expected_ram_raf( + _store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[F], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let ram = ram.ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_ram_raf_evaluation", + input: "ram", + })?; + let address = reverse_slice(local_point); + let unmap = unmap_eval(ram.log_k, ram.start_address, &address); + Ok(unmap * eval_by_name(evals, "stage2.ram_raf.eval.RamRa")?) +} + +fn expected_ram_output( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[F], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let ram = ram.ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_ram_output_check", + input: "ram", + })?; + let layout = ram + .output_layout + .ok_or(Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_ram_output_check", + input: "ram.output_layout", + })?; + let r_address = store.point("stage2.ram_output.r_address")?; + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle(r_address, &opening_point); + let io_mask = range_mask_eval(layout.io_start, layout.io_end, &opening_point); + let val_io = sparse_final_ram_eval( + ram.final_ram, + layout.io_start, + layout.io_end, + &opening_point, + ); + let val_final = eval_by_name(evals, "stage2.ram_output.eval.RamValFinal")?; + Ok(eq_eval * io_mask * (val_final - val_io)) +} + +fn eval_by_name( + evals: &[Stage2NamedEval], + name: &'static str, +) -> Result { + evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| eval.value) + .ok_or(Stage2KernelError::MissingValue { symbol: name }) +} + +fn reverse_slice(values: &[F]) -> Vec { + values.iter().rev().copied().collect() +} + +fn unmap_eval(log_k: usize, start_address: u64, point: &[F]) -> F { + point + .iter() + .enumerate() + .fold(F::from_u64(start_address), |acc, (index, &value)| { + acc + value.mul_pow_2(log_k - 1 - index).mul_u64(8) + }) +} + +fn range_mask_eval(start: usize, end: usize, point: &[F]) -> F { + eq_prefix_sum(end, point) - eq_prefix_sum(start, point) +} + +fn sparse_final_ram_eval(values: &[u64], start: usize, end: usize, point: &[F]) -> F { + let mut total = F::zero(); + for (offset, &value) in values[start..end].iter().enumerate() { + if value != 0 { + total += F::from_u64(value) * eq_eval_at_index(start + offset, point); + } + } + total +} + +fn ram_eval_reversed(values: &[u64], nonzero_values: &[(usize, u64)], point: &[F]) -> F { + let opening_point = reverse_slice(point); + if nonzero_values.len() * opening_point.len() <= values.len() { + nonzero_values + .iter() + .map(|&(index, value)| F::from_u64(value) * eq_eval_at_index(index, &opening_point)) + .sum() + } else { + let eq = EqPolynomial::::evals(&opening_point, None); + values + .par_iter() + .zip(eq.par_iter()) + .fold(F::Accumulator::default, |mut total, (&value, &weight)| { + if value != 0 { + total.fmadd(F::from_u64(value), weight); + } + total + }) + .reduce(F::Accumulator::default, |mut left, right| { + left.merge(right); + left + }) + .reduce() + } +} + +fn eq_prefix_sum(end: usize, point: &[F]) -> F { + let domain_len = 1usize << point.len(); + if end >= domain_len { + return F::one(); + } + let mut sum = F::zero(); + let mut prefix = F::one(); + for (bit, &r) in point.iter().enumerate() { + let mask = 1usize << (point.len() - 1 - bit); + if end & mask == 0 { + prefix *= F::one() - r; + } else { + sum += prefix * (F::one() - r); + prefix *= r; + } + } + sum +} + +fn eq_eval_at_index(index: usize, point: &[F]) -> F { + point.iter().enumerate().fold(F::one(), |acc, (bit, &r)| { + let mask = 1usize << (point.len() - 1 - bit); + if index & mask == 0 { + acc * (F::one() - r) + } else { + acc * r + } + }) +} + +const PRODUCT_REMAINDER_EVAL_NAMES: [(&str, &str); 8] = [ + ( + "stage2.product_virtual.remainder.eval.LeftInstructionInput", + "LeftInstructionInput", + ), + ( + "stage2.product_virtual.remainder.eval.RightInstructionInput", + "RightInstructionInput", + ), + ( + "stage2.product_virtual.remainder.eval.OpFlagJump", + "OpFlagJump", + ), + ( + "stage2.product_virtual.remainder.eval.OpFlagWriteLookupOutputToRD", + "OpFlagWriteLookupOutputToRD", + ), + ( + "stage2.product_virtual.remainder.eval.LookupOutput", + "LookupOutput", + ), + ( + "stage2.product_virtual.remainder.eval.InstructionFlagBranch", + "InstructionFlagBranch", + ), + ( + "stage2.product_virtual.remainder.eval.NextIsNoop", + "NextIsNoop", + ), + ( + "stage2.product_virtual.remainder.eval.OpFlagVirtualInstruction", + "OpFlagVirtualInstruction", + ), +]; + +const INSTRUCTION_LOOKUP_EVAL_NAMES: [(&str, &str); 5] = [ + ( + "stage2.instruction_lookup.claim_reduction.eval.LookupOutput", + "LookupOutput", + ), + ( + "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand", + "LeftLookupOperand", + ), + ( + "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand", + "RightLookupOperand", + ), + ( + "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput", + "LeftInstructionInput", + ), + ( + "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput", + "RightInstructionInput", + ), +]; + +fn product_remainder_final_evals( + cycles: &[Stage2ProductVirtualCycle], + point: &[F], + relation: Stage2Relation, +) -> Result>, Stage2KernelError> { + let values = product_remainder_output_evals(cycles, point, relation)?; + Ok(PRODUCT_REMAINDER_EVAL_NAMES + .iter() + .zip(values) + .map(|(&(name, oracle), value)| named_eval(name, oracle, value)) + .collect()) +} + +fn product_remainder_output_evals( + cycles: &[Stage2ProductVirtualCycle], + point: &[F], + relation: Stage2Relation, +) -> Result<[F; 8], Stage2KernelError> { + let expected = + 1usize + .checked_shl(point.len() as u32) + .ok_or(Stage2KernelError::InvalidInputLength { + input: "stage2.product_remainder_output.point", + expected: usize::BITS as usize, + actual: point.len(), + })?; + if cycles.len() != expected { + return Err(Stage2KernelError::InvalidInputLength { + input: relation.symbol(), + expected, + actual: cycles.len(), + }); + } + let opening_point = reverse_slice(point); + let (r_hi, r_lo) = opening_point.split_at(opening_point.len() / 2); + let (eq_out, eq_in) = rayon::join( + || EqPolynomial::::evals(r_hi, None), + || EqPolynomial::::evals(r_lo, None), + ); + let accumulators = (0..eq_out.len()) + .into_par_iter() + .map(|x_out| { + let start = x_out * eq_in.len(); + let mut inner = [F::Accumulator::default(); 8]; + for (x_in, &weight) in eq_in.iter().enumerate() { + accumulate_product_remainder_outputs(&mut inner, &cycles[start + x_in], weight); + } + let mut outer = [F::Accumulator::default(); 8]; + for (outer, inner) in outer.iter_mut().zip(inner) { + outer.fmadd(inner.reduce(), eq_out[x_out]); + } + outer + }) + .reduce( + || [F::Accumulator::default(); 8], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + Ok(accumulators.map(FieldAccumulator::reduce)) +} + +fn accumulate_product_remainder_outputs( + accumulators: &mut [F::Accumulator; 8], + cycle: &Stage2ProductVirtualCycle, + weight: F, +) { + accumulators[0].fmadd_u64(weight, cycle.instruction_left_input); + accumulators[1].fmadd(weight, F::from_i128(cycle.instruction_right_input)); + accumulators[2].fmadd_bool(weight, cycle.jump_flag); + accumulators[3].fmadd_bool(weight, cycle.write_lookup_output_to_rd_flag); + accumulators[4].fmadd_u64(weight, cycle.should_branch_lookup_output); + accumulators[5].fmadd_bool(weight, cycle.should_branch_flag); + accumulators[6].fmadd_bool(weight, !cycle.not_next_noop); + accumulators[7].fmadd_bool(weight, cycle.virtual_instruction_flag); +} + +fn accumulate_instruction_lookup_outputs( + accumulators: &mut [F::Accumulator; 5], + cycle: &Stage2InstructionLookupCycle, + weight: F, +) { + accumulators[0].fmadd_u64(weight, cycle.lookup_output); + accumulators[1].fmadd_u64(weight, cycle.left_lookup_operand); + accumulators[2].fmadd(weight, F::from_u128(cycle.right_lookup_operand)); + accumulators[3].fmadd_u64(weight, cycle.left_instruction_input); + accumulators[4].fmadd(weight, F::from_i128(cycle.right_instruction_input)); +} + +fn named_eval(name: &'static str, oracle: &'static str, value: F) -> Stage2NamedEval { + Stage2NamedEval { + name, + oracle, + value, + } +} + +fn claim_relation( + program: &'static Stage2CpuProgramPlan, + claim: &Stage2SumcheckClaimPlan, +) -> Result { + if let Some(relation) = claim.relation { + return Stage2Relation::from_symbol(relation) + .ok_or(Stage2KernelError::UnknownRelation { relation }); + } + let kernel_symbol = claim.kernel.ok_or(Stage2KernelError::MissingKernel { + driver: claim.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or(Stage2KernelError::MissingKernel { + driver: claim.symbol, + kernel: kernel_symbol, + })?; + kernel.relation_kind() +} + +fn instance_round_offset( + program: &'static Stage2CpuProgramPlan, + driver: &'static str, + claim: &'static str, +) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.source == driver && instance.claim == claim) + .map(|instance| instance.round_offset) + .ok_or(Stage2KernelError::MissingClaim { + batch: driver, + claim, + }) +} + +fn combine_univariate_polys( + polynomials: &[UnivariatePoly], + coefficients: &[F], +) -> UnivariatePoly { + let max_len = polynomials + .iter() + .map(|poly| poly.coefficients().len()) + .max() + .unwrap_or(0); + let mut combined = vec![F::zero(); max_len]; + for (poly, &coefficient) in polynomials.iter().zip(coefficients) { + for (combined, &term) in combined.iter_mut().zip(poly.coefficients()) { + *combined += term * coefficient; + } + } + trim_trailing_zero_coefficients(UnivariatePoly::new(combined), 3) +} + +fn trim_trailing_zero_coefficients( + polynomial: UnivariatePoly, + min_len: usize, +) -> UnivariatePoly { + let mut coefficients = polynomial.into_coefficients(); + while coefficients.len() > min_len && coefficients.last() == Some(&F::zero()) { + let _ = coefficients.pop(); + } + UnivariatePoly::new(coefficients) +} + +fn round_poly_from_factors(factors: &[Vec], degree: usize) -> UnivariatePoly { + let factor_slices = factors.iter().map(Vec::as_slice).collect::>(); + round_poly_from_factor_slices(&factor_slices, degree) +} + +fn round_poly_from_factor_slices(factors: &[&[F]], degree: usize) -> UnivariatePoly { + if factors.is_empty() { + return UnivariatePoly::zero(); + } + let half = factors[0].len() / 2; + let accumulators = if half >= DENSE_BIND_PAR_THRESHOLD { + (0..half) + .into_par_iter() + .map(|row| dense_product_coefficients(factors, row)) + .reduce( + || [F::Accumulator::default(); 4], + |mut left, right| { + for index in 0..left.len() { + left[index].merge(right[index]); + } + left + }, + ) + } else { + (0..half).fold([F::Accumulator::default(); 4], |mut total, row| { + let row_coeffs = dense_product_coefficients(factors, row); + for index in 0..total.len() { + total[index].merge(row_coeffs[index]); + } + total + }) + }; + UnivariatePoly::new( + accumulators[..=degree] + .iter() + .copied() + .map(FieldAccumulator::reduce) + .collect(), + ) +} + +fn dense_product_coefficients(factors: &[&[F]], row: usize) -> [F::Accumulator; 4] { + if factors.len() == 2 { + let left0 = factors[0][2 * row]; + let left_delta = factors[0][2 * row + 1] - left0; + let right0 = factors[1][2 * row]; + let right_delta = factors[1][2 * row + 1] - right0; + let mut accumulators = [F::Accumulator::default(); 4]; + accumulators[0].fmadd(left0, right0); + accumulators[1].fmadd(left_delta, right0); + accumulators[1].fmadd(left0, right_delta); + accumulators[2].fmadd(left_delta, right_delta); + return accumulators; + } + if factors.len() == 3 { + let first0 = factors[0][2 * row]; + let first_delta = factors[0][2 * row + 1] - first0; + let second0 = factors[1][2 * row]; + let second_delta = factors[1][2 * row + 1] - second0; + let third0 = factors[2][2 * row]; + let third_delta = factors[2][2 * row + 1] - third0; + let mut accumulators = [F::Accumulator::default(); 4]; + accumulate_cubic_product_coefficients( + &mut accumulators, + first0, + first_delta, + second0, + second_delta, + third0, + third_delta, + ); + return accumulators; + } + + let mut coefficients = [F::zero(); 4]; + coefficients[0] = F::one(); + for (degree, factor) in factors.iter().enumerate() { + let low = factor[2 * row]; + let delta = factor[2 * row + 1] - low; + for index in (0..=degree).rev() { + coefficients[index + 1] += coefficients[index] * delta; + coefficients[index] *= low; + } + } + let mut accumulators = [F::Accumulator::default(); 4]; + for (accumulator, coefficient) in accumulators.iter_mut().zip(coefficients) { + accumulator.acc_add(coefficient); + } + accumulators +} + +fn accumulate_cubic_product_coefficients( + coefficients: &mut [F::Accumulator; 4], + first0: F, + first_delta: F, + second0: F, + second_delta: F, + third0: F, + third_delta: F, +) { + let second0_third0 = second0 * third0; + let second_delta_third0 = second_delta * third0; + let second0_third_delta = second0 * third_delta; + let second_delta_third_delta = second_delta * third_delta; + + coefficients[0].fmadd(first0, second0_third0); + coefficients[1].fmadd(first_delta, second0_third0); + coefficients[1].fmadd(first0, second_delta_third0); + coefficients[1].fmadd(first0, second0_third_delta); + coefficients[2].fmadd(first_delta, second_delta_third0); + coefficients[2].fmadd(first_delta, second0_third_delta); + coefficients[2].fmadd(first0, second_delta_third_delta); + coefficients[3].fmadd(first_delta, second_delta_third_delta); +} + +fn line(lo: F, hi: F, x: F) -> F { + lo + x * (hi - lo) +} + +fn product_uniskip_base_evals( + store: &Stage2ValueStore, +) -> Result<[F; PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE], Stage2KernelError> { + Ok([ + store.scalar("stage2.input.stage1.Product")?, + store.scalar("stage2.input.stage1.ShouldBranch")?, + store.scalar("stage2.input.stage1.ShouldJump")?, + ]) +} + +#[tracing::instrument(skip_all, name = "stage2_product_virtual_uniskip_extended_evals")] +pub fn product_virtual_uniskip_extended_evals( + cycles: &[Stage2ProductVirtualCycle], + tau_low: &[Fr], +) -> Result<[Fr; PRODUCT_VIRTUAL_UNISKIP_DEGREE], Stage2KernelError> { + let expected = + 1usize + .checked_shl(tau_low.len() as u32) + .ok_or(Stage2KernelError::InvalidInputLength { + input: "stage2.product_virtual.tau_low", + expected: usize::BITS as usize, + actual: tau_low.len(), + })?; + if cycles.len() != expected { + return Err(Stage2KernelError::InvalidInputLength { + input: "stage2.product_virtual.cycles", + expected, + actual: cycles.len(), + }); + } + + let eq_evals = EqPolynomial::::evals(tau_low, None); + let accumulators = eq_evals + .par_iter() + .zip(cycles.par_iter()) + .fold( + || [FrSignedScalarAccumulator::zero(); PRODUCT_VIRTUAL_UNISKIP_DEGREE], + |mut local, (&weight, cycle)| { + for (target, accumulator) in local.iter_mut().enumerate() { + accumulator.fmadd_s256( + weight, + product_virtual_extended_fused_product(cycle, target), + ); + } + local + }, + ) + .reduce( + || [FrSignedScalarAccumulator::zero(); PRODUCT_VIRTUAL_UNISKIP_DEGREE], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + + Ok(accumulators.map(FrSignedScalarAccumulator::reduce)) +} + +fn product_virtual_extended_fused_product( + cycle: &Stage2ProductVirtualCycle, + target: usize, +) -> S256 { + let coefficients = PRODUCT_VIRTUAL_UNISKIP_TARGET_COEFFS[target]; + let left = coefficients[0] as i128 * cycle.instruction_left_input as i128 + + coefficients[1] as i128 * cycle.should_branch_lookup_output as i128 + + coefficients[2] as i128 * i128::from(cycle.jump_flag); + let right = coefficients[0] as i128 * cycle.instruction_right_input + + coefficients[1] as i128 * i128::from(cycle.should_branch_flag) + + coefficients[2] as i128 * i128::from(cycle.not_next_noop); + S128::from_i128(left).mul_trunc::<2, 4>(&S128::from_i128(right)) +} + +#[derive(Clone, Copy)] +struct FrSignedScalarAccumulator { + positive: Limbs<9>, + negative: Limbs<9>, +} + +impl FrSignedScalarAccumulator { + fn zero() -> Self { + Self { + positive: Limbs::zero(), + negative: Limbs::zero(), + } + } + + fn fmadd_s256(&mut self, field: Fr, scalar: S256) { + if scalar.magnitude_limbs() == [0u64; 4] { + return; + } + self.fmadd_limbs(field, scalar.as_magnitude(), scalar.is_positive); + } + + fn fmadd_limbs(&mut self, field: Fr, scalar: &Limbs, is_positive: bool) { + let mut product = Limbs::<9>::zero(); + product.fmadd::<4, L>(&field.inner_limbs(), scalar); + if is_positive { + self.positive.add_assign_trunc::<9>(&product); + } else { + self.negative.add_assign_trunc::<9>(&product); + } + } + + fn merge(&mut self, other: Self) { + self.positive.add_assign_trunc::<9>(&other.positive); + self.negative.add_assign_trunc::<9>(&other.negative); + } + + fn reduce(self) -> Fr { + match self.positive.cmp(&self.negative) { + Ordering::Greater | Ordering::Equal => { + Fr::from_barrett_reduced_limbs(self.positive.sub_trunc::<9, 9>(&self.negative)) + } + Ordering::Less => { + -Fr::from_barrett_reduced_limbs(self.negative.sub_trunc::<9, 9>(&self.positive)) + } + } + } +} + +fn build_product_uniskip_poly( + base_evals: &[F; PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE], + extended_evals: &[F], + tau_high: F, +) -> Result, Stage2KernelError> { + if extended_evals.len() != PRODUCT_VIRTUAL_UNISKIP_DEGREE { + return Err(Stage2KernelError::InvalidInputLength { + input: "product_uniskip_extended_evals", + expected: PRODUCT_VIRTUAL_UNISKIP_DEGREE, + actual: extended_evals.len(), + }); + } + + let mut t1_values = vec![F::zero(); PRODUCT_VIRTUAL_UNISKIP_EXTENDED_SIZE]; + for (index, value) in base_evals.iter().enumerate() { + let target = PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START + index as i64; + t1_values[(target - PRODUCT_VIRTUAL_UNISKIP_EXTENDED_START) as usize] = *value; + } + for (value, target) in extended_evals.iter().zip(product_uniskip_targets()) { + t1_values[(target - PRODUCT_VIRTUAL_UNISKIP_EXTENDED_START) as usize] = *value; + } + + let t1_coeffs = interpolate_to_coeffs(PRODUCT_VIRTUAL_UNISKIP_EXTENDED_START, &t1_values); + let lagrange_values = lagrange_evals( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + tau_high, + ); + let lagrange_coeffs = + interpolate_to_coeffs(PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, &lagrange_values); + + let mut coefficients = vec![F::zero(); PRODUCT_VIRTUAL_UNISKIP_NUM_COEFFS]; + for (i, &lagrange_coeff) in lagrange_coeffs.iter().enumerate() { + for (j, &t1_coeff) in t1_coeffs.iter().enumerate() { + coefficients[i + j] += lagrange_coeff * t1_coeff; + } + } + Ok(UnivariatePoly::new(coefficients)) +} + +fn product_uniskip_targets() -> [i64; PRODUCT_VIRTUAL_UNISKIP_DEGREE] { + [-2, 2] +} + +fn product_uniskip_sum_matches(poly: &UnivariatePoly, claim: F) -> bool { + (0..PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE) + .map(|index| { + poly.evaluate(F::from_i64( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START + index as i64, + )) + }) + .sum::() + == claim +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients() + .iter() + .rposition(|coefficient| *coefficient != F::zero()) + .unwrap_or(0) +} + +fn append_univariate_poly(transcript: &mut T, label: &'static str, poly: &UnivariatePoly) +where + F: Field, + T: Transcript, +{ + transcript.append(&LabelWithCount( + label.as_bytes(), + poly.coefficients().len() as u64, + )); + for coefficient in poly.coefficients() { + transcript.append(coefficient); + } +} + +fn append_compressed_univariate_poly( + transcript: &mut T, + label: &'static str, + poly: &UnivariatePoly, +) where + F: Field, + T: Transcript, +{ + let compressed = poly.compress(); + transcript.append(&LabelWithCount( + label.as_bytes(), + compressed.coeffs_except_linear_term().len() as u64, + )); + for coefficient in compressed.coeffs_except_linear_term() { + transcript.append(coefficient); + } +} + +fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &F) +where + F: Field, + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +fn append_opening_claims( + program: &'static Stage2CpuProgramPlan, + store: &mut Stage2ValueStore, + transcript: &mut T, + evals: &[Stage2NamedEval], +) -> Result>, Stage2KernelError> +where + F: Field, + T: Transcript, +{ + if program.opening_batches.is_empty() { + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } + return Ok(Vec::new()); + } + let _ = store.evaluate_available_points(program)?; + let mut opening_claims = Vec::new(); + let mut seen = seed_stage2_opening_aliases(store, program); + for batch in program.opening_batches { + for symbol in batch.claim_operands { + let claim = + find_opening_claim(program, symbol).ok_or(Stage2KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + let point = store.point(claim.point_source)?.to_vec(); + let value = store.scalar(claim.eval_source)?; + let duplicate = has_seen_opening(&seen, claim.claim_kind, claim.oracle, &point); + if !duplicate { + append_labeled_scalar(transcript, "opening_claim", &value); + seen.push((claim.claim_kind, claim.oracle, point.clone())); + } + opening_claims.push(Stage2OpeningClaimValue { + symbol: claim.symbol, + oracle: claim.oracle, + domain: claim.domain, + claim_kind: claim.claim_kind, + point: point.clone(), + eval: value, + }); + } + } + Ok(opening_claims) +} + +fn seed_stage2_opening_aliases( + store: &Stage2ValueStore, + program: &'static Stage2CpuProgramPlan, +) -> Vec<(&'static str, &'static str, Vec)> { + program + .opening_inputs + .iter() + .filter_map(|input| { + store + .try_point(input.symbol) + .map(|point| (input.claim_kind, input.oracle, point.to_vec())) + }) + .collect() +} + +fn has_seen_opening( + seen: &[(&'static str, &'static str, Vec)], + claim_kind: &'static str, + oracle: &'static str, + point: &[F], +) -> bool { + seen.iter().any(|(seen_kind, seen_oracle, seen_point)| { + *seen_kind == claim_kind && *seen_oracle == oracle && seen_point.as_slice() == point + }) +} + +fn driver_evals(context: Stage2KernelContext<'_>, value: F) -> Vec> { + context + .program + .evals_for_driver(context.driver.symbol) + .map(|eval| Stage2NamedEval { + name: eval.name, + oracle: eval.oracle, + value, + }) + .collect() +} + +fn verify_driver_evals( + driver: &'static str, + expected: &[Stage2NamedEval], + actual: &[Stage2NamedEval], +) -> Result<(), Stage2KernelError> { + if expected.len() != actual.len() { + return Err(Stage2KernelError::InvalidProof { + driver, + reason: "product uniskip eval count mismatch", + }); + } + for (expected, actual) in expected.iter().zip(actual) { + if expected.name != actual.name + || expected.oracle != actual.oracle + || expected.value != actual.value + { + return Err(Stage2KernelError::InvalidProof { + driver, + reason: "product uniskip eval mismatch", + }); + } + } + Ok(()) +} + +pub fn execute_stage2_program( + program: &'static Stage2CpuProgramPlan, + mode: Stage2ExecutionMode, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + F: Field, + E: Stage2KernelExecutor, + T: Transcript, +{ + verify_static_program_shape(program)?; + let mut artifacts = Stage2ExecutionArtifacts::default(); + if program.steps.is_empty() { + for squeeze in program.transcript_squeezes { + execute_stage2_squeeze(squeeze, executor, transcript, &mut artifacts)?; + } + for driver in program.drivers { + execute_stage2_driver(program, mode, driver, executor, transcript, &mut artifacts)?; + } + } else { + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = find_transcript_squeeze(program, step.symbol).ok_or( + Stage2KernelError::MissingValue { + symbol: step.symbol, + }, + )?; + execute_stage2_squeeze(squeeze, executor, transcript, &mut artifacts)?; + } + "sumcheck_driver" => { + let driver = find_driver(program, step.symbol).ok_or( + Stage2KernelError::MissingKernel { + driver: step.symbol, + kernel: step.symbol, + }, + )?; + execute_stage2_driver( + program, + mode, + driver, + executor, + transcript, + &mut artifacts, + )?; + } + _ => { + return Err(Stage2KernelError::InvalidProof { + driver: step.symbol, + reason: "unsupported stage2 program step", + }) + } + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +fn execute_stage2_squeeze( + squeeze: &'static Stage2TranscriptSqueezePlan, + executor: &mut E, + transcript: &mut T, + artifacts: &mut Stage2ExecutionArtifacts, +) -> Result<(), Stage2KernelError> +where + F: Field, + E: Stage2KernelExecutor, + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + executor.observe_challenge_vector(squeeze, &values)?; + artifacts.challenge_vectors.push(Stage2ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn execute_stage2_driver( + program: &'static Stage2CpuProgramPlan, + mode: Stage2ExecutionMode, + driver: &'static Stage2SumcheckDriverPlan, + executor: &mut E, + transcript: &mut T, + artifacts: &mut Stage2ExecutionArtifacts, +) -> Result<(), Stage2KernelError> +where + F: Field, + E: Stage2KernelExecutor, + T: Transcript, +{ + let kernel_symbol = driver.kernel.ok_or(Stage2KernelError::MissingKernel { + driver: driver.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or(Stage2KernelError::MissingKernel { + driver: driver.symbol, + kernel: kernel_symbol, + })?; + let batch = find_batch(program, driver.batch).ok_or(Stage2KernelError::MissingBatch { + driver: driver.symbol, + batch: driver.batch, + })?; + let context = Stage2KernelContext { + mode, + program, + kernel, + batch, + driver, + }; + let output = match mode { + Stage2ExecutionMode::Prover => executor.prove_sumcheck(context, transcript)?, + Stage2ExecutionMode::Verifier => executor.verify_sumcheck(context, transcript)?, + }; + executor.observe_sumcheck_output(&output)?; + artifacts + .opening_claims + .extend(output.opening_claims.clone()); + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_static_program_shape( + program: &'static Stage2CpuProgramPlan, +) -> Result<(), Stage2KernelError> { + for expr in program.field_exprs { + verify_count(expr.symbol, expr.operand_names.len(), expr.operands.len())?; + } + for batch in program.batches { + verify_count(batch.symbol, batch.count, batch.ordered_claims.len())?; + verify_count(batch.symbol, batch.count, batch.claim_operands.len())?; + } + for batch in program.opening_batches { + verify_count(batch.symbol, batch.count, batch.ordered_claims.len())?; + verify_count(batch.symbol, batch.count, batch.claim_operands.len())?; + } + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let _ = find_transcript_squeeze(program, step.symbol).ok_or( + Stage2KernelError::MissingValue { + symbol: step.symbol, + }, + )?; + } + "sumcheck_driver" => { + let _ = + find_driver(program, step.symbol).ok_or(Stage2KernelError::MissingKernel { + driver: step.symbol, + kernel: step.symbol, + })?; + } + _ => { + return Err(Stage2KernelError::InvalidProof { + driver: step.symbol, + reason: "unsupported stage2 program step", + }) + } + } + } + Ok(()) +} + +fn verify_count( + artifact: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage2KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage2KernelError::PlanCountMismatch { + artifact, + expected, + actual, + }) + } +} + +fn find_kernel<'a>( + program: &'a Stage2CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage2KernelPlan> { + program + .kernels + .iter() + .find(|kernel| kernel.symbol == symbol) +} + +fn find_batch<'a>( + program: &'a Stage2CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage2SumcheckBatchPlan> { + program.batches.iter().find(|batch| batch.symbol == symbol) +} + +fn find_driver<'a>( + program: &'a Stage2CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage2SumcheckDriverPlan> { + program + .drivers + .iter() + .find(|driver| driver.symbol == symbol) +} + +fn find_transcript_squeeze<'a>( + program: &'a Stage2CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage2TranscriptSqueezePlan> { + program + .transcript_squeezes + .iter() + .find(|squeeze| squeeze.symbol == symbol) +} + +fn find_opening_claim<'a>( + program: &'a Stage2CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage2OpeningClaimPlan> { + program + .opening_claims + .iter() + .find(|claim| claim.symbol == symbol) +} + +#[cfg(test)] +#[expect(clippy::expect_used, reason = "tests use explicit panic messages")] +mod tests { + use super::*; + use jolt_field::Fr; + use jolt_transcript::MockTranscript; + + #[test] + fn stage2_relation_and_abi_registry_is_complete() { + let relations = [ + Stage2Relation::ProductVirtualUniskip, + Stage2Relation::RamReadWrite, + Stage2Relation::ProductVirtualRemainder, + Stage2Relation::InstructionLookupClaimReduction, + Stage2Relation::RamRafEvaluation, + Stage2Relation::RamOutputCheck, + Stage2Relation::Batched, + ]; + for relation in relations { + assert_eq!( + Stage2Relation::from_symbol(relation.symbol()), + Some(relation) + ); + } + + let abis = [ + Stage2KernelAbi::ProductVirtualUniskip, + Stage2KernelAbi::RamReadWrite, + Stage2KernelAbi::ProductVirtualRemainder, + Stage2KernelAbi::InstructionLookupClaimReduction, + Stage2KernelAbi::RamRafEvaluation, + Stage2KernelAbi::RamOutputCheck, + Stage2KernelAbi::Batched, + ]; + for abi in abis { + assert_eq!(Stage2KernelAbi::from_name(abi.name()), Some(abi)); + } + } + + #[test] + fn stage2_field_exprs_match_jolt_input_claim_formulas() { + let product_expr = Stage2FieldExprPlan { + symbol: "product_expr", + kind: "weighted_sum", + formula: "jolt_stage2_product_virtual_uniskip_input", + operand_names: &[], + operands: &[], + }; + let product = evaluate_stage2_field_expr( + &product_expr, + &[ + Fr::from_u64(0), + Fr::from_u64(11), + Fr::from_u64(13), + Fr::from_u64(17), + ], + ) + .expect("product expression evaluates"); + assert_eq!(product, Fr::from_u64(13)); + + let ram_expr = Stage2FieldExprPlan { + symbol: "ram_expr", + kind: "weighted_sum", + formula: "jolt_stage2_ram_read_write_input", + operand_names: &[], + operands: &[], + }; + let ram = evaluate_stage2_field_expr( + &ram_expr, + &[Fr::from_u64(7), Fr::from_u64(11), Fr::from_u64(13)], + ) + .expect("ram expression evaluates"); + assert_eq!(ram, Fr::from_u64(102)); + + let instruction_expr = Stage2FieldExprPlan { + symbol: "instruction_expr", + kind: "weighted_sum", + formula: "jolt_stage2_instruction_lookup_input", + operand_names: &[], + operands: &[], + }; + let instruction = evaluate_stage2_field_expr( + &instruction_expr, + &[ + Fr::from_u64(2), + Fr::from_u64(1), + Fr::from_u64(3), + Fr::from_u64(5), + Fr::from_u64(7), + Fr::from_u64(11), + ], + ) + .expect("instruction expression evaluates"); + assert_eq!(instruction, Fr::from_u64(259)); + + let add_expr = Stage2FieldExprPlan { + symbol: "add_expr", + kind: "op", + formula: "field.add", + operand_names: &[], + operands: &[], + }; + let add = evaluate_stage2_field_expr(&add_expr, &[Fr::from_u64(5), Fr::from_u64(8)]) + .expect("field add evaluates"); + assert_eq!(add, Fr::from_u64(13)); + + let lagrange_expr = Stage2FieldExprPlan { + symbol: "lagrange_expr", + kind: "op", + formula: "poly.lagrange_basis_eval:-1:3:1", + operand_names: &[], + operands: &[], + }; + let weight = evaluate_stage2_field_expr(&lagrange_expr, &[Fr::from_u64(0)]) + .expect("lagrange basis evaluates"); + assert_eq!(weight, Fr::from_u64(1)); + } + + #[test] + fn value_store_resolves_challenges_openings_and_claims() { + let program = minimal_program( + vec![Stage2TranscriptSqueezePlan { + symbol: "gamma", + label: "gamma", + kind: "challenge_scalar", + count: 1, + }], + Vec::new(), + vec![Stage2FieldExprPlan { + symbol: "ram_expr", + kind: "weighted_sum", + formula: "jolt_stage2_ram_read_write_input", + operand_names: &["gamma", "read", "write"], + operands: &["gamma", "read", "write"], + }], + vec![Stage2SumcheckClaimPlan { + symbol: "claim", + stage: "stage2", + domain: "domain", + num_rounds: 1, + degree: 3, + claim: "claim", + kernel: Some("kernel"), + relation: None, + claim_value: "ram_expr", + input_openings: &["read", "write"], + }], + Vec::new(), + Vec::new(), + ); + + let mut store = Stage2ValueStore::with_opening_inputs(&[ + Stage2OpeningInputValue { + symbol: "read", + point: vec![Fr::from_u64(0)], + eval: Fr::from_u64(11), + }, + Stage2OpeningInputValue { + symbol: "write", + point: vec![Fr::from_u64(1)], + eval: Fr::from_u64(13), + }, + ]); + store + .observe_challenge_vector(program, &program.transcript_squeezes[0], &[Fr::from_u64(7)]) + .expect("challenge vector observed"); + assert_eq!( + store + .claim_value(program, &program.claims[0]) + .expect("claim value resolves"), + Fr::from_u64(102) + ); + } + + #[test] + fn value_store_records_sumcheck_instance_points_and_evals() { + let program = minimal_program( + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + vec![Stage2SumcheckInstanceResultPlan { + symbol: "instance", + source: "driver", + claim: "claim", + relation: "relation", + index: 0, + point_arity: 2, + num_rounds: 2, + round_offset: 1, + point_order: "as_is", + degree: 3, + }], + vec![Stage2SumcheckEvalPlan { + symbol: "eval.symbol", + source: "driver", + name: "eval.name", + index: 0, + oracle: "Oracle", + }], + ); + let output = Stage2SumcheckOutput { + driver: "driver", + point: vec![Fr::from_u64(3), Fr::from_u64(5), Fr::from_u64(7)], + evals: vec![Stage2NamedEval { + name: "eval.name", + oracle: "Oracle", + value: Fr::from_u64(11), + }], + opening_claims: Vec::new(), + proof: SumcheckProof::default(), + }; + let mut store = Stage2ValueStore::new(); + store + .observe_sumcheck_output(program, &output) + .expect("sumcheck output observed"); + + assert_eq!( + store.point("instance").expect("instance point"), + &[Fr::from_u64(5), Fr::from_u64(7)] + ); + assert_eq!( + store.scalar("eval.symbol").expect("eval symbol"), + Fr::from_u64(11) + ); + assert_eq!( + store.scalar("eval.name").expect("eval name"), + Fr::from_u64(11) + ); + } + + #[test] + fn product_virtual_uniskip_kernel_proves_first_round_arithmetic() { + let program = product_uniskip_program(); + let opening_inputs = [ + Stage2OpeningInputValue { + symbol: "stage2.input.stage1.Product", + point: vec![Fr::from_u64(0)], + eval: Fr::from_u64(11), + }, + Stage2OpeningInputValue { + symbol: "stage2.input.stage1.ShouldBranch", + point: vec![Fr::from_u64(0)], + eval: Fr::from_u64(13), + }, + Stage2OpeningInputValue { + symbol: "stage2.input.stage1.ShouldJump", + point: vec![Fr::from_u64(0)], + eval: Fr::from_u64(17), + }, + ]; + let extended_evals = [Fr::from_u64(23), Fr::from_u64(29)]; + let inputs = Stage2ProverInputs::new(&opening_inputs) + .with_product_uniskip_extended_evals(&extended_evals); + let mut executor = Stage2ProverKernelExecutor::new(inputs); + let mut transcript = MockTranscript::::new(b"stage2"); + + let artifacts = execute_stage2_program( + program, + Stage2ExecutionMode::Prover, + &mut executor, + &mut transcript, + ) + .expect("product uniskip proves"); + + assert_eq!(artifacts.challenge_vectors.len(), 1); + assert_eq!(artifacts.sumchecks.len(), 1); + let output = &artifacts.sumchecks[0]; + let poly = &output.proof.round_polynomials[0]; + let tau = artifacts.challenge_vectors[0].values[0]; + let input_claim = evaluate_stage2_field_expr( + &program.field_exprs[0], + &[tau, Fr::from_u64(11), Fr::from_u64(13), Fr::from_u64(17)], + ) + .expect("input claim evaluates"); + + assert_eq!( + poly.coefficients().len(), + PRODUCT_VIRTUAL_UNISKIP_NUM_COEFFS + ); + assert!(product_uniskip_sum_matches(poly, input_claim)); + assert_eq!(output.evals.len(), 1); + assert_eq!( + output.evals[0].name, + "stage2.product_virtual.uniskip.eval.UnivariateSkip" + ); + assert_eq!(output.evals[0].value, poly.evaluate(output.point[0])); + } + + #[test] + fn product_virtual_uniskip_kernel_requires_extended_evals() { + let program = product_uniskip_program(); + let opening_inputs = [ + Stage2OpeningInputValue { + symbol: "stage2.input.stage1.Product", + point: Vec::new(), + eval: Fr::from_u64(11), + }, + Stage2OpeningInputValue { + symbol: "stage2.input.stage1.ShouldBranch", + point: Vec::new(), + eval: Fr::from_u64(13), + }, + Stage2OpeningInputValue { + symbol: "stage2.input.stage1.ShouldJump", + point: Vec::new(), + eval: Fr::from_u64(17), + }, + ]; + let mut executor = + Stage2ProverKernelExecutor::new(Stage2ProverInputs::new(&opening_inputs)); + let mut transcript = MockTranscript::::new(b"stage2"); + + let error = execute_stage2_program( + program, + Stage2ExecutionMode::Prover, + &mut executor, + &mut transcript, + ) + .expect_err("missing extended evals are rejected"); + + assert_eq!( + error, + Stage2KernelError::MissingKernelInput { + kernel: "jolt_stage2_product_virtual_uniskip", + input: "product_uniskip_extended_evals", + } + ); + } + + #[test] + fn product_virtual_uniskip_extended_evals_use_trace_cycle_data() { + let cycles = [ + Stage2ProductVirtualCycle { + instruction_left_input: 7, + instruction_right_input: -3, + should_branch_lookup_output: 11, + write_lookup_output_to_rd_flag: true, + jump_flag: false, + should_branch_flag: true, + not_next_noop: true, + virtual_instruction_flag: false, + }, + Stage2ProductVirtualCycle { + instruction_left_input: 13, + instruction_right_input: 5, + should_branch_lookup_output: 17, + write_lookup_output_to_rd_flag: false, + jump_flag: true, + should_branch_flag: false, + not_next_noop: false, + virtual_instruction_flag: true, + }, + ]; + let tau_low = [Fr::from_u64(19)]; + let evals = product_virtual_uniskip_extended_evals(&cycles, &tau_low) + .expect("extended evals compute"); + + let eq = EqPolynomial::::evals(&tau_low, None); + let expected = core::array::from_fn(|target| { + eq.iter() + .zip(&cycles) + .map(|(&weight, cycle)| { + let product = product_virtual_extended_fused_product(cycle, target); + let mut accumulator = FrSignedScalarAccumulator::zero(); + accumulator.fmadd_s256(weight, product); + accumulator.reduce() + }) + .sum::() + }); + assert_eq!(evals, expected); + } + + #[test] + fn product_virtual_witness_builder_uses_product_opening_point() { + let cycles = [ + Stage2ProductVirtualCycle { + instruction_left_input: 7, + instruction_right_input: -3, + should_branch_lookup_output: 11, + write_lookup_output_to_rd_flag: true, + jump_flag: false, + should_branch_flag: true, + not_next_noop: true, + virtual_instruction_flag: false, + }, + Stage2ProductVirtualCycle { + instruction_left_input: 13, + instruction_right_input: 5, + should_branch_lookup_output: 17, + write_lookup_output_to_rd_flag: false, + jump_flag: true, + should_branch_flag: false, + not_next_noop: false, + virtual_instruction_flag: true, + }, + ]; + let opening_inputs = [Stage2OpeningInputValue { + symbol: "stage2.input.stage1.Product", + point: vec![Fr::from_u64(19)], + eval: Fr::from_u64(11), + }]; + let expected = product_virtual_uniskip_extended_evals(&cycles, &[Fr::from_u64(19)]) + .expect("extended evals compute"); + + let inputs = Stage2ProverInputs::new(&opening_inputs) + .with_product_virtual_witness(&cycles) + .expect("builder derives product virtual witness"); + + assert_eq!(inputs.product_virtual_cycles, Some(cycles.as_slice())); + assert_eq!( + inputs + .product_uniskip_extended_evals + .as_deref() + .expect("extended evals"), + expected.as_slice() + ); + } + + fn minimal_program( + transcript_squeezes: Vec, + field_constants: Vec, + field_exprs: Vec, + claims: Vec, + instance_results: Vec, + evals: Vec, + ) -> &'static Stage2CpuProgramPlan { + Box::leak(Box::new(Stage2CpuProgramPlan { + params: Stage2Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }, + steps: &[], + transcript_squeezes: leak_slice(transcript_squeezes), + opening_inputs: &[], + field_constants: leak_slice(field_constants), + field_exprs: leak_slice(field_exprs), + kernels: &[], + claims: leak_slice(claims), + batches: &[], + drivers: &[], + instance_results: leak_slice(instance_results), + evals: leak_slice(evals), + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_batches: &[], + })) + } + + fn leak_slice(values: Vec) -> &'static [T] { + Box::leak(values.into_boxed_slice()) + } + + fn product_uniskip_program() -> &'static Stage2CpuProgramPlan { + Box::leak(Box::new(Stage2CpuProgramPlan { + params: Stage2Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }, + steps: &[], + transcript_squeezes: leak_slice(vec![Stage2TranscriptSqueezePlan { + symbol: "stage2.product_virtual.tau_high", + label: "product_virtual_tau_high", + kind: "challenge_scalar", + count: 1, + }]), + opening_inputs: &[], + field_constants: &[], + field_exprs: leak_slice(vec![Stage2FieldExprPlan { + symbol: "stage2.product_virtual.uniskip.claim_expr", + kind: "weighted_sum", + formula: "jolt_stage2_product_virtual_uniskip_input", + operand_names: &[ + "stage2.product_virtual.tau_high", + "stage2.input.stage1.Product", + "stage2.input.stage1.ShouldBranch", + "stage2.input.stage1.ShouldJump", + ], + operands: &[ + "stage2.product_virtual.tau_high", + "stage2.input.stage1.Product", + "stage2.input.stage1.ShouldBranch", + "stage2.input.stage1.ShouldJump", + ], + }]), + kernels: leak_slice(vec![Stage2KernelPlan { + symbol: "jolt.cpu.stage2.product_virtual.uniskip", + relation: "jolt.stage2.product_virtual.uniskip", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage2_product_virtual_uniskip", + }]), + claims: leak_slice(vec![Stage2SumcheckClaimPlan { + symbol: "stage2.product_virtual.uniskip.input", + stage: "stage2", + domain: "jolt.stage2_uniskip_domain", + num_rounds: 1, + degree: 6, + claim: "stage2.product_virtual.weighted_stage1_outputs", + kernel: Some("jolt.cpu.stage2.product_virtual.uniskip"), + relation: None, + claim_value: "stage2.product_virtual.uniskip.claim_expr", + input_openings: &[ + "stage2.input.stage1.Product", + "stage2.input.stage1.ShouldBranch", + "stage2.input.stage1.ShouldJump", + ], + }]), + batches: leak_slice(vec![Stage2SumcheckBatchPlan { + symbol: "stage2.product_virtual.uniskip.batch", + stage: "stage2", + proof_slot: "stage2.product_virtual.uni_skip_first_round", + policy: "single_instance", + count: 1, + ordered_claims: &["stage2.product_virtual.uniskip.input"], + claim_operands: &["stage2.product_virtual.uniskip.input"], + claim_label: "uniskip_claim", + round_label: "uniskip_poly", + round_schedule: &[1], + }]), + drivers: leak_slice(vec![Stage2SumcheckDriverPlan { + symbol: "stage2.product_virtual.uniskip.sumcheck", + stage: "stage2", + proof_slot: "stage2.product_virtual.uni_skip_first_round", + kernel: Some("jolt.cpu.stage2.product_virtual.uniskip"), + relation: None, + batch: "stage2.product_virtual.uniskip.batch", + policy: "univariate_skip", + round_schedule: &[1], + claim_label: "uniskip_claim", + round_label: "uniskip_poly", + num_rounds: 1, + degree: 6, + }]), + instance_results: &[], + evals: leak_slice(vec![Stage2SumcheckEvalPlan { + symbol: "stage2.product_virtual.uniskip.eval.UnivariateSkip", + source: "stage2.product_virtual.uniskip.sumcheck", + name: "stage2.product_virtual.uniskip.eval.UnivariateSkip", + index: 0, + oracle: "UnivariateSkip", + }]), + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_batches: &[], + })) + } +} diff --git a/crates/jolt-kernels/src/stage3.rs b/crates/jolt-kernels/src/stage3.rs new file mode 100644 index 0000000000..c0d90870f5 --- /dev/null +++ b/crates/jolt-kernels/src/stage3.rs @@ -0,0 +1,4223 @@ +//! Stage 3 coarse-kernel ABI used by Bolt-generated Jolt prover code. + +#![expect( + clippy::too_many_arguments, + reason = "kernel constructors mirror generated staged protocol inputs" +)] + +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use crate::dense::DENSE_BIND_PAR_THRESHOLD; +use crate::split_eq::SplitEqState; +use jolt_field::{Field, FieldAccumulator}; +use jolt_poly::{EqPlusOnePolynomial, EqPlusOnePrefixSuffix, EqPolynomial, UnivariatePoly}; +use jolt_sumcheck::SumcheckProof; +use jolt_transcript::{Label, LabelWithCount, Transcript}; +use rayon::prelude::*; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage3ExecutionMode { + Prover, + Verifier, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage3Relation { + SpartanShift, + InstructionInput, + RegistersClaimReduction, + Batched, +} + +impl Stage3Relation { + pub fn from_symbol(symbol: &str) -> Option { + match symbol { + "jolt.stage3.spartan_shift" => Some(Self::SpartanShift), + "jolt.stage3.instruction_input" => Some(Self::InstructionInput), + "jolt.stage3.registers_claim_reduction" => Some(Self::RegistersClaimReduction), + "jolt.stage3.batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn symbol(self) -> &'static str { + match self { + Self::SpartanShift => "jolt.stage3.spartan_shift", + Self::InstructionInput => "jolt.stage3.instruction_input", + Self::RegistersClaimReduction => "jolt.stage3.registers_claim_reduction", + Self::Batched => "jolt.stage3.batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage3KernelAbi { + SpartanShift, + InstructionInput, + RegistersClaimReduction, + Batched, +} + +impl Stage3KernelAbi { + pub fn from_name(name: &str) -> Option { + match name { + "jolt_stage3_spartan_shift" => Some(Self::SpartanShift), + "jolt_stage3_instruction_input" => Some(Self::InstructionInput), + "jolt_stage3_registers_claim_reduction" => Some(Self::RegistersClaimReduction), + "jolt_stage3_batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn name(self) -> &'static str { + match self { + Self::SpartanShift => "jolt_stage3_spartan_shift", + Self::InstructionInput => "jolt_stage3_instruction_input", + Self::RegistersClaimReduction => "jolt_stage3_registers_claim_reduction", + Self::Batched => "jolt_stage3_batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +impl Stage3KernelPlan { + pub fn relation_kind(&self) -> Result { + Stage3Relation::from_symbol(self.relation).ok_or(Stage3KernelError::UnknownRelation { + relation: self.relation, + }) + } + + pub fn abi_kind(&self) -> Result { + Stage3KernelAbi::from_name(self.abi) + .ok_or(Stage3KernelError::UnknownKernelAbi { abi: self.abi }) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operand_names: &'static [&'static str], + pub operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3OpeningClaimEqualityPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub lhs: &'static str, + pub rhs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3CpuProgramPlan { + pub params: Stage3Params, + pub steps: &'static [Stage3ProgramStepPlan], + pub transcript_squeezes: &'static [Stage3TranscriptSqueezePlan], + pub opening_inputs: &'static [Stage3OpeningInputPlan], + pub field_constants: &'static [Stage3FieldConstantPlan], + pub field_exprs: &'static [Stage3FieldExprPlan], + pub kernels: &'static [Stage3KernelPlan], + pub claims: &'static [Stage3SumcheckClaimPlan], + pub batches: &'static [Stage3SumcheckBatchPlan], + pub drivers: &'static [Stage3SumcheckDriverPlan], + pub instance_results: &'static [Stage3SumcheckInstanceResultPlan], + pub evals: &'static [Stage3SumcheckEvalPlan], + pub point_slices: &'static [Stage3PointSlicePlan], + pub point_concats: &'static [Stage3PointConcatPlan], + pub opening_claims: &'static [Stage3OpeningClaimPlan], + pub opening_equalities: &'static [Stage3OpeningClaimEqualityPlan], + pub opening_batches: &'static [Stage3OpeningBatchPlan], +} + +impl Stage3CpuProgramPlan { + pub fn kernel(&self, symbol: &str) -> Option<&Stage3KernelPlan> { + find_kernel(self, symbol) + } + + pub fn batch(&self, symbol: &str) -> Option<&Stage3SumcheckBatchPlan> { + find_batch(self, symbol) + } + + pub fn claim(&self, symbol: &str) -> Option<&Stage3SumcheckClaimPlan> { + self.claims.iter().find(|claim| claim.symbol == symbol) + } + + pub fn instance_results_for_driver( + &self, + driver: &'static str, + ) -> impl Iterator { + self.instance_results + .iter() + .filter(move |instance| instance.source == driver) + } + + pub fn evals_for_driver( + &self, + driver: &'static str, + ) -> impl Iterator { + self.evals.iter().filter(move |eval| eval.source == driver) + } +} + +#[derive(Clone, Debug)] +pub struct Stage3NamedEval { + pub name: &'static str, + pub oracle: &'static str, + pub value: F, +} + +#[derive(Clone, Debug)] +pub struct Stage3SumcheckOutput { + pub driver: &'static str, + pub point: Vec, + pub evals: Vec>, + pub opening_claims: Vec>, + pub proof: SumcheckProof, +} + +#[derive(Clone, Debug)] +pub struct Stage3ChallengeVector { + pub symbol: &'static str, + pub values: Vec, +} + +#[derive(Clone, Debug)] +pub struct Stage3OpeningClaimValue { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub claim_kind: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Debug)] +pub struct Stage3ExecutionArtifacts { + pub challenge_vectors: Vec>, + pub sumchecks: Vec>, + pub opening_claims: Vec>, + pub opening_batches: Vec<&'static Stage3OpeningBatchPlan>, +} + +impl Default for Stage3ExecutionArtifacts { + fn default() -> Self { + Self { + challenge_vectors: Vec::new(), + sumchecks: Vec::new(), + opening_claims: Vec::new(), + opening_batches: Vec::new(), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stage3Proof { + pub sumchecks: Vec>, +} + +impl From> for Stage3Proof { + fn from(artifacts: Stage3ExecutionArtifacts) -> Self { + Self { + sumchecks: artifacts.sumchecks, + } + } +} + +#[derive(Clone, Debug)] +pub struct Stage3ScalarValue { + pub symbol: &'static str, + pub value: F, +} + +#[derive(Clone, Debug)] +pub struct Stage3PointValue { + pub symbol: &'static str, + pub point: Vec, +} + +#[derive(Clone, Debug)] +pub struct Stage3OpeningInputValue { + pub symbol: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage3Cycle { + pub unexpanded_pc: u64, + pub pc: u64, + pub is_virtual: bool, + pub is_first_in_sequence: bool, + pub is_noop: bool, + pub left_operand_is_rs1: bool, + pub rs1_value: u64, + pub left_operand_is_pc: bool, + pub right_operand_is_rs2: bool, + pub rs2_value: u64, + pub right_operand_is_imm: bool, + pub imm: i128, + pub rd_write_value: u64, +} + +impl Stage3Cycle { + pub fn padding() -> Self { + Self { + unexpanded_pc: 0, + pc: 0, + is_virtual: false, + is_first_in_sequence: false, + is_noop: true, + left_operand_is_rs1: false, + rs1_value: 0, + left_operand_is_pc: false, + right_operand_is_rs2: false, + rs2_value: 0, + right_operand_is_imm: false, + imm: 0, + rd_write_value: 0, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stage3ValueStore { + scalars: Vec>, + points: Vec>, +} + +impl Stage3ValueStore { + pub fn new() -> Self { + Self::default() + } + + pub fn with_opening_inputs(inputs: &[Stage3OpeningInputValue]) -> Self { + let mut store = Self::new(); + store.insert_opening_inputs(inputs); + store + } + + pub fn insert_opening_inputs(&mut self, inputs: &[Stage3OpeningInputValue]) { + for input in inputs { + self.insert_scalar(input.symbol, input.eval); + self.insert_point(input.symbol, input.point.clone()); + } + } + + pub fn insert_scalar(&mut self, symbol: &'static str, value: F) { + if let Some(existing) = self + .scalars + .iter_mut() + .find(|existing| existing.symbol == symbol) + { + existing.value = value; + } else { + self.scalars.push(Stage3ScalarValue { symbol, value }); + } + } + + pub fn insert_point(&mut self, symbol: &'static str, point: Vec) { + if let Some(existing) = self + .points + .iter_mut() + .find(|existing| existing.symbol == symbol) + { + existing.point = point; + } else { + self.points.push(Stage3PointValue { symbol, point }); + } + } + + pub fn try_scalar(&self, symbol: &str) -> Option { + self.scalars + .iter() + .find(|value| value.symbol == symbol) + .map(|value| value.value) + } + + pub fn scalar(&self, symbol: &'static str) -> Result { + self.try_scalar(symbol) + .ok_or(Stage3KernelError::MissingValue { symbol }) + } + + pub fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.points + .iter() + .find(|value| value.symbol == symbol) + .map(|value| value.point.as_slice()) + } + + pub fn point(&self, symbol: &'static str) -> Result<&[F], Stage3KernelError> { + self.try_point(symbol) + .ok_or(Stage3KernelError::MissingValue { symbol }) + } + + pub fn seed_constants( + &mut self, + program: &'static Stage3CpuProgramPlan, + ) -> Result<(), Stage3KernelError> { + for constant in program.field_constants { + self.insert_scalar(constant.symbol, F::from_u64(constant.value as u64)); + } + Ok(()) + } + + pub fn observe_challenge_vector( + &mut self, + plan: &'static Stage3TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage3KernelError> { + if matches!(plan.kind, "challenge_scalar" | "scalar") { + require_operand_count(plan.symbol, 1, values.len())?; + self.insert_scalar(plan.symbol, values[0]); + } + self.insert_point(plan.symbol, values.to_vec()); + Ok(()) + } + + pub fn observe_sumcheck_output( + &mut self, + program: &'static Stage3CpuProgramPlan, + output: &Stage3SumcheckOutput, + ) -> Result<(), Stage3KernelError> { + self.observe_sumcheck_values(program, output.driver, &output.point, &output.evals) + } + + pub fn observe_sumcheck_values( + &mut self, + program: &'static Stage3CpuProgramPlan, + driver: &'static str, + point: &[F], + evals: &[Stage3NamedEval], + ) -> Result<(), Stage3KernelError> { + self.insert_point(driver, point.to_vec()); + for instance in program.instance_results_for_driver(driver) { + let end = instance.round_offset + instance.point_arity; + let mut point = point + .get(instance.round_offset..end) + .ok_or(Stage3KernelError::InvalidInputLength { + input: instance.symbol, + expected: end, + actual: point.len(), + })? + .to_vec(); + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + _ => { + return Err(Stage3KernelError::InvalidProof { + driver, + reason: "unsupported point order", + }); + } + } + self.insert_point(instance.symbol, point); + } + for eval in program.evals_for_driver(driver) { + let value = evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| evals.get(eval.index)) + .ok_or(Stage3KernelError::MissingValue { + symbol: eval.symbol, + })? + .value; + self.insert_scalar(eval.symbol, value); + self.insert_scalar(eval.name, value); + } + Ok(()) + } + + pub fn evaluate_available_points( + &mut self, + program: &'static Stage3CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for slice in program.point_slices { + if self.try_point(slice.symbol).is_some() { + continue; + } + let Some(input) = self.try_point(slice.input) else { + continue; + }; + let end = slice.offset + slice.length; + let point = input + .get(slice.offset..end) + .ok_or(Stage3KernelError::InvalidInputLength { + input: slice.symbol, + expected: end, + actual: input.len(), + })? + .to_vec(); + self.insert_point(slice.symbol, point); + progress += 1; + } + for concat in program.point_concats { + if self.try_point(concat.symbol).is_some() { + continue; + } + let Some(point) = self.try_concat_point(concat) else { + continue; + }; + verify_count(concat.symbol, concat.arity, point.len())?; + self.insert_point(concat.symbol, point); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + pub fn evaluate_available_field_exprs( + &mut self, + program: &'static Stage3CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for expr in program.field_exprs { + if self.try_scalar(expr.symbol).is_some() { + continue; + } + let Some(operands) = self.try_expr_operands(expr) else { + continue; + }; + self.insert_scalar(expr.symbol, evaluate_stage3_field_expr(expr, &operands)?); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + pub fn verify_opening_equalities( + &self, + program: &'static Stage3CpuProgramPlan, + ) -> Result<(), Stage3KernelError> { + for equality in program.opening_equalities { + match equality.mode { + "point_and_eval" => { + if self.point(equality.lhs)? != self.point(equality.rhs)? + || self.scalar(equality.lhs)? != self.scalar(equality.rhs)? + { + return Err(Stage3KernelError::InvalidProof { + driver: equality.symbol, + reason: "opening claim equality failed", + }); + } + } + _ => { + return Err(Stage3KernelError::InvalidProof { + driver: equality.symbol, + reason: "unsupported opening equality mode", + }); + } + } + } + Ok(()) + } + + pub fn claim_value( + &mut self, + program: &'static Stage3CpuProgramPlan, + claim: &Stage3SumcheckClaimPlan, + ) -> Result { + let _ = self.evaluate_available_field_exprs(program)?; + self.scalar(claim.claim_value) + } + + pub fn batch_claim_values( + &mut self, + program: &'static Stage3CpuProgramPlan, + batch: &Stage3SumcheckBatchPlan, + ) -> Result, Stage3KernelError> { + batch + .claim_operands + .iter() + .map(|symbol| { + let claim = program + .claim(symbol) + .ok_or(Stage3KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + self.claim_value(program, claim) + }) + .collect() + } + + fn try_expr_operands(&self, expr: &Stage3FieldExprPlan) -> Option> { + expr.operands + .iter() + .map(|operand| self.try_scalar(operand)) + .collect() + } + + fn try_concat_point(&self, concat: &Stage3PointConcatPlan) -> Option> { + let mut point = Vec::with_capacity(concat.arity); + for input in concat.inputs { + point.extend_from_slice(self.try_point(input)?); + } + Some(point) + } +} + +pub fn evaluate_stage3_field_expr( + expr: &Stage3FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => single_operand(expr.symbol, operands), + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + _ => { + if let Some(exponent) = expr.formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + Stage3KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + Err(Stage3KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + }) + } + } +} + +fn pow_field(base: F, mut exponent: usize) -> F { + let mut result = F::one(); + let mut power = base; + while exponent != 0 { + if exponent & 1 == 1 { + result *= power; + } + power = power.square(); + exponent >>= 1; + } + result +} + +fn single_operand(symbol: &'static str, operands: &[F]) -> Result { + require_operand_count(symbol, 1, operands.len())?; + Ok(operands[0]) +} + +fn require_operand_count( + input: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage3KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage3KernelError::InvalidInputLength { + input, + expected, + actual, + }) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage3KernelContext<'a> { + pub mode: Stage3ExecutionMode, + pub program: &'static Stage3CpuProgramPlan, + pub kernel: &'a Stage3KernelPlan, + pub batch: &'a Stage3SumcheckBatchPlan, + pub driver: &'a Stage3SumcheckDriverPlan, +} + +impl Stage3KernelContext<'_> { + pub fn relation_kind(&self) -> Result { + self.kernel.relation_kind() + } + + pub fn abi_kind(&self) -> Result { + self.kernel.abi_kind() + } + + pub fn batch_claims(&self) -> Result, Stage3KernelError> { + self.batch + .claim_operands + .iter() + .map(|symbol| { + self.program + .claim(symbol) + .ok_or(Stage3KernelError::MissingClaim { + batch: self.batch.symbol, + claim: symbol, + }) + }) + .collect() + } +} + +pub trait Stage3KernelExecutor { + fn observe_challenge_vector( + &mut self, + _plan: &'static Stage3TranscriptSqueezePlan, + _values: &[F], + ) -> Result<(), Stage3KernelError> { + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + _output: &Stage3SumcheckOutput, + ) -> Result<(), Stage3KernelError> { + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage3KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage3KernelError> + where + T: Transcript; + + fn verify_sumcheck( + &mut self, + context: Stage3KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage3KernelError> + where + T: Transcript; +} + +#[derive(Clone, Debug, Default)] +pub struct UnsupportedStage3KernelExecutor; + +impl Stage3KernelExecutor for UnsupportedStage3KernelExecutor { + fn prove_sumcheck( + &mut self, + context: Stage3KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage3KernelError> + where + T: Transcript, + { + Err(Stage3KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage3KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage3KernelError> + where + T: Transcript, + { + Err(Stage3KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } +} + +#[derive(Clone, Copy)] +pub struct Stage3ProverInputs<'a, F: Field> { + pub opening_inputs: &'a [Stage3OpeningInputValue], + pub cycles: Option<&'a [Stage3Cycle]>, +} + +impl<'a, F: Field> Stage3ProverInputs<'a, F> { + pub fn new(opening_inputs: &'a [Stage3OpeningInputValue]) -> Self { + Self { + opening_inputs, + cycles: None, + } + } + + pub fn empty() -> Self { + Self { + opening_inputs: &[], + cycles: None, + } + } + + pub fn with_cycles(mut self, cycles: &'a [Stage3Cycle]) -> Self { + self.cycles = Some(cycles); + self + } +} + +#[derive(Clone)] +pub struct Stage3ProverKernelExecutor<'a, F: Field> { + pub inputs: Stage3ProverInputs<'a, F>, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage3ProverKernelExecutor<'a, F> { + pub fn new(inputs: Stage3ProverInputs<'a, F>) -> Self { + Self { + inputs, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage3CpuProgramPlan, + ) -> Result, Stage3KernelError> { + value_store_from_observations( + program, + self.inputs.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } +} + +impl Stage3KernelExecutor for Stage3ProverKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage3TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage3KernelError> { + self.challenge_vectors.push(Stage3ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage3SumcheckOutput, + ) -> Result<(), Stage3KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage3KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage3KernelError> + where + T: Transcript, + { + prove_stage3_kernel( + context, + &self.inputs, + self.value_store(context.program)?, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage3KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage3KernelError> + where + T: Transcript, + { + Err(Stage3KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage3ExecutionMode::Prover, + actual: Stage3ExecutionMode::Verifier, + }) + } +} + +#[derive(Clone)] +pub struct Stage3VerifierKernelExecutor<'a, F: Field> { + pub proof: &'a Stage3Proof, + pub opening_inputs: &'a [Stage3OpeningInputValue], + pub cursor: usize, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage3VerifierKernelExecutor<'a, F> { + pub fn new( + proof: &'a Stage3Proof, + opening_inputs: &'a [Stage3OpeningInputValue], + ) -> Self { + Self { + proof, + opening_inputs, + cursor: 0, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage3CpuProgramPlan, + ) -> Result, Stage3KernelError> { + value_store_from_observations( + program, + self.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } +} + +impl Stage3KernelExecutor for Stage3VerifierKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage3TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage3KernelError> { + self.challenge_vectors.push(Stage3ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage3SumcheckOutput, + ) -> Result<(), Stage3KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage3KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage3KernelError> + where + T: Transcript, + { + Err(Stage3KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage3ExecutionMode::Verifier, + actual: Stage3ExecutionMode::Prover, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage3KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage3KernelError> + where + T: Transcript, + { + let proof = + self.proof + .sumchecks + .get(self.cursor) + .ok_or(Stage3KernelError::MissingProof { + driver: context.driver.symbol, + })?; + self.cursor += 1; + verify_stage3_kernel( + context, + self.value_store(context.program)?, + proof, + transcript, + ) + } +} + +fn value_store_from_observations( + program: &'static Stage3CpuProgramPlan, + opening_inputs: &[Stage3OpeningInputValue], + challenge_vectors: &[Stage3ChallengeVector], + completed_sumchecks: &[Stage3SumcheckOutput], +) -> Result, Stage3KernelError> { + let mut store = Stage3ValueStore::with_opening_inputs(opening_inputs); + store.seed_constants(program)?; + for challenge in challenge_vectors { + let plan = program + .transcript_squeezes + .iter() + .find(|plan| plan.symbol == challenge.symbol) + .ok_or(Stage3KernelError::MissingValue { + symbol: challenge.symbol, + })?; + store.observe_challenge_vector(plan, &challenge.values)?; + } + for output in completed_sumchecks { + store.observe_sumcheck_output(program, output)?; + } + let _ = store.evaluate_available_points(program)?; + let _ = store.evaluate_available_field_exprs(program)?; + store.verify_opening_equalities(program)?; + Ok(store) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Stage3KernelError { + MissingKernel { + driver: &'static str, + kernel: &'static str, + }, + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + MissingClaim { + batch: &'static str, + claim: &'static str, + }, + MissingValue { + symbol: &'static str, + }, + PlanCountMismatch { + artifact: &'static str, + expected: usize, + actual: usize, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + UnsupportedFieldExpr { + symbol: &'static str, + formula: &'static str, + }, + UnknownRelation { + relation: &'static str, + }, + UnknownKernelAbi { + abi: &'static str, + }, + KernelNotImplemented { + abi: &'static str, + }, + WrongExecutorMode { + driver: &'static str, + expected: Stage3ExecutionMode, + actual: Stage3ExecutionMode, + }, + MissingProof { + driver: &'static str, + }, + MissingKernelInput { + kernel: &'static str, + input: &'static str, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, +} + +impl Display for Stage3KernelError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingKernel { driver, kernel } => { + write!( + formatter, + "stage3 driver @{driver} references missing kernel @{kernel}" + ) + } + Self::MissingBatch { driver, batch } => { + write!( + formatter, + "stage3 driver @{driver} references missing batch @{batch}" + ) + } + Self::MissingClaim { batch, claim } => { + write!( + formatter, + "stage3 batch @{batch} references missing claim @{claim}" + ) + } + Self::MissingValue { symbol } => { + write!(formatter, "stage3 value @{symbol} is not available") + } + Self::PlanCountMismatch { + artifact, + expected, + actual, + } => write!( + formatter, + "stage3 plan @{artifact} count mismatch: expected {expected}, got {actual}" + ), + Self::InvalidInputLength { + input, + expected, + actual, + } => write!( + formatter, + "stage3 input `{input}` length mismatch: expected {expected}, got {actual}" + ), + Self::UnsupportedFieldExpr { symbol, formula } => write!( + formatter, + "stage3 field expr @{symbol} uses unsupported formula `{formula}`" + ), + Self::UnknownRelation { relation } => { + write!(formatter, "stage3 relation @{relation} is not registered") + } + Self::UnknownKernelAbi { abi } => { + write!(formatter, "stage3 kernel ABI `{abi}` is not registered") + } + Self::KernelNotImplemented { abi } => { + write!(formatter, "stage3 kernel ABI `{abi}` is not implemented") + } + Self::WrongExecutorMode { + driver, + expected, + actual, + } => write!( + formatter, + "stage3 driver @{driver} ran with {actual:?} executor path, expected {expected:?}" + ), + Self::MissingProof { driver } => { + write!( + formatter, + "stage3 verifier missing proof for driver @{driver}" + ) + } + Self::MissingKernelInput { kernel, input } => { + write!( + formatter, + "stage3 kernel `{kernel}` missing input `{input}`" + ) + } + Self::InvalidProof { driver, reason } => { + write!( + formatter, + "stage3 proof for driver @{driver} is invalid: {reason}" + ) + } + } + } +} + +impl Error for Stage3KernelError {} + +fn prove_stage3_kernel( + context: Stage3KernelContext<'_>, + inputs: &Stage3ProverInputs<'_, F>, + store: Stage3ValueStore, + transcript: &mut T, +) -> Result, Stage3KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage3KernelAbi::Batched => prove_batched_stage3(context, inputs, store, transcript), + abi => Err(Stage3KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +fn verify_stage3_kernel( + context: Stage3KernelContext<'_>, + store: Stage3ValueStore, + proof: &Stage3SumcheckOutput, + transcript: &mut T, +) -> Result, Stage3KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage3KernelAbi::Batched => verify_batched_stage3(context, store, proof, transcript), + abi => Err(Stage3KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +#[tracing::instrument(skip_all, name = "Stage3::prove_batched")] +fn prove_batched_stage3( + context: Stage3KernelContext<'_>, + inputs: &Stage3ProverInputs<'_, F>, + mut store: Stage3ValueStore, + transcript: &mut T, +) -> Result, Stage3KernelError> +where + F: Field, + T: Transcript, +{ + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let two_inv = F::from_u64(2) + .inverse() + .ok_or(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "field element 2 is not invertible", + })?; + let mut instances = Vec::with_capacity(claims.len()); + for (index, claim) in claims.iter().enumerate() { + instances.push(Stage3BatchedInstance { + claim, + relation: claim_relation(context.program, claim)?, + offset: instance_round_offset(context.program, context.driver.symbol, claim.symbol)?, + previous_claim: input_claims[index].mul_pow_2(max_rounds - claim.num_rounds), + state: Stage3ProverInstanceState::new(context.program, claim, inputs, &store)?, + }); + } + + let mut point = Vec::with_capacity(max_rounds); + let mut round_polynomials = Vec::with_capacity(max_rounds); + let mut batched_claim = instances + .iter() + .zip(&batching_coeffs) + .map(|(instance, &coefficient)| instance.previous_claim * coefficient) + .sum::(); + for round in 0..max_rounds { + let mut individual_polys = Vec::with_capacity(instances.len()); + for instance in &mut instances { + let poly = if instance.is_active(round) { + instance + .state + .round_poly(round - instance.offset, instance.previous_claim)? + } else { + UnivariatePoly::new(vec![instance.previous_claim * two_inv]) + }; + #[cfg(debug_assertions)] + { + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != instance.previous_claim { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched instance round claim mismatch", + }); + } + } + individual_polys.push(poly); + } + let batched_poly = combine_univariate_polys(&individual_polys, &batching_coeffs); + #[cfg(debug_assertions)] + { + if batched_poly.evaluate(F::zero()) + batched_poly.evaluate(F::one()) != batched_claim { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round claim mismatch", + }); + } + } + append_compressed_univariate_poly(transcript, context.driver.round_label, &batched_poly); + let challenge = transcript.challenge(); + point.push(challenge); + batched_claim = batched_poly.evaluate(challenge); + for (instance, poly) in instances.iter_mut().zip(individual_polys) { + instance.previous_claim = poly.evaluate(challenge); + if instance.is_active(round) { + instance.state.ingest_challenge(challenge); + } + } + round_polynomials.push(batched_poly); + } + + let mut evals = Vec::new(); + for instance in &instances { + evals.extend(instance.state.final_evals(instance.relation)?); + } + let expected = + expected_batched_output_claim(context, &store, &evals, &point, &batching_coeffs)?; + if batched_claim != expected { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + store.observe_sumcheck_values(context.program, context.driver.symbol, &point, &evals)?; + let opening_claims = append_opening_claims(context.program, &mut store, transcript, &evals)?; + Ok(Stage3SumcheckOutput { + driver: context.driver.symbol, + point, + evals, + opening_claims, + proof: SumcheckProof { round_polynomials }, + }) +} + +fn verify_batched_stage3( + context: Stage3KernelContext<'_>, + mut store: Stage3ValueStore, + proof: &Stage3SumcheckOutput, + transcript: &mut T, +) -> Result, Stage3KernelError> +where + F: Field, + T: Transcript, +{ + if proof.driver != context.driver.symbol { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + if proof.proof.round_polynomials.len() != context.driver.num_rounds { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected batched round count", + }); + } + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let mut running_claim = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), &coefficient)| { + claim.mul_pow_2(max_rounds - plan.num_rounds) * coefficient + }) + .sum::(); + let mut point = Vec::with_capacity(max_rounds); + for poly in &proof.proof.round_polynomials { + if polynomial_degree(poly) > context.driver.degree { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched polynomial exceeds degree bound", + }); + } + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != running_claim { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round check failed", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, poly); + let challenge = transcript.challenge(); + running_claim = poly.evaluate(challenge); + point.push(challenge); + } + if !proof.point.is_empty() && proof.point != point { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched point mismatch", + }); + } + let expected = + expected_batched_output_claim(context, &store, &proof.evals, &point, &batching_coeffs)?; + if running_claim != expected { + return Err(Stage3KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + store.observe_sumcheck_values(context.program, context.driver.symbol, &point, &proof.evals)?; + let opening_claims = + append_opening_claims(context.program, &mut store, transcript, &proof.evals)?; + Ok(Stage3SumcheckOutput { + driver: context.driver.symbol, + point, + evals: proof.evals.clone(), + opening_claims, + proof: proof.proof.clone(), + }) +} + +struct Stage3BatchedInstance<'a, F: Field> { + claim: &'a Stage3SumcheckClaimPlan, + relation: Stage3Relation, + offset: usize, + previous_claim: F, + state: Stage3ProverInstanceState, +} + +impl Stage3BatchedInstance<'_, F> { + fn is_active(&self, round: usize) -> bool { + round >= self.offset && round < self.offset + self.claim.num_rounds + } +} + +enum Stage3ProverInstanceState { + SpartanShift(SpartanShiftState), + SumOfProducts(SumOfProductsState), +} + +impl Stage3ProverInstanceState { + fn new( + program: &'static Stage3CpuProgramPlan, + claim: &Stage3SumcheckClaimPlan, + inputs: &Stage3ProverInputs<'_, F>, + store: &Stage3ValueStore, + ) -> Result { + match claim_relation(program, claim)? { + Stage3Relation::SpartanShift => { + return spartan_shift_state(claim, inputs, store).map(Self::SpartanShift); + } + Stage3Relation::InstructionInput => instruction_input_state(claim, inputs, store), + Stage3Relation::RegistersClaimReduction => registers_state(claim, inputs, store), + relation @ Stage3Relation::Batched => Err(Stage3KernelError::KernelNotImplemented { + abi: relation.symbol(), + }), + } + .map(Self::SumOfProducts) + } + + fn round_poly( + &self, + _round: usize, + previous_claim: F, + ) -> Result, Stage3KernelError> { + match self { + Self::SpartanShift(state) => Ok(state.round_poly(previous_claim)), + Self::SumOfProducts(state) => Ok(state.round_poly(previous_claim)), + } + } + + fn ingest_challenge(&mut self, challenge: F) { + match self { + Self::SpartanShift(state) => state.bind(challenge), + Self::SumOfProducts(state) => state.bind(challenge), + } + } + + fn final_evals( + &self, + relation: Stage3Relation, + ) -> Result>, Stage3KernelError> { + match self { + Self::SpartanShift(state) => state.final_evals(relation), + Self::SumOfProducts(state) => state.final_evals(relation), + } + } +} + +#[derive(Clone)] +struct SpartanShiftState { + phase: SpartanShiftPhase, + r_outer: Vec, + r_product: Vec, + gamma: F, + gamma2: F, + gamma3: F, + gamma4: F, + point: Vec, +} + +#[derive(Clone)] +enum SpartanShiftPhase { + Phase1(SpartanShiftPhase1), + Phase2(SpartanShiftPhase2), +} + +#[derive(Clone)] +struct SpartanShiftPhase1 { + prefix_suffix_pairs: Vec<(Vec, Vec)>, + scratch: Vec<(Vec, Vec)>, + cycles: Vec, +} + +#[derive(Clone)] +struct SpartanShiftPhase2 { + eq_outer: Vec, + eq_product: Vec, + weighted_next_values: Vec, + not_noop: Vec, + unexpanded_pc: Vec, + pc: Vec, + is_virtual: Vec, + is_first_in_sequence: Vec, + is_noop: Vec, + scratch: Vec>, +} + +#[derive(Clone)] +struct SumOfProductsState { + kind: SumOfProductsKind, + factors: Vec>, + factor_scratch: Vec>, + split_eq: Option>, + terms: Vec>, + outputs: Vec, + deferred_outputs: Vec>, + point: Vec, +} + +#[derive(Clone, Copy)] +enum SumOfProductsKind { + InstructionInput, + Registers, +} + +#[derive(Clone)] +struct ProductTerm { + coefficient: F, +} + +#[derive(Clone, Copy)] +struct FactorOutput { + name: &'static str, + oracle: &'static str, + factor: usize, +} + +#[derive(Clone)] +struct DeferredOutput { + name: &'static str, + oracle: &'static str, + values: Vec, +} + +impl SumOfProductsState { + fn new( + kind: SumOfProductsKind, + factors: Vec>, + split_eq: Option>, + terms: Vec>, + outputs: Vec, + deferred_outputs: Vec>, + ) -> Self { + let factor_scratch = (0..factors.len()).map(|_| Vec::new()).collect(); + Self { + kind, + factors, + factor_scratch, + split_eq, + terms, + outputs, + deferred_outputs, + point: Vec::new(), + } + } + + fn round_poly(&self, previous_claim: F) -> UnivariatePoly { + let Some(split_eq) = self.split_eq.as_ref() else { + std::process::abort(); + }; + match self.kind { + SumOfProductsKind::InstructionInput => round_poly_from_instruction_input( + &self.factors, + &self.terms, + split_eq, + previous_claim, + ), + SumOfProductsKind::Registers => { + round_poly_from_registers(&self.factors, &self.terms, split_eq, previous_claim) + } + } + } + + fn bind(&mut self, challenge: F) { + let half = self.factors.first().map_or(0, |factor| factor.len() / 2); + if half >= DENSE_BIND_PAR_THRESHOLD { + self.factors + .par_iter_mut() + .zip(self.factor_scratch.par_iter_mut()) + .for_each(|(factor, scratch)| { + bind_dense_evals_reuse_serial(factor, scratch, challenge); + }); + } else { + for (factor, scratch) in self.factors.iter_mut().zip(&mut self.factor_scratch) { + bind_dense_evals_reuse_serial(factor, scratch, challenge); + } + } + if let Some(split_eq) = &mut self.split_eq { + split_eq.bind(challenge); + } + self.point.push(challenge); + } + + fn factor_eval(&self, index: usize, relation: Stage3Relation) -> Result { + self.factors + .get(index) + .and_then(|values| values.first()) + .copied() + .ok_or(Stage3KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty stage3 factor", + }) + } + + fn final_evals( + &self, + relation: Stage3Relation, + ) -> Result>, Stage3KernelError> { + let mut evals = self + .outputs + .iter() + .map(|output| { + Ok(named_eval( + output.name, + output.oracle, + self.factor_eval(output.factor, relation)?, + )) + }) + .collect::, _>>()?; + if !self.deferred_outputs.is_empty() { + let point = reverse_slice(&self.point); + let eq = EqPolynomial::::evals(&point, None); + evals.extend(self.deferred_outputs.iter().map(|output| { + named_eval( + output.name, + output.oracle, + deferred_output_eval(&output.values, &eq), + ) + })); + } + Ok(evals) + } +} + +impl SpartanShiftState { + fn new( + cycles: &[Stage3Cycle], + r_outer: &[F], + r_product: &[F], + gamma: F, + gamma2: F, + gamma3: F, + gamma4: F, + ) -> Self { + Self { + phase: SpartanShiftPhase::Phase1(SpartanShiftPhase1::new( + cycles, r_outer, r_product, gamma, gamma2, gamma3, gamma4, + )), + r_outer: r_outer.to_vec(), + r_product: r_product.to_vec(), + gamma, + gamma2, + gamma3, + gamma4, + point: Vec::new(), + } + } + + fn round_poly(&self, previous_claim: F) -> UnivariatePoly { + match &self.phase { + SpartanShiftPhase::Phase1(state) => state.round_poly(), + SpartanShiftPhase::Phase2(state) => state.round_poly(previous_claim), + } + } + + fn bind(&mut self, challenge: F) { + let transition = match &mut self.phase { + SpartanShiftPhase::Phase1(state) => { + if state.should_transition_to_phase2() { + true + } else { + state.bind(challenge); + false + } + } + SpartanShiftPhase::Phase2(state) => { + state.bind(challenge); + false + } + }; + self.point.push(challenge); + if transition { + let SpartanShiftPhase::Phase1(state) = &self.phase else { + unreachable!("checked phase before transition"); + }; + let phase2 = SpartanShiftPhase2::new( + &state.cycles, + &self.point, + &self.r_outer, + &self.r_product, + self.gamma, + self.gamma2, + self.gamma3, + self.gamma4, + ); + self.phase = SpartanShiftPhase::Phase2(phase2); + } + } + + fn final_evals( + &self, + relation: Stage3Relation, + ) -> Result>, Stage3KernelError> { + let SpartanShiftPhase::Phase2(state) = &self.phase else { + return Err(Stage3KernelError::InvalidProof { + driver: relation.symbol(), + reason: "spartan shift did not finish phase 2", + }); + }; + state.final_evals(relation) + } +} + +impl SpartanShiftPhase1 { + fn new( + cycles: &[Stage3Cycle], + r_outer: &[F], + r_product: &[F], + gamma: F, + gamma2: F, + gamma3: F, + gamma4: F, + ) -> Self { + let outer = EqPlusOnePrefixSuffix::new(r_outer); + let product = EqPlusOnePrefixSuffix::new(r_product); + let q_values = + spartan_shift_phase1_q_values(cycles, &outer, &product, gamma, gamma2, gamma3, gamma4); + let mut q_outer_0 = Vec::with_capacity(q_values.len()); + let mut q_outer_1 = Vec::with_capacity(q_values.len()); + let mut q_product_0 = Vec::with_capacity(q_values.len()); + let mut q_product_1 = Vec::with_capacity(q_values.len()); + for [outer_0, outer_1, product_0, product_1] in q_values { + q_outer_0.push(outer_0); + q_outer_1.push(outer_1); + q_product_0.push(product_0); + q_product_1.push(product_1); + } + let prefix_suffix_pairs = vec![ + (outer.prefix_0, q_outer_0), + (outer.prefix_1, q_outer_1), + (product.prefix_0, q_product_0), + (product.prefix_1, q_product_1), + ]; + let scratch = prefix_suffix_pairs + .iter() + .map(|_| (Vec::new(), Vec::new())) + .collect(); + Self { + prefix_suffix_pairs, + scratch, + cycles: cycles.to_vec(), + } + } + + fn round_poly(&self) -> UnivariatePoly { + let half = self.prefix_suffix_pairs[0].0.len() / 2; + round_poly_from_stage3_coefficients(half, 2, |row, acc| { + for (prefix, suffix) in &self.prefix_suffix_pairs { + let (prefix_0, prefix_delta) = linear_pair(prefix, row); + let (suffix_0, suffix_delta) = linear_pair(suffix, row); + accumulate_linear_product( + acc, + F::one(), + prefix_0, + prefix_delta, + suffix_0, + suffix_delta, + ); + } + }) + } + + fn bind(&mut self, challenge: F) { + for ((prefix, suffix), (prefix_scratch, suffix_scratch)) in self + .prefix_suffix_pairs + .iter_mut() + .zip(self.scratch.iter_mut()) + { + bind_dense_evals_reuse_serial(prefix, prefix_scratch, challenge); + bind_dense_evals_reuse_serial(suffix, suffix_scratch, challenge); + } + } + + fn should_transition_to_phase2(&self) -> bool { + self.prefix_suffix_pairs[0].0.len() == 2 + } +} + +impl SpartanShiftPhase2 { + fn new( + cycles: &[Stage3Cycle], + low_challenges: &[F], + r_outer: &[F], + r_product: &[F], + gamma: F, + gamma2: F, + gamma3: F, + gamma4: F, + ) -> Self { + let low_point = reverse_slice(low_challenges); + let low_eq = EqPolynomial::::evals(&low_point, None); + let eq_outer = spartan_shift_phase2_eq_plus_one(r_outer, &low_eq); + let eq_product = spartan_shift_phase2_eq_plus_one(r_product, &low_eq); + let ( + unexpanded_pc, + pc, + is_virtual, + is_first_in_sequence, + is_noop, + weighted_next_values, + not_noop, + ) = spartan_shift_phase2_outputs(cycles, &low_eq, gamma, gamma2, gamma3); + let not_noop = not_noop + .iter() + .map(|&value| gamma4 * value) + .collect::>(); + Self { + eq_outer, + eq_product, + weighted_next_values, + not_noop, + unexpanded_pc, + pc, + is_virtual, + is_first_in_sequence, + is_noop, + scratch: (0..9).map(|_| Vec::new()).collect(), + } + } + + fn round_poly(&self, _previous_claim: F) -> UnivariatePoly { + round_poly_from_stage3_coefficients(self.eq_outer.len() / 2, 2, |row, acc| { + let (eq_outer_0, eq_outer_delta) = linear_pair(&self.eq_outer, row); + let (eq_product_0, eq_product_delta) = linear_pair(&self.eq_product, row); + let (next_values_0, next_values_delta) = linear_pair(&self.weighted_next_values, row); + let (not_noop_0, not_noop_delta) = linear_pair(&self.not_noop, row); + accumulate_linear_product( + acc, + F::one(), + eq_outer_0, + eq_outer_delta, + next_values_0, + next_values_delta, + ); + accumulate_linear_product( + acc, + F::one(), + eq_product_0, + eq_product_delta, + not_noop_0, + not_noop_delta, + ); + }) + } + + fn bind(&mut self, challenge: F) { + bind_dense_evals_reuse_serial(&mut self.eq_outer, &mut self.scratch[0], challenge); + bind_dense_evals_reuse_serial(&mut self.eq_product, &mut self.scratch[1], challenge); + bind_dense_evals_reuse_serial( + &mut self.weighted_next_values, + &mut self.scratch[2], + challenge, + ); + bind_dense_evals_reuse_serial(&mut self.not_noop, &mut self.scratch[3], challenge); + bind_dense_evals_reuse_serial(&mut self.unexpanded_pc, &mut self.scratch[4], challenge); + bind_dense_evals_reuse_serial(&mut self.pc, &mut self.scratch[5], challenge); + bind_dense_evals_reuse_serial(&mut self.is_virtual, &mut self.scratch[6], challenge); + bind_dense_evals_reuse_serial( + &mut self.is_first_in_sequence, + &mut self.scratch[7], + challenge, + ); + bind_dense_evals_reuse_serial(&mut self.is_noop, &mut self.scratch[8], challenge); + } + + fn final_evals( + &self, + relation: Stage3Relation, + ) -> Result>, Stage3KernelError> { + let value = |values: &[F]| { + values + .first() + .copied() + .ok_or(Stage3KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty spartan shift output", + }) + }; + Ok(vec![ + named_eval( + "stage3.spartan_shift.eval.UnexpandedPC", + "UnexpandedPC", + value(&self.unexpanded_pc)?, + ), + named_eval("stage3.spartan_shift.eval.PC", "PC", value(&self.pc)?), + named_eval( + "stage3.spartan_shift.eval.OpFlagVirtualInstruction", + "OpFlagVirtualInstruction", + value(&self.is_virtual)?, + ), + named_eval( + "stage3.spartan_shift.eval.OpFlagIsFirstInSequence", + "OpFlagIsFirstInSequence", + value(&self.is_first_in_sequence)?, + ), + named_eval( + "stage3.spartan_shift.eval.InstructionFlagIsNoop", + "InstructionFlagIsNoop", + value(&self.is_noop)?, + ), + ]) + } +} + +fn spartan_shift_state( + claim: &Stage3SumcheckClaimPlan, + inputs: &Stage3ProverInputs<'_, F>, + store: &Stage3ValueStore, +) -> Result, Stage3KernelError> { + let cycles = stage3_cycles(inputs, claim.num_rounds)?; + let r_outer = store.point("stage3.input.stage1.NextPC")?; + let r_product = store.point("stage3.input.stage2.product_virtual.NextIsNoop")?; + let gamma = store.scalar("stage3.spartan_shift.gamma")?; + let gamma2 = store.scalar("stage3.spartan_shift.gamma2")?; + let gamma3 = store.scalar("stage3.spartan_shift.gamma3")?; + let gamma4 = store.scalar("stage3.spartan_shift.gamma4")?; + Ok(SpartanShiftState::new( + cycles, r_outer, r_product, gamma, gamma2, gamma3, gamma4, + )) +} + +fn instruction_input_state( + claim: &Stage3SumcheckClaimPlan, + inputs: &Stage3ProverInputs<'_, F>, + store: &Stage3ValueStore, +) -> Result, Stage3KernelError> { + let cycles = stage3_cycles(inputs, claim.num_rounds)?; + let eq_point = store.point("stage3.input.stage2.product_virtual.LeftInstructionInput")?; + let gamma = store.scalar("stage3.instruction_input.gamma")?; + let ( + right_operand_is_rs2, + rs2_value, + right_operand_is_imm, + imm, + left_operand_is_rs1, + rs1_value, + left_operand_is_pc, + unexpanded_pc, + ) = instruction_input_factors(cycles); + let factors = vec![ + right_operand_is_rs2, + rs2_value, + right_operand_is_imm, + imm, + left_operand_is_rs1, + rs1_value, + left_operand_is_pc, + unexpanded_pc, + ]; + Ok(SumOfProductsState::new( + SumOfProductsKind::InstructionInput, + factors, + Some(SplitEqState::new_low_to_high(eq_point, None)), + vec![ + ProductTerm { + coefficient: F::one(), + }, + ProductTerm { + coefficient: F::one(), + }, + ProductTerm { coefficient: gamma }, + ProductTerm { coefficient: gamma }, + ], + vec![ + FactorOutput { + name: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", + oracle: "InstructionFlagLeftOperandIsRs1Value", + factor: 4, + }, + FactorOutput { + name: "stage3.instruction_input.eval.Rs1Value", + oracle: "Rs1Value", + factor: 5, + }, + FactorOutput { + name: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", + oracle: "InstructionFlagLeftOperandIsPC", + factor: 6, + }, + FactorOutput { + name: "stage3.instruction_input.eval.UnexpandedPC", + oracle: "UnexpandedPC", + factor: 7, + }, + FactorOutput { + name: "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", + oracle: "InstructionFlagRightOperandIsRs2Value", + factor: 0, + }, + FactorOutput { + name: "stage3.instruction_input.eval.Rs2Value", + oracle: "Rs2Value", + factor: 1, + }, + FactorOutput { + name: "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", + oracle: "InstructionFlagRightOperandIsImm", + factor: 2, + }, + FactorOutput { + name: "stage3.instruction_input.eval.Imm", + oracle: "Imm", + factor: 3, + }, + ], + Vec::new(), + )) +} + +fn registers_state( + claim: &Stage3SumcheckClaimPlan, + inputs: &Stage3ProverInputs<'_, F>, + store: &Stage3ValueStore, +) -> Result, Stage3KernelError> { + let cycles = stage3_cycles(inputs, claim.num_rounds)?; + let eq_point = store.point("stage3.input.stage1.RdWriteValue")?; + let gamma = store.scalar("stage3.registers.gamma")?; + let gamma2 = store.scalar("stage3.registers.gamma2")?; + let (rd_write_value, rs1_value, rs2_value) = register_factors(cycles); + let factors = vec![rd_write_value, rs1_value, rs2_value]; + Ok(SumOfProductsState::new( + SumOfProductsKind::Registers, + factors, + Some(SplitEqState::new_low_to_high(eq_point, None)), + vec![ + ProductTerm { + coefficient: F::one(), + }, + ProductTerm { coefficient: gamma }, + ProductTerm { + coefficient: gamma2, + }, + ], + vec![ + FactorOutput { + name: "stage3.registers_claim_reduction.eval.RdWriteValue", + oracle: "RdWriteValue", + factor: 0, + }, + FactorOutput { + name: "stage3.registers_claim_reduction.eval.Rs1Value", + oracle: "Rs1Value", + factor: 1, + }, + FactorOutput { + name: "stage3.registers_claim_reduction.eval.Rs2Value", + oracle: "Rs2Value", + factor: 2, + }, + ], + Vec::new(), + )) +} + +fn stage3_cycles<'a, F: Field>( + inputs: &'a Stage3ProverInputs<'_, F>, + num_rounds: usize, +) -> Result<&'a [Stage3Cycle], Stage3KernelError> { + let cycles = inputs.cycles.ok_or(Stage3KernelError::MissingKernelInput { + kernel: "jolt_stage3_batched", + input: "cycles", + })?; + let expected = + 1usize + .checked_shl(num_rounds as u32) + .ok_or(Stage3KernelError::InvalidInputLength { + input: "stage3.cycles", + expected: usize::BITS as usize, + actual: num_rounds, + })?; + require_operand_count("stage3.cycles", expected, cycles.len())?; + Ok(cycles) +} + +type InstructionInputFactors = ( + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, +); +type RegisterFactors = (Vec, Vec, Vec); + +fn spartan_shift_phase1_q_values( + cycles: &[Stage3Cycle], + outer: &EqPlusOnePrefixSuffix, + product: &EqPlusOnePrefixSuffix, + gamma: F, + gamma2: F, + gamma3: F, + gamma4: F, +) -> Vec<[F; 4]> { + let prefix_len = outer.prefix_0.len(); + let suffix_len = outer.suffix_0.len(); + debug_assert_eq!(prefix_len * suffix_len, cycles.len()); + (0..prefix_len) + .into_par_iter() + .map(|x_lo| { + let mut acc = [F::Accumulator::default(); 4]; + for x_hi in 0..suffix_len { + let cycle = cycles[x_lo + x_hi * prefix_len]; + let mut weighted = F::from_u64(cycle.unexpanded_pc) + gamma * F::from_u64(cycle.pc); + if cycle.is_virtual { + weighted += gamma2; + } + if cycle.is_first_in_sequence { + weighted += gamma3; + } + acc[0].fmadd(outer.suffix_0[x_hi], weighted); + acc[1].fmadd(outer.suffix_1[x_hi], weighted); + if !cycle.is_noop { + acc[2].fmadd(gamma4, product.suffix_0[x_hi]); + acc[3].fmadd(gamma4, product.suffix_1[x_hi]); + } + } + [ + acc[0].reduce(), + acc[1].reduce(), + acc[2].reduce(), + acc[3].reduce(), + ] + }) + .collect() +} + +fn spartan_shift_phase2_eq_plus_one(point: &[F], low_eq: &[F]) -> Vec { + let split = EqPlusOnePrefixSuffix::new(point); + let prefix_0_eval = deferred_output_eval(&split.prefix_0, low_eq); + let prefix_1_eval = deferred_output_eval(&split.prefix_1, low_eq); + debug_assert_eq!(split.prefix_0.len(), low_eq.len()); + split + .suffix_0 + .iter() + .zip(split.suffix_1.iter()) + .map(|(&suffix_0, &suffix_1)| prefix_0_eval * suffix_0 + prefix_1_eval * suffix_1) + .collect() +} + +type SpartanShiftPhase2Outputs = (Vec, Vec, Vec, Vec, Vec, Vec, Vec); + +fn spartan_shift_phase2_outputs( + cycles: &[Stage3Cycle], + low_eq: &[F], + gamma: F, + gamma2: F, + gamma3: F, +) -> SpartanShiftPhase2Outputs { + let low_len = low_eq.len(); + let high_len = cycles.len() / low_len; + let mut unexpanded_pc = vec![F::zero(); high_len]; + let mut pc = vec![F::zero(); high_len]; + let mut is_virtual = vec![F::zero(); high_len]; + let mut is_first_in_sequence = vec![F::zero(); high_len]; + let mut is_noop = vec![F::zero(); high_len]; + let mut weighted_next_values = vec![F::zero(); high_len]; + let mut not_noop = vec![F::zero(); high_len]; + ( + &mut unexpanded_pc, + &mut pc, + &mut is_virtual, + &mut is_first_in_sequence, + &mut is_noop, + &mut weighted_next_values, + &mut not_noop, + 0..high_len, + ) + .into_par_iter() + .for_each( + |( + unexpanded_pc, + pc, + is_virtual, + is_first_in_sequence, + is_noop, + weighted_next_values, + not_noop, + x_hi, + )| { + let mut unexpanded_acc = F::Accumulator::default(); + let mut pc_acc = F::Accumulator::default(); + let mut virtual_acc = F::Accumulator::default(); + let mut first_acc = F::Accumulator::default(); + let mut noop_acc = F::Accumulator::default(); + let base = x_hi * low_len; + for (x_lo, &weight) in low_eq.iter().enumerate() { + let cycle = cycles[base + x_lo]; + unexpanded_acc.fmadd_u64(weight, cycle.unexpanded_pc); + pc_acc.fmadd_u64(weight, cycle.pc); + virtual_acc.fmadd_bool(weight, cycle.is_virtual); + first_acc.fmadd_bool(weight, cycle.is_first_in_sequence); + noop_acc.fmadd_bool(weight, cycle.is_noop); + } + *unexpanded_pc = unexpanded_acc.reduce(); + *pc = pc_acc.reduce(); + *is_virtual = virtual_acc.reduce(); + *is_first_in_sequence = first_acc.reduce(); + *is_noop = noop_acc.reduce(); + *weighted_next_values = *unexpanded_pc + + gamma * *pc + + gamma2 * *is_virtual + + gamma3 * *is_first_in_sequence; + *not_noop = F::one() - *is_noop; + }, + ); + ( + unexpanded_pc, + pc, + is_virtual, + is_first_in_sequence, + is_noop, + weighted_next_values, + not_noop, + ) +} + +fn instruction_input_factors(cycles: &[Stage3Cycle]) -> InstructionInputFactors { + let mut right_operand_is_rs2 = vec![F::zero(); cycles.len()]; + let mut rs2_value = vec![F::zero(); cycles.len()]; + let mut right_operand_is_imm = vec![F::zero(); cycles.len()]; + let mut imm = vec![F::zero(); cycles.len()]; + let mut left_operand_is_rs1 = vec![F::zero(); cycles.len()]; + let mut rs1_value = vec![F::zero(); cycles.len()]; + let mut left_operand_is_pc = vec![F::zero(); cycles.len()]; + let mut unexpanded_pc = vec![F::zero(); cycles.len()]; + ( + &mut right_operand_is_rs2, + &mut rs2_value, + &mut right_operand_is_imm, + &mut imm, + &mut left_operand_is_rs1, + &mut rs1_value, + &mut left_operand_is_pc, + &mut unexpanded_pc, + cycles, + ) + .into_par_iter() + .for_each( + |( + right_operand_is_rs2, + rs2_value, + right_operand_is_imm, + imm, + left_operand_is_rs1, + rs1_value, + left_operand_is_pc, + unexpanded_pc, + cycle, + )| { + *right_operand_is_rs2 = F::from_bool(cycle.right_operand_is_rs2); + *rs2_value = F::from_u64(cycle.rs2_value); + *right_operand_is_imm = F::from_bool(cycle.right_operand_is_imm); + *imm = F::from_i128(cycle.imm); + *left_operand_is_rs1 = F::from_bool(cycle.left_operand_is_rs1); + *rs1_value = F::from_u64(cycle.rs1_value); + *left_operand_is_pc = F::from_bool(cycle.left_operand_is_pc); + *unexpanded_pc = F::from_u64(cycle.unexpanded_pc); + }, + ); + ( + right_operand_is_rs2, + rs2_value, + right_operand_is_imm, + imm, + left_operand_is_rs1, + rs1_value, + left_operand_is_pc, + unexpanded_pc, + ) +} + +fn register_factors(cycles: &[Stage3Cycle]) -> RegisterFactors { + let mut rd_write_value = vec![F::zero(); cycles.len()]; + let mut rs1_value = vec![F::zero(); cycles.len()]; + let mut rs2_value = vec![F::zero(); cycles.len()]; + (&mut rd_write_value, &mut rs1_value, &mut rs2_value, cycles) + .into_par_iter() + .for_each(|(rd_write_value, rs1_value, rs2_value, cycle)| { + *rd_write_value = F::from_u64(cycle.rd_write_value); + *rs1_value = F::from_u64(cycle.rs1_value); + *rs2_value = F::from_u64(cycle.rs2_value); + }); + (rd_write_value, rs1_value, rs2_value) +} + +fn expected_batched_output_claim( + context: Stage3KernelContext<'_>, + store: &Stage3ValueStore, + evals: &[Stage3NamedEval], + point: &[F], + batching_coeffs: &[F], +) -> Result { + let mut expected = F::zero(); + for (claim, &coefficient) in context.batch_claims()?.iter().zip(batching_coeffs) { + let instance = context + .program + .instance_results + .iter() + .find(|instance| { + instance.claim == claim.symbol && instance.source == context.driver.symbol + }) + .ok_or(Stage3KernelError::MissingClaim { + batch: context.batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(Stage3KernelError::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let claim_value = match Stage3Relation::from_symbol(instance.relation).ok_or( + Stage3KernelError::UnknownRelation { + relation: instance.relation, + }, + )? { + Stage3Relation::SpartanShift => expected_spartan_shift(store, evals, local_point)?, + Stage3Relation::InstructionInput => { + expected_instruction_input(store, evals, local_point)? + } + Stage3Relation::RegistersClaimReduction => { + expected_registers(store, evals, local_point)? + } + relation @ Stage3Relation::Batched => { + return Err(Stage3KernelError::KernelNotImplemented { + abi: relation.symbol(), + }) + } + }; + expected += coefficient * claim_value; + } + Ok(expected) +} + +fn expected_spartan_shift( + store: &Stage3ValueStore, + evals: &[Stage3NamedEval], + local_point: &[F], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_outer = + EqPlusOnePolynomial::::new(store.point("stage3.input.stage1.NextPC")?.to_vec()) + .evaluate(&opening_point); + let eq_product = EqPlusOnePolynomial::::new( + store + .point("stage3.input.stage2.product_virtual.NextIsNoop")? + .to_vec(), + ) + .evaluate(&opening_point); + let gamma = store.scalar("stage3.spartan_shift.gamma")?; + let gamma2 = store.scalar("stage3.spartan_shift.gamma2")?; + let gamma3 = store.scalar("stage3.spartan_shift.gamma3")?; + let gamma4 = store.scalar("stage3.spartan_shift.gamma4")?; + let weighted_outer = eval_by_name(evals, "stage3.spartan_shift.eval.UnexpandedPC")? + + gamma * eval_by_name(evals, "stage3.spartan_shift.eval.PC")? + + gamma2 * eval_by_name(evals, "stage3.spartan_shift.eval.OpFlagVirtualInstruction")? + + gamma3 * eval_by_name(evals, "stage3.spartan_shift.eval.OpFlagIsFirstInSequence")?; + Ok(eq_outer * weighted_outer + + gamma4 + * eq_product + * (F::one() - eval_by_name(evals, "stage3.spartan_shift.eval.InstructionFlagIsNoop")?)) +} + +fn expected_instruction_input( + store: &Stage3ValueStore, + evals: &[Stage3NamedEval], + local_point: &[F], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle( + &opening_point, + store.point("stage3.input.stage2.product_virtual.LeftInstructionInput")?, + ); + let left = eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Rs1Value")? + + eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", + )? * eval_by_name(evals, "stage3.instruction_input.eval.UnexpandedPC")?; + let right = eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Rs2Value")? + + eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Imm")?; + Ok(eq_eval * (right + store.scalar("stage3.instruction_input.gamma")? * left)) +} + +fn expected_registers( + store: &Stage3ValueStore, + evals: &[Stage3NamedEval], + local_point: &[F], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle( + &opening_point, + store.point("stage3.input.stage1.RdWriteValue")?, + ); + Ok(eq_eval + * (eval_by_name(evals, "stage3.registers_claim_reduction.eval.RdWriteValue")? + + store.scalar("stage3.registers.gamma")? + * eval_by_name(evals, "stage3.registers_claim_reduction.eval.Rs1Value")? + + store.scalar("stage3.registers.gamma2")? + * eval_by_name(evals, "stage3.registers_claim_reduction.eval.Rs2Value")?)) +} + +fn eval_by_name( + evals: &[Stage3NamedEval], + name: &'static str, +) -> Result { + evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| eval.value) + .ok_or(Stage3KernelError::MissingValue { symbol: name }) +} + +fn deferred_output_eval(values: &[F], eq: &[F]) -> F { + debug_assert_eq!(values.len(), eq.len()); + if values.len() >= DENSE_BIND_PAR_THRESHOLD { + values + .par_iter() + .zip(eq.par_iter()) + .map(|(&value, &weight)| value * weight) + .sum() + } else { + values + .iter() + .zip(eq) + .map(|(&value, &weight)| value * weight) + .sum() + } +} + +fn reverse_slice(values: &[F]) -> Vec { + values.iter().rev().copied().collect() +} + +fn named_eval(name: &'static str, oracle: &'static str, value: F) -> Stage3NamedEval { + Stage3NamedEval { + name, + oracle, + value, + } +} + +fn claim_relation( + program: &'static Stage3CpuProgramPlan, + claim: &Stage3SumcheckClaimPlan, +) -> Result { + if let Some(relation) = claim.relation { + return Stage3Relation::from_symbol(relation) + .ok_or(Stage3KernelError::UnknownRelation { relation }); + } + let kernel_symbol = claim.kernel.ok_or(Stage3KernelError::MissingKernel { + driver: claim.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or(Stage3KernelError::MissingKernel { + driver: claim.symbol, + kernel: kernel_symbol, + })?; + kernel.relation_kind() +} + +fn instance_round_offset( + program: &'static Stage3CpuProgramPlan, + driver: &'static str, + claim: &'static str, +) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.source == driver && instance.claim == claim) + .map(|instance| instance.round_offset) + .ok_or(Stage3KernelError::MissingClaim { + batch: driver, + claim, + }) +} + +fn combine_univariate_polys( + polynomials: &[UnivariatePoly], + coefficients: &[F], +) -> UnivariatePoly { + let max_len = polynomials + .iter() + .map(|poly| poly.coefficients().len()) + .max() + .unwrap_or(0); + let mut combined = vec![F::zero(); max_len]; + for (poly, &coefficient) in polynomials.iter().zip(coefficients) { + for (combined, &term) in combined.iter_mut().zip(poly.coefficients()) { + *combined += term * coefficient; + } + } + UnivariatePoly::new(combined) +} + +fn round_poly_from_instruction_input( + factors: &[Vec], + terms: &[ProductTerm], + split_eq: &SplitEqState, + previous_claim: F, +) -> UnivariatePoly { + debug_assert_eq!(factors.len(), 8); + debug_assert_eq!(terms.len(), 4); + let gamma = terms[2].coefficient; + let (q_constant, q_quadratic) = + instruction_input_split_round_coefficients(factors, split_eq, gamma); + gruen_cubic_poly( + split_eq.current_target(), + q_constant, + q_quadratic, + previous_claim, + ) +} + +fn round_poly_from_registers( + factors: &[Vec], + terms: &[ProductTerm], + split_eq: &SplitEqState, + previous_claim: F, +) -> UnivariatePoly { + debug_assert_eq!(factors.len(), 3); + debug_assert_eq!(terms.len(), 3); + let gamma = terms[1].coefficient; + let gamma2 = terms[2].coefficient; + let q_constant = registers_split_round_constant(factors, split_eq, gamma, gamma2); + gruen_quadratic_poly(split_eq.current_target(), q_constant, previous_claim) +} + +fn instruction_input_split_round_coefficients( + factors: &[Vec], + split_eq: &SplitEqState, + gamma: F, +) -> (F, F) { + let e_in = split_eq.e_in(); + let e_out = split_eq.e_out(); + if e_in.len() > 1 { + instruction_input_low_round_coefficients(factors, e_in, e_out, gamma) + } else { + instruction_input_high_round_coefficients(factors, e_in[0], e_out, gamma) + } +} + +fn instruction_input_low_round_coefficients( + factors: &[Vec], + e_in: &[F], + e_out: &[F], + gamma: F, +) -> (F, F) { + let in_len = e_in.len(); + let in_pairs = in_len / 2; + if factors[0].len() / 2 >= DENSE_BIND_PAR_THRESHOLD { + let accumulators = (0..e_out.len()) + .into_par_iter() + .map(|x_out| { + let mut local = [F::Accumulator::default(); 2]; + let base_pair = x_out * in_pairs; + let out_weight = e_out[x_out]; + for pair in 0..in_pairs { + accumulate_instruction_input_quadratic_pair( + &mut local, + out_weight * (e_in[2 * pair] + e_in[2 * pair + 1]), + factors, + base_pair + pair, + gamma, + ); + } + local + }) + .reduce( + || [F::Accumulator::default(); 2], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + (accumulators[0].reduce(), accumulators[1].reduce()) + } else { + let mut total = [F::Accumulator::default(); 2]; + for (x_out, &out_weight) in e_out.iter().enumerate() { + let base_pair = x_out * in_pairs; + for pair in 0..in_pairs { + accumulate_instruction_input_quadratic_pair( + &mut total, + out_weight * (e_in[2 * pair] + e_in[2 * pair + 1]), + factors, + base_pair + pair, + gamma, + ); + } + } + (total[0].reduce(), total[1].reduce()) + } +} + +fn instruction_input_high_round_coefficients( + factors: &[Vec], + in_weight: F, + e_out: &[F], + gamma: F, +) -> (F, F) { + let pairs = e_out.len() / 2; + if pairs >= DENSE_BIND_PAR_THRESHOLD { + let accumulators = (0..pairs) + .into_par_iter() + .map(|pair| { + let mut local = [F::Accumulator::default(); 2]; + accumulate_instruction_input_quadratic_pair( + &mut local, + in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]), + factors, + pair, + gamma, + ); + local + }) + .reduce( + || [F::Accumulator::default(); 2], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + (accumulators[0].reduce(), accumulators[1].reduce()) + } else { + let mut total = [F::Accumulator::default(); 2]; + for pair in 0..pairs { + accumulate_instruction_input_quadratic_pair( + &mut total, + in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]), + factors, + pair, + gamma, + ); + } + (total[0].reduce(), total[1].reduce()) + } +} + +fn accumulate_instruction_input_quadratic_pair( + accumulators: &mut [F::Accumulator; 2], + weight: F, + factors: &[Vec], + row: usize, + gamma: F, +) { + accumulate_quadratic_coefficients( + accumulators, + weight, + F::one(), + &factors[0], + &factors[1], + row, + ); + accumulate_quadratic_coefficients( + accumulators, + weight, + F::one(), + &factors[2], + &factors[3], + row, + ); + accumulate_quadratic_coefficients(accumulators, weight, gamma, &factors[4], &factors[5], row); + accumulate_quadratic_coefficients(accumulators, weight, gamma, &factors[6], &factors[7], row); +} + +fn accumulate_quadratic_coefficients( + accumulators: &mut [F::Accumulator; 2], + weight: F, + scale: F, + left: &[F], + right: &[F], + row: usize, +) { + let (left_0, left_delta) = linear_pair(left, row); + let (right_0, right_delta) = linear_pair(right, row); + let scaled_weight = weight * scale; + accumulators[0].fmadd(scaled_weight * left_0, right_0); + accumulators[1].fmadd(scaled_weight * left_delta, right_delta); +} + +fn registers_split_round_constant( + factors: &[Vec], + split_eq: &SplitEqState, + gamma: F, + gamma2: F, +) -> F { + let e_in = split_eq.e_in(); + let e_out = split_eq.e_out(); + if e_in.len() > 1 { + registers_low_round_constant(factors, e_in, e_out, gamma, gamma2) + } else { + registers_high_round_constant(factors, e_in[0], e_out, gamma, gamma2) + } +} + +fn registers_low_round_constant( + factors: &[Vec], + e_in: &[F], + e_out: &[F], + gamma: F, + gamma2: F, +) -> F { + let in_len = e_in.len(); + let in_pairs = in_len / 2; + if factors[0].len() / 2 >= DENSE_BIND_PAR_THRESHOLD { + (0..e_out.len()) + .into_par_iter() + .map(|x_out| { + let mut local = F::Accumulator::default(); + let base_pair = x_out * in_pairs; + let out_weight = e_out[x_out]; + for pair in 0..in_pairs { + accumulate_register_constant( + &mut local, + out_weight * (e_in[2 * pair] + e_in[2 * pair + 1]), + factors, + base_pair + pair, + gamma, + gamma2, + ); + } + local + }) + .reduce(F::Accumulator::default, |mut left, right| { + left.merge(right); + left + }) + .reduce() + } else { + let mut total = F::Accumulator::default(); + for (x_out, &out_weight) in e_out.iter().enumerate() { + let base_pair = x_out * in_pairs; + for pair in 0..in_pairs { + accumulate_register_constant( + &mut total, + out_weight * (e_in[2 * pair] + e_in[2 * pair + 1]), + factors, + base_pair + pair, + gamma, + gamma2, + ); + } + } + total.reduce() + } +} + +fn registers_high_round_constant( + factors: &[Vec], + in_weight: F, + e_out: &[F], + gamma: F, + gamma2: F, +) -> F { + let pairs = e_out.len() / 2; + if pairs >= DENSE_BIND_PAR_THRESHOLD { + (0..pairs) + .into_par_iter() + .map(|pair| { + let mut local = F::Accumulator::default(); + accumulate_register_constant( + &mut local, + in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]), + factors, + pair, + gamma, + gamma2, + ); + local + }) + .reduce(F::Accumulator::default, |mut left, right| { + left.merge(right); + left + }) + .reduce() + } else { + let mut total = F::Accumulator::default(); + for pair in 0..pairs { + accumulate_register_constant( + &mut total, + in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]), + factors, + pair, + gamma, + gamma2, + ); + } + total.reduce() + } +} + +fn accumulate_register_constant( + accumulator: &mut F::Accumulator, + weight: F, + factors: &[Vec], + row: usize, + gamma: F, + gamma2: F, +) { + accumulator.fmadd(weight, factors[0][2 * row]); + accumulator.fmadd(weight * gamma, factors[1][2 * row]); + accumulator.fmadd(weight * gamma2, factors[2][2 * row]); +} + +fn gruen_cubic_poly( + target: F, + q_constant: F, + q_quadratic_coeff: F, + previous_claim: F, +) -> UnivariatePoly { + let eq_eval_1 = target; + let eq_eval_0 = F::one() - target; + let eq_delta = eq_eval_1 - eq_eval_0; + let eq_eval_2 = eq_eval_1 + eq_delta; + let eq_eval_3 = eq_eval_2 + eq_delta; + let cubic_eval_0 = eq_eval_0 * q_constant; + let cubic_eval_1 = previous_claim - cubic_eval_0; + let quadratic_eval_1 = cubic_eval_1 / eq_eval_1; + let e_times_2 = q_quadratic_coeff + q_quadratic_coeff; + let quadratic_eval_2 = quadratic_eval_1 + quadratic_eval_1 - q_constant + e_times_2; + let quadratic_eval_3 = quadratic_eval_2 + quadratic_eval_1 - q_constant + e_times_2 + e_times_2; + UnivariatePoly::from_evals(&[ + cubic_eval_0, + cubic_eval_1, + eq_eval_2 * quadratic_eval_2, + eq_eval_3 * quadratic_eval_3, + ]) +} + +fn gruen_quadratic_poly( + target: F, + q_constant: F, + previous_claim: F, +) -> UnivariatePoly { + let eq_eval_1 = target; + let eq_eval_0 = F::one() - target; + let eq_delta = eq_eval_1 - eq_eval_0; + let eq_eval_2 = eq_eval_1 + eq_delta; + let quadratic_eval_0 = eq_eval_0 * q_constant; + let quadratic_eval_1 = previous_claim - quadratic_eval_0; + let linear_eval_1 = quadratic_eval_1 / eq_eval_1; + let linear_eval_2 = linear_eval_1 + linear_eval_1 - q_constant; + UnivariatePoly::from_evals(&[ + quadratic_eval_0, + quadratic_eval_1, + eq_eval_2 * linear_eval_2, + ]) +} + +fn round_poly_from_stage3_coefficients( + half: usize, + degree: usize, + coefficients: C, +) -> UnivariatePoly +where + F: Field, + C: Fn(usize, &mut [F::Accumulator; 4]) + Sync, +{ + let accumulators = if half >= DENSE_BIND_PAR_THRESHOLD { + (0..half) + .into_par_iter() + .map(|row| { + let mut local = [F::Accumulator::default(); 4]; + coefficients(row, &mut local); + local + }) + .reduce( + || [F::Accumulator::default(); 4], + |mut left, right| { + for index in 0..left.len() { + left[index].merge(right[index]); + } + left + }, + ) + } else { + (0..half).fold([F::Accumulator::default(); 4], |mut total, row| { + coefficients(row, &mut total); + total + }) + }; + UnivariatePoly::new( + accumulators[..=degree] + .iter() + .copied() + .map(FieldAccumulator::reduce) + .collect(), + ) +} + +#[inline] +fn linear_pair(factor: &[F], row: usize) -> (F, F) { + let low = factor[2 * row]; + (low, factor[2 * row + 1] - low) +} + +#[inline] +fn accumulate_linear_product( + acc: &mut [F::Accumulator; 4], + scale: F, + left_0: F, + left_delta: F, + right_0: F, + right_delta: F, +) { + acc[0].fmadd(scale * left_0, right_0); + acc[1].fmadd(scale * left_delta, right_0); + acc[1].fmadd(scale * left_0, right_delta); + acc[2].fmadd(scale * left_delta, right_delta); +} + +#[inline] +fn bind_dense_evals_reuse_serial( + values: &mut Vec, + scratch: &mut Vec, + challenge: F, +) { + let half = values.len() / 2; + scratch.resize(half, F::zero()); + for (index, output) in scratch.iter_mut().enumerate() { + let low = values[index << 1]; + let high = values[(index << 1) + 1]; + *output = low + challenge * (high - low); + } + std::mem::swap(values, scratch); + scratch.clear(); +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients() + .iter() + .rposition(|coefficient| *coefficient != F::zero()) + .unwrap_or(0) +} + +fn append_compressed_univariate_poly( + transcript: &mut T, + label: &'static str, + poly: &UnivariatePoly, +) where + F: Field, + T: Transcript, +{ + let compressed = poly.compress(); + transcript.append(&LabelWithCount( + label.as_bytes(), + compressed.coeffs_except_linear_term().len() as u64, + )); + for coefficient in compressed.coeffs_except_linear_term() { + transcript.append(coefficient); + } +} + +fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &F) +where + F: Field, + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +fn append_opening_claims( + program: &'static Stage3CpuProgramPlan, + store: &mut Stage3ValueStore, + transcript: &mut T, + evals: &[Stage3NamedEval], +) -> Result>, Stage3KernelError> +where + F: Field, + T: Transcript, +{ + if program.opening_batches.is_empty() { + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } + return Ok(Vec::new()); + } + let _ = store.evaluate_available_points(program)?; + let mut opening_claims = Vec::new(); + let mut seen = seed_stage3_opening_aliases(store, program); + for batch in program.opening_batches { + for symbol in batch.claim_operands { + let claim = + find_opening_claim(program, symbol).ok_or(Stage3KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + let point = store.point(claim.point_source)?.to_vec(); + let value = store.scalar(claim.eval_source)?; + let duplicate = has_seen_opening(&seen, claim.claim_kind, claim.oracle, &point); + if !duplicate { + append_labeled_scalar(transcript, "opening_claim", &value); + seen.push((claim.claim_kind, claim.oracle, point.clone())); + } + opening_claims.push(Stage3OpeningClaimValue { + symbol: claim.symbol, + oracle: claim.oracle, + domain: claim.domain, + claim_kind: claim.claim_kind, + point: point.clone(), + eval: value, + }); + } + } + Ok(opening_claims) +} + +fn seed_stage3_opening_aliases( + store: &Stage3ValueStore, + program: &'static Stage3CpuProgramPlan, +) -> Vec<(&'static str, &'static str, Vec)> { + program + .opening_inputs + .iter() + .filter_map(|input| { + store + .try_point(input.symbol) + .map(|point| (input.claim_kind, input.oracle, point.to_vec())) + }) + .collect() +} + +fn has_seen_opening( + seen: &[(&'static str, &'static str, Vec)], + claim_kind: &'static str, + oracle: &'static str, + point: &[F], +) -> bool { + seen.iter().any(|(seen_kind, seen_oracle, seen_point)| { + *seen_kind == claim_kind && *seen_oracle == oracle && seen_point.as_slice() == point + }) +} + +fn find_opening_claim<'a>( + program: &'a Stage3CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage3OpeningClaimPlan> { + program + .opening_claims + .iter() + .find(|claim| claim.symbol == symbol) +} + +pub fn execute_stage3_program( + program: &'static Stage3CpuProgramPlan, + mode: Stage3ExecutionMode, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage3KernelError> +where + F: Field, + E: Stage3KernelExecutor, + T: Transcript, +{ + verify_static_program_shape(program)?; + let mut artifacts = Stage3ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = find_transcript_squeeze(program, step.symbol).ok_or( + Stage3KernelError::MissingValue { + symbol: step.symbol, + }, + )?; + execute_stage3_squeeze(squeeze, executor, transcript, &mut artifacts)?; + } + "sumcheck_driver" => { + let driver = + find_driver(program, step.symbol).ok_or(Stage3KernelError::MissingKernel { + driver: step.symbol, + kernel: step.symbol, + })?; + execute_stage3_driver(program, mode, driver, executor, transcript, &mut artifacts)?; + } + _ => { + return Err(Stage3KernelError::InvalidProof { + driver: step.symbol, + reason: "unsupported stage3 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +fn execute_stage3_squeeze( + squeeze: &'static Stage3TranscriptSqueezePlan, + executor: &mut E, + transcript: &mut T, + artifacts: &mut Stage3ExecutionArtifacts, +) -> Result<(), Stage3KernelError> +where + F: Field, + E: Stage3KernelExecutor, + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + executor.observe_challenge_vector(squeeze, &values)?; + artifacts.challenge_vectors.push(Stage3ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn execute_stage3_driver( + program: &'static Stage3CpuProgramPlan, + mode: Stage3ExecutionMode, + driver: &'static Stage3SumcheckDriverPlan, + executor: &mut E, + transcript: &mut T, + artifacts: &mut Stage3ExecutionArtifacts, +) -> Result<(), Stage3KernelError> +where + F: Field, + E: Stage3KernelExecutor, + T: Transcript, +{ + let kernel_symbol = driver.kernel.ok_or(Stage3KernelError::MissingKernel { + driver: driver.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or(Stage3KernelError::MissingKernel { + driver: driver.symbol, + kernel: kernel_symbol, + })?; + let batch = find_batch(program, driver.batch).ok_or(Stage3KernelError::MissingBatch { + driver: driver.symbol, + batch: driver.batch, + })?; + let context = Stage3KernelContext { + mode, + program, + kernel, + batch, + driver, + }; + let output = match mode { + Stage3ExecutionMode::Prover => executor.prove_sumcheck(context, transcript)?, + Stage3ExecutionMode::Verifier => executor.verify_sumcheck(context, transcript)?, + }; + executor.observe_sumcheck_output(&output)?; + artifacts + .opening_claims + .extend(output.opening_claims.clone()); + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_static_program_shape( + program: &'static Stage3CpuProgramPlan, +) -> Result<(), Stage3KernelError> { + for expr in program.field_exprs { + verify_count(expr.symbol, expr.operand_names.len(), expr.operands.len())?; + } + for batch in program.batches { + verify_count(batch.symbol, batch.count, batch.ordered_claims.len())?; + verify_count(batch.symbol, batch.count, batch.claim_operands.len())?; + } + for batch in program.opening_batches { + verify_count(batch.symbol, batch.count, batch.ordered_claims.len())?; + verify_count(batch.symbol, batch.count, batch.claim_operands.len())?; + } + for kernel in program.kernels { + let relation = kernel.relation_kind()?; + let abi = kernel.abi_kind()?; + if relation + .symbol() + .replace("jolt.stage3.", "jolt_stage3_") + .replace('.', "_") + != abi.name() + { + return Err(Stage3KernelError::InvalidProof { + driver: kernel.symbol, + reason: "kernel relation and ABI mismatch", + }); + } + } + Ok(()) +} + +fn verify_count( + artifact: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage3KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage3KernelError::PlanCountMismatch { + artifact, + expected, + actual, + }) + } +} + +fn find_kernel<'a>( + program: &'a Stage3CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage3KernelPlan> { + program + .kernels + .iter() + .find(|kernel| kernel.symbol == symbol) +} + +fn find_batch<'a>( + program: &'a Stage3CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage3SumcheckBatchPlan> { + program.batches.iter().find(|batch| batch.symbol == symbol) +} + +fn find_driver<'a>( + program: &'a Stage3CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage3SumcheckDriverPlan> { + program + .drivers + .iter() + .find(|driver| driver.symbol == symbol) +} + +fn find_transcript_squeeze<'a>( + program: &'a Stage3CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage3TranscriptSqueezePlan> { + program + .transcript_squeezes + .iter() + .find(|squeeze| squeeze.symbol == symbol) +} + +#[cfg(test)] +#[expect(clippy::expect_used, reason = "stage3 kernel tests fail fast")] +mod tests { + use super::*; + use jolt_field::Fr; + use jolt_transcript::Blake2bTranscript; + + #[test] + fn stage3_relation_and_abi_registry_is_complete() { + let relations = [ + Stage3Relation::SpartanShift, + Stage3Relation::InstructionInput, + Stage3Relation::RegistersClaimReduction, + Stage3Relation::Batched, + ]; + for relation in relations { + assert_eq!( + Stage3Relation::from_symbol(relation.symbol()), + Some(relation) + ); + } + + let abis = [ + Stage3KernelAbi::SpartanShift, + Stage3KernelAbi::InstructionInput, + Stage3KernelAbi::RegistersClaimReduction, + Stage3KernelAbi::Batched, + ]; + for abi in abis { + assert_eq!(Stage3KernelAbi::from_name(abi.name()), Some(abi)); + } + } + + #[test] + fn stage3_batched_kernel_proves_and_verifies_synthetic_trace() { + let program = synthetic_stage3_program(); + let cycles = synthetic_cycles(); + let opening_inputs = synthetic_opening_inputs(&cycles); + let prover_inputs = Stage3ProverInputs::new(&opening_inputs).with_cycles(&cycles); + let mut prover = Stage3ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage3_test"); + + let artifacts = execute_stage3_program( + program, + Stage3ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("stage3 prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].proof.round_polynomials.len(), 2); + let proof = Stage3Proof::from(artifacts); + let mut verifier = Stage3VerifierKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage3_test"); + let verified = execute_stage3_program( + program, + Stage3ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("stage3 verifier accepts prover proof"); + + assert_eq!(verified.sumchecks.len(), 1); + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + } + + #[test] + fn stage3_batched_kernel_rejects_tampered_eval() { + let program = synthetic_stage3_program(); + let cycles = synthetic_cycles(); + let opening_inputs = synthetic_opening_inputs(&cycles); + let mut prover = Stage3ProverKernelExecutor::new( + Stage3ProverInputs::new(&opening_inputs).with_cycles(&cycles), + ); + let mut prover_transcript = Blake2bTranscript::::new(b"stage3_test"); + let mut proof = Stage3Proof::from( + execute_stage3_program( + program, + Stage3ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("stage3 prover succeeds"), + ); + proof.sumchecks[0].evals[0].value += Fr::from_u64(1); + + let mut verifier = Stage3VerifierKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage3_test"); + let error = execute_stage3_program( + program, + Stage3ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect_err("tampered proof is rejected"); + + assert!(matches!(error, Stage3KernelError::InvalidProof { .. })); + } + + fn synthetic_stage3_program() -> &'static Stage3CpuProgramPlan { + let exprs = leak_slice(vec![ + field_expr( + "stage3.spartan_shift.gamma2", + "field.pow:2", + vec!["stage3.spartan_shift.gamma"], + ), + field_expr( + "stage3.spartan_shift.gamma3", + "field.mul", + vec!["stage3.spartan_shift.gamma2", "stage3.spartan_shift.gamma"], + ), + field_expr( + "stage3.spartan_shift.gamma4", + "field.mul", + vec!["stage3.spartan_shift.gamma2", "stage3.spartan_shift.gamma2"], + ), + field_expr( + "stage3.spartan_shift.term.NextPC", + "field.mul", + vec!["stage3.spartan_shift.gamma", "stage3.input.stage1.NextPC"], + ), + field_expr( + "stage3.spartan_shift.term.NextIsVirtual", + "field.mul", + vec![ + "stage3.spartan_shift.gamma2", + "stage3.input.stage1.NextIsVirtual", + ], + ), + field_expr( + "stage3.spartan_shift.term.NextIsFirstInSequence", + "field.mul", + vec![ + "stage3.spartan_shift.gamma3", + "stage3.input.stage1.NextIsFirstInSequence", + ], + ), + field_expr( + "stage3.spartan_shift.one_minus.NextIsNoop", + "field.sub", + vec![ + "stage3.field.one", + "stage3.input.stage2.product_virtual.NextIsNoop", + ], + ), + field_expr( + "stage3.spartan_shift.term.NextIsNoop", + "field.mul", + vec![ + "stage3.spartan_shift.gamma4", + "stage3.spartan_shift.one_minus.NextIsNoop", + ], + ), + field_expr( + "stage3.spartan_shift.partial.NextUnexpandedPCNextPC", + "field.add", + vec![ + "stage3.input.stage1.NextUnexpandedPC", + "stage3.spartan_shift.term.NextPC", + ], + ), + field_expr( + "stage3.spartan_shift.partial.NextIsVirtual", + "field.add", + vec![ + "stage3.spartan_shift.partial.NextUnexpandedPCNextPC", + "stage3.spartan_shift.term.NextIsVirtual", + ], + ), + field_expr( + "stage3.spartan_shift.partial.NextIsFirstInSequence", + "field.add", + vec![ + "stage3.spartan_shift.partial.NextIsVirtual", + "stage3.spartan_shift.term.NextIsFirstInSequence", + ], + ), + field_expr( + "stage3.spartan_shift.claim_expr", + "field.add", + vec![ + "stage3.spartan_shift.partial.NextIsFirstInSequence", + "stage3.spartan_shift.term.NextIsNoop", + ], + ), + field_expr( + "stage3.instruction_input.term.LeftInstructionInput", + "field.mul", + vec![ + "stage3.instruction_input.gamma", + "stage3.input.stage2.product_virtual.LeftInstructionInput", + ], + ), + field_expr( + "stage3.instruction_input.claim_expr", + "field.add", + vec![ + "stage3.input.stage2.product_virtual.RightInstructionInput", + "stage3.instruction_input.term.LeftInstructionInput", + ], + ), + field_expr( + "stage3.registers.gamma2", + "field.pow:2", + vec!["stage3.registers.gamma"], + ), + field_expr( + "stage3.registers.term.Rs1Value", + "field.mul", + vec!["stage3.registers.gamma", "stage3.input.stage1.Rs1Value"], + ), + field_expr( + "stage3.registers.term.Rs2Value", + "field.mul", + vec!["stage3.registers.gamma2", "stage3.input.stage1.Rs2Value"], + ), + field_expr( + "stage3.registers.partial.RdWriteValueRs1Value", + "field.add", + vec![ + "stage3.input.stage1.RdWriteValue", + "stage3.registers.term.Rs1Value", + ], + ), + field_expr( + "stage3.registers.claim_expr", + "field.add", + vec![ + "stage3.registers.partial.RdWriteValueRs1Value", + "stage3.registers.term.Rs2Value", + ], + ), + ]); + + Box::leak(Box::new(Stage3CpuProgramPlan { + params: Stage3Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }, + steps: leak_slice(vec![ + Stage3ProgramStepPlan { + kind: "transcript_squeeze", + symbol: "stage3.spartan_shift.gamma", + }, + Stage3ProgramStepPlan { + kind: "transcript_squeeze", + symbol: "stage3.instruction_input.gamma", + }, + Stage3ProgramStepPlan { + kind: "transcript_squeeze", + symbol: "stage3.registers.gamma", + }, + Stage3ProgramStepPlan { + kind: "sumcheck_driver", + symbol: "stage3.sumcheck", + }, + ]), + transcript_squeezes: leak_slice(vec![ + Stage3TranscriptSqueezePlan { + symbol: "stage3.spartan_shift.gamma", + label: "spartan_shift_gamma", + kind: "challenge_scalar", + count: 1, + }, + Stage3TranscriptSqueezePlan { + symbol: "stage3.instruction_input.gamma", + label: "instruction_input_gamma", + kind: "challenge_scalar", + count: 1, + }, + Stage3TranscriptSqueezePlan { + symbol: "stage3.registers.gamma", + label: "registers_gamma", + kind: "challenge_scalar", + count: 1, + }, + ]), + opening_inputs: leak_slice(stage3_opening_input_plans()), + field_constants: leak_slice(vec![Stage3FieldConstantPlan { + symbol: "stage3.field.one", + field: "bn254_fr", + value: 1, + }]), + field_exprs: exprs, + kernels: leak_slice(vec![ + kernel( + "jolt.cpu.stage3.spartan_shift", + "jolt.stage3.spartan_shift", + "jolt_stage3_spartan_shift", + ), + kernel( + "jolt.cpu.stage3.instruction_input", + "jolt.stage3.instruction_input", + "jolt_stage3_instruction_input", + ), + kernel( + "jolt.cpu.stage3.registers_claim_reduction", + "jolt.stage3.registers_claim_reduction", + "jolt_stage3_registers_claim_reduction", + ), + kernel( + "jolt.cpu.stage3.batched", + "jolt.stage3.batched", + "jolt_stage3_batched", + ), + ]), + claims: leak_slice(vec![ + claim( + "stage3.spartan_shift.input", + "jolt.cpu.stage3.spartan_shift", + "stage3.spartan_shift.claim_expr", + 2, + vec![ + "stage3.input.stage1.NextUnexpandedPC", + "stage3.input.stage1.NextPC", + "stage3.input.stage1.NextIsVirtual", + "stage3.input.stage1.NextIsFirstInSequence", + "stage3.input.stage2.product_virtual.NextIsNoop", + ], + ), + claim( + "stage3.instruction_input.input", + "jolt.cpu.stage3.instruction_input", + "stage3.instruction_input.claim_expr", + 3, + vec![ + "stage3.input.stage2.product_virtual.RightInstructionInput", + "stage3.input.stage2.product_virtual.LeftInstructionInput", + ], + ), + claim( + "stage3.registers_claim_reduction.input", + "jolt.cpu.stage3.registers_claim_reduction", + "stage3.registers.claim_expr", + 2, + vec![ + "stage3.input.stage1.RdWriteValue", + "stage3.input.stage1.Rs1Value", + "stage3.input.stage1.Rs2Value", + ], + ), + ]), + batches: leak_slice(vec![Stage3SumcheckBatchPlan { + symbol: "stage3.batch", + stage: "stage3", + proof_slot: "stage3.sumcheck", + policy: "jolt_core_stage3_aligned", + count: 3, + ordered_claims: leak_slice(vec![ + "stage3.spartan_shift.input", + "stage3.instruction_input.input", + "stage3.registers_claim_reduction.input", + ]), + claim_operands: leak_slice(vec![ + "stage3.spartan_shift.input", + "stage3.instruction_input.input", + "stage3.registers_claim_reduction.input", + ]), + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: leak_slice(vec![2]), + }]), + drivers: leak_slice(vec![Stage3SumcheckDriverPlan { + symbol: "stage3.sumcheck", + stage: "stage3", + proof_slot: "stage3.sumcheck", + kernel: Some("jolt.cpu.stage3.batched"), + relation: None, + batch: "stage3.batch", + policy: "jolt_core_stage3_aligned", + round_schedule: leak_slice(vec![2]), + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 2, + degree: 3, + }]), + instance_results: leak_slice(vec![ + instance( + "stage3.spartan_shift.instance", + "stage3.spartan_shift.input", + "jolt.stage3.spartan_shift", + 0, + 2, + ), + instance( + "stage3.instruction_input.instance", + "stage3.instruction_input.input", + "jolt.stage3.instruction_input", + 1, + 3, + ), + instance( + "stage3.registers_claim_reduction.instance", + "stage3.registers_claim_reduction.input", + "jolt.stage3.registers_claim_reduction", + 2, + 2, + ), + ]), + evals: leak_slice(stage3_eval_plans()), + point_slices: &[], + point_concats: &[], + opening_claims: leak_slice(stage3_opening_claim_plans()), + opening_equalities: leak_slice(vec![ + Stage3OpeningClaimEqualityPlan { + symbol: "stage3.instruction_input.left_claim_consistency", + mode: "point_and_eval", + lhs: "stage3.input.stage2.product_virtual.LeftInstructionInput", + rhs: "stage3.input.stage2.instruction_lookup.LeftInstructionInput", + }, + Stage3OpeningClaimEqualityPlan { + symbol: "stage3.instruction_input.right_claim_consistency", + mode: "point_and_eval", + lhs: "stage3.input.stage2.product_virtual.RightInstructionInput", + rhs: "stage3.input.stage2.instruction_lookup.RightInstructionInput", + }, + ]), + opening_batches: leak_slice(vec![Stage3OpeningBatchPlan { + symbol: "stage3.openings", + stage: "stage3", + proof_slot: "stage3.openings", + policy: "jolt_stage3_output_order", + count: 16, + ordered_claims: leak_slice( + stage3_opening_claim_plans() + .iter() + .map(|claim| claim.symbol) + .collect(), + ), + claim_operands: leak_slice( + stage3_opening_claim_plans() + .iter() + .map(|claim| claim.symbol) + .collect(), + ), + }]), + })) + } + + fn synthetic_cycles() -> [Stage3Cycle; 4] { + [ + Stage3Cycle { + unexpanded_pc: 10, + pc: 0, + is_virtual: false, + is_first_in_sequence: false, + is_noop: false, + left_operand_is_rs1: true, + rs1_value: 3, + left_operand_is_pc: false, + right_operand_is_rs2: true, + rs2_value: 5, + right_operand_is_imm: false, + imm: 0, + rd_write_value: 8, + }, + Stage3Cycle { + unexpanded_pc: 14, + pc: 1, + is_virtual: true, + is_first_in_sequence: true, + is_noop: false, + left_operand_is_rs1: false, + rs1_value: 0, + left_operand_is_pc: true, + right_operand_is_rs2: false, + rs2_value: 0, + right_operand_is_imm: true, + imm: -7, + rd_write_value: 21, + }, + Stage3Cycle { + unexpanded_pc: 18, + pc: 2, + is_virtual: false, + is_first_in_sequence: false, + is_noop: true, + left_operand_is_rs1: false, + rs1_value: 0, + left_operand_is_pc: false, + right_operand_is_rs2: false, + rs2_value: 0, + right_operand_is_imm: false, + imm: 0, + rd_write_value: 0, + }, + Stage3Cycle { + unexpanded_pc: 22, + pc: 3, + is_virtual: true, + is_first_in_sequence: false, + is_noop: false, + left_operand_is_rs1: true, + rs1_value: 9, + left_operand_is_pc: false, + right_operand_is_rs2: false, + rs2_value: 0, + right_operand_is_imm: true, + imm: 11, + rd_write_value: 20, + }, + ] + } + + fn synthetic_opening_inputs(cycles: &[Stage3Cycle]) -> Vec> { + let r_outer = vec![Fr::from_u64(2), Fr::from_u64(3)]; + let r_product = vec![Fr::from_u64(5), Fr::from_u64(7)]; + let r_instruction = vec![Fr::from_u64(11), Fr::from_u64(13)]; + let r_registers = vec![Fr::from_u64(17), Fr::from_u64(19)]; + vec![ + opening( + "stage3.input.stage1.NextUnexpandedPC", + &r_outer, + eq_plus_one_eval(cycles, &r_outer, |cycle| Fr::from_u64(cycle.unexpanded_pc)), + ), + opening( + "stage3.input.stage1.NextPC", + &r_outer, + eq_plus_one_eval(cycles, &r_outer, |cycle| Fr::from_u64(cycle.pc)), + ), + opening( + "stage3.input.stage1.NextIsVirtual", + &r_outer, + eq_plus_one_eval(cycles, &r_outer, |cycle| Fr::from_bool(cycle.is_virtual)), + ), + opening( + "stage3.input.stage1.NextIsFirstInSequence", + &r_outer, + eq_plus_one_eval(cycles, &r_outer, |cycle| { + Fr::from_bool(cycle.is_first_in_sequence) + }), + ), + opening( + "stage3.input.stage2.product_virtual.NextIsNoop", + &r_product, + eq_plus_one_eval(cycles, &r_product, |cycle| Fr::from_bool(cycle.is_noop)) + + r_product.iter().copied().product::(), + ), + opening( + "stage3.input.stage2.product_virtual.LeftInstructionInput", + &r_instruction, + mle_eval(cycles, &r_instruction, left_instruction_input), + ), + opening( + "stage3.input.stage2.product_virtual.RightInstructionInput", + &r_instruction, + mle_eval(cycles, &r_instruction, right_instruction_input), + ), + opening( + "stage3.input.stage2.instruction_lookup.LeftInstructionInput", + &r_instruction, + mle_eval(cycles, &r_instruction, left_instruction_input), + ), + opening( + "stage3.input.stage2.instruction_lookup.RightInstructionInput", + &r_instruction, + mle_eval(cycles, &r_instruction, right_instruction_input), + ), + opening( + "stage3.input.stage1.RdWriteValue", + &r_registers, + mle_eval(cycles, &r_registers, |cycle| { + Fr::from_u64(cycle.rd_write_value) + }), + ), + opening( + "stage3.input.stage1.Rs1Value", + &r_registers, + mle_eval(cycles, &r_registers, |cycle| Fr::from_u64(cycle.rs1_value)), + ), + opening( + "stage3.input.stage1.Rs2Value", + &r_registers, + mle_eval(cycles, &r_registers, |cycle| Fr::from_u64(cycle.rs2_value)), + ), + ] + } + + fn field_expr( + symbol: &'static str, + formula: &'static str, + operands: Vec<&'static str>, + ) -> Stage3FieldExprPlan { + let operands = leak_slice(operands); + Stage3FieldExprPlan { + symbol, + kind: "op", + formula, + operand_names: operands, + operands, + } + } + + fn kernel(symbol: &'static str, relation: &'static str, abi: &'static str) -> Stage3KernelPlan { + Stage3KernelPlan { + symbol, + relation, + kind: "sumcheck", + backend: "cpu", + abi, + } + } + + fn claim( + symbol: &'static str, + kernel: &'static str, + claim_value: &'static str, + degree: usize, + input_openings: Vec<&'static str>, + ) -> Stage3SumcheckClaimPlan { + Stage3SumcheckClaimPlan { + symbol, + stage: "stage3", + domain: "jolt.trace_domain", + num_rounds: 2, + degree, + claim: symbol, + kernel: Some(kernel), + relation: None, + claim_value, + input_openings: leak_slice(input_openings), + } + } + + fn instance( + symbol: &'static str, + claim: &'static str, + relation: &'static str, + index: usize, + degree: usize, + ) -> Stage3SumcheckInstanceResultPlan { + Stage3SumcheckInstanceResultPlan { + symbol, + source: "stage3.sumcheck", + claim, + relation, + index, + point_arity: 2, + num_rounds: 2, + round_offset: 0, + point_order: "reverse", + degree, + } + } + + fn opening(symbol: &'static str, point: &[Fr], eval: Fr) -> Stage3OpeningInputValue { + Stage3OpeningInputValue { + symbol, + point: point.to_vec(), + eval, + } + } + + fn mle_eval(cycles: &[Stage3Cycle], point: &[Fr], f: impl Fn(&Stage3Cycle) -> Fr) -> Fr { + EqPolynomial::::evals(point, None) + .iter() + .zip(cycles) + .map(|(&weight, cycle)| weight * f(cycle)) + .sum() + } + + fn eq_plus_one_eval( + cycles: &[Stage3Cycle], + point: &[Fr], + f: impl Fn(&Stage3Cycle) -> Fr, + ) -> Fr { + EqPlusOnePolynomial::::evals(point, None) + .1 + .iter() + .zip(cycles) + .map(|(&weight, cycle)| weight * f(cycle)) + .sum() + } + + fn left_instruction_input(cycle: &Stage3Cycle) -> Fr { + Fr::from_bool(cycle.left_operand_is_rs1) * Fr::from_u64(cycle.rs1_value) + + Fr::from_bool(cycle.left_operand_is_pc) * Fr::from_u64(cycle.unexpanded_pc) + } + + fn right_instruction_input(cycle: &Stage3Cycle) -> Fr { + Fr::from_bool(cycle.right_operand_is_rs2) * Fr::from_u64(cycle.rs2_value) + + Fr::from_bool(cycle.right_operand_is_imm) * Fr::from_i128(cycle.imm) + } + + fn stage3_opening_input_plans() -> Vec { + vec![ + opening_input_plan( + "stage3.input.stage1.NextUnexpandedPC", + "stage1", + "NextUnexpandedPC", + ), + opening_input_plan("stage3.input.stage1.NextPC", "stage1", "NextPC"), + opening_input_plan( + "stage3.input.stage1.NextIsVirtual", + "stage1", + "NextIsVirtual", + ), + opening_input_plan( + "stage3.input.stage1.NextIsFirstInSequence", + "stage1", + "NextIsFirstInSequence", + ), + opening_input_plan( + "stage3.input.stage2.product_virtual.NextIsNoop", + "stage2", + "NextIsNoop", + ), + opening_input_plan( + "stage3.input.stage2.product_virtual.LeftInstructionInput", + "stage2", + "LeftInstructionInput", + ), + opening_input_plan( + "stage3.input.stage2.product_virtual.RightInstructionInput", + "stage2", + "RightInstructionInput", + ), + opening_input_plan( + "stage3.input.stage2.instruction_lookup.LeftInstructionInput", + "stage2", + "LeftInstructionInput", + ), + opening_input_plan( + "stage3.input.stage2.instruction_lookup.RightInstructionInput", + "stage2", + "RightInstructionInput", + ), + opening_input_plan("stage3.input.stage1.RdWriteValue", "stage1", "RdWriteValue"), + opening_input_plan("stage3.input.stage1.Rs1Value", "stage1", "Rs1Value"), + opening_input_plan("stage3.input.stage1.Rs2Value", "stage1", "Rs2Value"), + ] + } + + fn opening_input_plan( + symbol: &'static str, + source_stage: &'static str, + oracle: &'static str, + ) -> Stage3OpeningInputPlan { + Stage3OpeningInputPlan { + symbol, + source_stage, + source_claim: symbol, + oracle, + domain: "jolt.trace_domain", + point_arity: 2, + claim_kind: "virtual", + } + } + + fn stage3_eval_plans() -> Vec { + vec![ + eval("stage3.spartan_shift.eval.UnexpandedPC", "UnexpandedPC", 0), + eval("stage3.spartan_shift.eval.PC", "PC", 1), + eval( + "stage3.spartan_shift.eval.OpFlagVirtualInstruction", + "OpFlagVirtualInstruction", + 2, + ), + eval( + "stage3.spartan_shift.eval.OpFlagIsFirstInSequence", + "OpFlagIsFirstInSequence", + 3, + ), + eval( + "stage3.spartan_shift.eval.InstructionFlagIsNoop", + "InstructionFlagIsNoop", + 4, + ), + eval( + "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", + "InstructionFlagLeftOperandIsRs1Value", + 5, + ), + eval("stage3.instruction_input.eval.Rs1Value", "Rs1Value", 6), + eval( + "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", + "InstructionFlagLeftOperandIsPC", + 7, + ), + eval( + "stage3.instruction_input.eval.UnexpandedPC", + "UnexpandedPC", + 8, + ), + eval( + "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", + "InstructionFlagRightOperandIsRs2Value", + 9, + ), + eval("stage3.instruction_input.eval.Rs2Value", "Rs2Value", 10), + eval( + "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", + "InstructionFlagRightOperandIsImm", + 11, + ), + eval("stage3.instruction_input.eval.Imm", "Imm", 12), + eval( + "stage3.registers_claim_reduction.eval.RdWriteValue", + "RdWriteValue", + 13, + ), + eval( + "stage3.registers_claim_reduction.eval.Rs1Value", + "Rs1Value", + 14, + ), + eval( + "stage3.registers_claim_reduction.eval.Rs2Value", + "Rs2Value", + 15, + ), + ] + } + + fn eval(symbol: &'static str, oracle: &'static str, index: usize) -> Stage3SumcheckEvalPlan { + Stage3SumcheckEvalPlan { + symbol, + source: "stage3.sumcheck", + name: symbol, + index, + oracle, + } + } + + fn stage3_opening_claim_plans() -> Vec { + stage3_eval_plans() + .into_iter() + .map(|eval| Stage3OpeningClaimPlan { + symbol: eval.symbol.replace(".eval.", ".opening.").leak(), + oracle: eval.oracle, + domain: "jolt.trace_domain", + point_arity: 2, + claim_kind: "virtual", + point_source: match eval.symbol { + name if name.starts_with("stage3.spartan_shift.") => { + "stage3.spartan_shift.instance" + } + name if name.starts_with("stage3.instruction_input.") => { + "stage3.instruction_input.instance" + } + _ => "stage3.registers_claim_reduction.instance", + }, + eval_source: eval.symbol, + }) + .collect() + } + + fn leak_slice(values: Vec) -> &'static [T] { + Box::leak(values.into_boxed_slice()) + } +} diff --git a/crates/jolt-kernels/src/stage4.rs b/crates/jolt-kernels/src/stage4.rs new file mode 100644 index 0000000000..27ef3df87e --- /dev/null +++ b/crates/jolt-kernels/src/stage4.rs @@ -0,0 +1,4667 @@ +//! Stage 4 coarse-kernel ABI used by Bolt-generated Jolt prover code. + +#![expect( + clippy::large_enum_variant, + reason = "kernel states stay inline to avoid boxing hot prover state" +)] +#![expect( + clippy::too_many_arguments, + reason = "kernel constructors mirror generated staged protocol inputs" +)] + +use std::error::Error; +use std::fmt::{self, Display, Formatter}; +use std::mem::MaybeUninit; + +use crate::dense::{bind_dense_evals_reuse, DENSE_BIND_PAR_THRESHOLD}; +use crate::split_eq::SplitEqState; +use crate::stage2::Stage2RamAccess; +use jolt_field::{Field, FieldAccumulator}; +use jolt_poly::{EqPolynomial, UnivariatePoly}; +use jolt_sumcheck::SumcheckProof; +use jolt_transcript::{Label, LabelWithCount, Transcript}; +use jolt_witness::{stage4_5_sparse_trace_witness, Stage45SparseTraceWitness}; +use rayon::prelude::*; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage4ExecutionMode { + Prover, + Verifier, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage4Relation { + RegistersReadWrite, + RamValCheck, + Batched, +} + +impl Stage4Relation { + pub fn from_symbol(symbol: &str) -> Option { + match symbol { + "jolt.stage4.registers_read_write" => Some(Self::RegistersReadWrite), + "jolt.stage4.ram_val_check" => Some(Self::RamValCheck), + "jolt.stage4.batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn symbol(self) -> &'static str { + match self { + Self::RegistersReadWrite => "jolt.stage4.registers_read_write", + Self::RamValCheck => "jolt.stage4.ram_val_check", + Self::Batched => "jolt.stage4.batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage4KernelAbi { + RegistersReadWrite, + RamValCheck, + Batched, +} + +impl Stage4KernelAbi { + pub fn from_name(name: &str) -> Option { + match name { + "jolt_stage4_registers_read_write" => Some(Self::RegistersReadWrite), + "jolt_stage4_ram_val_check" => Some(Self::RamValCheck), + "jolt_stage4_batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn name(self) -> &'static str { + match self { + Self::RegistersReadWrite => "jolt_stage4_registers_read_write", + Self::RamValCheck => "jolt_stage4_ram_val_check", + Self::Batched => "jolt_stage4_batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +impl Stage4KernelPlan { + pub fn relation_kind(&self) -> Result { + Stage4Relation::from_symbol(self.relation).ok_or(Stage4KernelError::UnknownRelation { + relation: self.relation, + }) + } + + pub fn abi_kind(&self) -> Result { + Stage4KernelAbi::from_name(self.abi) + .ok_or(Stage4KernelError::UnknownKernelAbi { abi: self.abi }) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4TranscriptAbsorbBytesPlan { + pub symbol: &'static str, + pub label: &'static str, + pub payload: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operand_names: &'static [&'static str], + pub operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4OpeningClaimEqualityPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub lhs: &'static str, + pub rhs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], + pub claim_operands: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4CpuProgramPlan { + pub role: &'static str, + pub params: Stage4Params, + pub steps: &'static [Stage4ProgramStepPlan], + pub transcript_squeezes: &'static [Stage4TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [Stage4TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [Stage4OpeningInputPlan], + pub field_constants: &'static [Stage4FieldConstantPlan], + pub field_exprs: &'static [Stage4FieldExprPlan], + pub kernels: &'static [Stage4KernelPlan], + pub claims: &'static [Stage4SumcheckClaimPlan], + pub batches: &'static [Stage4SumcheckBatchPlan], + pub drivers: &'static [Stage4SumcheckDriverPlan], + pub instance_results: &'static [Stage4SumcheckInstanceResultPlan], + pub evals: &'static [Stage4SumcheckEvalPlan], + pub point_slices: &'static [Stage4PointSlicePlan], + pub point_concats: &'static [Stage4PointConcatPlan], + pub opening_claims: &'static [Stage4OpeningClaimPlan], + pub opening_equalities: &'static [Stage4OpeningClaimEqualityPlan], + pub opening_batches: &'static [Stage4OpeningBatchPlan], +} + +impl Stage4CpuProgramPlan { + pub fn claim(&self, symbol: &str) -> Option<&Stage4SumcheckClaimPlan> { + self.claims.iter().find(|claim| claim.symbol == symbol) + } + + pub fn instance_results_for_driver( + &self, + driver: &'static str, + ) -> impl Iterator { + self.instance_results + .iter() + .filter(move |instance| instance.source == driver) + } + + pub fn evals_for_driver( + &self, + driver: &'static str, + ) -> impl Iterator { + self.evals.iter().filter(move |eval| eval.source == driver) + } +} + +#[derive(Clone, Debug)] +pub struct Stage4NamedEval { + pub name: &'static str, + pub oracle: &'static str, + pub value: F, +} + +#[derive(Clone, Debug)] +pub struct Stage4SumcheckOutput { + pub driver: &'static str, + pub point: Vec, + pub evals: Vec>, + pub opening_claims: Vec>, + pub proof: SumcheckProof, +} + +#[derive(Clone, Debug)] +pub struct Stage4ChallengeVector { + pub symbol: &'static str, + pub values: Vec, +} + +#[derive(Clone, Debug)] +pub struct Stage4OpeningClaimValue { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub claim_kind: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Debug)] +pub struct Stage4ExecutionArtifacts { + pub challenge_vectors: Vec>, + pub sumchecks: Vec>, + pub opening_claims: Vec>, + pub opening_batches: Vec<&'static Stage4OpeningBatchPlan>, +} + +impl Default for Stage4ExecutionArtifacts { + fn default() -> Self { + Self { + challenge_vectors: Vec::new(), + sumchecks: Vec::new(), + opening_claims: Vec::new(), + opening_batches: Vec::new(), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stage4Proof { + pub sumchecks: Vec>, +} + +impl From> for Stage4Proof { + fn from(artifacts: Stage4ExecutionArtifacts) -> Self { + Self { + sumchecks: artifacts.sumchecks, + } + } +} + +#[derive(Clone, Debug)] +pub struct Stage4OpeningInputValue { + pub symbol: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Copy)] +pub struct Stage4RegistersWitness<'a, F: Field> { + pub register_count: usize, + pub trace_len: usize, + pub registers_val: &'a [F], + pub rs1_ra: &'a [F], + pub rs2_ra: &'a [F], + pub rd_wa: &'a [F], + pub accesses: Option<&'a [Stage4RegisterAccess]>, + pub rd_inc: &'a [F], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4RegisterRead { + pub address: usize, + pub value: u64, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage4RegisterWrite { + pub address: usize, + pub pre_value: u64, + pub post_value: u64, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Stage4RegisterAccess { + pub rs1: Option, + pub rs2: Option, + pub rd: Option, +} + +pub fn stage4_5_sparse_trace_witness_from_accesses( + register_accesses: &[Stage4RegisterAccess], + ram_accesses: &[Stage2RamAccess], +) -> Stage45SparseTraceWitness { + stage4_5_sparse_trace_witness( + register_accesses.iter().map(|access| { + access + .rd + .map(|rd| (rd.address, rd.pre_value, rd.post_value)) + }), + ram_accesses.iter().map(|access| { + ( + access.remapped_address, + access.read_value, + access.write_value, + ) + }), + ) +} + +#[derive(Clone, Copy)] +pub struct Stage4RamWitness<'a, F: Field> { + pub ram_k: usize, + pub trace_len: usize, + pub ram_ra: &'a [F], + pub write_address_indices: Option<&'a [Option]>, + pub ram_inc: &'a [F], +} + +#[derive(Clone, Copy)] +pub struct Stage4ProverInputs<'a, F: Field> { + pub opening_inputs: &'a [Stage4OpeningInputValue], + pub registers: Option>, + pub ram: Option>, +} + +impl<'a, F: Field> Stage4ProverInputs<'a, F> { + pub fn new(opening_inputs: &'a [Stage4OpeningInputValue]) -> Self { + Self { + opening_inputs, + registers: None, + ram: None, + } + } + + pub fn empty() -> Self { + Self { + opening_inputs: &[], + registers: None, + ram: None, + } + } + + pub fn with_registers(mut self, registers: Stage4RegistersWitness<'a, F>) -> Self { + self.registers = Some(registers); + self + } + + pub fn with_ram(mut self, ram: Stage4RamWitness<'a, F>) -> Self { + self.ram = Some(ram); + self + } + + pub fn with_sparse_trace_witness( + self, + register_count: usize, + trace_len: usize, + ram_k: usize, + register_accesses: &'a [Stage4RegisterAccess], + rd_inc: &'a [F], + write_address_indices: &'a [Option], + ram_inc: &'a [F], + ) -> Self { + self.with_registers(Stage4RegistersWitness { + register_count, + trace_len, + registers_val: &[], + rs1_ra: &[], + rs2_ra: &[], + rd_wa: &[], + accesses: Some(register_accesses), + rd_inc, + }) + .with_ram(Stage4RamWitness { + ram_k, + trace_len, + ram_ra: &[], + write_address_indices: Some(write_address_indices), + ram_inc, + }) + } + + pub fn with_stage45_sparse_trace_witness( + self, + register_count: usize, + trace_len: usize, + ram_k: usize, + register_accesses: &'a [Stage4RegisterAccess], + witness: &'a Stage45SparseTraceWitness, + ) -> Self { + self.with_sparse_trace_witness( + register_count, + trace_len, + ram_k, + register_accesses, + &witness.rd_inc, + &witness.ram_addresses, + &witness.ram_inc, + ) + } +} + +#[derive(Clone, Debug)] +pub struct Stage4ScalarValue { + pub symbol: &'static str, + pub value: F, +} + +#[derive(Clone, Debug)] +pub struct Stage4PointValue { + pub symbol: &'static str, + pub point: Vec, +} + +#[derive(Clone, Debug, Default)] +pub struct Stage4ValueStore { + scalars: Vec>, + points: Vec>, +} + +impl Stage4ValueStore { + pub fn new() -> Self { + Self::default() + } + + pub fn with_opening_inputs(inputs: &[Stage4OpeningInputValue]) -> Self { + let mut store = Self::new(); + store.insert_opening_inputs(inputs); + store + } + + pub fn insert_opening_inputs(&mut self, inputs: &[Stage4OpeningInputValue]) { + for input in inputs { + self.insert_scalar(input.symbol, input.eval); + self.insert_point(input.symbol, input.point.clone()); + } + } + + pub fn insert_scalar(&mut self, symbol: &'static str, value: F) { + if let Some(existing) = self + .scalars + .iter_mut() + .find(|existing| existing.symbol == symbol) + { + existing.value = value; + } else { + self.scalars.push(Stage4ScalarValue { symbol, value }); + } + } + + pub fn insert_point(&mut self, symbol: &'static str, point: Vec) { + if let Some(existing) = self + .points + .iter_mut() + .find(|existing| existing.symbol == symbol) + { + existing.point = point; + } else { + self.points.push(Stage4PointValue { symbol, point }); + } + } + + pub fn try_scalar(&self, symbol: &str) -> Option { + self.scalars + .iter() + .find(|value| value.symbol == symbol) + .map(|value| value.value) + } + + pub fn scalar(&self, symbol: &'static str) -> Result { + self.try_scalar(symbol) + .ok_or(Stage4KernelError::MissingValue { symbol }) + } + + pub fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.points + .iter() + .find(|value| value.symbol == symbol) + .map(|value| value.point.as_slice()) + } + + pub fn point(&self, symbol: &'static str) -> Result<&[F], Stage4KernelError> { + self.try_point(symbol) + .ok_or(Stage4KernelError::MissingValue { symbol }) + } + + pub fn seed_constants( + &mut self, + program: &'static Stage4CpuProgramPlan, + ) -> Result<(), Stage4KernelError> { + for constant in program.field_constants { + self.insert_scalar(constant.symbol, F::from_u64(constant.value as u64)); + } + Ok(()) + } + + pub fn observe_challenge_vector( + &mut self, + plan: &'static Stage4TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage4KernelError> { + if matches!(plan.kind, "challenge_scalar" | "scalar") { + require_operand_count(plan.symbol, 1, values.len())?; + self.insert_scalar(plan.symbol, values[0]); + } + self.insert_point(plan.symbol, values.to_vec()); + let _ = values; + Ok(()) + } + + pub fn observe_sumcheck_output( + &mut self, + program: &'static Stage4CpuProgramPlan, + output: &Stage4SumcheckOutput, + ) -> Result<(), Stage4KernelError> { + self.observe_sumcheck_values(program, output.driver, &output.point, &output.evals) + } + + pub fn observe_sumcheck_values( + &mut self, + program: &'static Stage4CpuProgramPlan, + driver: &'static str, + point: &[F], + evals: &[Stage4NamedEval], + ) -> Result<(), Stage4KernelError> { + self.insert_point(driver, point.to_vec()); + for instance in program.instance_results_for_driver(driver) { + let end = instance.round_offset + instance.point_arity; + let mut instance_point = point + .get(instance.round_offset..end) + .ok_or(Stage4KernelError::InvalidInputLength { + input: instance.symbol, + expected: end, + actual: point.len(), + })? + .to_vec(); + match instance.point_order { + "as_is" => {} + "reverse" => instance_point.reverse(), + "stage4_registers_rw" => { + instance_point = + normalize_stage4_registers_rw_point(program, driver, &instance_point)?; + } + _ => { + return Err(Stage4KernelError::InvalidProof { + driver, + reason: "unsupported point order", + }); + } + } + self.insert_point(instance.symbol, instance_point); + } + for eval in program.evals_for_driver(driver) { + let value = evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| evals.get(eval.index)) + .ok_or(Stage4KernelError::MissingValue { + symbol: eval.symbol, + })? + .value; + self.insert_scalar(eval.symbol, value); + self.insert_scalar(eval.name, value); + } + let _ = self.evaluate_available_points(program)?; + let _ = self.evaluate_available_field_exprs(program)?; + self.verify_opening_equalities(program)?; + Ok(()) + } + + pub fn evaluate_available_points( + &mut self, + program: &'static Stage4CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for slice in program.point_slices { + if self.try_point(slice.symbol).is_some() { + continue; + } + let Some(input) = self.try_point(slice.input) else { + continue; + }; + let end = slice.offset + slice.length; + let point = input + .get(slice.offset..end) + .ok_or(Stage4KernelError::InvalidInputLength { + input: slice.symbol, + expected: end, + actual: input.len(), + })? + .to_vec(); + self.insert_point(slice.symbol, point); + progress += 1; + } + for concat in program.point_concats { + if self.try_point(concat.symbol).is_some() { + continue; + } + let Some(point) = self.try_concat_point(concat) else { + continue; + }; + verify_count(concat.symbol, concat.arity, point.len())?; + self.insert_point(concat.symbol, point); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + pub fn evaluate_available_field_exprs( + &mut self, + program: &'static Stage4CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for expr in program.field_exprs { + if self.try_scalar(expr.symbol).is_some() { + continue; + } + let Some(operands) = self.try_expr_operands(expr) else { + continue; + }; + self.insert_scalar(expr.symbol, evaluate_stage4_field_expr(expr, &operands)?); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + pub fn verify_opening_equalities( + &self, + program: &'static Stage4CpuProgramPlan, + ) -> Result<(), Stage4KernelError> { + for equality in program.opening_equalities { + match equality.mode { + "point_and_eval" => { + if self.point(equality.lhs)? != self.point(equality.rhs)? + || self.scalar(equality.lhs)? != self.scalar(equality.rhs)? + { + return Err(Stage4KernelError::InvalidProof { + driver: equality.symbol, + reason: "opening claim equality failed", + }); + } + } + _ => { + return Err(Stage4KernelError::InvalidProof { + driver: equality.symbol, + reason: "unsupported opening equality mode", + }); + } + } + } + Ok(()) + } + + pub fn claim_value( + &mut self, + program: &'static Stage4CpuProgramPlan, + claim: &Stage4SumcheckClaimPlan, + ) -> Result { + let _ = self.evaluate_available_field_exprs(program)?; + self.scalar(claim.claim_value) + } + + pub fn batch_claim_values( + &mut self, + program: &'static Stage4CpuProgramPlan, + batch: &Stage4SumcheckBatchPlan, + ) -> Result, Stage4KernelError> { + batch + .claim_operands + .iter() + .map(|symbol| { + let claim = program + .claim(symbol) + .ok_or(Stage4KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + self.claim_value(program, claim) + }) + .collect() + } + + fn try_expr_operands(&self, expr: &Stage4FieldExprPlan) -> Option> { + expr.operands + .iter() + .map(|operand| self.try_scalar(operand)) + .collect() + } + + fn try_concat_point(&self, concat: &Stage4PointConcatPlan) -> Option> { + let mut point = Vec::with_capacity(concat.arity); + for input in concat.inputs { + point.extend_from_slice(self.try_point(input)?); + } + Some(point) + } +} + +pub fn evaluate_stage4_field_expr( + expr: &Stage4FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => single_operand(expr.symbol, operands), + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + _ => { + if let Some(exponent) = expr.formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + Stage4KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + Err(Stage4KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula: expr.formula, + }) + } + } +} + +fn pow_field(base: F, mut exponent: usize) -> F { + let mut result = F::one(); + let mut power = base; + while exponent != 0 { + if exponent & 1 == 1 { + result *= power; + } + power = power.square(); + exponent >>= 1; + } + result +} + +fn single_operand(symbol: &'static str, operands: &[F]) -> Result { + require_operand_count(symbol, 1, operands.len())?; + Ok(operands[0]) +} + +fn require_operand_count( + input: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage4KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage4KernelError::InvalidInputLength { + input, + expected, + actual, + }) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage4KernelContext<'a> { + pub mode: Stage4ExecutionMode, + pub program: &'static Stage4CpuProgramPlan, + pub kernel: &'a Stage4KernelPlan, + pub batch: &'a Stage4SumcheckBatchPlan, + pub driver: &'a Stage4SumcheckDriverPlan, +} + +impl Stage4KernelContext<'_> { + pub fn relation_kind(&self) -> Result { + self.kernel.relation_kind() + } + + pub fn abi_kind(&self) -> Result { + self.kernel.abi_kind() + } + + pub fn batch_claims(&self) -> Result, Stage4KernelError> { + self.batch + .claim_operands + .iter() + .map(|symbol| { + self.program + .claim(symbol) + .ok_or(Stage4KernelError::MissingClaim { + batch: self.batch.symbol, + claim: symbol, + }) + }) + .collect() + } +} + +pub trait Stage4KernelExecutor { + fn observe_challenge_vector( + &mut self, + _plan: &'static Stage4TranscriptSqueezePlan, + _values: &[F], + ) -> Result<(), Stage4KernelError> { + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + _output: &Stage4SumcheckOutput, + ) -> Result<(), Stage4KernelError> { + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage4KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage4KernelError> + where + T: Transcript; + + fn verify_sumcheck( + &mut self, + context: Stage4KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage4KernelError> + where + T: Transcript; +} + +#[derive(Clone, Debug, Default)] +pub struct UnsupportedStage4KernelExecutor; + +impl Stage4KernelExecutor for UnsupportedStage4KernelExecutor { + fn prove_sumcheck( + &mut self, + context: Stage4KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage4KernelError> + where + T: Transcript, + { + Err(Stage4KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage4KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage4KernelError> + where + T: Transcript, + { + Err(Stage4KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } +} + +#[derive(Clone)] +pub struct Stage4ProverKernelExecutor<'a, F: Field> { + pub inputs: Stage4ProverInputs<'a, F>, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage4ProverKernelExecutor<'a, F> { + pub fn new(inputs: Stage4ProverInputs<'a, F>) -> Self { + Self { + inputs, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage4CpuProgramPlan, + ) -> Result, Stage4KernelError> { + value_store_from_observations( + program, + self.inputs.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } +} + +impl Stage4KernelExecutor for Stage4ProverKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage4TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage4KernelError> { + self.challenge_vectors.push(Stage4ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage4SumcheckOutput, + ) -> Result<(), Stage4KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage4KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage4KernelError> + where + T: Transcript, + { + prove_stage4_kernel( + context, + &self.inputs, + self.value_store(context.program)?, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage4KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage4KernelError> + where + T: Transcript, + { + Err(Stage4KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage4ExecutionMode::Prover, + actual: Stage4ExecutionMode::Verifier, + }) + } +} + +#[derive(Clone)] +pub struct Stage4VerifierKernelExecutor<'a, F: Field> { + pub proof: &'a Stage4Proof, + pub opening_inputs: &'a [Stage4OpeningInputValue], + pub cursor: usize, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage4VerifierKernelExecutor<'a, F> { + pub fn new( + proof: &'a Stage4Proof, + opening_inputs: &'a [Stage4OpeningInputValue], + ) -> Self { + Self { + proof, + opening_inputs, + cursor: 0, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage4CpuProgramPlan, + ) -> Result, Stage4KernelError> { + value_store_from_observations( + program, + self.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } +} + +impl Stage4KernelExecutor for Stage4VerifierKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage4TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage4KernelError> { + self.challenge_vectors.push(Stage4ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage4SumcheckOutput, + ) -> Result<(), Stage4KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage4KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage4KernelError> + where + T: Transcript, + { + Err(Stage4KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage4ExecutionMode::Verifier, + actual: Stage4ExecutionMode::Prover, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage4KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage4KernelError> + where + T: Transcript, + { + let proof = + self.proof + .sumchecks + .get(self.cursor) + .ok_or(Stage4KernelError::MissingProof { + driver: context.driver.symbol, + })?; + self.cursor += 1; + verify_stage4_kernel( + context, + self.value_store(context.program)?, + proof, + transcript, + ) + } +} + +fn value_store_from_observations( + program: &'static Stage4CpuProgramPlan, + opening_inputs: &[Stage4OpeningInputValue], + challenge_vectors: &[Stage4ChallengeVector], + completed_sumchecks: &[Stage4SumcheckOutput], +) -> Result, Stage4KernelError> { + let mut store = Stage4ValueStore::with_opening_inputs(opening_inputs); + store.seed_constants(program)?; + for challenge in challenge_vectors { + let plan = program + .transcript_squeezes + .iter() + .find(|plan| plan.symbol == challenge.symbol) + .ok_or(Stage4KernelError::MissingValue { + symbol: challenge.symbol, + })?; + store.observe_challenge_vector(plan, &challenge.values)?; + } + for output in completed_sumchecks { + store.observe_sumcheck_output(program, output)?; + } + let _ = store.evaluate_available_points(program)?; + let _ = store.evaluate_available_field_exprs(program)?; + store.verify_opening_equalities(program)?; + Ok(store) +} + +pub fn execute_stage4_program( + program: &'static Stage4CpuProgramPlan, + mode: Stage4ExecutionMode, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage4KernelError> +where + F: Field, + T: Transcript, + E: Stage4KernelExecutor, +{ + let mut artifacts = Stage4ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_squeeze(program, step.symbol).ok_or(Stage4KernelError::MissingValue { + symbol: step.symbol, + })?; + let values = transcript.challenge_vector(squeeze.count); + executor.observe_challenge_vector(squeeze, &values)?; + artifacts.challenge_vectors.push(Stage4ChallengeVector { + symbol: squeeze.symbol, + values, + }); + } + "transcript_absorb_bytes" => { + let absorb = find_absorb_bytes(program, step.symbol).ok_or( + Stage4KernelError::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage4_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_driver(program, step.symbol).ok_or(Stage4KernelError::MissingDriver { + driver: step.symbol, + })?; + let kernel_symbol = driver.kernel.ok_or(Stage4KernelError::MissingKernel { + driver: driver.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or( + Stage4KernelError::MissingKernel { + driver: driver.symbol, + kernel: kernel_symbol, + }, + )?; + let batch = + find_batch(program, driver.batch).ok_or(Stage4KernelError::MissingBatch { + driver: driver.symbol, + batch: driver.batch, + })?; + let context = Stage4KernelContext { + mode, + program, + kernel, + batch, + driver, + }; + let output = match mode { + Stage4ExecutionMode::Prover => executor.prove_sumcheck(context, transcript)?, + Stage4ExecutionMode::Verifier => { + executor.verify_sumcheck(context, transcript)? + } + }; + executor.observe_sumcheck_output(&output)?; + artifacts + .opening_claims + .extend(output.opening_claims.clone()); + artifacts.sumchecks.push(output); + } + _ => { + return Err(Stage4KernelError::InvalidProgramStep { + symbol: step.symbol, + kind: step.kind, + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +fn absorb_stage4_bytes(absorb: &'static Stage4TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn prove_stage4_kernel( + context: Stage4KernelContext<'_>, + inputs: &Stage4ProverInputs<'_, F>, + store: Stage4ValueStore, + transcript: &mut T, +) -> Result, Stage4KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage4KernelAbi::Batched => prove_batched_stage4(context, inputs, store, transcript), + abi => Err(Stage4KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +fn verify_stage4_kernel( + context: Stage4KernelContext<'_>, + store: Stage4ValueStore, + proof: &Stage4SumcheckOutput, + transcript: &mut T, +) -> Result, Stage4KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage4KernelAbi::Batched => verify_batched_stage4(context, store, proof, transcript), + abi => Err(Stage4KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +#[tracing::instrument(skip_all, name = "Stage4::prove_batched")] +fn prove_batched_stage4( + context: Stage4KernelContext<'_>, + inputs: &Stage4ProverInputs<'_, F>, + mut store: Stage4ValueStore, + transcript: &mut T, +) -> Result, Stage4KernelError> +where + F: Field, + T: Transcript, +{ + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let two_inv = F::from_u64(2) + .inverse() + .ok_or(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "field element 2 is not invertible", + })?; + let mut instances = Vec::with_capacity(claims.len()); + for (index, claim) in claims.iter().enumerate() { + let offset = instance_round_offset(context.program, context.driver.symbol, claim.symbol)?; + if offset + claim.num_rounds > max_rounds { + return Err(Stage4KernelError::InvalidInputLength { + input: claim.symbol, + expected: max_rounds, + actual: offset + claim.num_rounds, + }); + } + let active_scale = F::one().mul_pow_2(max_rounds - offset - claim.num_rounds); + instances.push(Stage4BatchedInstance { + claim, + relation: claim_relation(context.program, claim)?, + offset, + previous_claim: input_claims[index].mul_pow_2(max_rounds - claim.num_rounds), + state: Stage4ProverInstanceState::new( + context.program, + claim, + inputs, + &store, + active_scale, + )?, + }); + } + + let mut point = Vec::with_capacity(max_rounds); + let mut round_polynomials = Vec::with_capacity(max_rounds); + let mut batched_claim = instances + .iter() + .zip(&batching_coeffs) + .map(|(instance, &coefficient)| instance.previous_claim * coefficient) + .sum::(); + for round in 0..max_rounds { + let mut individual_polys = Vec::with_capacity(instances.len()); + for instance in &mut instances { + let poly = if instance.is_active(round) { + instance + .state + .round_poly(instance.previous_claim, instance.relation)? + } else { + UnivariatePoly::new(vec![instance.previous_claim * two_inv]) + }; + #[cfg(debug_assertions)] + { + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != instance.previous_claim { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched instance round claim mismatch", + }); + } + } + individual_polys.push(poly); + } + let batched_poly = combine_univariate_polys(&individual_polys, &batching_coeffs); + #[cfg(debug_assertions)] + { + if batched_poly.evaluate(F::zero()) + batched_poly.evaluate(F::one()) != batched_claim { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round claim mismatch", + }); + } + } + append_compressed_univariate_poly(transcript, context.driver.round_label, &batched_poly); + let challenge = transcript.challenge(); + point.push(challenge); + batched_claim = batched_poly.evaluate(challenge); + for (instance, poly) in instances.iter_mut().zip(individual_polys) { + instance.previous_claim = poly.evaluate(challenge); + if instance.is_active(round) { + instance.state.ingest_challenge(challenge); + } + } + round_polynomials.push(batched_poly); + } + + let mut evals = Vec::new(); + for instance in &instances { + evals.extend(instance.state.final_evals(instance.relation)?); + } + let expected = + expected_batched_output_claim(context, &store, &evals, &point, &batching_coeffs)?; + if batched_claim != expected { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + store.observe_sumcheck_values(context.program, context.driver.symbol, &point, &evals)?; + let opening_claims = append_opening_claims(context.program, &mut store, transcript, &evals)?; + Ok(Stage4SumcheckOutput { + driver: context.driver.symbol, + point, + evals, + opening_claims, + proof: SumcheckProof { round_polynomials }, + }) +} + +fn verify_batched_stage4( + context: Stage4KernelContext<'_>, + mut store: Stage4ValueStore, + proof: &Stage4SumcheckOutput, + transcript: &mut T, +) -> Result, Stage4KernelError> +where + F: Field, + T: Transcript, +{ + if proof.driver != context.driver.symbol { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + if proof.proof.round_polynomials.len() != context.driver.num_rounds { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected batched round count", + }); + } + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let mut running_claim = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), &coefficient)| { + claim.mul_pow_2(max_rounds - plan.num_rounds) * coefficient + }) + .sum::(); + let mut point = Vec::with_capacity(max_rounds); + for poly in &proof.proof.round_polynomials { + if polynomial_degree(poly) > context.driver.degree { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched polynomial exceeds degree bound", + }); + } + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != running_claim { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round check failed", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, poly); + let challenge = transcript.challenge(); + running_claim = poly.evaluate(challenge); + point.push(challenge); + } + if !proof.point.is_empty() && proof.point != point { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched point mismatch", + }); + } + let expected = + expected_batched_output_claim(context, &store, &proof.evals, &point, &batching_coeffs)?; + if running_claim != expected { + return Err(Stage4KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + let output = Stage4SumcheckOutput { + driver: context.driver.symbol, + point, + evals: proof.evals.clone(), + opening_claims: Vec::new(), + proof: proof.proof.clone(), + }; + store.observe_sumcheck_output(context.program, &output)?; + let opening_claims = + append_opening_claims(context.program, &mut store, transcript, &output.evals)?; + let output = Stage4SumcheckOutput { + opening_claims, + ..output + }; + Ok(output) +} + +struct Stage4BatchedInstance<'a, F: Field> { + claim: &'a Stage4SumcheckClaimPlan, + relation: Stage4Relation, + offset: usize, + previous_claim: F, + state: Stage4ProverInstanceState, +} + +impl Stage4BatchedInstance<'_, F> { + fn is_active(&self, round: usize) -> bool { + round >= self.offset && round < self.offset + self.claim.num_rounds + } +} + +enum Stage4ProverInstanceState { + Dense(DenseStage4State), + SparseRegisters(SparseRegistersState), +} + +impl Stage4ProverInstanceState { + fn new( + program: &'static Stage4CpuProgramPlan, + claim: &Stage4SumcheckClaimPlan, + inputs: &Stage4ProverInputs<'_, F>, + store: &Stage4ValueStore, + active_scale: F, + ) -> Result { + match claim_relation(program, claim)? { + Stage4Relation::RegistersReadWrite => { + registers_read_write_state(claim, inputs, store, active_scale) + } + Stage4Relation::RamValCheck => { + ram_val_check_state(claim, inputs, store, active_scale).map(Self::Dense) + } + relation @ Stage4Relation::Batched => Err(Stage4KernelError::KernelNotImplemented { + abi: relation.symbol(), + }), + } + } + + fn round_poly( + &mut self, + previous_claim: F, + relation: Stage4Relation, + ) -> Result, Stage4KernelError> { + match self { + Self::Dense(state) => state.round_poly(previous_claim, relation), + Self::SparseRegisters(state) => state.round_poly(previous_claim, relation), + } + } + + fn ingest_challenge(&mut self, challenge: F) { + match self { + Self::Dense(state) => state.bind(challenge), + Self::SparseRegisters(state) => state.bind(challenge), + } + } + + fn final_evals( + &self, + relation: Stage4Relation, + ) -> Result>, Stage4KernelError> { + match self { + Self::Dense(state) => state.final_evals(relation), + Self::SparseRegisters(state) => state.final_evals(relation), + } + } +} + +#[derive(Clone)] +struct DenseStage4State { + factors: Vec>, + factor_scratch: Vec>, + terms: Vec>, + outputs: Vec, + active_scale: F, +} + +#[derive(Clone)] +struct DenseTerm { + coefficient: F, + factors: Vec, +} + +#[derive(Clone, Copy)] +struct FactorOutput { + name: &'static str, + oracle: &'static str, + factor: usize, +} + +impl DenseStage4State { + fn new( + factors: Vec>, + terms: Vec>, + outputs: Vec, + active_scale: F, + ) -> Self { + let factor_scratch = (0..factors.len()).map(|_| Vec::new()).collect(); + Self { + factors, + factor_scratch, + terms, + outputs, + active_scale, + } + } + + fn round_poly( + &self, + previous_claim: F, + relation: Stage4Relation, + ) -> Result, Stage4KernelError> { + let first_len = self.factors.first().map_or(0, Vec::len); + if first_len == 0 || !first_len.is_power_of_two() { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 dense factor has invalid length", + }); + } + if self.factors.iter().any(|factor| factor.len() != first_len) { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 dense factors have inconsistent lengths", + }); + } + let poly = + round_poly_from_dense_terms(&self.factors, &self.terms, self.active_scale, relation)?; + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != previous_claim { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 relation input claim mismatch", + }); + } + Ok(poly) + } + + fn bind(&mut self, challenge: F) { + if self.factors.first().map_or(0, Vec::len) / 2 >= DENSE_BIND_PAR_THRESHOLD { + self.factors + .par_iter_mut() + .zip(self.factor_scratch.par_iter_mut()) + .for_each(|(factor, scratch)| { + bind_dense_evals_reuse(factor, scratch, challenge); + }); + } else { + for (factor, scratch) in self.factors.iter_mut().zip(&mut self.factor_scratch) { + bind_dense_evals_reuse(factor, scratch, challenge); + } + } + } + + fn factor_eval(&self, index: usize, relation: Stage4Relation) -> Result { + self.factors + .get(index) + .and_then(|values| values.first()) + .copied() + .ok_or(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty stage4 factor", + }) + } + + fn final_evals( + &self, + relation: Stage4Relation, + ) -> Result>, Stage4KernelError> { + self.outputs + .iter() + .map(|output| { + Ok(named_eval( + output.name, + output.oracle, + self.factor_eval(output.factor, relation)?, + )) + }) + .collect() + } +} + +#[derive(Clone)] +struct SparseRegistersState { + register_count: usize, + trace_len: usize, + current_trace_len: usize, + entries: Vec>, + entry_scratch: Vec>, + rs2_reads: Vec<(usize, usize)>, + eq_cycle: SplitEqState, + rd_inc: Vec, + rd_inc_scratch: Vec, + gamma: F, + gamma2: F, + active_scale: F, + bound_point: Vec, + dense: Option>, +} + +#[derive(Clone, Copy, Debug)] +struct SparseRegisterEntry { + row: usize, + col: u8, + val: F, + prev_val: u64, + next_val: u64, + read_ra: F, + rd_wa: F, +} + +impl SparseRegistersState { + fn new( + register_count: usize, + trace_len: usize, + accesses: &[Stage4RegisterAccess], + rd_inc: &[F], + trace_point: &[F], + gamma: F, + gamma2: F, + active_scale: F, + ) -> Result { + require_operand_count("stage4.registers.accesses", trace_len, accesses.len())?; + require_operand_count("stage4.registers.RdInc", trace_len, rd_inc.len())?; + let mut entries = Vec::with_capacity(accesses.len().saturating_mul(3)); + let mut rs2_reads = Vec::with_capacity(accesses.len()); + for (row, access) in accesses.iter().enumerate() { + append_sparse_register_entries( + register_count, + row, + *access, + gamma, + gamma2, + &mut entries, + )?; + if let Some(rs2) = access.rs2 { + rs2_reads.push((row, rs2.address)); + } + } + let eq_cycle = SplitEqState::new_low_to_high(trace_point, None); + let mut state = Self { + register_count, + trace_len, + current_trace_len: trace_len, + entries, + entry_scratch: Vec::new(), + rs2_reads, + eq_cycle, + rd_inc: rd_inc.to_vec(), + rd_inc_scratch: Vec::new(), + gamma, + gamma2, + active_scale, + bound_point: Vec::with_capacity( + log2_exact(register_count, "stage4.register_count")? + + log2_exact(trace_len, "stage4.trace_len")?, + ), + dense: None, + }; + if trace_len == 1 { + state.materialize_dense()?; + } + Ok(state) + } + + fn round_poly( + &mut self, + previous_claim: F, + relation: Stage4Relation, + ) -> Result, Stage4KernelError> { + if let Some(dense) = &self.dense { + return dense.round_poly(previous_claim, relation); + } + if self.current_trace_len <= 1 { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 sparse registers state was not materialized", + }); + } + let (mut q_constant, mut q_quadratic) = sparse_register_split_round_coefficients( + &self.entries, + &self.eq_cycle, + &self.rd_inc, + self.current_trace_len, + )?; + q_constant *= self.active_scale; + q_quadratic *= self.active_scale; + let poly = gruen_cubic_poly( + self.eq_cycle.current_target(), + q_constant, + q_quadratic, + previous_claim, + ); + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != previous_claim { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 sparse registers input claim mismatch", + }); + } + Ok(poly) + } + + fn bind(&mut self, challenge: F) { + self.bound_point.push(challenge); + if let Some(dense) = &mut self.dense { + dense.bind(challenge); + return; + } + bind_sparse_register_entries_into( + &self.entries, + self.current_trace_len, + challenge, + &mut self.entry_scratch, + ); + std::mem::swap(&mut self.entries, &mut self.entry_scratch); + self.entry_scratch.clear(); + self.eq_cycle.bind(challenge); + bind_dense_evals_reuse(&mut self.rd_inc, &mut self.rd_inc_scratch, challenge); + self.current_trace_len /= 2; + if self.current_trace_len == 1 { + let _ = self.materialize_dense(); + } + } + + fn final_evals( + &self, + relation: Stage4Relation, + ) -> Result>, Stage4KernelError> { + let dense = self.dense.as_ref().ok_or(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 sparse registers state was not materialized", + })?; + let registers_val = dense.factor_eval(1, relation)?; + let combined_read_ra = dense.factor_eval(2, relation)?; + let rd_wa = dense.factor_eval(3, relation)?; + let rd_inc = dense.factor_eval(4, relation)?; + let rs2_ra = self.final_rs2_read_eval(relation)?; + let gamma_inverse = self + .gamma + .inverse() + .ok_or(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 registers challenge is not invertible", + })?; + let rs1_ra = (combined_read_ra - self.gamma2 * rs2_ra) * gamma_inverse; + #[cfg(debug_assertions)] + { + let expected = self.gamma * rs1_ra + self.gamma2 * rs2_ra; + if combined_read_ra != expected { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 sparse registers final read claim mismatch", + }); + } + } + Ok(vec![ + named_eval( + "stage4.registers_read_write.eval.RegistersVal", + "RegistersVal", + registers_val, + ), + named_eval("stage4.registers_read_write.eval.Rs1Ra", "Rs1Ra", rs1_ra), + named_eval("stage4.registers_read_write.eval.Rs2Ra", "Rs2Ra", rs2_ra), + named_eval("stage4.registers_read_write.eval.RdWa", "RdWa", rd_wa), + named_eval("stage4.registers_read_write.eval.RdInc", "RdInc", rd_inc), + ]) + } + + fn materialize_dense(&mut self) -> Result<(), Stage4KernelError> { + let mut registers_val = vec![F::zero(); self.register_count]; + let mut read_ra = vec![F::zero(); self.register_count]; + let mut rd_wa = vec![F::zero(); self.register_count]; + for entry in &self.entries { + let col = usize::from(entry.col); + if entry.row != 0 || col >= self.register_count { + return Err(Stage4KernelError::InvalidInputLength { + input: "stage4.registers.accesses", + expected: self.register_count, + actual: col + 1, + }); + } + registers_val[col] = entry.val; + read_ra[col] = entry.read_ra; + rd_wa[col] = entry.rd_wa; + } + let eq_eval = self.eq_cycle.eval(); + let rd_inc_eval = + self.rd_inc + .first() + .copied() + .ok_or(Stage4KernelError::InvalidInputLength { + input: "stage4.registers.RdInc", + expected: 1, + actual: 0, + })?; + self.dense = Some(registers_combined_dense_state( + vec![eq_eval; self.register_count], + registers_val, + read_ra, + rd_wa, + vec![rd_inc_eval; self.register_count], + self.active_scale, + )); + Ok(()) + } + + fn final_rs2_read_eval(&self, relation: Stage4Relation) -> Result { + let trace_rounds = log2_exact(self.trace_len, "stage4.trace_len")?; + let register_rounds = log2_exact(self.register_count, "stage4.register_count")?; + if self.bound_point.len() != trace_rounds + register_rounds { + return Err(Stage4KernelError::InvalidInputLength { + input: "stage4.registers_read_write.instance", + expected: trace_rounds + register_rounds, + actual: self.bound_point.len(), + }); + } + let (cycle_point, address_point) = self.bound_point.split_at(trace_rounds); + let r_cycle = reverse_slice(cycle_point); + let r_address = reverse_slice(address_point); + let (cycle_eq, address_eq) = rayon::join( + || EqPolynomial::::evals(&r_cycle, None), + || EqPolynomial::::evals(&r_address, None), + ); + if cycle_eq.len() != self.trace_len || address_eq.len() != self.register_count { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 sparse registers final read point has invalid shape", + }); + } + Ok(sparse_register_read_eval( + &self.rs2_reads, + &cycle_eq, + &address_eq, + )) + } +} + +fn registers_read_write_state( + claim: &Stage4SumcheckClaimPlan, + inputs: &Stage4ProverInputs<'_, F>, + store: &Stage4ValueStore, + active_scale: F, +) -> Result, Stage4KernelError> { + let witness = inputs + .registers + .ok_or(Stage4KernelError::MissingKernelInput { + kernel: "jolt_stage4_batched", + input: "registers", + })?; + let expected_len = witness + .register_count + .checked_mul(witness.trace_len) + .ok_or(Stage4KernelError::InvalidInputLength { + input: "stage4.registers", + expected: usize::MAX, + actual: witness.register_count, + })?; + + let trace_point = store.point("stage4.input.stage3.registers.RdWriteValue")?; + let register_rounds = log2_exact(witness.register_count, "stage4.register_count")?; + let trace_rounds = log2_exact(witness.trace_len, "stage4.trace_len")?; + require_operand_count( + "stage4.registers.trace_point", + trace_rounds, + trace_point.len(), + )?; + require_operand_count( + claim.symbol, + register_rounds + trace_rounds, + claim.num_rounds, + )?; + + let gamma = store.scalar("stage4.registers_read_write.gamma")?; + let gamma2 = store + .try_scalar("stage4.registers_read_write.gamma2") + .unwrap_or_else(|| gamma * gamma); + + if let Some(accesses) = witness.accesses { + return SparseRegistersState::new( + witness.register_count, + witness.trace_len, + accesses, + witness.rd_inc, + trace_point, + gamma, + gamma2, + active_scale, + ) + .map(Stage4ProverInstanceState::SparseRegisters); + } + + require_operand_count( + "stage4.registers.RegistersVal", + expected_len, + witness.registers_val.len(), + )?; + require_operand_count("stage4.registers.Rs1Ra", expected_len, witness.rs1_ra.len())?; + require_operand_count("stage4.registers.Rs2Ra", expected_len, witness.rs2_ra.len())?; + require_operand_count("stage4.registers.RdWa", expected_len, witness.rd_wa.len())?; + require_operand_count( + "stage4.registers.RdInc", + witness.trace_len, + witness.rd_inc.len(), + )?; + let eq_cycle = EqPolynomial::::evals(trace_point, None); + let mut eq_cycle_expanded = Vec::with_capacity(expected_len); + let mut rd_inc_expanded = Vec::with_capacity(expected_len); + for _address in 0..witness.register_count { + eq_cycle_expanded.extend_from_slice(&eq_cycle); + rd_inc_expanded.extend_from_slice(witness.rd_inc); + } + + Ok(Stage4ProverInstanceState::Dense(registers_dense_state( + eq_cycle_expanded, + witness.registers_val.to_vec(), + witness.rs1_ra.to_vec(), + witness.rs2_ra.to_vec(), + witness.rd_wa.to_vec(), + rd_inc_expanded, + gamma, + gamma2, + active_scale, + ))) +} + +fn registers_dense_state( + eq_cycle: Vec, + registers_val: Vec, + rs1_ra: Vec, + rs2_ra: Vec, + rd_wa: Vec, + rd_inc: Vec, + gamma: F, + gamma2: F, + active_scale: F, +) -> DenseStage4State { + DenseStage4State::new( + vec![eq_cycle, registers_val, rs1_ra, rs2_ra, rd_wa, rd_inc], + vec![ + DenseTerm { + coefficient: F::one(), + factors: vec![0, 4, 1], + }, + DenseTerm { + coefficient: F::one(), + factors: vec![0, 4, 5], + }, + DenseTerm { + coefficient: gamma, + factors: vec![0, 2, 1], + }, + DenseTerm { + coefficient: gamma2, + factors: vec![0, 3, 1], + }, + ], + vec![ + FactorOutput { + name: "stage4.registers_read_write.eval.RegistersVal", + oracle: "RegistersVal", + factor: 1, + }, + FactorOutput { + name: "stage4.registers_read_write.eval.Rs1Ra", + oracle: "Rs1Ra", + factor: 2, + }, + FactorOutput { + name: "stage4.registers_read_write.eval.Rs2Ra", + oracle: "Rs2Ra", + factor: 3, + }, + FactorOutput { + name: "stage4.registers_read_write.eval.RdWa", + oracle: "RdWa", + factor: 4, + }, + FactorOutput { + name: "stage4.registers_read_write.eval.RdInc", + oracle: "RdInc", + factor: 5, + }, + ], + active_scale, + ) +} + +fn registers_combined_dense_state( + eq_cycle: Vec, + registers_val: Vec, + read_ra: Vec, + rd_wa: Vec, + rd_inc: Vec, + active_scale: F, +) -> DenseStage4State { + DenseStage4State::new( + vec![eq_cycle, registers_val, read_ra, rd_wa, rd_inc], + vec![ + DenseTerm { + coefficient: F::one(), + factors: vec![0, 3, 1], + }, + DenseTerm { + coefficient: F::one(), + factors: vec![0, 3, 4], + }, + DenseTerm { + coefficient: F::one(), + factors: vec![0, 2, 1], + }, + ], + Vec::new(), + active_scale, + ) +} + +fn append_sparse_register_entries( + register_count: usize, + row: usize, + access: Stage4RegisterAccess, + gamma: F, + gamma2: F, + entries: &mut Vec>, +) -> Result<(), Stage4KernelError> { + let start = entries.len(); + if let Some(rs1) = access.rs1 { + validate_register_address(register_count, rs1.address)?; + let col = sparse_register_col(rs1.address)?; + entries.push(SparseRegisterEntry { + row, + col, + val: F::from_u64(rs1.value), + prev_val: rs1.value, + next_val: rs1.value, + read_ra: gamma, + rd_wa: F::zero(), + }); + } + if let Some(rs2) = access.rs2 { + validate_register_address(register_count, rs2.address)?; + let col = sparse_register_col(rs2.address)?; + if let Some(entry) = entries[start..].iter_mut().find(|entry| entry.col == col) { + entry.read_ra += gamma2; + } else { + entries.push(SparseRegisterEntry { + row, + col, + val: F::from_u64(rs2.value), + prev_val: rs2.value, + next_val: rs2.value, + read_ra: gamma2, + rd_wa: F::zero(), + }); + } + } + if let Some(rd) = access.rd { + validate_register_address(register_count, rd.address)?; + let col = sparse_register_col(rd.address)?; + if let Some(entry) = entries[start..].iter_mut().find(|entry| entry.col == col) { + entry.rd_wa = F::one(); + entry.next_val = rd.post_value; + } else { + entries.push(SparseRegisterEntry { + row, + col, + val: F::from_u64(rd.pre_value), + prev_val: rd.pre_value, + next_val: rd.post_value, + read_ra: F::zero(), + rd_wa: F::one(), + }); + } + } + entries[start..].sort_by_key(|entry| entry.col); + Ok(()) +} + +fn sparse_register_col(address: usize) -> Result { + u8::try_from(address).map_err(|_| Stage4KernelError::InvalidInputLength { + input: "stage4.registers.accesses", + expected: usize::from(u8::MAX) + 1, + actual: address + 1, + }) +} + +fn validate_register_address( + register_count: usize, + address: usize, +) -> Result<(), Stage4KernelError> { + if address < register_count { + Ok(()) + } else { + Err(Stage4KernelError::InvalidInputLength { + input: "stage4.registers.accesses", + expected: register_count, + actual: address + 1, + }) + } +} + +fn sparse_register_split_round_coefficients( + entries: &[SparseRegisterEntry], + eq_cycle: &SplitEqState, + rd_inc: &[F], + current_trace_len: usize, +) -> Result<(F, F), Stage4KernelError> { + if let Some(entry) = entries.last() { + if entry.row >= current_trace_len { + return Err(Stage4KernelError::InvalidInputLength { + input: "stage4.registers.accesses", + expected: current_trace_len, + actual: entry.row + 1, + }); + } + } + let e_in = eq_cycle.e_in(); + let e_out = eq_cycle.e_out(); + if e_in.len() > 1 { + sparse_register_low_round_coefficients(entries, rd_inc, e_in, e_out) + } else { + sparse_register_high_round_coefficients(entries, rd_inc, e_in[0], e_out) + } +} + +fn sparse_register_low_round_coefficients( + entries: &[SparseRegisterEntry], + rd_inc: &[F], + e_in: &[F], + e_out: &[F], +) -> Result<(F, F), Stage4KernelError> { + let in_pairs = e_in.len() / 2; + if entries.len() >= DENSE_BIND_PAR_THRESHOLD { + let accumulators = entries + .par_chunk_by(|left, right| (left.row / 2) / in_pairs == (right.row / 2) / in_pairs) + .map(|entries| { + let mut local = [F::Accumulator::default(); 2]; + accumulate_sparse_register_low_outer_chunk( + &mut local, entries, rd_inc, e_in, e_out, in_pairs, + ); + local + }) + .reduce( + || [F::Accumulator::default(); 2], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + return Ok((accumulators[0].reduce(), accumulators[1].reduce())); + } + + let mut accumulators = [F::Accumulator::default(); 2]; + let mut cursor = 0usize; + while cursor < entries.len() { + let pair = entries[cursor].row / 2; + let x_out = pair / in_pairs; + let x_in = pair % in_pairs; + let weight = e_out[x_out] * (e_in[2 * x_in] + e_in[2 * x_in + 1]); + let even_row = 2 * pair; + let odd_row = even_row + 1; + let even_start = cursor; + while cursor < entries.len() && entries[cursor].row == even_row { + cursor += 1; + } + let even = &entries[even_start..cursor]; + let odd_start = cursor; + while cursor < entries.len() && entries[cursor].row == odd_row { + cursor += 1; + } + let odd = &entries[odd_start..cursor]; + accumulate_sparse_register_row_pair_body_coefficients( + &mut accumulators, + even, + odd, + rd_inc[even_row], + rd_inc[odd_row] - rd_inc[even_row], + weight, + ); + } + Ok((accumulators[0].reduce(), accumulators[1].reduce())) +} + +fn sparse_register_high_round_coefficients( + entries: &[SparseRegisterEntry], + rd_inc: &[F], + in_weight: F, + e_out: &[F], +) -> Result<(F, F), Stage4KernelError> { + if entries.len() >= DENSE_BIND_PAR_THRESHOLD { + let accumulators = entries + .par_chunk_by(|left, right| left.row / 2 == right.row / 2) + .map(|entries| { + let mut local = [F::Accumulator::default(); 2]; + let pair = entries[0].row / 2; + let weight = in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]); + accumulate_sparse_register_row_pair_chunk(&mut local, entries, rd_inc, weight); + local + }) + .reduce( + || [F::Accumulator::default(); 2], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + left.merge(right); + } + left + }, + ); + return Ok((accumulators[0].reduce(), accumulators[1].reduce())); + } + + let mut accumulators = [F::Accumulator::default(); 2]; + let mut cursor = 0usize; + while cursor < entries.len() { + let pair = entries[cursor].row / 2; + let weight = in_weight * (e_out[2 * pair] + e_out[2 * pair + 1]); + let start = cursor; + let even_row = 2 * pair; + while cursor < entries.len() + && (entries[cursor].row == even_row || entries[cursor].row == even_row + 1) + { + cursor += 1; + } + accumulate_sparse_register_row_pair_chunk( + &mut accumulators, + &entries[start..cursor], + rd_inc, + weight, + ); + } + Ok((accumulators[0].reduce(), accumulators[1].reduce())) +} + +fn accumulate_sparse_register_low_outer_chunk( + accumulators: &mut [F::Accumulator; 2], + entries: &[SparseRegisterEntry], + rd_inc: &[F], + e_in: &[F], + e_out: &[F], + in_pairs: usize, +) { + let x_out = (entries[0].row / 2) / in_pairs; + let out_weight = e_out[x_out]; + for entries in entries.chunk_by(|left, right| left.row / 2 == right.row / 2) { + let pair = entries[0].row / 2; + let x_in = pair % in_pairs; + let weight = out_weight * (e_in[2 * x_in] + e_in[2 * x_in + 1]); + accumulate_sparse_register_row_pair_chunk(accumulators, entries, rd_inc, weight); + } +} + +fn accumulate_sparse_register_row_pair_chunk( + accumulators: &mut [F::Accumulator; 2], + entries: &[SparseRegisterEntry], + rd_inc: &[F], + weight: F, +) { + let pair = entries[0].row / 2; + let even_row = 2 * pair; + let odd_start = entries.partition_point(|entry| entry.row == even_row); + let (even, odd) = entries.split_at(odd_start); + accumulate_sparse_register_row_pair_body_coefficients( + accumulators, + even, + odd, + rd_inc[even_row], + rd_inc[even_row + 1] - rd_inc[even_row], + weight, + ); +} + +#[derive(Clone)] +struct SparseRegisterPairRange { + pair: usize, + even: std::ops::Range, + odd: std::ops::Range, +} + +fn sparse_register_pair_ranges_into( + entries: &[SparseRegisterEntry], + current_trace_len: usize, + ranges: &mut Vec, +) -> Result<(), Stage4KernelError> { + let half = current_trace_len / 2; + ranges.clear(); + let mut cursor = 0usize; + while cursor < entries.len() { + let pair = entries[cursor].row / 2; + if pair >= half { + return Err(Stage4KernelError::InvalidInputLength { + input: "stage4.registers.accesses", + expected: current_trace_len, + actual: entries[cursor].row + 1, + }); + } + let even_row = 2 * pair; + let odd_row = even_row + 1; + let even_start = cursor; + while cursor < entries.len() && entries[cursor].row == even_row { + cursor += 1; + } + let even = even_start..cursor; + let odd_start = cursor; + while cursor < entries.len() && entries[cursor].row == odd_row { + cursor += 1; + } + let odd = odd_start..cursor; + ranges.push(SparseRegisterPairRange { pair, even, odd }); + } + Ok(()) +} + +fn sparse_register_pair_ranges( + entries: &[SparseRegisterEntry], + current_trace_len: usize, +) -> Result, Stage4KernelError> { + let mut ranges = Vec::new(); + sparse_register_pair_ranges_into(entries, current_trace_len, &mut ranges)?; + Ok(ranges) +} + +fn accumulate_sparse_register_row_pair_body_coefficients( + accumulators: &mut [F::Accumulator; 2], + even: &[SparseRegisterEntry], + odd: &[SparseRegisterEntry], + inc0: F, + inc_delta: F, + weight: F, +) { + let mut i = 0usize; + let mut j = 0usize; + while i < even.len() || j < odd.len() { + let (even_entry, odd_entry) = + if j >= odd.len() || (i < even.len() && even[i].col < odd[j].col) { + let pair = (Some(&even[i]), None); + i += 1; + pair + } else if i >= even.len() || odd[j].col < even[i].col { + let pair = (None, Some(&odd[j])); + j += 1; + pair + } else { + let pair = (Some(&even[i]), Some(&odd[j])); + i += 1; + j += 1; + pair + }; + accumulate_sparse_register_entry_pair_body_coefficients( + accumulators, + even_entry, + odd_entry, + inc0, + inc_delta, + weight, + ); + } +} + +#[derive(Clone, Copy)] +struct SparseRegisterEval { + val: F, + read_ra: F, + rd_wa: F, +} + +#[derive(Clone, Copy)] +struct SparseRegisterLinear { + val0: F, + val_delta: F, + read_ra0: F, + read_ra_delta: F, + rd_wa0: F, + rd_wa_delta: F, +} + +fn accumulate_sparse_register_entry_pair_body_coefficients( + accumulators: &mut [F::Accumulator; 2], + even: Option<&SparseRegisterEntry>, + odd: Option<&SparseRegisterEntry>, + inc0: F, + inc_delta: F, + weight: F, +) { + let linear = sparse_register_entry_linear(even, odd); + let val_inc0 = linear.val0 + inc0; + let val_inc_delta = linear.val_delta + inc_delta; + let body0 = linear.rd_wa0 * val_inc0 + linear.read_ra0 * linear.val0; + let body2 = linear.rd_wa_delta * val_inc_delta + linear.read_ra_delta * linear.val_delta; + + accumulators[0].fmadd(weight, body0); + accumulators[1].fmadd(weight, body2); +} + +fn sparse_register_entry_linear( + even: Option<&SparseRegisterEntry>, + odd: Option<&SparseRegisterEntry>, +) -> SparseRegisterLinear { + match (even, odd) { + (Some(even), Some(odd)) => SparseRegisterLinear { + val0: even.val, + val_delta: odd.val - even.val, + read_ra0: even.read_ra, + read_ra_delta: odd.read_ra - even.read_ra, + rd_wa0: even.rd_wa, + rd_wa_delta: odd.rd_wa - even.rd_wa, + }, + (Some(even), None) => SparseRegisterLinear { + val0: even.val, + val_delta: F::from_u64(even.next_val) - even.val, + read_ra0: even.read_ra, + read_ra_delta: -even.read_ra, + rd_wa0: even.rd_wa, + rd_wa_delta: -even.rd_wa, + }, + (None, Some(odd)) => SparseRegisterLinear { + val0: F::from_u64(odd.prev_val), + val_delta: odd.val - F::from_u64(odd.prev_val), + read_ra0: F::zero(), + read_ra_delta: odd.read_ra, + rd_wa0: F::zero(), + rd_wa_delta: odd.rd_wa, + }, + (None, None) => SparseRegisterLinear { + val0: F::zero(), + val_delta: F::zero(), + read_ra0: F::zero(), + read_ra_delta: F::zero(), + rd_wa0: F::zero(), + rd_wa_delta: F::zero(), + }, + } +} + +fn sparse_register_entry_eval( + even: Option<&SparseRegisterEntry>, + odd: Option<&SparseRegisterEntry>, + x: F, +) -> SparseRegisterEval { + match (even, odd) { + (Some(even), Some(odd)) => SparseRegisterEval { + val: linear_eval(even.val, odd.val, x), + read_ra: linear_eval(even.read_ra, odd.read_ra, x), + rd_wa: linear_eval(even.rd_wa, odd.rd_wa, x), + }, + (Some(even), None) => SparseRegisterEval { + val: linear_eval(even.val, F::from_u64(even.next_val), x), + read_ra: linear_eval(even.read_ra, F::zero(), x), + rd_wa: linear_eval(even.rd_wa, F::zero(), x), + }, + (None, Some(odd)) => SparseRegisterEval { + val: linear_eval(F::from_u64(odd.prev_val), odd.val, x), + read_ra: linear_eval(F::zero(), odd.read_ra, x), + rd_wa: linear_eval(F::zero(), odd.rd_wa, x), + }, + (None, None) => SparseRegisterEval { + val: F::zero(), + read_ra: F::zero(), + rd_wa: F::zero(), + }, + } +} + +fn bind_sparse_register_entries_into( + entries: &[SparseRegisterEntry], + current_trace_len: usize, + challenge: F, + output: &mut Vec>, +) { + output.clear(); + if entries.len() >= DENSE_BIND_PAR_THRESHOLD { + if let Ok(ranges) = sparse_register_pair_ranges(entries, current_trace_len) { + let bound_lengths = ranges + .par_iter() + .map(|range| { + sparse_register_row_pair_bound_len( + &entries[range.even.clone()], + &entries[range.odd.clone()], + ) + }) + .collect::>(); + let output_len = bound_lengths.iter().sum(); + output.reserve(output_len); + let mut spare = output.spare_capacity_mut(); + let mut output_slices = Vec::with_capacity(ranges.len()); + for &bound_len in &bound_lengths { + let (slice, rest) = spare.split_at_mut(bound_len); + output_slices.push(slice); + spare = rest; + } + ranges + .par_iter() + .zip(output_slices.into_par_iter()) + .for_each(|(range, output)| { + bind_sparse_register_row_pair_into( + &entries[range.even.clone()], + &entries[range.odd.clone()], + range.pair, + challenge, + output, + ); + }); + // SAFETY: every slot in `output_slices` was initialized exactly once by + // `bind_sparse_register_row_pair_into`. + unsafe { + output.set_len(output_len); + } + return; + } + } + + bind_sparse_register_entries_sequential_into(entries, challenge, output); +} + +fn bind_sparse_register_entries_sequential_into( + entries: &[SparseRegisterEntry], + challenge: F, + output: &mut Vec>, +) { + output.reserve(entries.len()); + let mut cursor = 0usize; + while cursor < entries.len() { + let pair = entries[cursor].row / 2; + let even_row = 2 * pair; + let odd_row = even_row + 1; + let even_start = cursor; + while cursor < entries.len() && entries[cursor].row == even_row { + cursor += 1; + } + let even = &entries[even_start..cursor]; + let odd_start = cursor; + while cursor < entries.len() && entries[cursor].row == odd_row { + cursor += 1; + } + let odd = &entries[odd_start..cursor]; + bind_sparse_register_row_pair(even, odd, pair, challenge, output); + } +} + +fn sparse_register_row_pair_bound_len( + even: &[SparseRegisterEntry], + odd: &[SparseRegisterEntry], +) -> usize { + let mut i = 0usize; + let mut j = 0usize; + let mut len = 0usize; + while i < even.len() || j < odd.len() { + if j >= odd.len() || (i < even.len() && even[i].col < odd[j].col) { + i += 1; + } else if i >= even.len() || odd[j].col < even[i].col { + j += 1; + } else { + i += 1; + j += 1; + } + len += 1; + } + len +} + +fn bind_sparse_register_row_pair_into( + even: &[SparseRegisterEntry], + odd: &[SparseRegisterEntry], + row: usize, + challenge: F, + output: &mut [MaybeUninit>], +) { + let mut i = 0usize; + let mut j = 0usize; + let mut out = 0usize; + while i < even.len() || j < odd.len() { + let (even_entry, odd_entry, col) = + if j >= odd.len() || (i < even.len() && even[i].col < odd[j].col) { + let pair = (Some(&even[i]), None, even[i].col); + i += 1; + pair + } else if i >= even.len() || odd[j].col < even[i].col { + let pair = (None, Some(&odd[j]), odd[j].col); + j += 1; + pair + } else { + let pair = (Some(&even[i]), Some(&odd[j]), even[i].col); + i += 1; + j += 1; + pair + }; + output[out] = MaybeUninit::new(bind_sparse_register_entry_pair( + even_entry, odd_entry, row, col, challenge, + )); + out += 1; + } + debug_assert_eq!(out, output.len()); +} + +fn bind_sparse_register_row_pair( + even: &[SparseRegisterEntry], + odd: &[SparseRegisterEntry], + row: usize, + challenge: F, + output: &mut Vec>, +) { + let mut i = 0usize; + let mut j = 0usize; + while i < even.len() || j < odd.len() { + let (even_entry, odd_entry, col) = + if j >= odd.len() || (i < even.len() && even[i].col < odd[j].col) { + let pair = (Some(&even[i]), None, even[i].col); + i += 1; + pair + } else if i >= even.len() || odd[j].col < even[i].col { + let pair = (None, Some(&odd[j]), odd[j].col); + j += 1; + pair + } else { + let pair = (Some(&even[i]), Some(&odd[j]), even[i].col); + i += 1; + j += 1; + pair + }; + output.push(bind_sparse_register_entry_pair( + even_entry, odd_entry, row, col, challenge, + )); + } +} + +fn bind_sparse_register_entry_pair( + even: Option<&SparseRegisterEntry>, + odd: Option<&SparseRegisterEntry>, + row: usize, + col: u8, + challenge: F, +) -> SparseRegisterEntry { + let value = sparse_register_entry_eval(even, odd, challenge); + let (prev_val, next_val) = match (even, odd) { + (Some(even), Some(odd)) => (even.prev_val, odd.next_val), + (Some(even), None) => (even.prev_val, even.next_val), + (None, Some(odd)) => (odd.prev_val, odd.next_val), + (None, None) => (0, 0), + }; + SparseRegisterEntry { + row, + col, + val: value.val, + prev_val, + next_val, + read_ra: value.read_ra, + rd_wa: value.rd_wa, + } +} + +fn sparse_register_read_eval( + reads: &[(usize, usize)], + cycle_eq: &[F], + address_eq: &[F], +) -> F { + if reads.len() >= DENSE_BIND_PAR_THRESHOLD { + reads + .par_iter() + .map(|&(row, col)| { + debug_assert!(row < cycle_eq.len()); + debug_assert!(col < address_eq.len()); + cycle_eq[row] * address_eq[col] + }) + .sum() + } else { + reads + .iter() + .map(|&(row, col)| { + debug_assert!(row < cycle_eq.len()); + debug_assert!(col < address_eq.len()); + cycle_eq[row] * address_eq[col] + }) + .sum() + } +} + +fn linear_eval(low: F, high: F, x: F) -> F { + low + x * (high - low) +} + +fn gruen_cubic_poly( + target: F, + q_constant: F, + q_quadratic_coeff: F, + previous_claim: F, +) -> UnivariatePoly { + let eq_eval_1 = target; + let eq_eval_0 = F::one() - target; + let eq_delta = eq_eval_1 - eq_eval_0; + let eq_eval_2 = eq_eval_1 + eq_delta; + let eq_eval_3 = eq_eval_2 + eq_delta; + let cubic_eval_0 = eq_eval_0 * q_constant; + let cubic_eval_1 = previous_claim - cubic_eval_0; + let quadratic_eval_1 = cubic_eval_1 / eq_eval_1; + let e_times_2 = q_quadratic_coeff + q_quadratic_coeff; + let quadratic_eval_2 = quadratic_eval_1 + quadratic_eval_1 - q_constant + e_times_2; + let quadratic_eval_3 = quadratic_eval_2 + quadratic_eval_1 - q_constant + e_times_2 + e_times_2; + UnivariatePoly::from_evals(&[ + cubic_eval_0, + cubic_eval_1, + eq_eval_2 * quadratic_eval_2, + eq_eval_3 * quadratic_eval_3, + ]) +} + +#[tracing::instrument(skip_all, name = "ram_val_check_state")] +fn ram_val_check_state( + claim: &Stage4SumcheckClaimPlan, + inputs: &Stage4ProverInputs<'_, F>, + store: &Stage4ValueStore, + active_scale: F, +) -> Result, Stage4KernelError> { + let witness = inputs.ram.ok_or(Stage4KernelError::MissingKernelInput { + kernel: "jolt_stage4_batched", + input: "ram", + })?; + let expected_len = witness.ram_k.checked_mul(witness.trace_len).ok_or( + Stage4KernelError::InvalidInputLength { + input: "stage4.ram", + expected: usize::MAX, + actual: witness.ram_k, + }, + )?; + require_operand_count( + "stage4.ram.RamInc", + witness.trace_len, + witness.ram_inc.len(), + )?; + + let ram_val_point = store.point("stage4.input.stage2.RamVal")?; + let trace_rounds = log2_exact(witness.trace_len, "stage4.ram.trace_len")?; + let ram_rounds = log2_exact(witness.ram_k, "stage4.ram_k")?; + require_operand_count("stage4.ram_val_check.input", trace_rounds, claim.num_rounds)?; + require_operand_count( + "stage4.input.stage2.RamVal", + ram_rounds + trace_rounds, + ram_val_point.len(), + )?; + let (address_point, cycle_point) = ram_val_point.split_at(ram_rounds); + let address_eq = EqPolynomial::::evals(address_point, None); + let ram_ra_at_address = ram_ra_at_address(witness, &address_eq, expected_len)?; + + let gamma = store.scalar("stage4.ram_val_check.gamma")?; + let mut lt_plus_gamma = lt_evals_big_endian(cycle_point); + require_operand_count( + "stage4.ram_val_check.lt", + witness.trace_len, + lt_plus_gamma.len(), + )?; + lt_plus_gamma + .par_iter_mut() + .for_each(|value| *value += gamma); + + Ok(DenseStage4State::new( + vec![lt_plus_gamma, ram_ra_at_address, witness.ram_inc.to_vec()], + vec![DenseTerm { + coefficient: F::one(), + factors: vec![0, 1, 2], + }], + vec![ + FactorOutput { + name: "stage4.ram_val_check.eval.RamRa", + oracle: "RamRa", + factor: 1, + }, + FactorOutput { + name: "stage4.ram_val_check.eval.RamInc", + oracle: "RamInc", + factor: 2, + }, + ], + active_scale, + )) +} + +fn ram_ra_at_address( + witness: Stage4RamWitness<'_, F>, + address_eq: &[F], + dense_len: usize, +) -> Result, Stage4KernelError> { + if !witness.ram_ra.is_empty() { + require_operand_count("stage4.ram.RamRa", dense_len, witness.ram_ra.len())?; + let mut output = vec![F::zero(); witness.trace_len]; + for (address, &weight) in address_eq.iter().enumerate() { + let base = address * witness.trace_len; + for (cycle, output) in output.iter_mut().enumerate() { + *output += weight * witness.ram_ra[base + cycle]; + } + } + return Ok(output); + } + + let Some(write_address_indices) = witness.write_address_indices else { + return Err(Stage4KernelError::MissingKernelInput { + kernel: "jolt_stage4_batched", + input: "ram_ra", + }); + }; + require_operand_count( + "stage4.ram.write_address_indices", + witness.trace_len, + write_address_indices.len(), + )?; + write_address_indices + .iter() + .map(|address| match address { + Some(address) => { + address_eq + .get(*address) + .copied() + .ok_or(Stage4KernelError::InvalidInputLength { + input: "stage4.ram.write_address_indices", + expected: address_eq.len(), + actual: address + 1, + }) + } + None => Ok(F::zero()), + }) + .collect() +} + +fn round_poly_from_dense_terms( + factors: &[Vec], + terms: &[DenseTerm], + active_scale: F, + relation: Stage4Relation, +) -> Result, Stage4KernelError> { + let half = factors.first().map_or(0, |factor| factor.len() / 2); + for term in terms { + if term.factors.len() > 3 { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 dense term exceeds degree bound", + }); + } + if term.factors.iter().any(|factor| *factor >= factors.len()) { + return Err(Stage4KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage4 dense term references missing factor", + }); + } + } + + let accumulators = if half >= DENSE_BIND_PAR_THRESHOLD { + (0..half) + .into_par_iter() + .map(|row| dense_row_coefficients(factors, terms, row)) + .reduce( + || [F::Accumulator::default(); 4], + |mut left, right| { + for index in 0..left.len() { + left[index].merge(right[index]); + } + left + }, + ) + } else { + (0..half).fold([F::Accumulator::default(); 4], |mut total, row| { + let row_coefficients = dense_row_coefficients(factors, terms, row); + for index in 0..total.len() { + total[index].merge(row_coefficients[index]); + } + total + }) + }; + + Ok(UnivariatePoly::new( + accumulators + .into_iter() + .map(FieldAccumulator::reduce) + .map(|coefficient| coefficient * active_scale) + .collect(), + )) +} + +fn dense_row_coefficients( + factors: &[Vec], + terms: &[DenseTerm], + row: usize, +) -> [F::Accumulator; 4] { + let mut coefficients = [F::Accumulator::default(); 4]; + for term in terms { + match term.factors.as_slice() { + [] => coefficients[0].acc_add(term.coefficient), + [first] => { + let (first0, first_delta) = linear_factor_pair(&factors[*first], row); + coefficients[0].fmadd(term.coefficient, first0); + coefficients[1].fmadd(term.coefficient, first_delta); + } + [first, second] => { + let (first0, first_delta) = linear_factor_pair(&factors[*first], row); + let (second0, second_delta) = linear_factor_pair(&factors[*second], row); + accumulate_quadratic_coefficients( + &mut coefficients, + term.coefficient, + first0, + first_delta, + second0, + second_delta, + ); + } + [first, second, third] => { + let (first0, first_delta) = linear_factor_pair(&factors[*first], row); + let (second0, second_delta) = linear_factor_pair(&factors[*second], row); + let (third0, third_delta) = linear_factor_pair(&factors[*third], row); + accumulate_cubic_coefficients( + &mut coefficients, + term.coefficient, + first0, + first_delta, + second0, + second_delta, + third0, + third_delta, + ); + } + _ => unreachable!("dense terms are validated before evaluation"), + } + } + coefficients +} + +#[inline] +fn linear_factor_pair(factor: &[F], row: usize) -> (F, F) { + let low = factor[2 * row]; + (low, factor[2 * row + 1] - low) +} + +#[inline] +fn accumulate_quadratic_coefficients( + coefficients: &mut [F::Accumulator; 4], + scale: F, + first0: F, + first_delta: F, + second0: F, + second_delta: F, +) { + coefficients[0].fmadd(scale * first0, second0); + coefficients[1].fmadd(scale * first_delta, second0); + coefficients[1].fmadd(scale * first0, second_delta); + coefficients[2].fmadd(scale * first_delta, second_delta); +} + +#[inline] +fn accumulate_cubic_coefficients( + coefficients: &mut [F::Accumulator; 4], + scale: F, + first0: F, + first_delta: F, + second0: F, + second_delta: F, + third0: F, + third_delta: F, +) { + let second0_third0 = second0 * third0; + let second_delta_third0 = second_delta * third0; + let second0_third_delta = second0 * third_delta; + let second_delta_third_delta = second_delta * third_delta; + let scaled_first0 = scale * first0; + let scaled_first_delta = scale * first_delta; + + coefficients[0].fmadd(scaled_first0, second0_third0); + coefficients[1].fmadd(scaled_first_delta, second0_third0); + coefficients[1].fmadd(scaled_first0, second_delta_third0); + coefficients[1].fmadd(scaled_first0, second0_third_delta); + coefficients[2].fmadd(scaled_first_delta, second_delta_third0); + coefficients[2].fmadd(scaled_first_delta, second0_third_delta); + coefficients[2].fmadd(scaled_first0, second_delta_third_delta); + coefficients[3].fmadd(scaled_first_delta, second_delta_third_delta); +} + +fn expected_batched_output_claim( + context: Stage4KernelContext<'_>, + store: &Stage4ValueStore, + evals: &[Stage4NamedEval], + point: &[F], + batching_coeffs: &[F], +) -> Result { + let mut expected = F::zero(); + for (claim, &coefficient) in context.batch_claims()?.iter().zip(batching_coeffs) { + let instance = context + .program + .instance_results + .iter() + .find(|instance| { + instance.claim == claim.symbol && instance.source == context.driver.symbol + }) + .ok_or(Stage4KernelError::MissingClaim { + batch: context.batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(Stage4KernelError::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let claim_value = match Stage4Relation::from_symbol(instance.relation).ok_or( + Stage4KernelError::UnknownRelation { + relation: instance.relation, + }, + )? { + Stage4Relation::RegistersReadWrite => { + expected_registers_read_write(store, evals, local_point)? + } + Stage4Relation::RamValCheck => expected_ram_val_check(store, evals, local_point)?, + relation @ Stage4Relation::Batched => { + return Err(Stage4KernelError::KernelNotImplemented { + abi: relation.symbol(), + }) + } + }; + expected += coefficient * claim_value; + } + Ok(expected) +} + +fn expected_registers_read_write( + store: &Stage4ValueStore, + evals: &[Stage4NamedEval], + local_point: &[F], +) -> Result { + let trace_point = store.point("stage4.input.stage3.registers.RdWriteValue")?; + let r_cycle = normalize_stage4_registers_rw_cycle_point( + local_point, + trace_point.len(), + "stage4.registers_read_write.instance", + )?; + let eq_eval = EqPolynomial::::mle(&r_cycle, trace_point); + let registers_val = eval_by_name(evals, "stage4.registers_read_write.eval.RegistersVal")?; + let rs1_ra = eval_by_name(evals, "stage4.registers_read_write.eval.Rs1Ra")?; + let rs2_ra = eval_by_name(evals, "stage4.registers_read_write.eval.Rs2Ra")?; + let rd_wa = eval_by_name(evals, "stage4.registers_read_write.eval.RdWa")?; + let rd_inc = eval_by_name(evals, "stage4.registers_read_write.eval.RdInc")?; + let gamma = store.scalar("stage4.registers_read_write.gamma")?; + Ok(eq_eval + * (rd_wa * (registers_val + rd_inc) + + gamma * (rs1_ra * registers_val + gamma * rs2_ra * registers_val))) +} + +fn expected_ram_val_check( + store: &Stage4ValueStore, + evals: &[Stage4NamedEval], + local_point: &[F], +) -> Result { + let ram_val_point = store.point("stage4.input.stage2.RamVal")?; + let r_cycle_prime = reverse_slice(local_point); + let r_cycle = suffix_point( + ram_val_point, + r_cycle_prime.len(), + "stage4.input.stage2.RamVal", + )?; + let lt_eval = lt_polynomial_eval(&r_cycle_prime, r_cycle); + let gamma = store.scalar("stage4.ram_val_check.gamma")?; + let ram_ra = eval_by_name(evals, "stage4.ram_val_check.eval.RamRa")?; + let ram_inc = eval_by_name(evals, "stage4.ram_val_check.eval.RamInc")?; + Ok(ram_inc * ram_ra * (lt_eval + gamma)) +} + +fn eval_by_name( + evals: &[Stage4NamedEval], + name: &'static str, +) -> Result { + evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| eval.value) + .ok_or(Stage4KernelError::MissingValue { symbol: name }) +} + +fn named_eval(name: &'static str, oracle: &'static str, value: F) -> Stage4NamedEval { + Stage4NamedEval { + name, + oracle, + value, + } +} + +fn claim_relation( + program: &'static Stage4CpuProgramPlan, + claim: &Stage4SumcheckClaimPlan, +) -> Result { + if let Some(relation) = claim.relation { + return Stage4Relation::from_symbol(relation) + .ok_or(Stage4KernelError::UnknownRelation { relation }); + } + let kernel_symbol = claim.kernel.ok_or(Stage4KernelError::MissingKernel { + driver: claim.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or(Stage4KernelError::MissingKernel { + driver: claim.symbol, + kernel: kernel_symbol, + })?; + kernel.relation_kind() +} + +fn instance_round_offset( + program: &'static Stage4CpuProgramPlan, + driver: &'static str, + claim: &'static str, +) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.source == driver && instance.claim == claim) + .map(|instance| instance.round_offset) + .ok_or(Stage4KernelError::MissingClaim { + batch: driver, + claim, + }) +} + +fn combine_univariate_polys( + polynomials: &[UnivariatePoly], + coefficients: &[F], +) -> UnivariatePoly { + let max_len = polynomials + .iter() + .map(|poly| poly.coefficients().len()) + .max() + .unwrap_or(0); + let mut combined = vec![F::zero(); max_len]; + for (poly, &coefficient) in polynomials.iter().zip(coefficients) { + for (combined, &term) in combined.iter_mut().zip(poly.coefficients()) { + *combined += term * coefficient; + } + } + UnivariatePoly::new(combined) +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients() + .iter() + .rposition(|coefficient| *coefficient != F::zero()) + .unwrap_or(0) +} + +fn append_compressed_univariate_poly( + transcript: &mut T, + label: &'static str, + poly: &UnivariatePoly, +) where + F: Field, + T: Transcript, +{ + let compressed = poly.compress(); + transcript.append(&LabelWithCount( + label.as_bytes(), + compressed.coeffs_except_linear_term().len() as u64, + )); + for coefficient in compressed.coeffs_except_linear_term() { + transcript.append(coefficient); + } +} + +fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &F) +where + F: Field, + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +fn append_opening_claims( + program: &'static Stage4CpuProgramPlan, + store: &mut Stage4ValueStore, + transcript: &mut T, + evals: &[Stage4NamedEval], +) -> Result>, Stage4KernelError> +where + F: Field, + T: Transcript, +{ + if program.opening_batches.is_empty() { + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } + return Ok(Vec::new()); + } + let _ = store.evaluate_available_points(program)?; + let mut opening_claims = Vec::new(); + let mut seen = seed_stage4_opening_aliases(store, program); + for batch in program.opening_batches { + for symbol in batch.claim_operands { + let claim = + find_opening_claim(program, symbol).ok_or(Stage4KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + let point = store.point(claim.point_source)?.to_vec(); + let value = store.scalar(claim.eval_source)?; + let duplicate = has_seen_opening(&seen, claim.claim_kind, claim.oracle, &point); + if !duplicate { + append_labeled_scalar(transcript, "opening_claim", &value); + seen.push((claim.claim_kind, claim.oracle, point.clone())); + } + opening_claims.push(Stage4OpeningClaimValue { + symbol: claim.symbol, + oracle: claim.oracle, + domain: claim.domain, + claim_kind: claim.claim_kind, + point: point.clone(), + eval: value, + }); + } + } + Ok(opening_claims) +} + +fn seed_stage4_opening_aliases( + store: &Stage4ValueStore, + program: &'static Stage4CpuProgramPlan, +) -> Vec<(&'static str, &'static str, Vec)> { + program + .opening_inputs + .iter() + .filter_map(|input| { + store + .try_point(input.symbol) + .map(|point| (input.claim_kind, input.oracle, point.to_vec())) + }) + .collect() +} + +fn has_seen_opening( + seen: &[(&'static str, &'static str, Vec)], + claim_kind: &'static str, + oracle: &'static str, + point: &[F], +) -> bool { + seen.iter().any(|(seen_kind, seen_oracle, seen_point)| { + *seen_kind == claim_kind && *seen_oracle == oracle && seen_point.as_slice() == point + }) +} + +fn find_opening_claim<'a>( + program: &'a Stage4CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage4OpeningClaimPlan> { + program + .opening_claims + .iter() + .find(|claim| claim.symbol == symbol) +} + +fn normalize_stage4_registers_rw_point( + program: &'static Stage4CpuProgramPlan, + driver: &'static str, + point: &[F], +) -> Result, Stage4KernelError> { + let driver_plan = + find_driver(program, driver).ok_or(Stage4KernelError::MissingDriver { driver })?; + if driver_plan.round_schedule.len() != 2 { + return Err(Stage4KernelError::InvalidProof { + driver, + reason: "stage4 registers point normalization requires [cycle, address] schedule", + }); + } + let cycle_rounds = driver_plan.round_schedule[0]; + let address_rounds = driver_plan.round_schedule[1]; + if point.len() != cycle_rounds + address_rounds { + return Err(Stage4KernelError::InvalidInputLength { + input: "stage4.registers_read_write.instance", + expected: cycle_rounds + address_rounds, + actual: point.len(), + }); + } + let (cycle, address) = point.split_at(cycle_rounds); + Ok(address + .iter() + .rev() + .copied() + .chain(cycle.iter().rev().copied()) + .collect()) +} + +fn normalize_stage4_registers_rw_cycle_point( + point: &[F], + cycle_rounds: usize, + input: &'static str, +) -> Result, Stage4KernelError> { + let cycle = point + .get(..cycle_rounds) + .filter(|cycle| cycle.len() == cycle_rounds) + .ok_or(Stage4KernelError::InvalidInputLength { + input, + expected: cycle_rounds, + actual: point.len(), + })?; + Ok(cycle.iter().rev().copied().collect()) +} + +fn suffix_point<'a, F: Field>( + point: &'a [F], + length: usize, + input: &'static str, +) -> Result<&'a [F], Stage4KernelError> { + point + .get(point.len().saturating_sub(length)..) + .filter(|suffix| suffix.len() == length) + .ok_or(Stage4KernelError::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +fn reverse_slice(values: &[F]) -> Vec { + values.iter().rev().copied().collect() +} + +fn lt_polynomial_eval(x: &[F], y: &[F]) -> F { + debug_assert_eq!(x.len(), y.len()); + let mut lt_eval = F::zero(); + let mut eq_term = F::one(); + for (x_i, y_i) in x.iter().zip(y.iter()) { + lt_eval += (F::one() - *x_i) * *y_i * eq_term; + eq_term *= F::one() - *x_i - *y_i + *x_i * *y_i + *x_i * *y_i; + } + lt_eval +} + +fn lt_evals_big_endian(point: &[F]) -> Vec { + let mut evals = vec![F::zero(); 1usize << point.len()]; + for (index, r) in point.iter().rev().enumerate() { + let (left, right) = evals.split_at_mut(1usize << index); + left.iter_mut().zip(right).for_each(|(left, right)| { + *right = *left * *r; + *left += *r - *right; + }); + } + evals +} + +#[cfg(test)] +fn boolean_point_from_index(index: usize, bits: usize) -> Vec { + (0..bits) + .rev() + .map(|bit| F::from_bool(((index >> bit) & 1) == 1)) + .collect() +} + +fn log2_exact(value: usize, input: &'static str) -> Result { + if value.is_power_of_two() { + Ok(value.ilog2() as usize) + } else { + Err(Stage4KernelError::InvalidInputLength { + input, + expected: value.next_power_of_two(), + actual: value, + }) + } +} + +fn verify_count( + artifact: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage4KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage4KernelError::PlanCountMismatch { + artifact, + expected, + actual, + }) + } +} + +fn find_squeeze( + program: &'static Stage4CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage4TranscriptSqueezePlan> { + program + .transcript_squeezes + .iter() + .find(|squeeze| squeeze.symbol == symbol) +} + +fn find_absorb_bytes( + program: &'static Stage4CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage4TranscriptAbsorbBytesPlan> { + program + .transcript_absorb_bytes + .iter() + .find(|absorb| absorb.symbol == symbol) +} + +fn find_driver( + program: &'static Stage4CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage4SumcheckDriverPlan> { + program + .drivers + .iter() + .find(|driver| driver.symbol == symbol) +} + +fn find_kernel( + program: &'static Stage4CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage4KernelPlan> { + program + .kernels + .iter() + .find(|kernel| kernel.symbol == symbol) +} + +fn find_batch( + program: &'static Stage4CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage4SumcheckBatchPlan> { + program.batches.iter().find(|batch| batch.symbol == symbol) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Stage4KernelError { + MissingClaim { + batch: &'static str, + claim: &'static str, + }, + MissingValue { + symbol: &'static str, + }, + MissingDriver { + driver: &'static str, + }, + MissingKernel { + driver: &'static str, + kernel: &'static str, + }, + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + UnknownRelation { + relation: &'static str, + }, + UnknownKernelAbi { + abi: &'static str, + }, + PlanCountMismatch { + artifact: &'static str, + expected: usize, + actual: usize, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + UnsupportedFieldExpr { + symbol: &'static str, + formula: &'static str, + }, + KernelNotImplemented { + abi: &'static str, + }, + WrongExecutorMode { + driver: &'static str, + expected: Stage4ExecutionMode, + actual: Stage4ExecutionMode, + }, + MissingProof { + driver: &'static str, + }, + MissingKernelInput { + kernel: &'static str, + input: &'static str, + }, + InvalidProgramStep { + symbol: &'static str, + kind: &'static str, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, +} + +impl Display for Stage4KernelError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingClaim { batch, claim } => { + write!( + formatter, + "stage4 batch @{batch} references missing claim @{claim}" + ) + } + Self::MissingValue { symbol } => { + write!(formatter, "stage4 value @{symbol} is not available") + } + Self::MissingDriver { driver } => { + write!(formatter, "stage4 driver @{driver} is not available") + } + Self::MissingKernel { driver, kernel } => { + write!( + formatter, + "stage4 driver @{driver} references missing kernel @{kernel}" + ) + } + Self::MissingBatch { driver, batch } => { + write!( + formatter, + "stage4 driver @{driver} references missing batch @{batch}" + ) + } + Self::UnknownRelation { relation } => { + write!(formatter, "stage4 relation @{relation} is not registered") + } + Self::UnknownKernelAbi { abi } => { + write!(formatter, "stage4 kernel ABI `{abi}` is not registered") + } + Self::PlanCountMismatch { + artifact, + expected, + actual, + } => write!( + formatter, + "stage4 plan @{artifact} count mismatch: expected {expected}, got {actual}" + ), + Self::InvalidInputLength { + input, + expected, + actual, + } => write!( + formatter, + "stage4 input `{input}` length mismatch: expected {expected}, got {actual}" + ), + Self::UnsupportedFieldExpr { symbol, formula } => write!( + formatter, + "stage4 field expr @{symbol} uses unsupported formula `{formula}`" + ), + Self::KernelNotImplemented { abi } => { + write!(formatter, "stage4 kernel ABI `{abi}` is not implemented") + } + Self::WrongExecutorMode { + driver, + expected, + actual, + } => write!( + formatter, + "stage4 driver @{driver} ran with {actual:?} executor path, expected {expected:?}" + ), + Self::MissingProof { driver } => { + write!( + formatter, + "stage4 verifier missing proof for driver @{driver}" + ) + } + Self::MissingKernelInput { kernel, input } => { + write!( + formatter, + "stage4 kernel `{kernel}` missing input `{input}`" + ) + } + Self::InvalidProgramStep { symbol, kind } => { + write!( + formatter, + "stage4 program step @{symbol} has unsupported kind `{kind}`" + ) + } + Self::InvalidProof { driver, reason } => { + write!( + formatter, + "stage4 proof for driver @{driver} is invalid: {reason}" + ) + } + } + } +} + +impl Error for Stage4KernelError {} + +#[cfg(test)] +#[expect(clippy::expect_used, reason = "stage4 kernel tests fail fast")] +mod tests { + use super::*; + use jolt_field::Fr; + use jolt_transcript::Blake2bTranscript; + + #[test] + fn lt_evals_big_endian_matches_pointwise() { + let point = frs(&[3, 5, 7]); + let table = lt_evals_big_endian(&point); + + assert_eq!(table.len(), 8); + for (index, value) in table.iter().enumerate() { + let bits = boolean_point_from_index::(index, point.len()); + assert_eq!(*value, lt_polynomial_eval(&bits, &point)); + } + } + + #[test] + fn stage4_batched_kernel_proves_and_verifies_synthetic_witness() { + let program = synthetic_stage4_program(); + let data = SyntheticStage4Data::new(); + let opening_inputs = data.opening_inputs(); + let prover_inputs = Stage4ProverInputs::new(&opening_inputs) + .with_registers(data.registers_witness()) + .with_ram(data.ram_witness()); + let mut prover = Stage4ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage4_test"); + + let artifacts = execute_stage4_program( + program, + Stage4ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("stage4 prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].proof.round_polynomials.len(), 3); + let proof = Stage4Proof::from(artifacts); + let mut verifier = Stage4VerifierKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage4_test"); + let verified = execute_stage4_program( + program, + Stage4ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("stage4 verifier accepts prover proof"); + + assert_eq!(verified.sumchecks.len(), 1); + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + } + + #[test] + fn stage4_batched_kernel_proves_with_sparse_ram_addresses() { + let program = synthetic_stage4_program(); + let data = SyntheticStage4Data::new(); + let opening_inputs = data.opening_inputs(); + let prover_inputs = Stage4ProverInputs::new(&opening_inputs) + .with_registers(data.registers_witness()) + .with_ram(data.sparse_ram_witness()); + let mut prover = Stage4ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage4_test"); + + let artifacts = execute_stage4_program( + program, + Stage4ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("stage4 prover succeeds with sparse RAM addresses"); + + let proof = Stage4Proof::from(artifacts); + let mut verifier = Stage4VerifierKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage4_test"); + let verified = execute_stage4_program( + program, + Stage4ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("stage4 verifier accepts sparse RAM proof"); + + assert_eq!(verified.sumchecks.len(), 1); + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + } + + #[test] + fn stage4_batched_kernel_proves_with_sparse_register_accesses() { + let program = synthetic_stage4_program(); + let data = SyntheticStage4Data::new(); + let opening_inputs = data.opening_inputs(); + let prover_inputs = Stage4ProverInputs::new(&opening_inputs) + .with_registers(data.sparse_registers_witness()) + .with_ram(data.sparse_ram_witness()); + let mut prover = Stage4ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage4_test"); + + let artifacts = execute_stage4_program( + program, + Stage4ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("stage4 prover succeeds with sparse register accesses"); + + let proof = Stage4Proof::from(artifacts); + let mut verifier = Stage4VerifierKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage4_test"); + let verified = execute_stage4_program( + program, + Stage4ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("stage4 verifier accepts sparse register proof"); + + assert_eq!(verified.sumchecks.len(), 1); + assert_eq!(prover_transcript.state(), verifier_transcript.state()); + } + + #[test] + fn stage4_batched_kernel_rejects_tampered_eval() { + let program = synthetic_stage4_program(); + let data = SyntheticStage4Data::new(); + let opening_inputs = data.opening_inputs(); + let mut prover = Stage4ProverKernelExecutor::new( + Stage4ProverInputs::new(&opening_inputs) + .with_registers(data.registers_witness()) + .with_ram(data.ram_witness()), + ); + let mut prover_transcript = Blake2bTranscript::::new(b"stage4_test"); + let mut proof = Stage4Proof::from( + execute_stage4_program( + program, + Stage4ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("stage4 prover succeeds"), + ); + proof.sumchecks[0].evals[0].value += Fr::from_u64(1); + + let mut verifier = Stage4VerifierKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage4_test"); + let error = execute_stage4_program( + program, + Stage4ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect_err("tampered proof is rejected"); + + assert!(matches!(error, Stage4KernelError::InvalidProof { .. })); + } + + #[test] + fn sparse_trace_witness_from_accesses_groups_stage4_and_stage2_accesses() { + let register_accesses = [ + Stage4RegisterAccess { + rd: Some(Stage4RegisterWrite { + address: 2, + pre_value: 5, + post_value: 9, + }), + ..Stage4RegisterAccess::default() + }, + Stage4RegisterAccess::default(), + ]; + let ram_accesses = [ + Stage2RamAccess { + remapped_address: Some(7), + read_value: 11, + write_value: 3, + }, + Stage2RamAccess::noop(), + ]; + + let witness = + stage4_5_sparse_trace_witness_from_accesses::(®ister_accesses, &ram_accesses); + + assert_eq!(witness.rd_inc, vec![Fr::from_u64(4), Fr::from_u64(0)]); + assert_eq!(witness.rd_write_addresses, vec![Some(2), None]); + assert_eq!(witness.ram_addresses, vec![Some(7), None]); + assert_eq!(witness.ram_inc, vec![-Fr::from_u64(8), Fr::from_u64(0)]); + } + + #[derive(Clone)] + struct SyntheticStage4Data { + registers_val: Vec, + rs1_ra: Vec, + rs2_ra: Vec, + rd_wa: Vec, + register_accesses: Vec, + rd_inc: Vec, + ram_ra: Vec, + ram_write_addresses: Vec>, + ram_inc: Vec, + } + + impl SyntheticStage4Data { + fn new() -> Self { + Self { + registers_val: frs(&[10, 12, 12, 15, 20, 20, 21, 21]), + rs1_ra: frs(&[1, 0, 1, 0, 0, 1, 0, 1]), + rs2_ra: frs(&[0, 1, 0, 1, 1, 0, 1, 0]), + rd_wa: frs(&[1, 0, 1, 0, 0, 1, 0, 1]), + register_accesses: synthetic_register_accesses(), + rd_inc: frs(&[2, 1, 3, 4]), + ram_ra: frs(&[1, 0, 1, 0, 0, 1, 0, 1]), + ram_write_addresses: vec![Some(0), Some(1), Some(0), Some(1)], + ram_inc: frs(&[5, 7, 0, 2]), + } + } + + fn registers_witness(&self) -> Stage4RegistersWitness<'_, Fr> { + Stage4RegistersWitness { + register_count: 2, + trace_len: 4, + registers_val: &self.registers_val, + rs1_ra: &self.rs1_ra, + rs2_ra: &self.rs2_ra, + rd_wa: &self.rd_wa, + accesses: None, + rd_inc: &self.rd_inc, + } + } + + fn sparse_registers_witness(&self) -> Stage4RegistersWitness<'_, Fr> { + Stage4RegistersWitness { + register_count: 2, + trace_len: 4, + registers_val: &[], + rs1_ra: &[], + rs2_ra: &[], + rd_wa: &[], + accesses: Some(&self.register_accesses), + rd_inc: &self.rd_inc, + } + } + + fn ram_witness(&self) -> Stage4RamWitness<'_, Fr> { + Stage4RamWitness { + ram_k: 2, + trace_len: 4, + ram_ra: &self.ram_ra, + write_address_indices: None, + ram_inc: &self.ram_inc, + } + } + + fn sparse_ram_witness(&self) -> Stage4RamWitness<'_, Fr> { + Stage4RamWitness { + ram_k: 2, + trace_len: 4, + ram_ra: &[], + write_address_indices: Some(&self.ram_write_addresses), + ram_inc: &self.ram_inc, + } + } + + fn opening_inputs(&self) -> Vec> { + let trace_point = frs(&[5, 7]); + let ram_address_point = frs(&[11]); + let ram_cycle_point = frs(&[13, 17]); + let ram_val_point = [ram_address_point.as_slice(), ram_cycle_point.as_slice()].concat(); + let rd_write_values = self.rd_write_values(); + let rs1_values = self.read_values(&self.rs1_ra); + let rs2_values = self.read_values(&self.rs2_ra); + let ram_init = ram_initial_eval(&ram_address_point); + let ram_ra_at_address = self.ram_ra_at_address(&ram_address_point); + let ram_val_delta = ram_val_delta(&ram_ra_at_address, &self.ram_inc, &ram_cycle_point); + let ram_final_delta = ram_final_delta(&ram_ra_at_address, &self.ram_inc); + vec![ + opening( + "stage4.input.stage3.registers.RdWriteValue", + &trace_point, + mle_eval(&rd_write_values, &trace_point), + ), + opening( + "stage4.input.stage3.registers.Rs1Value", + &trace_point, + mle_eval(&rs1_values, &trace_point), + ), + opening( + "stage4.input.stage3.registers.Rs2Value", + &trace_point, + mle_eval(&rs2_values, &trace_point), + ), + opening( + "stage4.input.stage3.instruction.Rs1Value", + &trace_point, + mle_eval(&rs1_values, &trace_point), + ), + opening( + "stage4.input.stage3.instruction.Rs2Value", + &trace_point, + mle_eval(&rs2_values, &trace_point), + ), + opening( + "stage4.input.stage2.RamVal", + &ram_val_point, + ram_init + ram_val_delta, + ), + opening( + "stage4.input.stage2.RamValFinal", + &ram_address_point, + ram_init + ram_final_delta, + ), + opening( + "stage4.input.initial_ram.RamValInit", + &ram_address_point, + ram_init, + ), + ] + } + + fn rd_write_values(&self) -> Vec { + (0..4) + .map(|cycle| { + (0..2) + .map(|address| { + let index = address * 4 + cycle; + self.rd_wa[index] * (self.registers_val[index] + self.rd_inc[cycle]) + }) + .sum() + }) + .collect() + } + + fn read_values(&self, read_address: &[Fr]) -> Vec { + (0..4) + .map(|cycle| { + (0..2) + .map(|address| { + let index = address * 4 + cycle; + read_address[index] * self.registers_val[index] + }) + .sum() + }) + .collect() + } + + fn ram_ra_at_address(&self, address_point: &[Fr]) -> Vec { + let address_eq = EqPolynomial::::evals(address_point, None); + (0..4) + .map(|cycle| { + (0..2) + .map(|address| address_eq[address] * self.ram_ra[address * 4 + cycle]) + .sum() + }) + .collect() + } + } + + fn synthetic_stage4_program() -> &'static Stage4CpuProgramPlan { + Box::leak(Box::new(Stage4CpuProgramPlan { + role: "prover", + params: Stage4Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }, + steps: leak_slice(vec![ + Stage4ProgramStepPlan { + kind: "transcript_squeeze", + symbol: "stage4.registers_read_write.gamma", + }, + Stage4ProgramStepPlan { + kind: "transcript_absorb_bytes", + symbol: "stage4.ram_val_check.domain_separator", + }, + Stage4ProgramStepPlan { + kind: "transcript_squeeze", + symbol: "stage4.ram_val_check.gamma", + }, + Stage4ProgramStepPlan { + kind: "sumcheck_driver", + symbol: "stage4.sumcheck", + }, + ]), + transcript_squeezes: leak_slice(vec![ + Stage4TranscriptSqueezePlan { + symbol: "stage4.registers_read_write.gamma", + label: "registers_read_write_gamma", + kind: "challenge_scalar", + count: 1, + }, + Stage4TranscriptSqueezePlan { + symbol: "stage4.ram_val_check.gamma", + label: "ram_val_check_gamma", + kind: "challenge_scalar", + count: 1, + }, + ]), + transcript_absorb_bytes: leak_slice(vec![Stage4TranscriptAbsorbBytesPlan { + symbol: "stage4.ram_val_check.domain_separator", + label: "ram_val_check_gamma", + payload: "", + }]), + opening_inputs: leak_slice(stage4_opening_input_plans()), + field_constants: &[], + field_exprs: leak_slice(stage4_field_exprs()), + kernels: leak_slice(vec![ + kernel( + "jolt.cpu.stage4.registers_read_write", + "jolt.stage4.registers_read_write", + "jolt_stage4_registers_read_write", + ), + kernel( + "jolt.cpu.stage4.ram_val_check", + "jolt.stage4.ram_val_check", + "jolt_stage4_ram_val_check", + ), + kernel( + "jolt.cpu.stage4.batched", + "jolt.stage4.batched", + "jolt_stage4_batched", + ), + ]), + claims: leak_slice(vec![ + Stage4SumcheckClaimPlan { + symbol: "stage4.registers_read_write.input", + stage: "stage4", + domain: "jolt.stage4_registers_rw_domain", + num_rounds: 3, + degree: 3, + claim: "stage4.registers_read_write.weighted_values", + kernel: Some("jolt.cpu.stage4.registers_read_write"), + relation: None, + claim_value: "stage4.registers_read_write.claim_expr", + input_openings: leak_slice(vec![ + "stage4.input.stage3.registers.RdWriteValue", + "stage4.input.stage3.registers.Rs1Value", + "stage4.input.stage3.registers.Rs2Value", + ]), + }, + Stage4SumcheckClaimPlan { + symbol: "stage4.ram_val_check.input", + stage: "stage4", + domain: "jolt.trace_domain", + num_rounds: 2, + degree: 3, + claim: "stage4.ram_val_check.weighted_values", + kernel: Some("jolt.cpu.stage4.ram_val_check"), + relation: None, + claim_value: "stage4.ram_val_check.claim_expr", + input_openings: leak_slice(vec![ + "stage4.input.stage2.RamVal", + "stage4.input.stage2.RamValFinal", + "stage4.input.initial_ram.RamValInit", + ]), + }, + ]), + batches: leak_slice(vec![Stage4SumcheckBatchPlan { + symbol: "stage4.batch", + stage: "stage4", + proof_slot: "stage4.sumcheck", + policy: "jolt_core_stage4_aligned", + count: 2, + ordered_claims: leak_slice(vec![ + "stage4.registers_read_write.input", + "stage4.ram_val_check.input", + ]), + claim_operands: leak_slice(vec![ + "stage4.registers_read_write.input", + "stage4.ram_val_check.input", + ]), + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: leak_slice(vec![2, 1]), + }]), + drivers: leak_slice(vec![Stage4SumcheckDriverPlan { + symbol: "stage4.sumcheck", + stage: "stage4", + proof_slot: "stage4.sumcheck", + kernel: Some("jolt.cpu.stage4.batched"), + relation: None, + batch: "stage4.batch", + policy: "jolt_core_stage4_aligned", + round_schedule: leak_slice(vec![2, 1]), + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 3, + degree: 3, + }]), + instance_results: leak_slice(vec![ + Stage4SumcheckInstanceResultPlan { + symbol: "stage4.registers_read_write.instance", + source: "stage4.sumcheck", + claim: "stage4.registers_read_write.input", + relation: "jolt.stage4.registers_read_write", + index: 0, + point_arity: 3, + num_rounds: 3, + round_offset: 0, + point_order: "stage4_registers_rw", + degree: 3, + }, + Stage4SumcheckInstanceResultPlan { + symbol: "stage4.ram_val_check.instance", + source: "stage4.sumcheck", + claim: "stage4.ram_val_check.input", + relation: "jolt.stage4.ram_val_check", + index: 1, + point_arity: 2, + num_rounds: 2, + round_offset: 1, + point_order: "reverse", + degree: 3, + }, + ]), + evals: leak_slice(stage4_eval_plans()), + point_slices: leak_slice(vec![ + Stage4PointSlicePlan { + symbol: "stage4.registers_read_write.point.RdInc", + source: "stage4.registers_read_write.instance", + offset: 1, + length: 2, + input: "stage4.registers_read_write.instance", + }, + Stage4PointSlicePlan { + symbol: "stage4.ram_val_check.point.RamAddress", + source: "stage4.input.stage2.RamVal", + offset: 0, + length: 1, + input: "stage4.input.stage2.RamVal", + }, + ]), + point_concats: leak_slice(vec![Stage4PointConcatPlan { + symbol: "stage4.ram_val_check.point.RamRa", + layout: "address_then_cycle", + arity: 3, + inputs: leak_slice(vec![ + "stage4.ram_val_check.point.RamAddress", + "stage4.ram_val_check.instance", + ]), + }]), + opening_claims: leak_slice(stage4_opening_claim_plans()), + opening_equalities: leak_slice(vec![ + Stage4OpeningClaimEqualityPlan { + symbol: "stage4.registers.rs1_claim_consistency", + mode: "point_and_eval", + lhs: "stage4.input.stage3.registers.Rs1Value", + rhs: "stage4.input.stage3.instruction.Rs1Value", + }, + Stage4OpeningClaimEqualityPlan { + symbol: "stage4.registers.rs2_claim_consistency", + mode: "point_and_eval", + lhs: "stage4.input.stage3.registers.Rs2Value", + rhs: "stage4.input.stage3.instruction.Rs2Value", + }, + ]), + opening_batches: leak_slice(vec![Stage4OpeningBatchPlan { + symbol: "stage4.openings", + stage: "stage4", + proof_slot: "stage4.openings", + policy: "jolt_stage4_output_order", + count: 7, + ordered_claims: leak_slice( + stage4_opening_claim_plans() + .iter() + .map(|claim| claim.symbol) + .collect(), + ), + claim_operands: leak_slice( + stage4_opening_claim_plans() + .iter() + .map(|claim| claim.symbol) + .collect(), + ), + }]), + })) + } + + fn stage4_field_exprs() -> Vec { + vec![ + field_expr( + "stage4.registers_read_write.gamma2", + "field.pow:2", + vec!["stage4.registers_read_write.gamma"], + ), + field_expr( + "stage4.registers_read_write.term.Rs1Value", + "field.mul", + vec![ + "stage4.registers_read_write.gamma", + "stage4.input.stage3.registers.Rs1Value", + ], + ), + field_expr( + "stage4.registers_read_write.term.Rs2Value", + "field.mul", + vec![ + "stage4.registers_read_write.gamma2", + "stage4.input.stage3.registers.Rs2Value", + ], + ), + field_expr( + "stage4.registers_read_write.partial.RdWriteValueRs1Value", + "field.add", + vec![ + "stage4.input.stage3.registers.RdWriteValue", + "stage4.registers_read_write.term.Rs1Value", + ], + ), + field_expr( + "stage4.registers_read_write.claim_expr", + "field.add", + vec![ + "stage4.registers_read_write.partial.RdWriteValueRs1Value", + "stage4.registers_read_write.term.Rs2Value", + ], + ), + field_expr( + "stage4.ram_val_check.delta.RamVal", + "field.sub", + vec![ + "stage4.input.stage2.RamVal", + "stage4.input.initial_ram.RamValInit", + ], + ), + field_expr( + "stage4.ram_val_check.delta.RamValFinal", + "field.sub", + vec![ + "stage4.input.stage2.RamValFinal", + "stage4.input.initial_ram.RamValInit", + ], + ), + field_expr( + "stage4.ram_val_check.term.RamValFinal", + "field.mul", + vec![ + "stage4.ram_val_check.gamma", + "stage4.ram_val_check.delta.RamValFinal", + ], + ), + field_expr( + "stage4.ram_val_check.claim_expr", + "field.add", + vec![ + "stage4.ram_val_check.delta.RamVal", + "stage4.ram_val_check.term.RamValFinal", + ], + ), + ] + } + + fn stage4_opening_input_plans() -> Vec { + vec![ + opening_input_plan( + "stage4.input.stage3.registers.RdWriteValue", + "RdWriteValue", + 2, + ), + opening_input_plan("stage4.input.stage3.registers.Rs1Value", "Rs1Value", 2), + opening_input_plan("stage4.input.stage3.registers.Rs2Value", "Rs2Value", 2), + opening_input_plan("stage4.input.stage3.instruction.Rs1Value", "Rs1Value", 2), + opening_input_plan("stage4.input.stage3.instruction.Rs2Value", "Rs2Value", 2), + opening_input_plan("stage4.input.stage2.RamVal", "RamVal", 3), + opening_input_plan("stage4.input.stage2.RamValFinal", "RamValFinal", 1), + opening_input_plan("stage4.input.initial_ram.RamValInit", "RamValInit", 1), + ] + } + + fn stage4_eval_plans() -> Vec { + vec![ + eval( + "stage4.registers_read_write.eval.RegistersVal", + "RegistersVal", + 0, + ), + eval("stage4.registers_read_write.eval.Rs1Ra", "Rs1Ra", 1), + eval("stage4.registers_read_write.eval.Rs2Ra", "Rs2Ra", 2), + eval("stage4.registers_read_write.eval.RdWa", "RdWa", 3), + eval("stage4.registers_read_write.eval.RdInc", "RdInc", 4), + eval("stage4.ram_val_check.eval.RamRa", "RamRa", 0), + eval("stage4.ram_val_check.eval.RamInc", "RamInc", 1), + ] + } + + fn stage4_opening_claim_plans() -> Vec { + vec![ + opening_claim( + "stage4.registers_read_write.opening.RegistersVal", + "RegistersVal", + "stage4.registers_read_write.instance", + "stage4.registers_read_write.eval.RegistersVal", + 3, + "virtual", + ), + opening_claim( + "stage4.registers_read_write.opening.Rs1Ra", + "Rs1Ra", + "stage4.registers_read_write.instance", + "stage4.registers_read_write.eval.Rs1Ra", + 3, + "virtual", + ), + opening_claim( + "stage4.registers_read_write.opening.Rs2Ra", + "Rs2Ra", + "stage4.registers_read_write.instance", + "stage4.registers_read_write.eval.Rs2Ra", + 3, + "virtual", + ), + opening_claim( + "stage4.registers_read_write.opening.RdWa", + "RdWa", + "stage4.registers_read_write.instance", + "stage4.registers_read_write.eval.RdWa", + 3, + "virtual", + ), + opening_claim( + "stage4.registers_read_write.opening.RdInc", + "RdInc", + "stage4.registers_read_write.point.RdInc", + "stage4.registers_read_write.eval.RdInc", + 2, + "committed", + ), + opening_claim( + "stage4.ram_val_check.opening.RamRa", + "RamRa", + "stage4.ram_val_check.point.RamRa", + "stage4.ram_val_check.eval.RamRa", + 3, + "virtual", + ), + opening_claim( + "stage4.ram_val_check.opening.RamInc", + "RamInc", + "stage4.ram_val_check.instance", + "stage4.ram_val_check.eval.RamInc", + 2, + "committed", + ), + ] + } + + fn field_expr( + symbol: &'static str, + formula: &'static str, + operands: Vec<&'static str>, + ) -> Stage4FieldExprPlan { + let operands = leak_slice(operands); + Stage4FieldExprPlan { + symbol, + kind: "op", + formula, + operand_names: operands, + operands, + } + } + + fn kernel(symbol: &'static str, relation: &'static str, abi: &'static str) -> Stage4KernelPlan { + Stage4KernelPlan { + symbol, + relation, + kind: "sumcheck", + backend: "cpu", + abi, + } + } + + fn opening_input_plan( + symbol: &'static str, + oracle: &'static str, + point_arity: usize, + ) -> Stage4OpeningInputPlan { + Stage4OpeningInputPlan { + symbol, + source_stage: "synthetic", + source_claim: symbol, + oracle, + domain: "synthetic", + point_arity, + claim_kind: "virtual", + } + } + + fn eval(symbol: &'static str, oracle: &'static str, index: usize) -> Stage4SumcheckEvalPlan { + Stage4SumcheckEvalPlan { + symbol, + source: "stage4.sumcheck", + name: symbol, + index, + oracle, + } + } + + fn opening_claim( + symbol: &'static str, + oracle: &'static str, + point_source: &'static str, + eval_source: &'static str, + point_arity: usize, + claim_kind: &'static str, + ) -> Stage4OpeningClaimPlan { + Stage4OpeningClaimPlan { + symbol, + oracle, + domain: "synthetic", + point_arity, + claim_kind, + point_source, + eval_source, + } + } + + fn opening(symbol: &'static str, point: &[Fr], eval: Fr) -> Stage4OpeningInputValue { + Stage4OpeningInputValue { + symbol, + point: point.to_vec(), + eval, + } + } + + fn mle_eval(values: &[Fr], point: &[Fr]) -> Fr { + EqPolynomial::::evals(point, None) + .iter() + .zip(values) + .map(|(&weight, &value)| weight * value) + .sum() + } + + fn ram_initial_eval(address_point: &[Fr]) -> Fr { + let initial = frs(&[11, 13]); + mle_eval(&initial, address_point) + } + + fn ram_val_delta(ram_ra_at_address: &[Fr], ram_inc: &[Fr], cycle_point: &[Fr]) -> Fr { + (0..4) + .map(|cycle| { + let cycle_bits = boolean_point_from_index::(cycle, 2); + ram_inc[cycle] + * ram_ra_at_address[cycle] + * lt_polynomial_eval(&cycle_bits, cycle_point) + }) + .sum() + } + + fn ram_final_delta(ram_ra_at_address: &[Fr], ram_inc: &[Fr]) -> Fr { + ram_ra_at_address + .iter() + .zip(ram_inc) + .map(|(&ra, &inc)| ra * inc) + .sum() + } + + fn synthetic_register_accesses() -> Vec { + vec![ + Stage4RegisterAccess { + rs1: Some(Stage4RegisterRead { + address: 0, + value: 10, + }), + rs2: Some(Stage4RegisterRead { + address: 1, + value: 20, + }), + rd: Some(Stage4RegisterWrite { + address: 0, + pre_value: 10, + post_value: 12, + }), + }, + Stage4RegisterAccess { + rs1: Some(Stage4RegisterRead { + address: 1, + value: 20, + }), + rs2: Some(Stage4RegisterRead { + address: 0, + value: 12, + }), + rd: Some(Stage4RegisterWrite { + address: 1, + pre_value: 20, + post_value: 21, + }), + }, + Stage4RegisterAccess { + rs1: Some(Stage4RegisterRead { + address: 0, + value: 12, + }), + rs2: Some(Stage4RegisterRead { + address: 1, + value: 21, + }), + rd: Some(Stage4RegisterWrite { + address: 0, + pre_value: 12, + post_value: 15, + }), + }, + Stage4RegisterAccess { + rs1: Some(Stage4RegisterRead { + address: 1, + value: 21, + }), + rs2: Some(Stage4RegisterRead { + address: 0, + value: 15, + }), + rd: Some(Stage4RegisterWrite { + address: 1, + pre_value: 21, + post_value: 25, + }), + }, + ] + } + + fn frs(values: &[u64]) -> Vec { + values.iter().copied().map(Fr::from_u64).collect() + } + + fn leak_slice(values: Vec) -> &'static [T] { + Box::leak(values.into_boxed_slice()) + } +} diff --git a/crates/jolt-kernels/src/stage5.rs b/crates/jolt-kernels/src/stage5.rs new file mode 100644 index 0000000000..41a6c40dd0 --- /dev/null +++ b/crates/jolt-kernels/src/stage5.rs @@ -0,0 +1,4949 @@ +//! Stage 5 coarse-kernel ABI used by Bolt-generated Jolt prover code. + +#![expect( + clippy::large_enum_variant, + reason = "kernel states stay inline to avoid boxing hot prover state" +)] +#![expect( + clippy::too_many_arguments, + reason = "kernel constructors mirror generated staged protocol inputs" +)] + +use std::collections::HashMap; +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use crate::dense::{bind_dense_evals_reuse, DENSE_BIND_PAR_THRESHOLD}; +use jolt_field::{Field, FieldAccumulator, FieldScalarAccumulator}; +use jolt_lookup_tables::{ + tables::{ + prefixes::{PrefixEval, ALL_PREFIXES, NUM_PREFIXES}, + Suffixes, + }, + uninterleave_bits, LookupBits, LookupTableKind, +}; +use jolt_poly::{bind_high_to_low, EqPolynomial, UnivariatePoly}; +use jolt_transcript::{Label, LabelWithCount, Transcript}; +use jolt_witness::Stage45SparseTraceWitness; +use rayon::prelude::*; + +type PrefixPairEvals = ([PrefixEval; NUM_PREFIXES], [PrefixEval; NUM_PREFIXES]); + +pub use crate::stage4::{ + Stage4ChallengeVector as Stage5ChallengeVector, Stage4CpuProgramPlan as Stage5CpuProgramPlan, + Stage4ExecutionArtifacts as Stage5ExecutionArtifacts, + Stage4ExecutionMode as Stage5ExecutionMode, Stage4FieldConstantPlan as Stage5FieldConstantPlan, + Stage4FieldExprPlan as Stage5FieldExprPlan, Stage4KernelPlan as Stage5KernelPlan, + Stage4NamedEval as Stage5NamedEval, Stage4OpeningBatchPlan as Stage5OpeningBatchPlan, + Stage4OpeningClaimEqualityPlan as Stage5OpeningClaimEqualityPlan, + Stage4OpeningClaimPlan as Stage5OpeningClaimPlan, + Stage4OpeningClaimValue as Stage5OpeningClaimValue, + Stage4OpeningInputPlan as Stage5OpeningInputPlan, + Stage4OpeningInputValue as Stage5OpeningInputValue, Stage4Params as Stage5Params, + Stage4PointConcatPlan as Stage5PointConcatPlan, Stage4PointSlicePlan as Stage5PointSlicePlan, + Stage4ProgramStepPlan as Stage5ProgramStepPlan, Stage4Proof as Stage5Proof, + Stage4SumcheckBatchPlan as Stage5SumcheckBatchPlan, + Stage4SumcheckClaimPlan as Stage5SumcheckClaimPlan, + Stage4SumcheckDriverPlan as Stage5SumcheckDriverPlan, + Stage4SumcheckEvalPlan as Stage5SumcheckEvalPlan, + Stage4SumcheckInstanceResultPlan as Stage5SumcheckInstanceResultPlan, + Stage4SumcheckOutput as Stage5SumcheckOutput, + Stage4TranscriptAbsorbBytesPlan as Stage5TranscriptAbsorbBytesPlan, + Stage4TranscriptSqueezePlan as Stage5TranscriptSqueezePlan, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage5Relation { + InstructionReadRaf, + RamRaClaimReduction, + RegistersValEvaluation, + Batched, +} + +impl Stage5Relation { + pub fn from_symbol(symbol: &str) -> Option { + match symbol { + "jolt.stage5.instruction_read_raf" => Some(Self::InstructionReadRaf), + "jolt.stage5.ram_ra_claim_reduction" => Some(Self::RamRaClaimReduction), + "jolt.stage5.registers_val_evaluation" => Some(Self::RegistersValEvaluation), + "jolt.stage5.batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn symbol(self) -> &'static str { + match self { + Self::InstructionReadRaf => "jolt.stage5.instruction_read_raf", + Self::RamRaClaimReduction => "jolt.stage5.ram_ra_claim_reduction", + Self::RegistersValEvaluation => "jolt.stage5.registers_val_evaluation", + Self::Batched => "jolt.stage5.batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage5KernelAbi { + InstructionReadRaf, + RamRaClaimReduction, + RegistersValEvaluation, + Batched, +} + +impl Stage5KernelAbi { + pub fn from_name(name: &str) -> Option { + match name { + "jolt_stage5_instruction_read_raf" => Some(Self::InstructionReadRaf), + "jolt_stage5_ram_ra_claim_reduction" => Some(Self::RamRaClaimReduction), + "jolt_stage5_registers_val_evaluation" => Some(Self::RegistersValEvaluation), + "jolt_stage5_batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn name(self) -> &'static str { + match self { + Self::InstructionReadRaf => "jolt_stage5_instruction_read_raf", + Self::RamRaClaimReduction => "jolt_stage5_ram_ra_claim_reduction", + Self::RegistersValEvaluation => "jolt_stage5_registers_val_evaluation", + Self::Batched => "jolt_stage5_batched", + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Stage5KernelError { + MissingClaim { + batch: &'static str, + claim: &'static str, + }, + MissingValue { + symbol: &'static str, + }, + MissingDriver { + driver: &'static str, + }, + MissingKernel { + driver: &'static str, + kernel: &'static str, + }, + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + UnknownRelation { + relation: &'static str, + }, + UnknownKernelAbi { + abi: &'static str, + }, + PlanCountMismatch { + artifact: &'static str, + expected: usize, + actual: usize, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + UnsupportedFieldExpr { + symbol: &'static str, + formula: &'static str, + }, + KernelNotImplemented { + abi: &'static str, + }, + WrongExecutorMode { + driver: &'static str, + expected: Stage5ExecutionMode, + actual: Stage5ExecutionMode, + }, + MissingProof { + driver: &'static str, + }, + MissingKernelInput { + kernel: &'static str, + input: &'static str, + }, + InvalidProgramStep { + symbol: &'static str, + kind: &'static str, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, +} + +impl Display for Stage5KernelError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingClaim { batch, claim } => { + write!( + formatter, + "stage5 batch @{batch} references missing claim @{claim}" + ) + } + Self::MissingValue { symbol } => { + write!(formatter, "stage5 value @{symbol} is not available") + } + Self::MissingDriver { driver } => { + write!(formatter, "stage5 driver @{driver} is not available") + } + Self::MissingKernel { driver, kernel } => { + write!( + formatter, + "stage5 driver @{driver} references missing kernel @{kernel}" + ) + } + Self::MissingBatch { driver, batch } => { + write!( + formatter, + "stage5 driver @{driver} references missing batch @{batch}" + ) + } + Self::UnknownRelation { relation } => { + write!(formatter, "unknown stage5 relation `{relation}`") + } + Self::UnknownKernelAbi { abi } => { + write!(formatter, "unknown stage5 kernel ABI `{abi}`") + } + Self::PlanCountMismatch { + artifact, + expected, + actual, + } => { + write!( + formatter, + "stage5 {artifact} plan count mismatch: expected {expected}, got {actual}" + ) + } + Self::InvalidInputLength { + input, + expected, + actual, + } => { + write!( + formatter, + "stage5 input `{input}` has length {actual}, expected {expected}" + ) + } + Self::UnsupportedFieldExpr { symbol, formula } => { + write!( + formatter, + "stage5 field expr @{symbol} uses unsupported formula `{formula}`" + ) + } + Self::KernelNotImplemented { abi } => { + write!(formatter, "stage5 kernel ABI `{abi}` is not implemented") + } + Self::WrongExecutorMode { + driver, + expected, + actual, + } => { + write!( + formatter, + "stage5 driver @{driver} expected {expected:?} executor, got {actual:?}" + ) + } + Self::MissingProof { driver } => { + write!(formatter, "stage5 proof for driver @{driver} is missing") + } + Self::MissingKernelInput { kernel, input } => { + write!( + formatter, + "stage5 kernel `{kernel}` is missing required input `{input}`" + ) + } + Self::InvalidProgramStep { symbol, kind } => { + write!( + formatter, + "stage5 program step @{symbol} has invalid kind `{kind}`" + ) + } + Self::InvalidProof { driver, reason } => { + write!( + formatter, + "stage5 proof for driver @{driver} is invalid: {reason}" + ) + } + } + } +} + +impl Error for Stage5KernelError {} + +#[derive(Clone, Copy)] +pub struct Stage5RegistersValWitness<'a, F: Field> { + pub register_count: usize, + pub trace_len: usize, + pub rd_inc: &'a [F], + pub rd_wa: &'a [F], + pub rd_write_addresses: Option<&'a [Option]>, +} + +#[derive(Clone, Copy)] +pub struct Stage5RamRaWitness<'a, F: Field> { + pub ram_k: usize, + pub trace_len: usize, + pub ram_ra: &'a [F], + pub remapped_addresses: Option<&'a [Option]>, +} + +#[derive(Clone, Copy)] +pub struct Stage5InstructionReadRafWitness<'a> { + pub trace_len: usize, + pub lookup_indices: &'a [u128], + pub lookup_table_indices: &'a [Option], + pub is_interleaved_operands: &'a [bool], + pub ra_virtual_log_k_chunk: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5InstructionReadRafEvaluations { + pub lookup_table_flags: Vec, + pub instruction_ra: Vec, + pub instruction_raf_flag: F, +} + +#[derive(Clone, Copy)] +pub struct Stage5ProverInputs<'a, F: Field> { + pub opening_inputs: &'a [Stage5OpeningInputValue], + pub instruction_read_raf: Option>, + pub ram_ra: Option>, + pub registers_val: Option>, +} + +impl<'a, F: Field> Stage5ProverInputs<'a, F> { + pub fn new(opening_inputs: &'a [Stage5OpeningInputValue]) -> Self { + Self { + opening_inputs, + instruction_read_raf: None, + ram_ra: None, + registers_val: None, + } + } + + pub fn empty() -> Self { + Self { + opening_inputs: &[], + instruction_read_raf: None, + ram_ra: None, + registers_val: None, + } + } + + pub fn with_instruction_read_raf( + mut self, + instruction_read_raf: Stage5InstructionReadRafWitness<'a>, + ) -> Self { + self.instruction_read_raf = Some(instruction_read_raf); + self + } + + pub fn with_ram_ra(mut self, ram_ra: Stage5RamRaWitness<'a, F>) -> Self { + self.ram_ra = Some(ram_ra); + self + } + + pub fn with_registers_val(mut self, registers_val: Stage5RegistersValWitness<'a, F>) -> Self { + self.registers_val = Some(registers_val); + self + } + + pub fn with_sparse_trace_witness( + self, + trace_len: usize, + ram_k: usize, + register_count: usize, + lookup_indices: &'a [u128], + lookup_table_indices: &'a [Option], + is_interleaved_operands: &'a [bool], + ra_virtual_log_k_chunk: usize, + remapped_addresses: &'a [Option], + rd_inc: &'a [F], + rd_write_addresses: &'a [Option], + ) -> Self { + self.with_instruction_read_raf(Stage5InstructionReadRafWitness { + trace_len, + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + ra_virtual_log_k_chunk, + }) + .with_ram_ra(Stage5RamRaWitness { + ram_k, + trace_len, + ram_ra: &[], + remapped_addresses: Some(remapped_addresses), + }) + .with_registers_val(Stage5RegistersValWitness { + register_count, + trace_len, + rd_inc, + rd_wa: &[], + rd_write_addresses: Some(rd_write_addresses), + }) + } + + pub fn with_stage45_sparse_trace_witness( + self, + trace_len: usize, + ram_k: usize, + register_count: usize, + lookup_indices: &'a [u128], + lookup_table_indices: &'a [Option], + is_interleaved_operands: &'a [bool], + ra_virtual_log_k_chunk: usize, + witness: &'a Stage45SparseTraceWitness, + ) -> Self { + self.with_sparse_trace_witness( + trace_len, + ram_k, + register_count, + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + ra_virtual_log_k_chunk, + &witness.ram_addresses, + &witness.rd_inc, + &witness.rd_write_addresses, + ) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage5KernelContext<'a> { + pub mode: Stage5ExecutionMode, + pub program: &'static Stage5CpuProgramPlan, + pub kernel: &'a Stage5KernelPlan, + pub batch: &'a Stage5SumcheckBatchPlan, + pub driver: &'a Stage5SumcheckDriverPlan, +} + +impl Stage5KernelContext<'_> { + pub fn relation_kind(&self) -> Result { + Stage5Relation::from_symbol(self.kernel.relation).ok_or( + Stage5KernelError::UnknownRelation { + relation: self.kernel.relation, + }, + ) + } + + pub fn abi_kind(&self) -> Result { + Stage5KernelAbi::from_name(self.kernel.abi).ok_or(Stage5KernelError::UnknownKernelAbi { + abi: self.kernel.abi, + }) + } + + pub fn batch_claims(&self) -> Result, Stage5KernelError> { + self.batch + .claim_operands + .iter() + .map(|symbol| { + self.program + .claim(symbol) + .ok_or(Stage5KernelError::MissingClaim { + batch: self.batch.symbol, + claim: symbol, + }) + }) + .collect() + } +} + +pub trait Stage5KernelExecutor { + fn observe_challenge_vector( + &mut self, + _plan: &'static Stage5TranscriptSqueezePlan, + _values: &[F], + ) -> Result<(), Stage5KernelError> { + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + _output: &Stage5SumcheckOutput, + ) -> Result<(), Stage5KernelError> { + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript; + + fn verify_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript; +} + +#[derive(Clone, Debug, Default)] +pub struct UnsupportedStage5KernelExecutor; + +impl Stage5KernelExecutor for UnsupportedStage5KernelExecutor { + fn prove_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript, + { + Err(Stage5KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript, + { + Err(Stage5KernelError::KernelNotImplemented { + abi: context.kernel.abi, + }) + } +} + +#[derive(Clone)] +pub struct Stage5ProverKernelExecutor<'a, F: Field> { + pub inputs: Stage5ProverInputs<'a, F>, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage5ProverKernelExecutor<'a, F> { + pub fn new(inputs: Stage5ProverInputs<'a, F>) -> Self { + Self { + inputs, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage5CpuProgramPlan, + ) -> Result, Stage5KernelError> { + value_store_from_observations( + program, + self.inputs.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } +} + +impl Stage5KernelExecutor for Stage5ProverKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage5TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage5KernelError> { + self.challenge_vectors.push(Stage5ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage5SumcheckOutput, + ) -> Result<(), Stage5KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript, + { + prove_stage5_kernel( + context, + &self.inputs, + self.value_store(context.program)?, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript, + { + Err(Stage5KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage5ExecutionMode::Prover, + actual: Stage5ExecutionMode::Verifier, + }) + } +} + +#[derive(Clone)] +pub struct Stage5ProofCarryingKernelExecutor<'a, F: Field> { + pub proof: &'a Stage5Proof, + pub opening_inputs: &'a [Stage5OpeningInputValue], + pub cursor: usize, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage5ProofCarryingKernelExecutor<'a, F> { + pub fn new( + proof: &'a Stage5Proof, + opening_inputs: &'a [Stage5OpeningInputValue], + ) -> Self { + Self { + proof, + opening_inputs, + cursor: 0, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage5CpuProgramPlan, + ) -> Result, Stage5KernelError> { + value_store_from_observations( + program, + self.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } + + fn next_proof( + &mut self, + driver: &'static str, + ) -> Result<&'a Stage5SumcheckOutput, Stage5KernelError> { + let proof = self + .proof + .sumchecks + .get(self.cursor) + .ok_or(Stage5KernelError::MissingProof { driver })?; + self.cursor += 1; + Ok(proof) + } +} + +impl Stage5KernelExecutor for Stage5ProofCarryingKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage5TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage5KernelError> { + self.challenge_vectors.push(Stage5ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage5SumcheckOutput, + ) -> Result<(), Stage5KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript, + { + let proof = self.next_proof(context.driver.symbol)?; + verify_stage5_kernel( + context, + self.value_store(context.program)?, + proof, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript, + { + let proof = self.next_proof(context.driver.symbol)?; + verify_stage5_kernel( + context, + self.value_store(context.program)?, + proof, + transcript, + ) + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stage5ValueStore { + scalars: Vec<(&'static str, F)>, + points: Vec<(&'static str, Vec)>, +} + +impl Stage5ValueStore { + pub fn with_opening_inputs(inputs: &[Stage5OpeningInputValue]) -> Self { + let mut store = Self::default(); + for input in inputs { + store.insert_scalar(input.symbol, input.eval); + store.insert_point(input.symbol, input.point.clone()); + } + store + } + + pub fn seed_constants(&mut self, program: &'static Stage5CpuProgramPlan) { + for constant in program.field_constants { + self.insert_scalar(constant.symbol, F::from_u64(constant.value as u64)); + } + } + + pub fn observe_challenge_vector( + &mut self, + program: &'static Stage5CpuProgramPlan, + plan: &'static Stage5TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage5KernelError> { + self.insert_point(plan.symbol, values.to_vec()); + if matches!(plan.kind, "challenge_scalar" | "scalar") { + require_operand_count(plan.symbol, 1, values.len())?; + self.insert_scalar(plan.symbol, values[0]); + } + let _ = self.evaluate_available_field_exprs(program)?; + Ok(()) + } + + pub fn observe_sumcheck_output( + &mut self, + program: &'static Stage5CpuProgramPlan, + output: &Stage5SumcheckOutput, + ) -> Result<(), Stage5KernelError> { + self.observe_sumcheck_values(program, output.driver, &output.point, &output.evals) + } + + pub fn observe_sumcheck_values( + &mut self, + program: &'static Stage5CpuProgramPlan, + driver: &'static str, + point: &[F], + evals: &[Stage5NamedEval], + ) -> Result<(), Stage5KernelError> { + self.insert_point(driver, point.to_vec()); + for instance in program + .instance_results + .iter() + .filter(|instance| instance.source == driver) + { + let end = instance.round_offset + instance.point_arity; + let mut instance_point = point + .get(instance.round_offset..end) + .ok_or(Stage5KernelError::InvalidInputLength { + input: instance.symbol, + expected: end, + actual: point.len(), + })? + .to_vec(); + match instance.point_order { + "as_is" => {} + "instruction_read_raf" => { + instance_point = normalize_instruction_read_raf_point(&instance_point)?; + } + "reverse" => instance_point.reverse(), + _ => { + return Err(Stage5KernelError::InvalidProof { + driver, + reason: "unsupported point order", + }); + } + } + self.insert_point(instance.symbol, instance_point); + } + for eval in program.evals.iter().filter(|eval| eval.source == driver) { + let value = evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| evals.get(eval.index)) + .ok_or(Stage5KernelError::MissingValue { + symbol: eval.symbol, + })? + .value; + self.insert_scalar(eval.symbol, value); + self.insert_scalar(eval.name, value); + } + let _ = self.evaluate_available_points(program)?; + let _ = self.evaluate_available_field_exprs(program)?; + self.verify_opening_equalities(program)?; + Ok(()) + } + + pub fn claim_value( + &mut self, + program: &'static Stage5CpuProgramPlan, + claim: &Stage5SumcheckClaimPlan, + ) -> Result { + let _ = self.evaluate_available_field_exprs(program)?; + self.scalar(claim.claim_value) + } + + pub fn batch_claim_values( + &mut self, + program: &'static Stage5CpuProgramPlan, + batch: &Stage5SumcheckBatchPlan, + ) -> Result, Stage5KernelError> { + batch + .claim_operands + .iter() + .map(|symbol| { + let claim = program + .claim(symbol) + .ok_or(Stage5KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + self.claim_value(program, claim) + }) + .collect() + } + + pub fn evaluate_available_points( + &mut self, + program: &'static Stage5CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for slice in program.point_slices { + if self.try_point(slice.symbol).is_some() { + continue; + } + let Some(input) = self.try_point(slice.input) else { + continue; + }; + let end = slice.offset + slice.length; + let point = input + .get(slice.offset..end) + .ok_or(Stage5KernelError::InvalidInputLength { + input: slice.symbol, + expected: end, + actual: input.len(), + })? + .to_vec(); + self.insert_point(slice.symbol, point); + progress += 1; + } + for concat in program.point_concats { + if self.try_point(concat.symbol).is_some() { + continue; + } + let Some(point) = self.try_concat_point(concat) else { + continue; + }; + require_operand_count(concat.symbol, concat.arity, point.len())?; + self.insert_point(concat.symbol, point); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + pub fn evaluate_available_field_exprs( + &mut self, + program: &'static Stage5CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for expr in program.field_exprs { + if self.try_scalar(expr.symbol).is_some() { + continue; + } + let Some(operands) = self.try_expr_operands(expr) else { + continue; + }; + self.insert_scalar(expr.symbol, evaluate_stage5_field_expr(expr, &operands)?); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + pub fn verify_opening_equalities( + &self, + program: &'static Stage5CpuProgramPlan, + ) -> Result<(), Stage5KernelError> { + for equality in program.opening_equalities { + match equality.mode { + "point_and_eval" => { + if self.point(equality.lhs)? != self.point(equality.rhs)? + || self.scalar(equality.lhs)? != self.scalar(equality.rhs)? + { + return Err(Stage5KernelError::InvalidProof { + driver: equality.symbol, + reason: "opening claim equality failed", + }); + } + } + _ => { + return Err(Stage5KernelError::InvalidProof { + driver: equality.symbol, + reason: "unsupported opening equality mode", + }); + } + } + } + Ok(()) + } + + pub fn insert_scalar(&mut self, symbol: &'static str, value: F) { + if let Some((_, existing)) = self + .scalars + .iter_mut() + .find(|(existing, _)| *existing == symbol) + { + *existing = value; + } else { + self.scalars.push((symbol, value)); + } + } + + pub fn insert_point(&mut self, symbol: &'static str, point: Vec) { + if let Some((_, existing)) = self + .points + .iter_mut() + .find(|(existing, _)| *existing == symbol) + { + *existing = point; + } else { + self.points.push((symbol, point)); + } + } + + pub fn scalar(&self, symbol: &'static str) -> Result { + self.try_scalar(symbol) + .ok_or(Stage5KernelError::MissingValue { symbol }) + } + + pub fn try_scalar(&self, symbol: &str) -> Option { + self.scalars + .iter() + .find(|(existing, _)| *existing == symbol) + .map(|(_, value)| *value) + } + + pub fn point(&self, symbol: &'static str) -> Result<&[F], Stage5KernelError> { + self.try_point(symbol) + .ok_or(Stage5KernelError::MissingValue { symbol }) + } + + pub fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.points + .iter() + .find(|(existing, _)| *existing == symbol) + .map(|(_, point)| point.as_slice()) + } + + fn try_expr_operands(&self, expr: &Stage5FieldExprPlan) -> Option> { + expr.operands + .iter() + .map(|operand| self.try_scalar(operand)) + .collect() + } + + fn try_concat_point(&self, concat: &Stage5PointConcatPlan) -> Option> { + let mut point = Vec::with_capacity(concat.arity); + for input in concat.inputs { + point.extend_from_slice(self.try_point(input)?); + } + Some(point) + } +} + +fn value_store_from_observations( + program: &'static Stage5CpuProgramPlan, + opening_inputs: &[Stage5OpeningInputValue], + challenge_vectors: &[Stage5ChallengeVector], + completed_sumchecks: &[Stage5SumcheckOutput], +) -> Result, Stage5KernelError> { + let mut store = Stage5ValueStore::with_opening_inputs(opening_inputs); + store.seed_constants(program); + for challenge in challenge_vectors { + let plan = program + .transcript_squeezes + .iter() + .find(|plan| plan.symbol == challenge.symbol) + .ok_or(Stage5KernelError::MissingValue { + symbol: challenge.symbol, + })?; + store.observe_challenge_vector(program, plan, &challenge.values)?; + } + for output in completed_sumchecks { + store.observe_sumcheck_output(program, output)?; + } + let _ = store.evaluate_available_points(program)?; + let _ = store.evaluate_available_field_exprs(program)?; + store.verify_opening_equalities(program)?; + Ok(store) +} + +pub fn evaluate_stage5_field_expr( + expr: &Stage5FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => single_operand(expr.symbol, operands), + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + formula => { + if let Some(exponent) = formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + Stage5KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + Err(Stage5KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }) + } + } +} + +fn prove_stage5_kernel( + context: Stage5KernelContext<'_>, + inputs: &Stage5ProverInputs<'_, F>, + store: Stage5ValueStore, + transcript: &mut T, +) -> Result, Stage5KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage5KernelAbi::Batched => prove_batched_stage5(context, inputs, store, transcript), + abi => Err(Stage5KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +fn verify_stage5_kernel( + context: Stage5KernelContext<'_>, + store: Stage5ValueStore, + proof: &Stage5SumcheckOutput, + transcript: &mut T, +) -> Result, Stage5KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage5KernelAbi::Batched => verify_batched_stage5(context, store, proof, transcript), + abi => Err(Stage5KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +#[tracing::instrument(skip_all, name = "Stage5::prove_batched")] +fn prove_batched_stage5( + context: Stage5KernelContext<'_>, + inputs: &Stage5ProverInputs<'_, F>, + mut store: Stage5ValueStore, + transcript: &mut T, +) -> Result, Stage5KernelError> +where + F: Field, + T: Transcript, +{ + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let two_inv = F::from_u64(2) + .inverse() + .ok_or(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "field element 2 is not invertible", + })?; + let mut instances = Vec::with_capacity(claims.len()); + for (index, claim) in claims.iter().enumerate() { + let offset = instance_round_offset(context.program, context.driver.symbol, claim.symbol)?; + if offset + claim.num_rounds > max_rounds { + return Err(Stage5KernelError::InvalidInputLength { + input: claim.symbol, + expected: max_rounds, + actual: offset + claim.num_rounds, + }); + } + let active_scale = F::one().mul_pow_2(max_rounds - offset - claim.num_rounds); + instances.push(Stage5BatchedInstance { + claim, + relation: claim_relation(context.program, claim)?, + offset, + previous_claim: input_claims[index].mul_pow_2(max_rounds - claim.num_rounds), + state: Stage5ProverInstanceState::new( + context.program, + claim, + inputs, + &store, + active_scale, + )?, + }); + } + + let mut point = Vec::with_capacity(max_rounds); + let mut round_polynomials = Vec::with_capacity(max_rounds); + let mut batched_claim = instances + .iter() + .zip(&batching_coeffs) + .map(|(instance, &coefficient)| instance.previous_claim * coefficient) + .sum::(); + for round in 0..max_rounds { + let mut individual_polys = Vec::with_capacity(instances.len()); + for instance in &mut instances { + let poly = if instance.is_active(round) { + instance + .state + .round_poly(instance.previous_claim, instance.relation)? + } else { + UnivariatePoly::new(vec![instance.previous_claim * two_inv]) + }; + individual_polys.push(poly); + } + let batched_poly = combine_univariate_polys(&individual_polys, &batching_coeffs); + if batched_poly.evaluate(F::zero()) + batched_poly.evaluate(F::one()) != batched_claim { + return Err(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round claim mismatch", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, &batched_poly); + let challenge = transcript.challenge(); + point.push(challenge); + batched_claim = batched_poly.evaluate(challenge); + for (instance, poly) in instances.iter_mut().zip(individual_polys) { + instance.previous_claim = poly.evaluate(challenge); + if instance.is_active(round) { + instance.state.ingest_challenge(challenge); + } + } + round_polynomials.push(batched_poly); + } + + let mut evals = Vec::new(); + for instance in &instances { + evals.extend(instance.state.final_evals(instance.relation)?); + } + let expected = + expected_batched_output_claim(context, &store, &evals, &point, &batching_coeffs)?; + if batched_claim != expected { + return Err(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + store.observe_sumcheck_values(context.program, context.driver.symbol, &point, &evals)?; + let opening_claims = append_opening_claims(context.program, &mut store, transcript, &evals)?; + Ok(Stage5SumcheckOutput { + driver: context.driver.symbol, + point, + evals, + opening_claims, + proof: jolt_sumcheck::SumcheckProof { round_polynomials }, + }) +} + +fn verify_batched_stage5( + context: Stage5KernelContext<'_>, + mut store: Stage5ValueStore, + proof: &Stage5SumcheckOutput, + transcript: &mut T, +) -> Result, Stage5KernelError> +where + F: Field, + T: Transcript, +{ + if proof.driver != context.driver.symbol { + return Err(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + if proof.proof.round_polynomials.len() != context.driver.num_rounds { + return Err(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected batched round count", + }); + } + + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let mut running_claim = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), &coefficient)| { + claim.mul_pow_2(max_rounds - plan.num_rounds) * coefficient + }) + .sum::(); + let mut point = Vec::with_capacity(max_rounds); + for poly in &proof.proof.round_polynomials { + if polynomial_degree(poly) > context.driver.degree { + return Err(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched polynomial exceeds degree bound", + }); + } + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != running_claim { + return Err(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round check failed", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, poly); + let challenge = transcript.challenge(); + running_claim = poly.evaluate(challenge); + point.push(challenge); + } + if !proof.point.is_empty() && proof.point != point { + return Err(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched point mismatch", + }); + } + let expected = + expected_batched_output_claim(context, &store, &proof.evals, &point, &batching_coeffs)?; + if running_claim != expected { + return Err(Stage5KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + + let output = Stage5SumcheckOutput { + driver: context.driver.symbol, + point, + evals: proof.evals.clone(), + opening_claims: Vec::new(), + proof: proof.proof.clone(), + }; + store.observe_sumcheck_output(context.program, &output)?; + let opening_claims = + append_opening_claims(context.program, &mut store, transcript, &output.evals)?; + let output = Stage5SumcheckOutput { + opening_claims, + ..output + }; + Ok(output) +} + +struct Stage5BatchedInstance<'a, F: Field> { + claim: &'a Stage5SumcheckClaimPlan, + relation: Stage5Relation, + offset: usize, + previous_claim: F, + state: Stage5ProverInstanceState, +} + +impl Stage5BatchedInstance<'_, F> { + fn is_active(&self, round: usize) -> bool { + round >= self.offset && round < self.offset + self.claim.num_rounds + } +} + +enum Stage5ProverInstanceState { + Dense(DenseStage5State), + InstructionReadRaf(InstructionReadRafStage5State), +} + +impl Stage5ProverInstanceState { + fn new( + program: &'static Stage5CpuProgramPlan, + claim: &Stage5SumcheckClaimPlan, + inputs: &Stage5ProverInputs<'_, F>, + store: &Stage5ValueStore, + active_scale: F, + ) -> Result { + match claim_relation(program, claim)? { + Stage5Relation::InstructionReadRaf => { + instruction_read_raf_state(program, claim, inputs, store, active_scale) + .map(Self::InstructionReadRaf) + } + Stage5Relation::RegistersValEvaluation => { + registers_val_evaluation_state(claim, inputs, store, active_scale).map(Self::Dense) + } + Stage5Relation::RamRaClaimReduction => { + ram_ra_claim_reduction_state(claim, inputs, store, active_scale).map(Self::Dense) + } + relation @ Stage5Relation::Batched => Err(Stage5KernelError::KernelNotImplemented { + abi: relation.symbol(), + }), + } + } + + fn round_poly( + &mut self, + previous_claim: F, + relation: Stage5Relation, + ) -> Result, Stage5KernelError> { + match self { + Self::Dense(state) => state.round_poly(previous_claim, relation), + Self::InstructionReadRaf(state) => state.round_poly(previous_claim, relation), + } + } + + fn ingest_challenge(&mut self, challenge: F) { + match self { + Self::Dense(state) => state.bind(challenge), + Self::InstructionReadRaf(state) => state.bind(challenge), + } + } + + fn final_evals( + &self, + relation: Stage5Relation, + ) -> Result>, Stage5KernelError> { + match self { + Self::Dense(state) => state.final_evals(relation), + Self::InstructionReadRaf(state) => state.final_evals(relation), + } + } +} + +struct DenseStage5State { + factors: Vec>, + factor_scratch: Vec>, + terms: Vec>, + outputs: Vec, + active_scale: F, +} + +#[derive(Clone)] +struct DenseTerm { + coefficient: F, + factors: Vec, +} + +#[derive(Clone, Copy)] +struct FactorOutput { + name: &'static str, + oracle: &'static str, + factor: usize, +} + +#[derive(Clone, Copy)] +enum InstructionReadRafOutputKind { + LookupTableFlag(usize), + InstructionRa(usize), + InstructionRafFlag, +} + +#[derive(Clone, Copy)] +struct InstructionReadRafOutputPlan { + index: usize, + name: &'static str, + oracle: &'static str, + kind: InstructionReadRafOutputKind, +} + +struct InstructionReadRafStage5State { + trace_len: usize, + lookup_indices: Vec, + lookup_table_indices: Vec>, + is_interleaved_operands: Vec, + lookup_groups: Vec>, + lookup_group_indices_by_cycle: Vec, + lookup_groups_by_table: Vec>, + ra_virtual_log_k_chunk: usize, + u_evals: Vec, + gamma: F, + gamma2: F, + active_scale: F, + round: usize, + address_challenges: Vec, + cycle_challenges: Vec, + address_phase: Option>, + left_operand_checkpoint: F, + right_operand_checkpoint: F, + identity_checkpoint: F, + read_prefix_checkpoints: Vec>, + cycle_state: Option>, + outputs: Vec, +} + +struct InstructionReadRafLookupGroup { + lookup_index: u128, + lookup_table_index: Option, + is_interleaved_operands: bool, + u_eval_sum: F, + phase_u_eval_sum: F, +} + +struct InstructionReadRafAddressPhase { + phase: usize, + left_operand_prefix: Vec, + right_operand_prefix: Vec, + identity_prefix: Vec, + raf_shift_half_q: Vec, + raf_left_q: Vec, + raf_right_q: Vec, + raf_shift_full_q: Vec, + raf_identity_q: Vec, + read_prefix_polys: Vec>, + read_suffix_polys: Vec>, +} + +struct InstructionReadRafReadTablePhase { + table: LookupTableKind<64>, + suffix_polys: Vec>, +} + +struct InstructionReadRafCycleState { + factors: Vec>, + factor_scratch: Vec>, +} + +impl DenseStage5State { + fn new( + factors: Vec>, + terms: Vec>, + outputs: Vec, + active_scale: F, + ) -> Self { + let factor_scratch = (0..factors.len()).map(|_| Vec::new()).collect(); + Self { + factors, + factor_scratch, + terms, + outputs, + active_scale, + } + } + + fn round_poly( + &self, + previous_claim: F, + relation: Stage5Relation, + ) -> Result, Stage5KernelError> { + let first_len = self.factors.first().map_or(0, Vec::len); + if first_len == 0 || !first_len.is_power_of_two() { + return Err(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage5 dense factor has invalid length", + }); + } + if self.factors.iter().any(|factor| factor.len() != first_len) { + return Err(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage5 dense factors have inconsistent lengths", + }); + } + let poly = + round_poly_from_dense_terms(&self.factors, &self.terms, self.active_scale, relation)?; + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != previous_claim { + return Err(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage5 relation input claim mismatch", + }); + } + Ok(poly) + } + + fn bind(&mut self, challenge: F) { + if self.factors.first().map_or(0, Vec::len) / 2 >= DENSE_BIND_PAR_THRESHOLD { + self.factors + .par_iter_mut() + .zip(self.factor_scratch.par_iter_mut()) + .for_each(|(factor, scratch)| { + bind_dense_evals_reuse(factor, scratch, challenge); + }); + } else { + for (factor, scratch) in self.factors.iter_mut().zip(&mut self.factor_scratch) { + bind_dense_evals_reuse(factor, scratch, challenge); + } + } + } + + fn factor_eval(&self, index: usize, relation: Stage5Relation) -> Result { + self.factors + .get(index) + .and_then(|values| values.first()) + .copied() + .ok_or(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty stage5 factor", + }) + } + + fn final_evals( + &self, + relation: Stage5Relation, + ) -> Result>, Stage5KernelError> { + self.outputs + .iter() + .map(|output| { + Ok(named_eval( + output.name, + output.oracle, + self.factor_eval(output.factor, relation)?, + )) + }) + .collect() + } +} + +impl InstructionReadRafStage5State { + const LOG_K: usize = 128; + const ADDRESS_CHUNK_BITS: usize = 8; + + fn round_poly( + &mut self, + previous_claim: F, + relation: Stage5Relation, + ) -> Result, Stage5KernelError> { + if self.round < Self::LOG_K { + self.ensure_address_phase(); + let Some(address_phase) = self.address_phase.as_ref() else { + std::process::abort(); + }; + let (read, raf_components) = if address_phase.left_operand_prefix.len() <= 32 { + ( + address_phase.read_table_round_evals(), + address_phase.raf_round_component_evals(), + ) + } else { + rayon::join( + || address_phase.read_table_round_evals(), + || address_phase.raf_round_component_evals(), + ) + }; + let eval_at_0 = (read[0] + + self.gamma * raf_components[0][0] + + self.gamma2 * (raf_components[1][0] + raf_components[2][0])) + * self.active_scale; + let eval_at_2 = (read[1] + + self.gamma * raf_components[0][1] + + self.gamma2 * (raf_components[1][1] + raf_components[2][1])) + * self.active_scale; + let result = Ok(UnivariatePoly::from_evals_and_hint( + previous_claim, + &[eval_at_0, eval_at_2], + )); + return result; + } + + if self.cycle_state.is_none() { + self.cycle_state = Some(self.materialize_cycle_state()?); + } + let Some(cycle_state) = self.cycle_state.as_ref() else { + std::process::abort(); + }; + cycle_state.round_poly(previous_claim, self.active_scale, relation) + } + + fn bind(&mut self, challenge: F) { + if self.round < Self::LOG_K { + self.ensure_address_phase(); + self.address_challenges.push(challenge); + if let Some(phase) = &mut self.address_phase { + phase.bind(challenge); + } + if (self.round + 1).is_multiple_of(Self::ADDRESS_CHUNK_BITS) { + self.finish_address_phase(); + } + } else { + self.cycle_challenges.push(challenge); + if let Some(cycle_state) = &mut self.cycle_state { + cycle_state.bind(challenge); + } + } + self.round += 1; + } + + fn final_evals( + &self, + relation: Stage5Relation, + ) -> Result>, Stage5KernelError> { + require_operand_count( + "stage5.instruction_read_raf.address_challenges", + Self::LOG_K, + self.address_challenges.len(), + )?; + require_operand_count( + "stage5.instruction_read_raf.cycle_challenges", + log2_exact(self.trace_len, "stage5.instruction_read_raf.trace_len")?, + self.cycle_challenges.len(), + )?; + + let normalized_cycle_point = reverse_slice(&self.cycle_challenges); + let evaluations = self.instruction_read_raf_output_evals_from_groups( + &self.address_challenges, + &normalized_cycle_point, + )?; + self.outputs + .iter() + .map(|output| { + let value = match output.kind { + InstructionReadRafOutputKind::LookupTableFlag(index) => { + evaluations.lookup_table_flags.get(index).copied().ok_or( + Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.lookup_table_flags", + expected: evaluations.lookup_table_flags.len(), + actual: index + 1, + }, + )? + } + InstructionReadRafOutputKind::InstructionRa(index) => { + evaluations.instruction_ra.get(index).copied().ok_or( + Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.instruction_ra", + expected: evaluations.instruction_ra.len(), + actual: index + 1, + }, + )? + } + InstructionReadRafOutputKind::InstructionRafFlag => { + evaluations.instruction_raf_flag + } + }; + Ok(named_eval(output.name, output.oracle, value)) + }) + .collect::, _>>() + .map_err(|error| match error { + Stage5KernelError::InvalidInputLength { .. } => error, + _ => Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "invalid instruction read raf output", + }, + }) + } + + fn ensure_address_phase(&mut self) { + debug_assert!(Self::LOG_K.is_multiple_of(Self::ADDRESS_CHUNK_BITS)); + let phase = self.round / Self::ADDRESS_CHUNK_BITS; + if self + .address_phase + .as_ref() + .is_some_and(|address_phase| address_phase.phase == phase) + { + return; + } + self.address_phase = Some(self.build_address_phase(phase)); + } + + fn build_address_phase(&self, phase: usize) -> InstructionReadRafAddressPhase { + let chunk_bits = Self::ADDRESS_CHUNK_BITS; + let poly_len = 1usize << chunk_bits; + let suffix_len = Self::LOG_K - (phase + 1) * chunk_bits; + let left_operand_prefix = + operand_prefix_poly(self.left_operand_checkpoint, chunk_bits, true); + let right_operand_prefix = + operand_prefix_poly(self.right_operand_checkpoint, chunk_bits, false); + let identity_prefix = identity_prefix_poly(self.identity_checkpoint, chunk_bits); + let read_prefix_polys = ALL_PREFIXES + .par_iter() + .map(|prefix| { + (0..poly_len) + .map(|bits| { + prefix + .evaluate( + &self.read_prefix_checkpoints, + LookupBits::new(bits as u128, chunk_bits), + suffix_len, + ) + .into_inner() + }) + .collect::>() + }) + .collect::>(); + + let shift_half_value = 1u128 << (suffix_len / 2); + let shift_full_value = 1u128 << suffix_len; + let shift_half = F::from_u128(shift_half_value); + let shift_full = F::from_u128(shift_full_value); + let suffix_mask = if suffix_len == 128 { + u128::MAX + } else { + (1u128 << suffix_len) - 1 + }; + + let q_total_len = 5 * poly_len; + let q_chunk_size = self + .lookup_groups + .len() + .div_ceil(rayon::current_num_threads()) + .max(1); + let q_rows = self + .lookup_groups + .par_chunks(q_chunk_size) + .fold( + || vec![F::zero(); q_total_len], + |mut acc, groups| { + let shift_half_offset = 0; + let left_offset = poly_len; + let right_offset = 2 * poly_len; + let shift_full_offset = 3 * poly_len; + let identity_offset = 4 * poly_len; + + for group in groups { + let index = ((group.lookup_index >> suffix_len) as usize) & (poly_len - 1); + let suffix_bits = group.lookup_index & suffix_mask; + let weight = group.phase_u_eval_sum; + + if group.is_interleaved_operands { + acc[shift_half_offset + index] += weight; + let (left_suffix, right_suffix) = uninterleave_bits(suffix_bits); + if left_suffix != 0 { + acc[left_offset + index] += weight.mul_u64(left_suffix); + } + if right_suffix != 0 { + acc[right_offset + index] += weight.mul_u64(right_suffix); + } + } else { + acc[shift_full_offset + index] += weight; + if suffix_bits != 0 { + acc[identity_offset + index] += weight.mul_u128(suffix_bits); + } + } + } + acc + }, + ) + .reduce( + || vec![F::zero(); q_total_len], + |mut left, right| { + for (left_value, right_value) in left.iter_mut().zip(right) { + *left_value += right_value; + } + left + }, + ); + let mut raf_shift_half_q = q_rows[..poly_len].to_vec(); + let raf_left_q = q_rows[poly_len..2 * poly_len].to_vec(); + let raf_right_q = q_rows[2 * poly_len..3 * poly_len].to_vec(); + let mut raf_shift_full_q = q_rows[3 * poly_len..4 * poly_len].to_vec(); + let raf_identity_q = q_rows[4 * poly_len..5 * poly_len].to_vec(); + if shift_half_value != 1 { + for value in &mut raf_shift_half_q { + *value *= shift_half; + } + } + if shift_full_value != 1 { + for value in &mut raf_shift_full_q { + *value *= shift_full; + } + } + + let tables = LookupTableKind::<64>::all(); + let read_suffix_polys = tables + .par_iter() + .enumerate() + .filter_map(|(table_index, table)| { + if self.lookup_groups_by_table[table_index].is_empty() { + return None; + } + let suffixes = table.suffixes(); + let mut accumulators = + vec![vec![F::ScalarAccumulator::default(); poly_len]; suffixes.len()]; + let mut one_suffix = None; + let mut boolean_suffixes = Vec::new(); + let mut valued_suffixes = Vec::new(); + for (suffix_index, suffix) in suffixes.iter().enumerate() { + if matches!(suffix, Suffixes::One) { + one_suffix = Some(suffix_index); + } else if suffix.is_01_valued() { + boolean_suffixes.push((suffix_index, suffix)); + } else { + valued_suffixes.push((suffix_index, suffix)); + } + } + for &group_index in &self.lookup_groups_by_table[table_index] { + let group = &self.lookup_groups[group_index]; + let index = ((group.lookup_index >> suffix_len) as usize) & (poly_len - 1); + let suffix_bits = LookupBits::new(group.lookup_index & suffix_mask, suffix_len); + let weight = group.phase_u_eval_sum; + if let Some(suffix_index) = one_suffix { + accumulators[suffix_index][index].add(weight); + } + for &(suffix_index, suffix) in &boolean_suffixes { + let suffix_value = suffix.suffix_mle(suffix_bits); + debug_assert!(suffix_value == 0 || suffix_value == 1); + if suffix_value == 1 { + accumulators[suffix_index][index].add(weight); + } + } + for &(suffix_index, suffix) in &valued_suffixes { + let suffix_value = suffix.suffix_mle(suffix_bits); + accumulators[suffix_index][index].add_mul_u64(weight, suffix_value); + } + } + let polys = accumulators + .into_iter() + .map(|poly| { + poly.into_iter() + .map(FieldScalarAccumulator::reduce) + .collect::>() + }) + .collect::>(); + Some(InstructionReadRafReadTablePhase { + table: *table, + suffix_polys: polys, + }) + }) + .collect::>(); + + InstructionReadRafAddressPhase { + phase, + left_operand_prefix, + right_operand_prefix, + identity_prefix, + raf_shift_half_q, + raf_left_q, + raf_right_q, + raf_shift_full_q, + raf_identity_q, + read_prefix_polys, + read_suffix_polys, + } + } + + fn finish_address_phase(&mut self) { + let Some(phase) = self.address_phase.take() else { + return; + }; + self.left_operand_checkpoint = phase.left_operand_prefix[0]; + self.right_operand_checkpoint = phase.right_operand_prefix[0]; + self.identity_checkpoint = phase.identity_prefix[0]; + self.read_prefix_checkpoints = phase + .read_prefix_polys + .iter() + .map(|poly| PrefixEval::from(poly[0])) + .collect(); + + let chunk_bits = Self::ADDRESS_CHUNK_BITS; + let start = phase.phase * chunk_bits; + let end = start + chunk_bits; + let point = &self.address_challenges[start..end]; + let shift = Self::LOG_K - end; + let mask = (1u128 << chunk_bits) - 1; + let eq_table = (0..(1usize << chunk_bits)) + .map(|bits| eq_eval_at_bits(point, bits as u128, chunk_bits)) + .collect::>(); + self.lookup_groups.par_iter_mut().for_each(|group| { + let chunk_value = (group.lookup_index >> shift) & mask; + group.phase_u_eval_sum *= eq_table[chunk_value as usize]; + }); + } + + fn materialize_cycle_state( + &self, + ) -> Result, Stage5KernelError> { + require_operand_count( + "stage5.instruction_read_raf.address_challenges", + Self::LOG_K, + self.address_challenges.len(), + )?; + let tables = LookupTableKind::<64>::all(); + let ra_chunks = Self::LOG_K / self.ra_virtual_log_k_chunk; + let mut factors = Vec::with_capacity(2 + ra_chunks); + factors.push(self.u_evals.clone()); + let table_values_at_address = tables + .par_iter() + .enumerate() + .map(|(table_index, table)| { + if self.lookup_groups_by_table[table_index].is_empty() { + F::zero() + } else { + table.evaluate_mle::(&self.address_challenges) + } + }) + .collect::>(); + let raf_interleaved = self.gamma * operand_polynomial_eval(&self.address_challenges, true) + + self.gamma2 * operand_polynomial_eval(&self.address_challenges, false); + let raf_identity = self.gamma2 * identity_polynomial_eval(&self.address_challenges); + factors.push( + (0..self.trace_len) + .into_par_iter() + .map(|cycle| { + let table_value = self.lookup_table_indices[cycle] + .map_or_else(F::zero, |table_index| table_values_at_address[table_index]); + let raf_value = if self.is_interleaved_operands[cycle] { + raf_interleaved + } else { + raf_identity + }; + table_value + raf_value + }) + .collect(), + ); + + let chunk_bits = self.ra_virtual_log_k_chunk; + let chunk_mask = if chunk_bits == 128 { + u128::MAX + } else { + (1u128 << chunk_bits) - 1 + }; + for chunk in 0..ra_chunks { + let chunk_point = + &self.address_challenges[chunk * chunk_bits..(chunk + 1) * chunk_bits]; + let eq_tables = eq_eval_bit_chunk_tables(chunk_point, 8); + let shift = Self::LOG_K - (chunk + 1) * chunk_bits; + factors.push( + self.lookup_indices + .par_iter() + .map(|&lookup_index| { + let chunk_value = (lookup_index >> shift) & chunk_mask; + eq_eval_at_bits_from_chunk_tables(&eq_tables, chunk_value, chunk_bits, 8) + }) + .collect(), + ); + } + InstructionReadRafCycleState::new(factors, Stage5Relation::InstructionReadRaf) + } + + fn instruction_read_raf_output_evals_from_groups( + &self, + address_point: &[F], + cycle_point: &[F], + ) -> Result, Stage5KernelError> { + require_operand_count( + "stage5.instruction_read_raf.address_point", + Self::LOG_K, + address_point.len(), + )?; + let trace_len_from_point = 1usize.checked_shl(cycle_point.len() as u32).ok_or( + Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.cycle_point", + expected: usize::BITS as usize, + actual: cycle_point.len(), + }, + )?; + require_operand_count( + "stage5.instruction_read_raf.trace_len", + trace_len_from_point, + self.trace_len, + )?; + + let tables = LookupTableKind::<64>::all(); + let table_count = tables.len(); + let cycle_eq = EqPolynomial::::evals(cycle_point, None); + require_operand_count( + "stage5.instruction_read_raf.eq_cycle", + self.trace_len, + cycle_eq.len(), + )?; + + let mut lookup_table_flags = vec![F::zero(); table_count]; + let mut instruction_raf_flag = F::zero(); + let mut group_weights = vec![F::zero(); self.lookup_groups.len()]; + for (&group_index, &weight) in self.lookup_group_indices_by_cycle.iter().zip(&cycle_eq) { + group_weights[group_index] += weight; + } + + for (group, &weight) in self.lookup_groups.iter().zip(&group_weights) { + if let Some(table_index) = group.lookup_table_index { + let Some(flag) = lookup_table_flags.get_mut(table_index) else { + return Err(Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.lookup_table_indices", + expected: table_count, + actual: table_index + 1, + }); + }; + *flag += weight; + } + if !group.is_interleaved_operands { + instruction_raf_flag += weight; + } + } + + let ra_chunks = Self::LOG_K / self.ra_virtual_log_k_chunk; + let cycle_state = self + .cycle_state + .as_ref() + .ok_or(Stage5KernelError::InvalidProof { + driver: Stage5Relation::InstructionReadRaf.symbol(), + reason: "instruction read raf cycle state is not materialized", + })?; + let instruction_ra = (0..ra_chunks) + .map(|chunk| { + cycle_state + .factor_eval(2 + chunk) + .ok_or(Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.instruction_ra", + expected: cycle_state.factors.len(), + actual: 2 + chunk + 1, + }) + }) + .collect::, _>>()?; + + Ok(Stage5InstructionReadRafEvaluations { + lookup_table_flags, + instruction_ra, + instruction_raf_flag, + }) + } +} + +impl InstructionReadRafAddressPhase { + fn read_table_round_evals(&self) -> [F; 2] { + let len = self.read_prefix_polys.first().map_or(0, |poly| poly.len()); + debug_assert!(len > 1); + debug_assert!(self.read_prefix_polys.iter().all(|poly| poly.len() == len)); + debug_assert!(self + .read_suffix_polys + .iter() + .flat_map(|read_table| read_table.suffix_polys.iter()) + .all(|poly| poly.len() == len)); + let half = len / 2; + let prefix_evals = (0..half) + .map(|row| { + ( + self.read_prefix_evals(row, false), + self.read_prefix_evals(row, true), + ) + }) + .collect::>(); + if half <= 16 { + self.read_suffix_polys + .iter() + .fold([F::zero(), F::zero()], |mut total, read_table| { + let eval = read_table_component_eval(read_table, half, &prefix_evals); + total[0] += eval[0]; + total[1] += eval[1]; + total + }) + } else { + self.read_suffix_polys + .par_iter() + .map(|read_table| read_table_component_eval(read_table, half, &prefix_evals)) + .reduce( + || [F::zero(), F::zero()], + |mut left, right| { + left[0] += right[0]; + left[1] += right[1]; + left + }, + ) + } + } + + fn read_prefix_evals(&self, row: usize, at_2: bool) -> [PrefixEval; NUM_PREFIXES] { + let half = self.read_prefix_polys[0].len() / 2; + let mut values = [PrefixEval::from(F::zero()); NUM_PREFIXES]; + for (value, poly) in values.iter_mut().zip(&self.read_prefix_polys) { + let low = poly[row]; + let eval = if at_2 { + let high = poly[row + half]; + high + high - low + } else { + low + }; + *value = PrefixEval::from(eval); + } + values + } + + fn raf_round_component_evals(&self) -> [[F; 2]; 3] { + let (left_0, left_2) = prefix_suffix_round_evals( + Some(&self.left_operand_prefix), + &self.raf_shift_half_q, + &self.raf_left_q, + ); + let (right_0, right_2) = prefix_suffix_round_evals( + Some(&self.right_operand_prefix), + &self.raf_shift_half_q, + &self.raf_right_q, + ); + let (identity_0, identity_2) = prefix_suffix_round_evals( + Some(&self.identity_prefix), + &self.raf_shift_full_q, + &self.raf_identity_q, + ); + [ + [left_0, left_2], + [right_0, right_2], + [identity_0, identity_2], + ] + } + + fn bind(&mut self, challenge: F) { + if self.left_operand_prefix.len() <= 32 { + bind_high_to_low(&mut self.left_operand_prefix, challenge); + bind_high_to_low(&mut self.right_operand_prefix, challenge); + bind_high_to_low(&mut self.identity_prefix, challenge); + bind_high_to_low(&mut self.raf_shift_half_q, challenge); + bind_high_to_low(&mut self.raf_left_q, challenge); + bind_high_to_low(&mut self.raf_right_q, challenge); + bind_high_to_low(&mut self.raf_shift_full_q, challenge); + bind_high_to_low(&mut self.raf_identity_q, challenge); + for poly in &mut self.read_prefix_polys { + bind_high_to_low(poly, challenge); + } + for read_table in &mut self.read_suffix_polys { + for poly in &mut read_table.suffix_polys { + bind_high_to_low(poly, challenge); + } + } + return; + } + + let left_operand_prefix = &mut self.left_operand_prefix; + let right_operand_prefix = &mut self.right_operand_prefix; + let identity_prefix = &mut self.identity_prefix; + let raf_shift_half_q = &mut self.raf_shift_half_q; + let raf_left_q = &mut self.raf_left_q; + let raf_right_q = &mut self.raf_right_q; + let raf_shift_full_q = &mut self.raf_shift_full_q; + let raf_identity_q = &mut self.raf_identity_q; + let read_prefix_polys = &mut self.read_prefix_polys; + let read_suffix_polys = &mut self.read_suffix_polys; + rayon::scope(|scope| { + scope.spawn(|_| { + bind_high_to_low(left_operand_prefix, challenge); + bind_high_to_low(right_operand_prefix, challenge); + bind_high_to_low(identity_prefix, challenge); + }); + scope.spawn(|_| { + bind_high_to_low(raf_shift_half_q, challenge); + bind_high_to_low(raf_left_q, challenge); + bind_high_to_low(raf_right_q, challenge); + bind_high_to_low(raf_shift_full_q, challenge); + bind_high_to_low(raf_identity_q, challenge); + }); + scope.spawn(|_| { + read_prefix_polys + .par_iter_mut() + .for_each(|poly| bind_high_to_low(poly, challenge)); + }); + scope.spawn(|_| { + read_suffix_polys.par_iter_mut().for_each(|read_table| { + for poly in &mut read_table.suffix_polys { + bind_high_to_low(poly, challenge); + } + }); + }); + }); + } +} + +fn read_table_component_eval( + read_table: &InstructionReadRafReadTablePhase, + half: usize, + prefix_evals: &[PrefixPairEvals], +) -> [F; 2] { + let mut eval_0 = F::zero(); + let mut eval_2_left = F::zero(); + let mut eval_2_right = F::zero(); + for row in 0..half { + let (prefixes_0, prefixes_2) = &prefix_evals[row]; + let mut suffixes_left = [F::zero(); 4]; + let mut suffixes_right = [F::zero(); 4]; + for (suffix_index, poly) in read_table.suffix_polys.iter().enumerate() { + suffixes_left[suffix_index] = poly[row]; + suffixes_right[suffix_index] = poly[row + half]; + } + let suffix_count = read_table.suffix_polys.len(); + eval_0 += read_table + .table + .combine(prefixes_0, &suffixes_left[..suffix_count]); + eval_2_left += read_table + .table + .combine(prefixes_2, &suffixes_left[..suffix_count]); + eval_2_right += read_table + .table + .combine(prefixes_2, &suffixes_right[..suffix_count]); + } + [eval_0, eval_2_right + eval_2_right - eval_2_left] +} + +#[inline] +fn prefix_suffix_round_evals(prefix: Option<&[F]>, q0: &[F], q1: &[F]) -> (F, F) { + let len = q0.len(); + debug_assert_eq!(q1.len(), len); + debug_assert!(len > 1); + let half = len / 2; + let mut eval_0 = F::zero(); + let mut eval_2_left = F::zero(); + let mut eval_2_right = F::zero(); + for row in 0..half { + let (prefix_0, prefix_2) = prefix.map_or((F::one(), F::one()), |poly| { + debug_assert_eq!(poly.len(), len); + let low = poly[row]; + let high = poly[row + half]; + (low, high + high - low) + }); + eval_0 += prefix_0 * q0[row] + q1[row]; + eval_2_left += prefix_2 * q0[row] + q1[row]; + eval_2_right += prefix_2 * q0[row + half] + q1[row + half]; + } + (eval_0, eval_2_right + eval_2_right - eval_2_left) +} + +fn operand_prefix_poly(checkpoint: F, chunk_bits: usize, left: bool) -> Vec { + debug_assert!(chunk_bits.is_multiple_of(2)); + let shift = 1u128 << (chunk_bits / 2); + (0..(1usize << chunk_bits)) + .map(|bits| { + let lookup_bits = LookupBits::new(bits as u128, chunk_bits); + let (left_bits, right_bits) = lookup_bits.uninterleave(); + let operand_bits: u64 = if left { + left_bits.into() + } else { + right_bits.into() + }; + checkpoint.mul_u128(shift) + F::from_u64(operand_bits) + }) + .collect() +} + +fn identity_prefix_poly(checkpoint: F, chunk_bits: usize) -> Vec { + let shift = 1u128 << chunk_bits; + (0..(1usize << chunk_bits)) + .map(|bits| checkpoint.mul_u128(shift) + F::from_u64(bits as u64)) + .collect() +} + +impl InstructionReadRafCycleState { + fn new(factors: Vec>, relation: Stage5Relation) -> Result { + let first_len = factors.first().map_or(0, Vec::len); + if first_len == 0 || !first_len.is_power_of_two() { + return Err(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "instruction read raf cycle factor has invalid length", + }); + } + if factors.iter().any(|factor| factor.len() != first_len) { + return Err(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "instruction read raf cycle factors have inconsistent lengths", + }); + } + let factor_scratch = (0..factors.len()).map(|_| Vec::new()).collect(); + Ok(Self { + factors, + factor_scratch, + }) + } + + fn round_poly( + &self, + previous_claim: F, + active_scale: F, + relation: Stage5Relation, + ) -> Result, Stage5KernelError> { + let coefficients = self.round_coefficients(active_scale); + let poly = UnivariatePoly::new(coefficients); + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != previous_claim { + return Err(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "instruction read raf cycle input claim mismatch", + }); + } + Ok(poly) + } + + fn factor_eval(&self, index: usize) -> Option { + self.factors + .get(index) + .and_then(|factor| factor.first()) + .copied() + } + + fn round_coefficients(&self, active_scale: F) -> Vec { + const MAX_DEGREE_PLUS_ONE: usize = 16; + let degree = self.factors.len(); + debug_assert!(degree < MAX_DEGREE_PLUS_ONE); + let half = self.factors[0].len() / 2; + let mut sums = if half >= DENSE_BIND_PAR_THRESHOLD { + (0..half) + .into_par_iter() + .fold( + || [F::zero(); MAX_DEGREE_PLUS_ONE], + |mut sums, row| { + accumulate_cycle_row_coefficients(&mut sums, &self.factors, degree, row); + sums + }, + ) + .reduce( + || [F::zero(); MAX_DEGREE_PLUS_ONE], + |mut left, right| { + for coefficient_index in 0..=degree { + left[coefficient_index] += right[coefficient_index]; + } + left + }, + ) + } else { + (0..half).fold([F::zero(); MAX_DEGREE_PLUS_ONE], |mut sums, row| { + accumulate_cycle_row_coefficients(&mut sums, &self.factors, degree, row); + sums + }) + }; + sums[..=degree] + .iter_mut() + .map(|coefficient| *coefficient * active_scale) + .collect() + } + + fn bind(&mut self, challenge: F) { + if self.factors.first().map_or(0, Vec::len) / 2 >= DENSE_BIND_PAR_THRESHOLD { + self.factors + .par_iter_mut() + .zip(self.factor_scratch.par_iter_mut()) + .for_each(|(factor, scratch)| { + bind_dense_evals_reuse(factor, scratch, challenge); + }); + } else { + for (factor, scratch) in self.factors.iter_mut().zip(&mut self.factor_scratch) { + bind_dense_evals_reuse(factor, scratch, challenge); + } + } + } +} + +fn accumulate_cycle_row_coefficients( + sums: &mut [F; N], + factors: &[Vec], + degree: usize, + row: usize, +) { + let mut coefficients = [F::zero(); N]; + coefficients[0] = F::one(); + for (current_degree, factor) in factors.iter().enumerate() { + let low = factor[2 * row]; + let diff = factor[2 * row + 1] - low; + coefficients[current_degree + 1] = F::zero(); + for coefficient_index in (0..=current_degree).rev() { + let coefficient = coefficients[coefficient_index]; + coefficients[coefficient_index + 1] += coefficient * diff; + coefficients[coefficient_index] = coefficient * low; + } + } + for coefficient_index in 0..=degree { + sums[coefficient_index] += coefficients[coefficient_index]; + } +} + +fn instruction_read_raf_state( + program: &'static Stage5CpuProgramPlan, + claim: &Stage5SumcheckClaimPlan, + inputs: &Stage5ProverInputs<'_, F>, + store: &Stage5ValueStore, + active_scale: F, +) -> Result, Stage5KernelError> { + const LOG_K: usize = 128; + const XLEN: usize = 64; + + let witness = inputs + .instruction_read_raf + .ok_or(Stage5KernelError::MissingKernelInput { + kernel: "jolt_stage5_instruction_read_raf", + input: "instruction_read_raf", + })?; + let trace_rounds = log2_exact(witness.trace_len, "stage5.instruction_read_raf.trace_len")?; + require_operand_count( + "stage5.instruction_read_raf.input", + LOG_K + trace_rounds, + claim.num_rounds, + )?; + require_operand_count( + "stage5.instruction_read_raf.lookup_indices", + witness.trace_len, + witness.lookup_indices.len(), + )?; + require_operand_count( + "stage5.instruction_read_raf.lookup_table_indices", + witness.trace_len, + witness.lookup_table_indices.len(), + )?; + require_operand_count( + "stage5.instruction_read_raf.is_interleaved_operands", + witness.trace_len, + witness.is_interleaved_operands.len(), + )?; + if witness.ra_virtual_log_k_chunk == 0 || !LOG_K.is_multiple_of(witness.ra_virtual_log_k_chunk) + { + return Err(Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.ra_virtual_log_k_chunk", + expected: LOG_K, + actual: witness.ra_virtual_log_k_chunk, + }); + } + + let table_count = LookupTableKind::::all().len(); + for table_index in witness.lookup_table_indices.iter().flatten() { + if *table_index >= table_count { + return Err(Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.lookup_table_indices", + expected: table_count, + actual: table_index + 1, + }); + } + } + + let r_reduction = store.point("stage5.input.stage2.instruction.LookupOutput")?; + require_operand_count( + "stage5.input.stage2.instruction.LookupOutput", + trace_rounds, + r_reduction.len(), + )?; + let u_evals = EqPolynomial::::evals(r_reduction, None); + require_operand_count( + "stage5.instruction_read_raf.u_evals", + witness.trace_len, + u_evals.len(), + )?; + + let gamma = store.scalar("stage5.instruction_read_raf.gamma")?; + let gamma2 = store + .try_scalar("stage5.instruction_read_raf.gamma2") + .unwrap_or_else(|| gamma * gamma); + let ra_chunks = LOG_K / witness.ra_virtual_log_k_chunk; + let outputs = instruction_read_raf_output_plans(program, claim, table_count, ra_chunks)?; + + let (lookup_groups, lookup_group_indices_by_cycle) = + instruction_read_raf_lookup_groups(witness, &u_evals)?; + let mut lookup_groups_by_table = vec![Vec::new(); table_count]; + for (group_index, group) in lookup_groups.iter().enumerate() { + if let Some(table_index) = group.lookup_table_index { + lookup_groups_by_table[table_index].push(group_index); + } + } + + Ok(InstructionReadRafStage5State { + trace_len: witness.trace_len, + lookup_indices: witness.lookup_indices.to_vec(), + lookup_table_indices: witness.lookup_table_indices.to_vec(), + is_interleaved_operands: witness.is_interleaved_operands.to_vec(), + lookup_groups, + lookup_group_indices_by_cycle, + lookup_groups_by_table, + ra_virtual_log_k_chunk: witness.ra_virtual_log_k_chunk, + u_evals, + gamma, + gamma2, + active_scale, + round: 0, + address_challenges: Vec::with_capacity(LOG_K), + cycle_challenges: Vec::with_capacity(trace_rounds), + address_phase: None, + left_operand_checkpoint: F::zero(), + right_operand_checkpoint: F::zero(), + identity_checkpoint: F::zero(), + read_prefix_checkpoints: ALL_PREFIXES + .iter() + .map(|prefix| prefix.default_checkpoint::()) + .collect(), + cycle_state: None, + outputs, + }) +} + +fn instruction_read_raf_lookup_groups( + witness: Stage5InstructionReadRafWitness<'_>, + u_evals: &[F], +) -> Result<(Vec>, Vec), Stage5KernelError> { + require_operand_count( + "stage5.instruction_read_raf.group_u_evals", + witness.trace_len, + u_evals.len(), + )?; + + let mut index_by_key: std::collections::HashMap<(u128, Option, bool), usize> = + std::collections::HashMap::with_capacity(witness.trace_len); + let mut groups = Vec::>::new(); + let mut group_indices_by_cycle = Vec::with_capacity(witness.trace_len); + for (cycle, u_eval) in u_evals.iter().copied().enumerate().take(witness.trace_len) { + let key = ( + witness.lookup_indices[cycle], + witness.lookup_table_indices[cycle], + witness.is_interleaved_operands[cycle], + ); + if let Some(&group_index) = index_by_key.get(&key) { + groups[group_index].u_eval_sum += u_eval; + groups[group_index].phase_u_eval_sum += u_eval; + group_indices_by_cycle.push(group_index); + } else { + let group_index = groups.len(); + let _ = index_by_key.insert(key, group_index); + groups.push(InstructionReadRafLookupGroup { + lookup_index: key.0, + lookup_table_index: key.1, + is_interleaved_operands: key.2, + u_eval_sum: u_eval, + phase_u_eval_sum: u_eval, + }); + group_indices_by_cycle.push(group_index); + } + } + Ok((groups, group_indices_by_cycle)) +} + +fn instruction_read_raf_output_plans( + program: &'static Stage5CpuProgramPlan, + claim: &Stage5SumcheckClaimPlan, + table_count: usize, + ra_chunks: usize, +) -> Result, Stage5KernelError> { + let instance = program + .instance_results + .iter() + .find(|instance| { + instance.claim == claim.symbol + && instance.relation == Stage5Relation::InstructionReadRaf.symbol() + }) + .ok_or(Stage5KernelError::MissingClaim { + batch: "stage5.instruction_read_raf.outputs", + claim: claim.symbol, + })?; + + let mut outputs = Vec::with_capacity(table_count + ra_chunks + 1); + let mut table_flags = vec![false; table_count]; + let mut instruction_ra = vec![false; ra_chunks]; + let mut has_raf_flag = false; + + for eval in program + .evals + .iter() + .filter(|eval| eval.source == instance.source) + { + if let Some(suffix) = eval + .name + .strip_prefix("stage5.instruction_read_raf.eval.LookupTableFlag_") + { + let index = parse_instruction_read_raf_eval_index( + suffix, + "stage5.instruction_read_raf.eval.LookupTableFlag_", + )?; + if index >= table_count || table_flags[index] { + return Err(Stage5KernelError::InvalidProof { + driver: eval.name, + reason: "invalid instruction read raf table flag eval", + }); + } + table_flags[index] = true; + outputs.push(InstructionReadRafOutputPlan { + index: eval.index, + name: eval.name, + oracle: eval.oracle, + kind: InstructionReadRafOutputKind::LookupTableFlag(index), + }); + continue; + } + + if let Some(suffix) = eval + .name + .strip_prefix("stage5.instruction_read_raf.eval.InstructionRa_") + { + let index = parse_instruction_read_raf_eval_index( + suffix, + "stage5.instruction_read_raf.eval.InstructionRa_", + )?; + if index >= ra_chunks || instruction_ra[index] { + return Err(Stage5KernelError::InvalidProof { + driver: eval.name, + reason: "invalid instruction read raf ra eval", + }); + } + instruction_ra[index] = true; + outputs.push(InstructionReadRafOutputPlan { + index: eval.index, + name: eval.name, + oracle: eval.oracle, + kind: InstructionReadRafOutputKind::InstructionRa(index), + }); + continue; + } + + if eval.name == "stage5.instruction_read_raf.eval.InstructionRafFlag" { + if has_raf_flag { + return Err(Stage5KernelError::InvalidProof { + driver: eval.name, + reason: "duplicate instruction read raf flag eval", + }); + } + has_raf_flag = true; + outputs.push(InstructionReadRafOutputPlan { + index: eval.index, + name: eval.name, + oracle: eval.oracle, + kind: InstructionReadRafOutputKind::InstructionRafFlag, + }); + } + } + + if table_flags.iter().any(|seen| !*seen) { + return Err(Stage5KernelError::MissingValue { + symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_", + }); + } + if instruction_ra.iter().any(|seen| !*seen) { + return Err(Stage5KernelError::MissingValue { + symbol: "stage5.instruction_read_raf.eval.InstructionRa_", + }); + } + if !has_raf_flag { + return Err(Stage5KernelError::MissingValue { + symbol: "stage5.instruction_read_raf.eval.InstructionRafFlag", + }); + } + outputs.sort_by_key(|output| output.index); + Ok(outputs) +} + +fn parse_instruction_read_raf_eval_index( + suffix: &str, + prefix: &'static str, +) -> Result { + suffix + .parse::() + .map_err(|_| Stage5KernelError::InvalidProof { + driver: prefix, + reason: "invalid instruction read raf eval suffix", + }) +} + +fn ram_ra_claim_reduction_state( + claim: &Stage5SumcheckClaimPlan, + inputs: &Stage5ProverInputs<'_, F>, + store: &Stage5ValueStore, + active_scale: F, +) -> Result, Stage5KernelError> { + let witness = inputs.ram_ra.ok_or(Stage5KernelError::MissingKernelInput { + kernel: "jolt_stage5_batched", + input: "ram_ra", + })?; + let trace_rounds = log2_exact(witness.trace_len, "stage5.ram.trace_len")?; + let ram_rounds = log2_exact(witness.ram_k, "stage5.ram_k")?; + require_operand_count( + "stage5.ram_ra_claim_reduction.input", + trace_rounds, + claim.num_rounds, + )?; + + let ram_raf_point = store.point("stage5.input.stage2.ram_raf.RamRa")?; + let ram_rw_point = store.point("stage5.input.stage2.ram_read_write.RamRa")?; + let ram_val_point = store.point("stage5.input.stage4.ram_val_check.RamRa")?; + for (input, point) in [ + ("stage5.input.stage2.ram_raf.RamRa", ram_raf_point), + ("stage5.input.stage2.ram_read_write.RamRa", ram_rw_point), + ("stage5.input.stage4.ram_val_check.RamRa", ram_val_point), + ] { + require_operand_count(input, ram_rounds + trace_rounds, point.len())?; + } + let (address_point, r_cycle_raf) = ram_raf_point.split_at(ram_rounds); + let (_, r_cycle_rw) = ram_rw_point.split_at(ram_rounds); + let (_, r_cycle_val) = ram_val_point.split_at(ram_rounds); + let address_eq = EqPolynomial::::evals(address_point, None); + let ram_ra = ram_ra_at_address(witness, &address_eq)?; + let gamma = store.scalar("stage5.ram_ra_claim_reduction.gamma")?; + let gamma2 = store + .try_scalar("stage5.ram_ra_claim_reduction.gamma2") + .unwrap_or_else(|| gamma * gamma); + let mut eq_combined = EqPolynomial::::evals(r_cycle_raf, None); + let eq_rw = EqPolynomial::::evals(r_cycle_rw, None); + let eq_val = EqPolynomial::::evals(r_cycle_val, None); + require_operand_count( + "stage5.ram_ra_claim_reduction.eq", + witness.trace_len, + eq_combined.len(), + )?; + for ((combined, rw), val) in eq_combined.iter_mut().zip(eq_rw).zip(eq_val) { + *combined += gamma * rw + gamma2 * val; + } + + Ok(DenseStage5State::new( + vec![eq_combined, ram_ra], + vec![DenseTerm { + coefficient: F::one(), + factors: vec![0, 1], + }], + vec![FactorOutput { + name: "stage5.ram_ra_claim_reduction.eval.RamRa", + oracle: "RamRa", + factor: 1, + }], + active_scale, + )) +} + +fn ram_ra_at_address( + witness: Stage5RamRaWitness<'_, F>, + address_eq: &[F], +) -> Result, Stage5KernelError> { + let expected_len = witness.ram_k.checked_mul(witness.trace_len).ok_or( + Stage5KernelError::InvalidInputLength { + input: "stage5.ram_ra_claim_reduction.RamRa", + expected: usize::MAX, + actual: witness.ram_k, + }, + )?; + if !witness.ram_ra.is_empty() { + require_operand_count( + "stage5.ram_ra_claim_reduction.RamRa", + expected_len, + witness.ram_ra.len(), + )?; + let mut output = vec![F::zero(); witness.trace_len]; + for (address, &weight) in address_eq.iter().enumerate() { + let base = address * witness.trace_len; + for (cycle, output) in output.iter_mut().enumerate() { + *output += weight * witness.ram_ra[base + cycle]; + } + } + return Ok(output); + } + + let Some(remapped_addresses) = witness.remapped_addresses else { + return Err(Stage5KernelError::MissingKernelInput { + kernel: "jolt_stage5_batched", + input: "ram_ra.remapped_addresses", + }); + }; + require_operand_count( + "stage5.ram_ra_claim_reduction.remapped_addresses", + witness.trace_len, + remapped_addresses.len(), + )?; + remapped_addresses + .iter() + .map(|address| match address { + Some(address) => { + address_eq + .get(*address) + .copied() + .ok_or(Stage5KernelError::InvalidInputLength { + input: "stage5.ram_ra_claim_reduction.remapped_addresses", + expected: address_eq.len(), + actual: address + 1, + }) + } + None => Ok(F::zero()), + }) + .collect() +} + +fn registers_val_evaluation_state( + claim: &Stage5SumcheckClaimPlan, + inputs: &Stage5ProverInputs<'_, F>, + store: &Stage5ValueStore, + active_scale: F, +) -> Result, Stage5KernelError> { + let witness = inputs + .registers_val + .ok_or(Stage5KernelError::MissingKernelInput { + kernel: "jolt_stage5_batched", + input: "registers_val", + })?; + require_operand_count( + "stage5.registers_val_evaluation.RdInc", + witness.trace_len, + witness.rd_inc.len(), + )?; + require_operand_count( + "stage5.registers_val_evaluation.input", + log2_exact(witness.trace_len, "stage5.trace_len")?, + claim.num_rounds, + )?; + + let registers_val_point = store.point("stage5.input.stage4.registers.RegistersVal")?; + let register_rounds = log2_exact(witness.register_count, "stage5.register_count")?; + let trace_rounds = log2_exact(witness.trace_len, "stage5.trace_len")?; + require_operand_count( + "stage5.input.stage4.registers.RegistersVal", + register_rounds + trace_rounds, + registers_val_point.len(), + )?; + let (address_point, cycle_point) = registers_val_point.split_at(register_rounds); + let address_eq = EqPolynomial::::evals(address_point, None); + let rd_wa_at_address = rd_wa_at_register_address(witness, &address_eq)?; + let lt = lt_evals_big_endian(cycle_point); + require_operand_count( + "stage5.registers_val_evaluation.lt", + witness.trace_len, + lt.len(), + )?; + + Ok(DenseStage5State::new( + vec![witness.rd_inc.to_vec(), rd_wa_at_address, lt], + vec![DenseTerm { + coefficient: F::one(), + factors: vec![0, 1, 2], + }], + vec![ + FactorOutput { + name: "stage5.registers_val_evaluation.eval.RdInc", + oracle: "RdInc", + factor: 0, + }, + FactorOutput { + name: "stage5.registers_val_evaluation.eval.RdWa", + oracle: "RdWa", + factor: 1, + }, + ], + active_scale, + )) +} + +fn rd_wa_at_register_address( + witness: Stage5RegistersValWitness<'_, F>, + address_eq: &[F], +) -> Result, Stage5KernelError> { + let expected_len = witness + .register_count + .checked_mul(witness.trace_len) + .ok_or(Stage5KernelError::InvalidInputLength { + input: "stage5.registers_val_evaluation.RdWa", + expected: usize::MAX, + actual: witness.register_count, + })?; + if !witness.rd_wa.is_empty() { + require_operand_count( + "stage5.registers_val_evaluation.RdWa", + expected_len, + witness.rd_wa.len(), + )?; + let mut output = vec![F::zero(); witness.trace_len]; + for (address, &weight) in address_eq.iter().enumerate() { + let base = address * witness.trace_len; + for (cycle, output) in output.iter_mut().enumerate() { + *output += weight * witness.rd_wa[base + cycle]; + } + } + return Ok(output); + } + + let Some(rd_write_addresses) = witness.rd_write_addresses else { + return Err(Stage5KernelError::MissingKernelInput { + kernel: "jolt_stage5_batched", + input: "registers_val.rd_wa", + }); + }; + require_operand_count( + "stage5.registers_val_evaluation.rd_write_addresses", + witness.trace_len, + rd_write_addresses.len(), + )?; + rd_write_addresses + .iter() + .map(|address| match address { + Some(address) => { + address_eq + .get(*address) + .copied() + .ok_or(Stage5KernelError::InvalidInputLength { + input: "stage5.registers_val_evaluation.rd_write_addresses", + expected: address_eq.len(), + actual: address + 1, + }) + } + None => Ok(F::zero()), + }) + .collect() +} + +fn expected_batched_output_claim( + context: Stage5KernelContext<'_>, + store: &Stage5ValueStore, + evals: &[Stage5NamedEval], + point: &[F], + batching_coeffs: &[F], +) -> Result { + let mut expected = F::zero(); + for (claim, &coefficient) in context.batch_claims()?.iter().zip(batching_coeffs) { + let instance = context + .program + .instance_results + .iter() + .find(|instance| { + instance.claim == claim.symbol && instance.source == context.driver.symbol + }) + .ok_or(Stage5KernelError::MissingClaim { + batch: context.batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(Stage5KernelError::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(instance.relation); + let value = match Stage5Relation::from_symbol(relation) + .ok_or(Stage5KernelError::UnknownRelation { relation })? + { + Stage5Relation::InstructionReadRaf => { + expected_instruction_read_raf(store, evals, local_point)? + } + Stage5Relation::RamRaClaimReduction => { + expected_ram_ra_claim_reduction(store, evals, local_point)? + } + Stage5Relation::RegistersValEvaluation => { + expected_registers_val_evaluation(store, evals, local_point)? + } + relation @ Stage5Relation::Batched => { + return Err(Stage5KernelError::KernelNotImplemented { + abi: relation.symbol(), + }) + } + }; + expected += coefficient * value; + } + Ok(expected) +} + +fn expected_instruction_read_raf( + store: &Stage5ValueStore, + evals: &[Stage5NamedEval], + local_point: &[F], +) -> Result { + const LOG_K: usize = 128; + const XLEN: usize = 64; + + if local_point.len() < LOG_K { + return Err(Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.point", + expected: LOG_K, + actual: local_point.len(), + }); + } + + let (r_address_prime, r_cycle) = local_point.split_at(LOG_K); + let r_cycle_prime = reverse_slice(r_cycle); + let r_reduction = store.point("stage5.input.stage2.instruction.LookupOutput")?; + let eq_eval_r_reduction = EqPolynomial::::mle(r_reduction, &r_cycle_prime); + + let left_operand_eval = operand_polynomial_eval(r_address_prime, true); + let right_operand_eval = operand_polynomial_eval(r_address_prime, false); + let identity_poly_eval = identity_polynomial_eval(r_address_prime); + + let table_values = LookupTableKind::::all() + .iter() + .map(|table| table.evaluate_mle::(r_address_prime)) + .collect::>(); + let table_flag_claims = indexed_evals_by_prefix( + evals, + "stage5.instruction_read_raf.eval.LookupTableFlag_", + table_values.len(), + )?; + let val_claim = table_values + .into_iter() + .zip(table_flag_claims) + .map(|(table_value, flag_claim)| table_value * flag_claim) + .sum::(); + + let ra_claim = + indexed_evals_by_prefix_any(evals, "stage5.instruction_read_raf.eval.InstructionRa_")? + .into_iter() + .product::(); + let raf_flag_claim = + eval_by_name(evals, "stage5.instruction_read_raf.eval.InstructionRafFlag")?; + let gamma = store.scalar("stage5.instruction_read_raf.gamma")?; + + let raf_claim = (F::one() - raf_flag_claim) * (left_operand_eval + gamma * right_operand_eval) + + raf_flag_claim * gamma * identity_poly_eval; + Ok(eq_eval_r_reduction * ra_claim * (val_claim + gamma * raf_claim)) +} + +pub fn instruction_read_raf_output_evals( + witness: Stage5InstructionReadRafWitness<'_>, + address_point: &[F], + cycle_point: &[F], +) -> Result, Stage5KernelError> { + const LOG_K: usize = 128; + const XLEN: usize = 64; + + require_operand_count( + "stage5.instruction_read_raf.address_point", + LOG_K, + address_point.len(), + )?; + let trace_len_from_point = 1usize.checked_shl(cycle_point.len() as u32).ok_or( + Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.cycle_point", + expected: usize::BITS as usize, + actual: cycle_point.len(), + }, + )?; + require_operand_count( + "stage5.instruction_read_raf.trace_len", + trace_len_from_point, + witness.trace_len, + )?; + require_operand_count( + "stage5.instruction_read_raf.lookup_indices", + witness.trace_len, + witness.lookup_indices.len(), + )?; + require_operand_count( + "stage5.instruction_read_raf.lookup_table_indices", + witness.trace_len, + witness.lookup_table_indices.len(), + )?; + require_operand_count( + "stage5.instruction_read_raf.is_interleaved_operands", + witness.trace_len, + witness.is_interleaved_operands.len(), + )?; + if witness.ra_virtual_log_k_chunk == 0 || !LOG_K.is_multiple_of(witness.ra_virtual_log_k_chunk) + { + return Err(Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.ra_virtual_log_k_chunk", + expected: LOG_K, + actual: witness.ra_virtual_log_k_chunk, + }); + } + + let table_count = LookupTableKind::::all().len(); + let ra_chunks = LOG_K / witness.ra_virtual_log_k_chunk; + let cycle_eq = EqPolynomial::::evals(cycle_point, None); + require_operand_count( + "stage5.instruction_read_raf.eq_cycle", + witness.trace_len, + cycle_eq.len(), + )?; + + let mut grouped_weights = + HashMap::<(u128, Option, bool), F>::with_capacity(witness.trace_len.min(1 << 14)); + for (((&lookup_index, table_index), is_interleaved), &weight) in witness + .lookup_indices + .iter() + .zip(witness.lookup_table_indices.iter()) + .zip(witness.is_interleaved_operands.iter()) + .zip(&cycle_eq) + { + *grouped_weights + .entry((lookup_index, *table_index, *is_interleaved)) + .or_insert_with(F::zero) += weight; + } + + let mut lookup_table_flags = vec![F::zero(); table_count]; + let mut instruction_raf_flag = F::zero(); + for ((_, table_index, is_interleaved), &weight) in &grouped_weights { + if let Some(table_index) = table_index { + let Some(flag) = lookup_table_flags.get_mut(*table_index) else { + return Err(Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.lookup_table_indices", + expected: table_count, + actual: *table_index + 1, + }); + }; + *flag += weight; + } + if !*is_interleaved { + instruction_raf_flag += weight; + } + } + + let chunk_bits = witness.ra_virtual_log_k_chunk; + let chunk_mask = if chunk_bits == 128 { + u128::MAX + } else { + (1u128 << chunk_bits) - 1 + }; + let instruction_ra = (0..ra_chunks) + .map(|chunk| { + let chunk_point = &address_point[chunk * chunk_bits..(chunk + 1) * chunk_bits]; + let eq_tables = eq_eval_bit_chunk_tables(chunk_point, 8); + let shift = LOG_K - (chunk + 1) * chunk_bits; + grouped_weights + .iter() + .map(|((lookup_index, _, _), &cycle_weight)| { + let chunk_value = (*lookup_index >> shift) & chunk_mask; + cycle_weight + * eq_eval_at_bits_from_chunk_tables(&eq_tables, chunk_value, chunk_bits, 8) + }) + .sum() + }) + .collect(); + + Ok(Stage5InstructionReadRafEvaluations { + lookup_table_flags, + instruction_ra, + instruction_raf_flag, + }) +} + +fn expected_ram_ra_claim_reduction( + store: &Stage5ValueStore, + evals: &[Stage5NamedEval], + local_point: &[F], +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle_raf = suffix_point( + store.point("stage5.input.stage2.ram_raf.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage2.ram_raf.RamRa", + )?; + let r_cycle_rw = suffix_point( + store.point("stage5.input.stage2.ram_read_write.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage2.ram_read_write.RamRa", + )?; + let r_cycle_val = suffix_point( + store.point("stage5.input.stage4.ram_val_check.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage4.ram_val_check.RamRa", + )?; + let gamma = store.scalar("stage5.ram_ra_claim_reduction.gamma")?; + let eq_combined = EqPolynomial::::mle(r_cycle_raf, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(r_cycle_rw, &r_cycle_reduced) + + gamma.square() * EqPolynomial::::mle(r_cycle_val, &r_cycle_reduced); + let ram_ra = eval_by_name(evals, "stage5.ram_ra_claim_reduction.eval.RamRa")?; + Ok(eq_combined * ram_ra) +} + +fn expected_registers_val_evaluation( + store: &Stage5ValueStore, + evals: &[Stage5NamedEval], + local_point: &[F], +) -> Result { + let registers_val_point = store.point("stage5.input.stage4.registers.RegistersVal")?; + let r_cycle = suffix_point( + registers_val_point, + local_point.len(), + "stage5.input.stage4.registers.RegistersVal", + )?; + let r_reduced = reverse_slice(local_point); + let lt_eval = lt_polynomial_eval(&r_reduced, r_cycle); + let rd_inc = eval_by_name(evals, "stage5.registers_val_evaluation.eval.RdInc")?; + let rd_wa = eval_by_name(evals, "stage5.registers_val_evaluation.eval.RdWa")?; + Ok(rd_inc * rd_wa * lt_eval) +} + +fn eval_by_name( + evals: &[Stage5NamedEval], + name: &'static str, +) -> Result { + evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| eval.value) + .ok_or(Stage5KernelError::MissingValue { symbol: name }) +} + +fn named_eval(name: &'static str, oracle: &'static str, value: F) -> Stage5NamedEval { + Stage5NamedEval { + name, + oracle, + value, + } +} + +fn claim_relation( + program: &'static Stage5CpuProgramPlan, + claim: &Stage5SumcheckClaimPlan, +) -> Result { + if let Some(relation) = claim.relation { + return Stage5Relation::from_symbol(relation) + .ok_or(Stage5KernelError::UnknownRelation { relation }); + } + let kernel_symbol = claim.kernel.ok_or(Stage5KernelError::MissingKernel { + driver: claim.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or(Stage5KernelError::MissingKernel { + driver: claim.symbol, + kernel: kernel_symbol, + })?; + Stage5Relation::from_symbol(kernel.relation).ok_or(Stage5KernelError::UnknownRelation { + relation: kernel.relation, + }) +} + +fn instance_round_offset( + program: &'static Stage5CpuProgramPlan, + driver: &'static str, + claim: &'static str, +) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.source == driver && instance.claim == claim) + .map(|instance| instance.round_offset) + .ok_or(Stage5KernelError::MissingClaim { + batch: driver, + claim, + }) +} + +fn combine_univariate_polys( + polynomials: &[UnivariatePoly], + coefficients: &[F], +) -> UnivariatePoly { + let max_len = polynomials + .iter() + .map(|poly| poly.coefficients().len()) + .max() + .unwrap_or(0); + let mut combined = vec![F::zero(); max_len]; + for (poly, &coefficient) in polynomials.iter().zip(coefficients) { + for (combined, &term) in combined.iter_mut().zip(poly.coefficients()) { + *combined += term * coefficient; + } + } + UnivariatePoly::new(combined) +} + +fn round_poly_from_dense_terms( + factors: &[Vec], + terms: &[DenseTerm], + active_scale: F, + relation: Stage5Relation, +) -> Result, Stage5KernelError> { + let half = factors.first().map_or(0, |factor| factor.len() / 2); + for term in terms { + if term.factors.len() > 3 { + return Err(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage5 dense term exceeds degree bound", + }); + } + if term.factors.iter().any(|factor| *factor >= factors.len()) { + return Err(Stage5KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage5 dense term references missing factor", + }); + } + } + + let accumulators = if half >= DENSE_BIND_PAR_THRESHOLD { + (0..half) + .into_par_iter() + .map(|row| dense_row_coefficients(factors, terms, row)) + .reduce( + || [F::Accumulator::default(); 4], + |mut left, right| { + for index in 0..left.len() { + left[index].merge(right[index]); + } + left + }, + ) + } else { + (0..half).fold([F::Accumulator::default(); 4], |mut total, row| { + let row_coefficients = dense_row_coefficients(factors, terms, row); + for index in 0..total.len() { + total[index].merge(row_coefficients[index]); + } + total + }) + }; + + Ok(UnivariatePoly::new( + accumulators + .into_iter() + .map(FieldAccumulator::reduce) + .map(|coefficient| coefficient * active_scale) + .collect(), + )) +} + +fn dense_row_coefficients( + factors: &[Vec], + terms: &[DenseTerm], + row: usize, +) -> [F::Accumulator; 4] { + let mut coefficients = [F::Accumulator::default(); 4]; + for term in terms { + match term.factors.as_slice() { + [] => coefficients[0].acc_add(term.coefficient), + [first] => { + let (first0, first_delta) = linear_factor_pair(&factors[*first], row); + coefficients[0].fmadd(term.coefficient, first0); + coefficients[1].fmadd(term.coefficient, first_delta); + } + [first, second] => { + let (first0, first_delta) = linear_factor_pair(&factors[*first], row); + let (second0, second_delta) = linear_factor_pair(&factors[*second], row); + accumulate_quadratic_coefficients( + &mut coefficients, + term.coefficient, + first0, + first_delta, + second0, + second_delta, + ); + } + [first, second, third] => { + let (first0, first_delta) = linear_factor_pair(&factors[*first], row); + let (second0, second_delta) = linear_factor_pair(&factors[*second], row); + let (third0, third_delta) = linear_factor_pair(&factors[*third], row); + accumulate_cubic_coefficients( + &mut coefficients, + term.coefficient, + first0, + first_delta, + second0, + second_delta, + third0, + third_delta, + ); + } + _ => unreachable!("dense terms are validated before evaluation"), + } + } + coefficients +} + +#[inline] +fn linear_factor_pair(factor: &[F], row: usize) -> (F, F) { + let low = factor[2 * row]; + (low, factor[2 * row + 1] - low) +} + +#[inline] +fn accumulate_quadratic_coefficients( + coefficients: &mut [F::Accumulator; 4], + scale: F, + first0: F, + first_delta: F, + second0: F, + second_delta: F, +) { + coefficients[0].fmadd(scale * first0, second0); + coefficients[1].fmadd(scale * first_delta, second0); + coefficients[1].fmadd(scale * first0, second_delta); + coefficients[2].fmadd(scale * first_delta, second_delta); +} + +#[inline] +fn accumulate_cubic_coefficients( + coefficients: &mut [F::Accumulator; 4], + scale: F, + first0: F, + first_delta: F, + second0: F, + second_delta: F, + third0: F, + third_delta: F, +) { + let second0_third0 = second0 * third0; + let second_delta_third0 = second_delta * third0; + let second0_third_delta = second0 * third_delta; + let second_delta_third_delta = second_delta * third_delta; + let scaled_first0 = scale * first0; + let scaled_first_delta = scale * first_delta; + + coefficients[0].fmadd(scaled_first0, second0_third0); + coefficients[1].fmadd(scaled_first_delta, second0_third0); + coefficients[1].fmadd(scaled_first0, second_delta_third0); + coefficients[1].fmadd(scaled_first0, second0_third_delta); + coefficients[2].fmadd(scaled_first_delta, second_delta_third0); + coefficients[2].fmadd(scaled_first_delta, second0_third_delta); + coefficients[2].fmadd(scaled_first0, second_delta_third_delta); + coefficients[3].fmadd(scaled_first_delta, second_delta_third_delta); +} + +fn indexed_evals_by_prefix( + evals: &[Stage5NamedEval], + prefix: &'static str, + count: usize, +) -> Result, Stage5KernelError> { + let mut values = vec![None; count]; + for eval in evals { + let Some(suffix) = eval.name.strip_prefix(prefix) else { + continue; + }; + let index = suffix + .parse::() + .map_err(|_| Stage5KernelError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval suffix", + })?; + if index >= count || values[index].is_some() { + return Err(Stage5KernelError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval", + }); + } + values[index] = Some(eval.value); + } + values + .into_iter() + .map(|value| value.ok_or(Stage5KernelError::MissingValue { symbol: prefix })) + .collect() +} + +fn indexed_evals_by_prefix_any( + evals: &[Stage5NamedEval], + prefix: &'static str, +) -> Result, Stage5KernelError> { + let mut indexed_values = Vec::new(); + for eval in evals { + let Some(suffix) = eval.name.strip_prefix(prefix) else { + continue; + }; + let index = suffix + .parse::() + .map_err(|_| Stage5KernelError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval suffix", + })?; + if indexed_values + .iter() + .any(|(existing_index, _)| *existing_index == index) + { + return Err(Stage5KernelError::InvalidProof { + driver: prefix, + reason: "duplicate indexed eval", + }); + } + indexed_values.push((index, eval.value)); + } + if indexed_values.is_empty() { + return Err(Stage5KernelError::MissingValue { symbol: prefix }); + } + indexed_values.sort_by_key(|(index, _)| *index); + for (expected, (actual, _)) in indexed_values.iter().enumerate() { + if *actual != expected { + return Err(Stage5KernelError::InvalidProof { + driver: prefix, + reason: "non-contiguous indexed eval", + }); + } + } + Ok(indexed_values.into_iter().map(|(_, value)| value).collect()) +} + +fn append_compressed_univariate_poly( + transcript: &mut T, + label: &'static str, + poly: &UnivariatePoly, +) where + F: Field, + T: Transcript, +{ + let compressed = poly.compress(); + transcript.append(&LabelWithCount( + label.as_bytes(), + compressed.coeffs_except_linear_term().len() as u64, + )); + for coefficient in compressed.coeffs_except_linear_term() { + transcript.append(coefficient); + } +} + +fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &F) +where + F: Field, + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +fn append_opening_claims( + program: &'static Stage5CpuProgramPlan, + store: &mut Stage5ValueStore, + transcript: &mut T, + evals: &[Stage5NamedEval], +) -> Result>, Stage5KernelError> +where + F: Field, + T: Transcript, +{ + if program.opening_batches.is_empty() { + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } + return Ok(Vec::new()); + } + let _ = store.evaluate_available_points(program)?; + let mut opening_claims = Vec::new(); + let mut seen = program + .opening_inputs + .iter() + .filter_map(|input| { + store + .try_point(input.symbol) + .map(|point| (input.claim_kind, input.oracle, point.to_vec())) + }) + .collect::>(); + for batch in program.opening_batches { + for symbol in batch.claim_operands { + let claim = + find_opening_claim(program, symbol).ok_or(Stage5KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + let point = store.point(claim.point_source)?.to_vec(); + let duplicate = seen.iter().any(|(kind, oracle, seen_point)| { + *kind == claim.claim_kind && *oracle == claim.oracle && seen_point == &point + }); + let value = store.scalar(claim.eval_source)?; + if !duplicate { + append_labeled_scalar(transcript, "opening_claim", &value); + seen.push((claim.claim_kind, claim.oracle, point.clone())); + } + opening_claims.push(Stage5OpeningClaimValue { + symbol: claim.symbol, + oracle: claim.oracle, + domain: claim.domain, + claim_kind: claim.claim_kind, + point: point.clone(), + eval: value, + }); + } + } + Ok(opening_claims) +} + +fn find_opening_claim<'a>( + program: &'a Stage5CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage5OpeningClaimPlan> { + program + .opening_claims + .iter() + .find(|claim| claim.symbol == symbol) +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients() + .iter() + .rposition(|coefficient| *coefficient != F::zero()) + .unwrap_or(0) +} + +fn pow_field(base: F, mut exponent: usize) -> F { + let mut result = F::one(); + let mut power = base; + while exponent != 0 { + if exponent & 1 == 1 { + result *= power; + } + power = power.square(); + exponent >>= 1; + } + result +} + +fn single_operand(symbol: &'static str, operands: &[F]) -> Result { + require_operand_count(symbol, 1, operands.len())?; + Ok(operands[0]) +} + +fn require_operand_count( + input: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage5KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage5KernelError::InvalidInputLength { + input, + expected, + actual, + }) + } +} + +fn suffix_point<'a, F: Field>( + point: &'a [F], + length: usize, + input: &'static str, +) -> Result<&'a [F], Stage5KernelError> { + point + .get(point.len().saturating_sub(length)..) + .filter(|suffix| suffix.len() == length) + .ok_or(Stage5KernelError::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +fn reverse_slice(values: &[F]) -> Vec { + values.iter().rev().copied().collect() +} + +fn normalize_instruction_read_raf_point( + point: &[F], +) -> Result, Stage5KernelError> { + const LOG_K: usize = 128; + if point.len() < LOG_K { + return Err(Stage5KernelError::InvalidInputLength { + input: "stage5.instruction_read_raf.point", + expected: LOG_K, + actual: point.len(), + }); + } + let mut normalized = point.to_vec(); + normalized[LOG_K..].reverse(); + Ok(normalized) +} + +fn lt_polynomial_eval(x: &[F], y: &[F]) -> F { + debug_assert_eq!(x.len(), y.len()); + let mut lt_eval = F::zero(); + let mut eq_term = F::one(); + for (x_i, y_i) in x.iter().zip(y.iter()) { + lt_eval += (F::one() - *x_i) * *y_i * eq_term; + eq_term *= F::one() - *x_i - *y_i + *x_i * *y_i + *x_i * *y_i; + } + lt_eval +} + +fn lt_evals_big_endian(point: &[F]) -> Vec { + let mut evals = vec![F::zero(); 1usize << point.len()]; + for (index, r) in point.iter().rev().enumerate() { + let (left, right) = evals.split_at_mut(1usize << index); + left.iter_mut().zip(right).for_each(|(left, right)| { + *right = *left * *r; + *left += *r - *right; + }); + } + evals +} + +fn operand_polynomial_eval(point: &[F], left: bool) -> F { + let stride_offset = usize::from(!left); + let operand_bits = point.len() / 2; + (0..operand_bits) + .map(|index| point[2 * index + stride_offset].mul_pow_2(operand_bits - 1 - index)) + .sum() +} + +fn eq_eval_at_bits(point: &[F], bits: u128, num_bits: usize) -> F { + debug_assert_eq!(point.len(), num_bits); + point + .iter() + .enumerate() + .map(|(index, &challenge)| { + if ((bits >> (num_bits - 1 - index)) & 1) == 1 { + challenge + } else { + F::one() - challenge + } + }) + .product() +} + +fn eq_eval_bit_chunk_tables(point: &[F], chunk_bits: usize) -> Vec> { + point + .chunks(chunk_bits) + .map(|chunk| { + let len = chunk.len(); + (0..(1usize << len)) + .map(|bits| eq_eval_at_bits(chunk, bits as u128, len)) + .collect() + }) + .collect() +} + +fn eq_eval_at_bits_from_chunk_tables( + tables: &[Vec], + bits: u128, + num_bits: usize, + chunk_bits: usize, +) -> F { + tables + .iter() + .enumerate() + .map(|(chunk_index, table)| { + let start = chunk_index * chunk_bits; + let len = table.len().ilog2() as usize; + let shift = num_bits - start - len; + let mask = (1u128 << len) - 1; + table[((bits >> shift) & mask) as usize] + }) + .product() +} + +#[cfg(test)] +#[inline] +fn lookup_bit(lookup_index: u128, index: usize, total_bits: usize) -> bool { + ((lookup_index >> (total_bits - 1 - index)) & 1) == 1 +} + +fn identity_polynomial_eval(point: &[F]) -> F { + point + .iter() + .enumerate() + .map(|(index, value)| value.mul_pow_2(point.len() - 1 - index)) + .sum() +} + +fn log2_exact(value: usize, input: &'static str) -> Result { + if value.is_power_of_two() { + Ok(value.ilog2() as usize) + } else { + Err(Stage5KernelError::InvalidInputLength { + input, + expected: value.next_power_of_two(), + actual: value, + }) + } +} + +pub fn execute_stage5_program( + program: &'static Stage5CpuProgramPlan, + mode: Stage5ExecutionMode, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage5KernelError> +where + F: Field, + T: Transcript, + E: Stage5KernelExecutor, +{ + let mut artifacts = Stage5ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_squeeze(program, step.symbol).ok_or(Stage5KernelError::MissingValue { + symbol: step.symbol, + })?; + let values = transcript.challenge_vector(squeeze.count); + executor.observe_challenge_vector(squeeze, &values)?; + artifacts.challenge_vectors.push(Stage5ChallengeVector { + symbol: squeeze.symbol, + values, + }); + } + "transcript_absorb_bytes" => { + let absorb = find_absorb_bytes(program, step.symbol).ok_or( + Stage5KernelError::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage5_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_driver(program, step.symbol).ok_or(Stage5KernelError::MissingDriver { + driver: step.symbol, + })?; + let kernel_symbol = driver.kernel.ok_or(Stage5KernelError::MissingKernel { + driver: driver.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or( + Stage5KernelError::MissingKernel { + driver: driver.symbol, + kernel: kernel_symbol, + }, + )?; + let batch = + find_batch(program, driver.batch).ok_or(Stage5KernelError::MissingBatch { + driver: driver.symbol, + batch: driver.batch, + })?; + let context = Stage5KernelContext { + mode, + program, + kernel, + batch, + driver, + }; + let output = match mode { + Stage5ExecutionMode::Prover => executor.prove_sumcheck(context, transcript)?, + Stage5ExecutionMode::Verifier => { + executor.verify_sumcheck(context, transcript)? + } + }; + executor.observe_sumcheck_output(&output)?; + artifacts + .opening_claims + .extend(output.opening_claims.clone()); + artifacts.sumchecks.push(output); + } + _ => { + return Err(Stage5KernelError::InvalidProgramStep { + symbol: step.symbol, + kind: step.kind, + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +fn absorb_stage5_bytes(absorb: &'static Stage5TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn find_squeeze<'a>( + program: &'a Stage5CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage5TranscriptSqueezePlan> { + program + .transcript_squeezes + .iter() + .find(|plan| plan.symbol == symbol) +} + +fn find_absorb_bytes<'a>( + program: &'a Stage5CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage5TranscriptAbsorbBytesPlan> { + program + .transcript_absorb_bytes + .iter() + .find(|plan| plan.symbol == symbol) +} + +fn find_driver<'a>( + program: &'a Stage5CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage5SumcheckDriverPlan> { + program + .drivers + .iter() + .find(|driver| driver.symbol == symbol) +} + +fn find_kernel<'a>( + program: &'a Stage5CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage5KernelPlan> { + program + .kernels + .iter() + .find(|kernel| kernel.symbol == symbol) +} + +fn find_batch<'a>( + program: &'a Stage5CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage5SumcheckBatchPlan> { + program.batches.iter().find(|batch| batch.symbol == symbol) +} + +#[cfg(test)] +#[expect( + clippy::expect_used, + reason = "tests use expect to keep failure context concise" +)] +mod tests { + use super::*; + use jolt_field::{Field, Fr}; + use jolt_sumcheck::SumcheckProof; + use jolt_transcript::Blake2bTranscript; + + const PARAMS: Stage5Params = Stage5Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }; + const STEPS: &[Stage5ProgramStepPlan] = &[ + Stage5ProgramStepPlan { + kind: "transcript_squeeze", + symbol: "stage5.gamma", + }, + Stage5ProgramStepPlan { + kind: "sumcheck_driver", + symbol: "stage5.sumcheck", + }, + ]; + const SQUEEZES: &[Stage5TranscriptSqueezePlan] = &[Stage5TranscriptSqueezePlan { + symbol: "stage5.gamma", + label: "stage5_gamma", + kind: "challenge_scalar", + count: 1, + }]; + const KERNELS: &[Stage5KernelPlan] = &[Stage5KernelPlan { + symbol: "jolt.cpu.stage5.batched", + relation: "jolt.stage5.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage5_batched", + }]; + const CLAIM_INPUTS: &[&str] = &[]; + const CLAIMS: &[Stage5SumcheckClaimPlan] = &[Stage5SumcheckClaimPlan { + symbol: "stage5.claim", + stage: "stage5", + domain: "jolt.trace_domain", + num_rounds: 1, + degree: 1, + claim: "stage5.claim", + kernel: Some("jolt.cpu.stage5.batched"), + relation: Some("jolt.stage5.batched"), + claim_value: "stage5.gamma", + input_openings: CLAIM_INPUTS, + }]; + const ORDERED_CLAIMS: &[&str] = &["stage5.claim"]; + const ROUND_SCHEDULE: &[usize] = &[1]; + const BATCHES: &[Stage5SumcheckBatchPlan] = &[Stage5SumcheckBatchPlan { + symbol: "stage5.batch", + stage: "stage5", + proof_slot: "stage5.sumcheck", + policy: "test", + count: 1, + ordered_claims: ORDERED_CLAIMS, + claim_operands: ORDERED_CLAIMS, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: ROUND_SCHEDULE, + }]; + const DRIVERS: &[Stage5SumcheckDriverPlan] = &[Stage5SumcheckDriverPlan { + symbol: "stage5.sumcheck", + stage: "stage5", + proof_slot: "stage5.sumcheck", + kernel: Some("jolt.cpu.stage5.batched"), + relation: Some("jolt.stage5.batched"), + batch: "stage5.batch", + policy: "test", + round_schedule: ROUND_SCHEDULE, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 1, + degree: 1, + }]; + const PROGRAM: Stage5CpuProgramPlan = Stage5CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: SQUEEZES, + transcript_absorb_bytes: &[], + opening_inputs: &[], + field_constants: &[], + field_exprs: &[], + kernels: KERNELS, + claims: CLAIMS, + batches: BATCHES, + drivers: DRIVERS, + instance_results: &[], + evals: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + + const REGISTERS_STEPS: &[Stage5ProgramStepPlan] = &[Stage5ProgramStepPlan { + kind: "sumcheck_driver", + symbol: "stage5.registers.sumcheck", + }]; + const REGISTERS_OPENING_INPUTS: &[Stage5OpeningInputPlan] = &[Stage5OpeningInputPlan { + symbol: "stage5.input.stage4.registers.RegistersVal", + source_stage: "stage4", + source_claim: "stage4.registers_read_write.opening.RegistersVal", + oracle: "RegistersVal", + domain: "jolt.stage4_registers_rw_domain", + point_arity: 3, + claim_kind: "virtual", + }]; + const REGISTERS_KERNELS: &[Stage5KernelPlan] = &[ + Stage5KernelPlan { + symbol: "jolt.cpu.stage5.registers_val_evaluation", + relation: "jolt.stage5.registers_val_evaluation", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage5_registers_val_evaluation", + }, + Stage5KernelPlan { + symbol: "jolt.cpu.stage5.batched", + relation: "jolt.stage5.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage5_batched", + }, + ]; + const REGISTERS_CLAIM_INPUTS: &[&str] = &["stage5.input.stage4.registers.RegistersVal"]; + const REGISTERS_CLAIMS: &[Stage5SumcheckClaimPlan] = &[Stage5SumcheckClaimPlan { + symbol: "stage5.registers_val_evaluation.input", + stage: "stage5", + domain: "jolt.trace_domain", + num_rounds: 2, + degree: 3, + claim: "stage5.registers_val_evaluation.registers_val", + kernel: Some("jolt.cpu.stage5.registers_val_evaluation"), + relation: Some("jolt.stage5.registers_val_evaluation"), + claim_value: "stage5.input.stage4.registers.RegistersVal", + input_openings: REGISTERS_CLAIM_INPUTS, + }]; + const REGISTERS_ORDERED_CLAIMS: &[&str] = &["stage5.registers_val_evaluation.input"]; + const REGISTERS_ROUND_SCHEDULE: &[usize] = &[2]; + const REGISTERS_BATCHES: &[Stage5SumcheckBatchPlan] = &[Stage5SumcheckBatchPlan { + symbol: "stage5.registers.batch", + stage: "stage5", + proof_slot: "stage5.registers.sumcheck", + policy: "test", + count: 1, + ordered_claims: REGISTERS_ORDERED_CLAIMS, + claim_operands: REGISTERS_ORDERED_CLAIMS, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: REGISTERS_ROUND_SCHEDULE, + }]; + const REGISTERS_DRIVERS: &[Stage5SumcheckDriverPlan] = &[Stage5SumcheckDriverPlan { + symbol: "stage5.registers.sumcheck", + stage: "stage5", + proof_slot: "stage5.registers.sumcheck", + kernel: Some("jolt.cpu.stage5.batched"), + relation: Some("jolt.stage5.batched"), + batch: "stage5.registers.batch", + policy: "test", + round_schedule: REGISTERS_ROUND_SCHEDULE, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 2, + degree: 3, + }]; + const REGISTERS_INSTANCES: &[Stage5SumcheckInstanceResultPlan] = + &[Stage5SumcheckInstanceResultPlan { + symbol: "stage5.registers_val_evaluation.instance", + source: "stage5.registers.sumcheck", + claim: "stage5.registers_val_evaluation.input", + relation: "jolt.stage5.registers_val_evaluation", + index: 0, + point_arity: 2, + num_rounds: 2, + round_offset: 0, + point_order: "reverse", + degree: 3, + }]; + const REGISTERS_EVALS: &[Stage5SumcheckEvalPlan] = &[ + Stage5SumcheckEvalPlan { + symbol: "stage5.registers_val_evaluation.eval.RdInc", + source: "stage5.registers.sumcheck", + name: "stage5.registers_val_evaluation.eval.RdInc", + index: 0, + oracle: "RdInc", + }, + Stage5SumcheckEvalPlan { + symbol: "stage5.registers_val_evaluation.eval.RdWa", + source: "stage5.registers.sumcheck", + name: "stage5.registers_val_evaluation.eval.RdWa", + index: 1, + oracle: "RdWa", + }, + ]; + const REGISTERS_PROGRAM: Stage5CpuProgramPlan = Stage5CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: REGISTERS_STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: REGISTERS_OPENING_INPUTS, + field_constants: &[], + field_exprs: &[], + kernels: REGISTERS_KERNELS, + claims: REGISTERS_CLAIMS, + batches: REGISTERS_BATCHES, + drivers: REGISTERS_DRIVERS, + instance_results: REGISTERS_INSTANCES, + evals: REGISTERS_EVALS, + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + + const RAM_RA_STEPS: &[Stage5ProgramStepPlan] = &[Stage5ProgramStepPlan { + kind: "sumcheck_driver", + symbol: "stage5.ram_ra.sumcheck", + }]; + const RAM_RA_OPENING_INPUTS: &[Stage5OpeningInputPlan] = &[ + Stage5OpeningInputPlan { + symbol: "stage5.input.stage2.ram_raf.RamRa", + source_stage: "stage2", + source_claim: "stage2.ram_raf.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: 3, + claim_kind: "virtual", + }, + Stage5OpeningInputPlan { + symbol: "stage5.input.stage2.ram_read_write.RamRa", + source_stage: "stage2", + source_claim: "stage2.ram_read_write.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: 3, + claim_kind: "virtual", + }, + Stage5OpeningInputPlan { + symbol: "stage5.input.stage4.ram_val_check.RamRa", + source_stage: "stage4", + source_claim: "stage4.ram_val_check.opening.RamRa", + oracle: "RamRa", + domain: "jolt.stage2_ram_rw_domain", + point_arity: 3, + claim_kind: "virtual", + }, + ]; + const RAM_RA_FIELD_CONSTANTS: &[Stage5FieldConstantPlan] = &[Stage5FieldConstantPlan { + symbol: "stage5.ram_ra_claim_reduction.gamma", + field: "bn254_fr", + value: 2, + }]; + const RAM_RA_GAMMA2_OPERANDS: &[&str] = &["stage5.ram_ra_claim_reduction.gamma"]; + const RAM_RA_RW_TERM_OPERANDS: &[&str] = &[ + "stage5.ram_ra_claim_reduction.gamma", + "stage5.input.stage2.ram_read_write.RamRa", + ]; + const RAM_RA_VAL_TERM_OPERANDS: &[&str] = &[ + "stage5.ram_ra_claim_reduction.gamma2", + "stage5.input.stage4.ram_val_check.RamRa", + ]; + const RAM_RA_PARTIAL_OPERANDS: &[&str] = &[ + "stage5.input.stage2.ram_raf.RamRa", + "stage5.ram_ra_claim_reduction.term.RamRaReadWrite", + ]; + const RAM_RA_CLAIM_OPERANDS_EXPR: &[&str] = &[ + "stage5.ram_ra_claim_reduction.partial.RafReadWrite", + "stage5.ram_ra_claim_reduction.term.RamRaValCheck", + ]; + const RAM_RA_FIELD_EXPRS: &[Stage5FieldExprPlan] = &[ + Stage5FieldExprPlan { + symbol: "stage5.ram_ra_claim_reduction.gamma2", + kind: "op", + formula: "field.pow:2", + operand_names: RAM_RA_GAMMA2_OPERANDS, + operands: RAM_RA_GAMMA2_OPERANDS, + }, + Stage5FieldExprPlan { + symbol: "stage5.ram_ra_claim_reduction.term.RamRaReadWrite", + kind: "op", + formula: "field.mul", + operand_names: RAM_RA_RW_TERM_OPERANDS, + operands: RAM_RA_RW_TERM_OPERANDS, + }, + Stage5FieldExprPlan { + symbol: "stage5.ram_ra_claim_reduction.term.RamRaValCheck", + kind: "op", + formula: "field.mul", + operand_names: RAM_RA_VAL_TERM_OPERANDS, + operands: RAM_RA_VAL_TERM_OPERANDS, + }, + Stage5FieldExprPlan { + symbol: "stage5.ram_ra_claim_reduction.partial.RafReadWrite", + kind: "op", + formula: "field.add", + operand_names: RAM_RA_PARTIAL_OPERANDS, + operands: RAM_RA_PARTIAL_OPERANDS, + }, + Stage5FieldExprPlan { + symbol: "stage5.ram_ra_claim_reduction.claim_expr", + kind: "op", + formula: "field.add", + operand_names: RAM_RA_CLAIM_OPERANDS_EXPR, + operands: RAM_RA_CLAIM_OPERANDS_EXPR, + }, + ]; + const RAM_RA_KERNELS: &[Stage5KernelPlan] = &[ + Stage5KernelPlan { + symbol: "jolt.cpu.stage5.ram_ra_claim_reduction", + relation: "jolt.stage5.ram_ra_claim_reduction", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage5_ram_ra_claim_reduction", + }, + Stage5KernelPlan { + symbol: "jolt.cpu.stage5.batched", + relation: "jolt.stage5.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage5_batched", + }, + ]; + const RAM_RA_CLAIM_INPUTS: &[&str] = &[ + "stage5.input.stage2.ram_raf.RamRa", + "stage5.input.stage2.ram_read_write.RamRa", + "stage5.input.stage4.ram_val_check.RamRa", + ]; + const RAM_RA_CLAIMS: &[Stage5SumcheckClaimPlan] = &[Stage5SumcheckClaimPlan { + symbol: "stage5.ram_ra_claim_reduction.input", + stage: "stage5", + domain: "jolt.trace_domain", + num_rounds: 2, + degree: 2, + claim: "stage5.ram_ra_claim_reduction.weighted_ram_ra", + kernel: Some("jolt.cpu.stage5.ram_ra_claim_reduction"), + relation: Some("jolt.stage5.ram_ra_claim_reduction"), + claim_value: "stage5.ram_ra_claim_reduction.claim_expr", + input_openings: RAM_RA_CLAIM_INPUTS, + }]; + const RAM_RA_ORDERED_CLAIMS: &[&str] = &["stage5.ram_ra_claim_reduction.input"]; + const RAM_RA_ROUND_SCHEDULE: &[usize] = &[2]; + const RAM_RA_BATCHES: &[Stage5SumcheckBatchPlan] = &[Stage5SumcheckBatchPlan { + symbol: "stage5.ram_ra.batch", + stage: "stage5", + proof_slot: "stage5.ram_ra.sumcheck", + policy: "test", + count: 1, + ordered_claims: RAM_RA_ORDERED_CLAIMS, + claim_operands: RAM_RA_ORDERED_CLAIMS, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: RAM_RA_ROUND_SCHEDULE, + }]; + const RAM_RA_DRIVERS: &[Stage5SumcheckDriverPlan] = &[Stage5SumcheckDriverPlan { + symbol: "stage5.ram_ra.sumcheck", + stage: "stage5", + proof_slot: "stage5.ram_ra.sumcheck", + kernel: Some("jolt.cpu.stage5.batched"), + relation: Some("jolt.stage5.batched"), + batch: "stage5.ram_ra.batch", + policy: "test", + round_schedule: RAM_RA_ROUND_SCHEDULE, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 2, + degree: 2, + }]; + const RAM_RA_INSTANCES: &[Stage5SumcheckInstanceResultPlan] = + &[Stage5SumcheckInstanceResultPlan { + symbol: "stage5.ram_ra_claim_reduction.instance", + source: "stage5.ram_ra.sumcheck", + claim: "stage5.ram_ra_claim_reduction.input", + relation: "jolt.stage5.ram_ra_claim_reduction", + index: 0, + point_arity: 2, + num_rounds: 2, + round_offset: 0, + point_order: "reverse", + degree: 2, + }]; + const RAM_RA_EVALS: &[Stage5SumcheckEvalPlan] = &[Stage5SumcheckEvalPlan { + symbol: "stage5.ram_ra_claim_reduction.eval.RamRa", + source: "stage5.ram_ra.sumcheck", + name: "stage5.ram_ra_claim_reduction.eval.RamRa", + index: 0, + oracle: "RamRa", + }]; + const RAM_RA_PROGRAM: Stage5CpuProgramPlan = Stage5CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: RAM_RA_STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: RAM_RA_OPENING_INPUTS, + field_constants: RAM_RA_FIELD_CONSTANTS, + field_exprs: RAM_RA_FIELD_EXPRS, + kernels: RAM_RA_KERNELS, + claims: RAM_RA_CLAIMS, + batches: RAM_RA_BATCHES, + drivers: RAM_RA_DRIVERS, + instance_results: RAM_RA_INSTANCES, + evals: RAM_RA_EVALS, + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + + #[derive(Default)] + struct RecordingExecutor { + observed_challenges: usize, + proved: bool, + } + + impl Stage5KernelExecutor for RecordingExecutor { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage5TranscriptSqueezePlan, + values: &[Fr], + ) -> Result<(), Stage5KernelError> { + assert_eq!(plan.symbol, "stage5.gamma"); + assert_eq!(values.len(), 1); + self.observed_challenges += 1; + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript, + { + assert_eq!(context.mode, Stage5ExecutionMode::Prover); + assert_eq!(context.abi_kind()?, Stage5KernelAbi::Batched); + assert_eq!(context.relation_kind()?, Stage5Relation::Batched); + assert_eq!(context.batch_claims()?.len(), 1); + self.proved = true; + Ok(Stage5SumcheckOutput { + driver: context.driver.symbol, + point: vec![Fr::from_u64(7)], + evals: Vec::new(), + opening_claims: Vec::new(), + proof: SumcheckProof { + round_polynomials: Vec::new(), + }, + }) + } + + fn verify_sumcheck( + &mut self, + context: Stage5KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage5KernelError> + where + T: Transcript, + { + Err(Stage5KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage5ExecutionMode::Prover, + actual: Stage5ExecutionMode::Verifier, + }) + } + } + + #[test] + fn stage5_program_executes_with_stage5_abi_and_relation_names() { + let mut transcript = Blake2bTranscript::::new(b"Jolt"); + let mut executor = RecordingExecutor::default(); + + let artifacts = execute_stage5_program( + &PROGRAM, + Stage5ExecutionMode::Prover, + &mut executor, + &mut transcript, + ) + .expect("stage5 program executes"); + + assert_eq!(executor.observed_challenges, 1); + assert!(executor.proved); + assert_eq!(artifacts.challenge_vectors.len(), 1); + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].driver, "stage5.sumcheck"); + } + + #[test] + fn stage5_registers_val_prover_produces_verifiable_sumcheck() { + let registers_point = frs(&[1, 2, 3]); + let rd_inc = frs(&[2, 3, 4, 5]); + let rd_write_addresses = [Some(0usize), Some(1usize), None, Some(1usize)]; + let input_eval = registers_val_input_claim(®isters_point, &rd_inc, &rd_write_addresses); + let opening_inputs = vec![Stage5OpeningInputValue { + symbol: "stage5.input.stage4.registers.RegistersVal", + point: registers_point, + eval: input_eval, + }]; + let prover_inputs = Stage5ProverInputs::new(&opening_inputs).with_registers_val( + Stage5RegistersValWitness { + register_count: 2, + trace_len: 4, + rd_inc: &rd_inc, + rd_wa: &[], + rd_write_addresses: Some(&rd_write_addresses), + }, + ); + let mut prover = Stage5ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"Jolt"); + let artifacts = execute_stage5_program( + ®ISTERS_PROGRAM, + Stage5ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("registers val prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 2); + + let proof = Stage5Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage5ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"Jolt"); + let verified = execute_stage5_program( + ®ISTERS_PROGRAM, + Stage5ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("registers val verifier accepts prover output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn stage5_ram_ra_prover_produces_verifiable_sumcheck() { + let ram_raf_point = frs(&[1, 2, 3]); + let ram_rw_point = frs(&[1, 5, 7]); + let ram_val_point = frs(&[1, 11, 13]); + let remapped_addresses = [Some(0usize), Some(1usize), None, Some(1usize)]; + let gamma = Fr::from_u64(2); + let claim_raf = ram_ra_input_claim(&ram_raf_point, &remapped_addresses); + let claim_rw = ram_ra_input_claim(&ram_rw_point, &remapped_addresses); + let claim_val = ram_ra_input_claim(&ram_val_point, &remapped_addresses); + let opening_inputs = vec![ + Stage5OpeningInputValue { + symbol: "stage5.input.stage2.ram_raf.RamRa", + point: ram_raf_point, + eval: claim_raf, + }, + Stage5OpeningInputValue { + symbol: "stage5.input.stage2.ram_read_write.RamRa", + point: ram_rw_point, + eval: claim_rw, + }, + Stage5OpeningInputValue { + symbol: "stage5.input.stage4.ram_val_check.RamRa", + point: ram_val_point, + eval: claim_val, + }, + ]; + let mut store = Stage5ValueStore::with_opening_inputs(&opening_inputs); + store.seed_constants(&RAM_RA_PROGRAM); + let _ = store + .evaluate_available_field_exprs(&RAM_RA_PROGRAM) + .expect("field exprs"); + assert_eq!( + claim_raf + gamma * claim_rw + gamma.square() * claim_val, + store + .scalar("stage5.ram_ra_claim_reduction.claim_expr") + .expect("claim expr") + ); + + let prover_inputs = + Stage5ProverInputs::new(&opening_inputs).with_ram_ra(Stage5RamRaWitness { + ram_k: 2, + trace_len: 4, + ram_ra: &[], + remapped_addresses: Some(&remapped_addresses), + }); + let mut prover = Stage5ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"Jolt"); + let artifacts = execute_stage5_program( + &RAM_RA_PROGRAM, + Stage5ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("ram ra prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 1); + + let proof = Stage5Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage5ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"Jolt"); + let verified = execute_stage5_program( + &RAM_RA_PROGRAM, + Stage5ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("ram ra verifier accepts prover output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn stage5_instruction_read_raf_prover_produces_verifiable_sumcheck() { + const CHUNK_BITS: usize = 64; + let r_reduction = frs(&[3, 5]); + let lookup_indices = [ + 0u128, + 1u128 << 127, + (0x1234u128 << 64) | 0xABCDu128, + (0x55AAu128 << 96) | (0xAA55u128 << 32), + ]; + let table_indices = [Some(0usize), Some(0usize), None, Some(0usize)]; + let is_interleaved = [true, false, true, false]; + let gamma = Fr::from_u64(2); + let (lookup_output_claim, left_claim, right_claim) = instruction_read_raf_input_claim( + &r_reduction, + &lookup_indices, + &table_indices, + &is_interleaved, + ); + let input_claim = lookup_output_claim + gamma * left_claim + gamma.square() * right_claim; + let opening_inputs = vec![ + Stage5OpeningInputValue { + symbol: "stage5.input.stage2.instruction.LookupOutput", + point: r_reduction.clone(), + eval: lookup_output_claim, + }, + Stage5OpeningInputValue { + symbol: "stage5.input.stage2.instruction.LeftLookupOperand", + point: r_reduction.clone(), + eval: left_claim, + }, + Stage5OpeningInputValue { + symbol: "stage5.input.stage2.instruction.RightLookupOperand", + point: r_reduction, + eval: right_claim, + }, + Stage5OpeningInputValue { + symbol: "stage5.instruction_read_raf.claim_expr", + point: Vec::new(), + eval: input_claim, + }, + ]; + let program = instruction_read_raf_test_program(2, 128 / CHUNK_BITS); + let prover_inputs = Stage5ProverInputs::new(&opening_inputs).with_instruction_read_raf( + Stage5InstructionReadRafWitness { + trace_len: 4, + lookup_indices: &lookup_indices, + lookup_table_indices: &table_indices, + is_interleaved_operands: &is_interleaved, + ra_virtual_log_k_chunk: CHUNK_BITS, + }, + ); + let mut prover = Stage5ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"Jolt"); + let artifacts = execute_stage5_program( + program, + Stage5ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("instruction read raf prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!( + artifacts.sumchecks[0].evals.len(), + LookupTableKind::<64>::all().len() + 128 / CHUNK_BITS + 1 + ); + + let proof = Stage5Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage5ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"Jolt"); + let verified = execute_stage5_program( + program, + Stage5ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("instruction read raf verifier accepts prover output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn instruction_read_raf_output_evals_follow_trace_flags_and_ra_chunks() { + const CHUNK_BITS: usize = 16; + let lookup_indices = [ + 0u128, + 1u128 << 112, + 0xABCDu128 << 96, + (0x12u128 << 112) | 0x34u128, + ]; + let table_indices = [Some(0usize), Some(2usize), None, Some(0usize)]; + let is_interleaved = [true, false, true, false]; + let address_point = (0..128) + .map(|index| Fr::from_u64(index as u64 + 2)) + .collect::>(); + let cycle_point = frs(&[3, 5]); + let witness = Stage5InstructionReadRafWitness { + trace_len: 4, + lookup_indices: &lookup_indices, + lookup_table_indices: &table_indices, + is_interleaved_operands: &is_interleaved, + ra_virtual_log_k_chunk: CHUNK_BITS, + }; + + let output = instruction_read_raf_output_evals(witness, &address_point, &cycle_point) + .expect("instruction read raf evals"); + let cycle_eq = EqPolynomial::::evals(&cycle_point, None); + assert_eq!(output.lookup_table_flags[0], cycle_eq[0] + cycle_eq[3]); + assert_eq!(output.lookup_table_flags[2], cycle_eq[1]); + assert_eq!(output.instruction_raf_flag, cycle_eq[1] + cycle_eq[3]); + assert_eq!(output.instruction_ra.len(), 8); + + for chunk in 0..output.instruction_ra.len() { + let chunk_point = &address_point[chunk * CHUNK_BITS..(chunk + 1) * CHUNK_BITS]; + let shift = 128 - (chunk + 1) * CHUNK_BITS; + let expected = lookup_indices + .iter() + .zip(&cycle_eq) + .map(|(&lookup_index, &cycle_weight)| { + let chunk_value = (lookup_index >> shift) & ((1u128 << CHUNK_BITS) - 1); + cycle_weight * manual_eq_bits(chunk_point, chunk_value, CHUNK_BITS) + }) + .sum::(); + assert_eq!(output.instruction_ra[chunk], expected, "chunk={chunk}"); + } + } + + fn frs(values: &[u64]) -> Vec { + values.iter().map(|value| Fr::from_u64(*value)).collect() + } + + fn manual_eq_bits(point: &[Fr], bits: u128, num_bits: usize) -> Fr { + point + .iter() + .enumerate() + .map(|(index, &challenge)| { + if ((bits >> (num_bits - 1 - index)) & 1) == 1 { + challenge + } else { + Fr::from_u64(1) - challenge + } + }) + .product() + } + + fn registers_val_input_claim( + registers_point: &[Fr], + rd_inc: &[Fr], + rd_write_addresses: &[Option], + ) -> Fr { + let (address_point, cycle_point) = registers_point.split_at(1); + let address_eq = EqPolynomial::::evals(address_point, None); + let lt = lt_evals_big_endian(cycle_point); + rd_inc + .iter() + .zip(rd_write_addresses) + .zip(lt) + .map(|((&inc, address), lt)| { + let wa = address + .and_then(|address| address_eq.get(address)) + .copied() + .unwrap_or_else(|| Fr::from_u64(0)); + inc * wa * lt + }) + .sum() + } + + fn ram_ra_input_claim(ram_ra_point: &[Fr], remapped_addresses: &[Option]) -> Fr { + let (address_point, cycle_point) = ram_ra_point.split_at(1); + let address_eq = EqPolynomial::::evals(address_point, None); + let eq_cycle = EqPolynomial::::evals(cycle_point, None); + remapped_addresses + .iter() + .zip(eq_cycle) + .map(|(address, eq)| { + let ra = address + .and_then(|address| address_eq.get(address)) + .copied() + .unwrap_or_else(|| Fr::from_u64(0)); + ra * eq + }) + .sum() + } + + fn instruction_read_raf_input_claim( + r_reduction: &[Fr], + lookup_indices: &[u128], + table_indices: &[Option], + is_interleaved: &[bool], + ) -> (Fr, Fr, Fr) { + let tables = LookupTableKind::<64>::all(); + let eq_cycle = EqPolynomial::::evals(r_reduction, None); + let mut lookup_output_claim = Fr::from_u64(0); + let mut left_claim = Fr::from_u64(0); + let mut right_claim = Fr::from_u64(0); + for (((&lookup_index, table_index), &is_interleaved), &cycle_weight) in lookup_indices + .iter() + .zip(table_indices) + .zip(is_interleaved) + .zip(&eq_cycle) + { + let address_point = field_bit_point_128(lookup_index); + if let Some(table_index) = table_index { + lookup_output_claim += + cycle_weight * tables[*table_index].evaluate_mle::(&address_point); + } + if is_interleaved { + left_claim += cycle_weight * operand_polynomial_eval(&address_point, true); + right_claim += cycle_weight * operand_polynomial_eval(&address_point, false); + } else { + right_claim += cycle_weight * identity_polynomial_eval(&address_point); + } + } + (lookup_output_claim, left_claim, right_claim) + } + + fn field_bit_point_128(value: u128) -> Vec { + (0..128) + .map(|index| Fr::from_bool(lookup_bit(value, index, 128))) + .collect() + } + + fn instruction_read_raf_test_program( + trace_rounds: usize, + ra_chunks: usize, + ) -> &'static Stage5CpuProgramPlan { + let table_count = LookupTableKind::<64>::all().len(); + let driver_symbol = "stage5.instruction_read_raf.sumcheck"; + let claim_symbol = "stage5.instruction_read_raf.input"; + let ordered_claims = Box::leak(vec![claim_symbol].into_boxed_slice()); + let round_schedule = Box::leak(vec![128, trace_rounds].into_boxed_slice()); + let claim_inputs = Box::leak( + vec![ + "stage5.input.stage2.instruction.LookupOutput", + "stage5.input.stage2.instruction.LeftLookupOperand", + "stage5.input.stage2.instruction.RightLookupOperand", + ] + .into_boxed_slice(), + ); + let mut evals = Vec::with_capacity(table_count + ra_chunks + 1); + for index in 0..table_count { + let name = leak_test_str(format!( + "stage5.instruction_read_raf.eval.LookupTableFlag_{index}" + )); + let oracle = leak_test_str(format!("LookupTableFlag_{index}")); + evals.push(Stage5SumcheckEvalPlan { + symbol: name, + source: driver_symbol, + name, + index, + oracle, + }); + } + for index in 0..ra_chunks { + let name = leak_test_str(format!( + "stage5.instruction_read_raf.eval.InstructionRa_{index}" + )); + let oracle = leak_test_str(format!("InstructionRa_{index}")); + evals.push(Stage5SumcheckEvalPlan { + symbol: name, + source: driver_symbol, + name, + index: table_count + index, + oracle, + }); + } + evals.push(Stage5SumcheckEvalPlan { + symbol: "stage5.instruction_read_raf.eval.InstructionRafFlag", + source: driver_symbol, + name: "stage5.instruction_read_raf.eval.InstructionRafFlag", + index: table_count + ra_chunks, + oracle: "InstructionRafFlag", + }); + + Box::leak(Box::new(Stage5CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: Box::leak( + vec![Stage5ProgramStepPlan { + kind: "sumcheck_driver", + symbol: driver_symbol, + }] + .into_boxed_slice(), + ), + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: Box::leak( + vec![ + Stage5OpeningInputPlan { + symbol: "stage5.input.stage2.instruction.LookupOutput", + source_stage: "stage2", + source_claim: + "stage2.instruction_lookup.claim_reduction.opening.LookupOutput", + oracle: "LookupOutput", + domain: "jolt.trace_domain", + point_arity: trace_rounds, + claim_kind: "virtual", + }, + Stage5OpeningInputPlan { + symbol: "stage5.input.stage2.instruction.LeftLookupOperand", + source_stage: "stage2", + source_claim: + "stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand", + oracle: "LeftLookupOperand", + domain: "jolt.trace_domain", + point_arity: trace_rounds, + claim_kind: "virtual", + }, + Stage5OpeningInputPlan { + symbol: "stage5.input.stage2.instruction.RightLookupOperand", + source_stage: "stage2", + source_claim: + "stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand", + oracle: "RightLookupOperand", + domain: "jolt.trace_domain", + point_arity: trace_rounds, + claim_kind: "virtual", + }, + ] + .into_boxed_slice(), + ), + field_constants: Box::leak( + vec![Stage5FieldConstantPlan { + symbol: "stage5.instruction_read_raf.gamma", + field: "bn254_fr", + value: 2, + }] + .into_boxed_slice(), + ), + field_exprs: &[], + kernels: Box::leak( + vec![ + Stage5KernelPlan { + symbol: "jolt.cpu.stage5.instruction_read_raf", + relation: "jolt.stage5.instruction_read_raf", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage5_instruction_read_raf", + }, + Stage5KernelPlan { + symbol: "jolt.cpu.stage5.batched", + relation: "jolt.stage5.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage5_batched", + }, + ] + .into_boxed_slice(), + ), + claims: Box::leak( + vec![Stage5SumcheckClaimPlan { + symbol: claim_symbol, + stage: "stage5", + domain: "jolt.stage5_instruction_read_raf_domain", + num_rounds: 128 + trace_rounds, + degree: ra_chunks + 2, + claim: "stage5.instruction_read_raf.weighted_lookup_values", + kernel: Some("jolt.cpu.stage5.instruction_read_raf"), + relation: Some("jolt.stage5.instruction_read_raf"), + claim_value: "stage5.instruction_read_raf.claim_expr", + input_openings: claim_inputs, + }] + .into_boxed_slice(), + ), + batches: Box::leak( + vec![Stage5SumcheckBatchPlan { + symbol: "stage5.instruction_read_raf.batch", + stage: "stage5", + proof_slot: driver_symbol, + policy: "test", + count: 1, + ordered_claims, + claim_operands: ordered_claims, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule, + }] + .into_boxed_slice(), + ), + drivers: Box::leak( + vec![Stage5SumcheckDriverPlan { + symbol: driver_symbol, + stage: "stage5", + proof_slot: driver_symbol, + kernel: Some("jolt.cpu.stage5.batched"), + relation: Some("jolt.stage5.batched"), + batch: "stage5.instruction_read_raf.batch", + policy: "test", + round_schedule, + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 128 + trace_rounds, + degree: ra_chunks + 2, + }] + .into_boxed_slice(), + ), + instance_results: Box::leak( + vec![Stage5SumcheckInstanceResultPlan { + symbol: "stage5.instruction_read_raf.instance", + source: driver_symbol, + claim: claim_symbol, + relation: "jolt.stage5.instruction_read_raf", + index: 0, + point_arity: 128 + trace_rounds, + num_rounds: 128 + trace_rounds, + round_offset: 0, + point_order: "instruction_read_raf", + degree: ra_chunks + 2, + }] + .into_boxed_slice(), + ), + evals: Box::leak(evals.into_boxed_slice()), + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + })) + } + + fn leak_test_str(value: String) -> &'static str { + Box::leak(value.into_boxed_str()) + } + + fn named_eval_values(evals: &[Stage5NamedEval]) -> Vec<(&'static str, &'static str, Fr)> { + evals + .iter() + .map(|eval| (eval.name, eval.oracle, eval.value)) + .collect() + } +} diff --git a/crates/jolt-kernels/src/stage6.rs b/crates/jolt-kernels/src/stage6.rs new file mode 100644 index 0000000000..b5cc499a41 --- /dev/null +++ b/crates/jolt-kernels/src/stage6.rs @@ -0,0 +1,7163 @@ +//! Stage 6 coarse-kernel ABI used by Bolt-generated Jolt prover code. + +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use crate::dense::{bind_dense_evals_reuse, DENSE_BIND_PAR_THRESHOLD}; +use jolt_field::Field; +use jolt_poly::{ + BindingOrder, EqPolynomial, ExpandingTable, GruenSplitEqPolynomial, UnivariatePoly, +}; +use jolt_transcript::{Label, LabelWithCount, Transcript}; +pub use jolt_witness::Stage6WitnessParams; +use jolt_witness::{ + stage6_witness_polynomials, CycleInput, Stage6OpeningInputRef, Stage6WitnessInputs, + Stage6WitnessPolynomials, Stage6WitnessSlices, +}; +use rayon::prelude::*; + +pub use crate::stage4::{ + Stage4ChallengeVector as Stage6ChallengeVector, + Stage4ExecutionArtifacts as Stage6ExecutionArtifacts, + Stage4ExecutionMode as Stage6ExecutionMode, Stage4FieldConstantPlan as Stage6FieldConstantPlan, + Stage4FieldExprPlan as Stage6FieldExprPlan, Stage4NamedEval as Stage6NamedEval, + Stage4OpeningBatchPlan as Stage6OpeningBatchPlan, + Stage4OpeningClaimEqualityPlan as Stage6OpeningClaimEqualityPlan, + Stage4OpeningClaimPlan as Stage6OpeningClaimPlan, + Stage4OpeningClaimValue as Stage6OpeningClaimValue, + Stage4OpeningInputPlan as Stage6OpeningInputPlan, + Stage4OpeningInputValue as Stage6OpeningInputValue, Stage4Params as Stage6Params, + Stage4PointConcatPlan as Stage6PointConcatPlan, Stage4PointSlicePlan as Stage6PointSlicePlan, + Stage4ProgramStepPlan as Stage6ProgramStepPlan, Stage4Proof as Stage6Proof, + Stage4SumcheckBatchPlan as Stage6SumcheckBatchPlan, + Stage4SumcheckClaimPlan as Stage6SumcheckClaimPlan, + Stage4SumcheckDriverPlan as Stage6SumcheckDriverPlan, + Stage4SumcheckEvalPlan as Stage6SumcheckEvalPlan, + Stage4SumcheckInstanceResultPlan as Stage6SumcheckInstanceResultPlan, + Stage4SumcheckOutput as Stage6SumcheckOutput, + Stage4TranscriptAbsorbBytesPlan as Stage6TranscriptAbsorbBytesPlan, + Stage4TranscriptSqueezePlan as Stage6TranscriptSqueezePlan, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6PointZeroPlan { + pub symbol: &'static str, + pub field: &'static str, + pub arity: usize, +} + +impl Stage6KernelPlan { + pub fn relation_kind(&self) -> Result { + Stage6Relation::from_symbol(self.relation).ok_or(Stage6KernelError::UnknownRelation { + relation: self.relation, + }) + } + + pub fn abi_kind(&self) -> Result { + Stage6KernelAbi::from_name(self.abi) + .ok_or(Stage6KernelError::UnknownKernelAbi { abi: self.abi }) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6CpuProgramPlan { + pub role: &'static str, + pub params: Stage6Params, + pub steps: &'static [Stage6ProgramStepPlan], + pub transcript_squeezes: &'static [Stage6TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [Stage6TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [Stage6OpeningInputPlan], + pub field_constants: &'static [Stage6FieldConstantPlan], + pub field_exprs: &'static [Stage6FieldExprPlan], + pub kernels: &'static [Stage6KernelPlan], + pub claims: &'static [Stage6SumcheckClaimPlan], + pub batches: &'static [Stage6SumcheckBatchPlan], + pub drivers: &'static [Stage6SumcheckDriverPlan], + pub instance_results: &'static [Stage6SumcheckInstanceResultPlan], + pub evals: &'static [Stage6SumcheckEvalPlan], + pub point_zeros: &'static [Stage6PointZeroPlan], + pub point_slices: &'static [Stage6PointSlicePlan], + pub point_concats: &'static [Stage6PointConcatPlan], + pub opening_claims: &'static [Stage6OpeningClaimPlan], + pub opening_equalities: &'static [Stage6OpeningClaimEqualityPlan], + pub opening_batches: &'static [Stage6OpeningBatchPlan], +} + +impl Stage6CpuProgramPlan { + pub fn claim(&self, symbol: &str) -> Option<&Stage6SumcheckClaimPlan> { + self.claims.iter().find(|claim| claim.symbol == symbol) + } + + pub fn instance_results_for_driver( + &self, + driver: &'static str, + ) -> impl Iterator { + self.instance_results + .iter() + .filter(move |instance| instance.source == driver) + } + + pub fn evals_for_driver( + &self, + driver: &'static str, + ) -> impl Iterator { + self.evals.iter().filter(move |eval| eval.source == driver) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage6Relation { + BytecodeReadRaf, + Booleanity, + HammingBooleanity, + RamRaVirtual, + InstructionRaVirtual, + IncClaimReduction, + Batched, +} + +impl Stage6Relation { + pub fn from_symbol(symbol: &str) -> Option { + match symbol { + "jolt.stage6.bytecode_read_raf" => Some(Self::BytecodeReadRaf), + "jolt.stage6.booleanity" => Some(Self::Booleanity), + "jolt.stage6.hamming_booleanity" => Some(Self::HammingBooleanity), + "jolt.stage6.ram_ra_virtual" => Some(Self::RamRaVirtual), + "jolt.stage6.instruction_ra_virtual" => Some(Self::InstructionRaVirtual), + "jolt.stage6.inc_claim_reduction" => Some(Self::IncClaimReduction), + "jolt.stage6.batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn symbol(self) -> &'static str { + match self { + Self::BytecodeReadRaf => "jolt.stage6.bytecode_read_raf", + Self::Booleanity => "jolt.stage6.booleanity", + Self::HammingBooleanity => "jolt.stage6.hamming_booleanity", + Self::RamRaVirtual => "jolt.stage6.ram_ra_virtual", + Self::InstructionRaVirtual => "jolt.stage6.instruction_ra_virtual", + Self::IncClaimReduction => "jolt.stage6.inc_claim_reduction", + Self::Batched => "jolt.stage6.batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage6KernelAbi { + BytecodeReadRaf, + Booleanity, + HammingBooleanity, + RamRaVirtual, + InstructionRaVirtual, + IncClaimReduction, + Batched, +} + +impl Stage6KernelAbi { + pub fn from_name(name: &str) -> Option { + match name { + "jolt_stage6_bytecode_read_raf" => Some(Self::BytecodeReadRaf), + "jolt_stage6_booleanity" => Some(Self::Booleanity), + "jolt_stage6_hamming_booleanity" => Some(Self::HammingBooleanity), + "jolt_stage6_ram_ra_virtual" => Some(Self::RamRaVirtual), + "jolt_stage6_instruction_ra_virtual" => Some(Self::InstructionRaVirtual), + "jolt_stage6_inc_claim_reduction" => Some(Self::IncClaimReduction), + "jolt_stage6_batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn name(self) -> &'static str { + match self { + Self::BytecodeReadRaf => "jolt_stage6_bytecode_read_raf", + Self::Booleanity => "jolt_stage6_booleanity", + Self::HammingBooleanity => "jolt_stage6_hamming_booleanity", + Self::RamRaVirtual => "jolt_stage6_ram_ra_virtual", + Self::InstructionRaVirtual => "jolt_stage6_instruction_ra_virtual", + Self::IncClaimReduction => "jolt_stage6_inc_claim_reduction", + Self::Batched => "jolt_stage6_batched", + } + } +} + +const BYTECODE_READ_RAF_STAGE_COUNT: usize = 5; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Stage6KernelError { + MissingClaim { + batch: &'static str, + claim: &'static str, + }, + MissingValue { + symbol: &'static str, + }, + MissingDriver { + driver: &'static str, + }, + MissingKernel { + driver: &'static str, + kernel: &'static str, + }, + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + UnknownRelation { + relation: &'static str, + }, + UnknownKernelAbi { + abi: &'static str, + }, + PlanCountMismatch { + artifact: &'static str, + expected: usize, + actual: usize, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + UnsupportedFieldExpr { + symbol: &'static str, + formula: &'static str, + }, + KernelNotImplemented { + abi: &'static str, + }, + WrongExecutorMode { + driver: &'static str, + expected: Stage6ExecutionMode, + actual: Stage6ExecutionMode, + }, + MissingProof { + driver: &'static str, + }, + MissingKernelInput { + kernel: &'static str, + input: &'static str, + }, + InvalidProgramStep { + symbol: &'static str, + kind: &'static str, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, +} + +impl Display for Stage6KernelError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingClaim { batch, claim } => { + write!( + formatter, + "stage6 batch @{batch} references missing claim @{claim}" + ) + } + Self::MissingValue { symbol } => { + write!(formatter, "stage6 value @{symbol} is not available") + } + Self::MissingDriver { driver } => { + write!(formatter, "stage6 driver @{driver} is not available") + } + Self::MissingKernel { driver, kernel } => { + write!( + formatter, + "stage6 driver @{driver} references missing kernel @{kernel}" + ) + } + Self::MissingBatch { driver, batch } => { + write!( + formatter, + "stage6 driver @{driver} references missing batch @{batch}" + ) + } + Self::UnknownRelation { relation } => { + write!(formatter, "unknown stage6 relation `{relation}`") + } + Self::UnknownKernelAbi { abi } => { + write!(formatter, "unknown stage6 kernel ABI `{abi}`") + } + Self::PlanCountMismatch { + artifact, + expected, + actual, + } => { + write!( + formatter, + "stage6 {artifact} plan count mismatch: expected {expected}, got {actual}" + ) + } + Self::InvalidInputLength { + input, + expected, + actual, + } => { + write!( + formatter, + "stage6 input `{input}` has length {actual}, expected {expected}" + ) + } + Self::UnsupportedFieldExpr { symbol, formula } => { + write!( + formatter, + "stage6 field expr @{symbol} uses unsupported formula `{formula}`" + ) + } + Self::KernelNotImplemented { abi } => { + write!(formatter, "stage6 kernel ABI `{abi}` is not implemented") + } + Self::WrongExecutorMode { + driver, + expected, + actual, + } => { + write!( + formatter, + "stage6 driver @{driver} expected {expected:?} executor, got {actual:?}" + ) + } + Self::MissingProof { driver } => { + write!(formatter, "stage6 proof for driver @{driver} is missing") + } + Self::MissingKernelInput { kernel, input } => { + write!( + formatter, + "stage6 kernel `{kernel}` is missing required input `{input}`" + ) + } + Self::InvalidProgramStep { symbol, kind } => { + write!( + formatter, + "stage6 program step @{symbol} has invalid kind `{kind}`" + ) + } + Self::InvalidProof { driver, reason } => { + write!( + formatter, + "stage6 proof for driver @{driver} is invalid: {reason}" + ) + } + } + } +} + +impl Error for Stage6KernelError {} + +#[derive(Clone, Copy)] +pub struct Stage6BooleanityWitness<'a, F: Field> { + pub chunks: &'a [&'a [F]], + pub index_chunks: Option<&'a [&'a [Option]]>, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage6BytecodeEntry { + pub address: F, + pub imm: F, + pub circuit_flags: [bool; 14], + pub rd: Option, + pub rs1: Option, + pub rs2: Option, + pub lookup_table: Option, + pub is_interleaved: bool, + pub is_branch: bool, + pub left_is_rs1: bool, + pub left_is_pc: bool, + pub right_is_rs2: bool, + pub right_is_imm: bool, + pub is_noop: bool, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage6BytecodeReadRafData<'a, F: Field> { + pub entries: &'a [Stage6BytecodeEntry], + pub entry_bytecode_index: usize, + pub num_lookup_tables: usize, +} + +impl From> for Stage6BytecodeEntry { + fn from(entry: jolt_witness::Stage6BytecodeEntry) -> Self { + Self { + address: entry.address, + imm: entry.imm, + circuit_flags: entry.circuit_flags, + rd: entry.rd, + rs1: entry.rs1, + rs2: entry.rs2, + lookup_table: entry.lookup_table, + is_interleaved: entry.is_interleaved, + is_branch: entry.is_branch, + left_is_rs1: entry.left_is_rs1, + left_is_pc: entry.left_is_pc, + right_is_rs2: entry.right_is_rs2, + right_is_imm: entry.right_is_imm, + is_noop: entry.is_noop, + } + } +} + +#[derive(Clone, Debug)] +pub struct Stage6BytecodeReadRafDataStorage { + entries: Vec>, + entry_bytecode_index: usize, + num_lookup_tables: usize, +} + +impl Stage6BytecodeReadRafDataStorage { + pub fn from_witness_entries( + entries: &[jolt_witness::Stage6BytecodeEntry], + entry_bytecode_index: usize, + num_lookup_tables: usize, + ) -> Self { + Self { + entries: entries.iter().copied().map(Into::into).collect(), + entry_bytecode_index, + num_lookup_tables, + } + } + + pub fn as_input(&self) -> Stage6BytecodeReadRafData<'_, F> { + Stage6BytecodeReadRafData { + entries: &self.entries, + entry_bytecode_index: self.entry_bytecode_index, + num_lookup_tables: self.num_lookup_tables, + } + } +} + +#[derive(Clone, Copy)] +pub struct Stage6BytecodeReadRafWitness<'a, F: Field> { + pub data: Stage6BytecodeReadRafData<'a, F>, + pub bytecode_ra_chunks: &'a [&'a [F]], + pub bytecode_ra_chunk_lens: Option<&'a [usize]>, + pub bytecode_ra_index_chunks: Option<&'a [&'a [Option]]>, +} + +#[derive(Clone, Copy)] +pub struct Stage6HammingBooleanityWitness<'a, F: Field> { + pub hamming_weight: &'a [F], +} + +#[derive(Clone, Copy)] +pub struct Stage6IncClaimReductionWitness<'a, F: Field> { + pub ram_inc: &'a [F], + pub rd_inc: &'a [F], +} + +#[derive(Clone, Copy)] +pub struct Stage6RamRaVirtualWitness<'a, F: Field> { + pub ram_ra_chunks: &'a [&'a [F]], +} + +#[derive(Clone, Copy)] +pub struct Stage6InstructionRaVirtualWitness<'a, F: Field> { + pub instruction_ra_chunks: &'a [&'a [F]], + pub virtual_count: usize, +} + +pub fn stage6_witness_from_opening_inputs( + params: Stage6WitnessParams, + cycle_inputs: &[CycleInput], + opening_inputs: &[Stage6OpeningInputValue], +) -> Stage6WitnessPolynomials { + let opening_refs = opening_inputs + .iter() + .map(|input| Stage6OpeningInputRef { + symbol: input.symbol, + point: input.point.as_slice(), + }) + .collect::>(); + stage6_witness_polynomials(Stage6WitnessInputs { + params, + cycle_inputs, + opening_inputs: &opening_refs, + }) +} + +#[derive(Clone, Copy)] +pub struct Stage6ProverInputs<'a, F: Field> { + pub opening_inputs: &'a [Stage6OpeningInputValue], + pub bytecode_read_raf: Option>, + pub booleanity: Option>, + pub hamming_booleanity: Option>, + pub inc_claim_reduction: Option>, + pub ram_ra_virtual: Option>, + pub instruction_ra_virtual: Option>, +} + +impl<'a, F: Field> Stage6ProverInputs<'a, F> { + pub fn new(opening_inputs: &'a [Stage6OpeningInputValue]) -> Self { + Self { + opening_inputs, + bytecode_read_raf: None, + booleanity: None, + hamming_booleanity: None, + inc_claim_reduction: None, + ram_ra_virtual: None, + instruction_ra_virtual: None, + } + } + + pub fn empty() -> Self { + Self { + opening_inputs: &[], + bytecode_read_raf: None, + booleanity: None, + hamming_booleanity: None, + inc_claim_reduction: None, + ram_ra_virtual: None, + instruction_ra_virtual: None, + } + } + + pub fn with_hamming_booleanity( + mut self, + hamming_booleanity: Stage6HammingBooleanityWitness<'a, F>, + ) -> Self { + self.hamming_booleanity = Some(hamming_booleanity); + self + } + + pub fn with_booleanity(mut self, booleanity: Stage6BooleanityWitness<'a, F>) -> Self { + self.booleanity = Some(booleanity); + self + } + + pub fn with_bytecode_read_raf( + mut self, + bytecode_read_raf: Stage6BytecodeReadRafWitness<'a, F>, + ) -> Self { + self.bytecode_read_raf = Some(bytecode_read_raf); + self + } + + pub fn with_inc_claim_reduction( + mut self, + inc_claim_reduction: Stage6IncClaimReductionWitness<'a, F>, + ) -> Self { + self.inc_claim_reduction = Some(inc_claim_reduction); + self + } + + pub fn with_ram_ra_virtual(mut self, ram_ra_virtual: Stage6RamRaVirtualWitness<'a, F>) -> Self { + self.ram_ra_virtual = Some(ram_ra_virtual); + self + } + + pub fn with_instruction_ra_virtual( + mut self, + instruction_ra_virtual: Stage6InstructionRaVirtualWitness<'a, F>, + ) -> Self { + self.instruction_ra_virtual = Some(instruction_ra_virtual); + self + } + + pub fn with_stage6_witness( + self, + bytecode_data: Stage6BytecodeReadRafData<'a, F>, + witness: &'a Stage6WitnessPolynomials, + slices: &'a Stage6WitnessSlices<'a, F>, + instruction_ra_virtual_count: usize, + ) -> Self { + self.with_bytecode_read_raf(Stage6BytecodeReadRafWitness { + data: bytecode_data, + bytecode_ra_chunks: &slices.bytecode_ra_read_raf_chunks, + bytecode_ra_chunk_lens: Some(&slices.bytecode_ra_read_raf_chunk_lens), + bytecode_ra_index_chunks: Some(&slices.bytecode_ra_index_chunks), + }) + .with_booleanity(Stage6BooleanityWitness { + chunks: &slices.booleanity_chunks, + index_chunks: Some(&slices.booleanity_index_chunks), + }) + .with_hamming_booleanity(Stage6HammingBooleanityWitness { + hamming_weight: &witness.hamming_weight, + }) + .with_ram_ra_virtual(Stage6RamRaVirtualWitness { + ram_ra_chunks: &slices.ram_ra_virtual_chunks, + }) + .with_instruction_ra_virtual(Stage6InstructionRaVirtualWitness { + instruction_ra_chunks: &slices.instruction_ra_virtual_chunks, + virtual_count: instruction_ra_virtual_count, + }) + .with_inc_claim_reduction(Stage6IncClaimReductionWitness { + ram_inc: &witness.ram_inc, + rd_inc: &witness.rd_inc, + }) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage6KernelContext<'a> { + pub mode: Stage6ExecutionMode, + pub program: &'static Stage6CpuProgramPlan, + pub kernel: &'a Stage6KernelPlan, + pub batch: &'a Stage6SumcheckBatchPlan, + pub driver: &'a Stage6SumcheckDriverPlan, +} + +impl Stage6KernelContext<'_> { + pub fn relation_kind(&self) -> Result { + self.kernel.relation_kind() + } + + pub fn abi_kind(&self) -> Result { + self.kernel.abi_kind() + } + + pub fn batch_claims(&self) -> Result, Stage6KernelError> { + self.batch + .claim_operands + .iter() + .map(|symbol| { + self.program + .claim(symbol) + .ok_or(Stage6KernelError::MissingClaim { + batch: self.batch.symbol, + claim: symbol, + }) + }) + .collect() + } +} + +pub trait Stage6KernelExecutor { + fn observe_challenge_vector( + &mut self, + _plan: &'static Stage6TranscriptSqueezePlan, + _values: &[F], + ) -> Result<(), Stage6KernelError> { + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + _output: &Stage6SumcheckOutput, + ) -> Result<(), Stage6KernelError> { + Ok(()) + } + + fn opening_claim_values( + &self, + _program: &'static Stage6CpuProgramPlan, + ) -> Result>, Stage6KernelError> { + Ok(Vec::new()) + } + + fn prove_sumcheck( + &mut self, + context: Stage6KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage6KernelError> + where + T: Transcript; + + fn verify_sumcheck( + &mut self, + context: Stage6KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage6KernelError> + where + T: Transcript; +} + +#[derive(Clone, Debug, Default)] +pub struct UnsupportedStage6KernelExecutor; + +impl Stage6KernelExecutor for UnsupportedStage6KernelExecutor { + fn prove_sumcheck( + &mut self, + context: Stage6KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage6KernelError> + where + T: Transcript, + { + let abi = context.abi_kind()?; + let _ = context.relation_kind()?; + Err(Stage6KernelError::KernelNotImplemented { abi: abi.name() }) + } + + fn verify_sumcheck( + &mut self, + context: Stage6KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage6KernelError> + where + T: Transcript, + { + let abi = context.abi_kind()?; + let _ = context.relation_kind()?; + Err(Stage6KernelError::KernelNotImplemented { abi: abi.name() }) + } +} + +#[derive(Clone)] +pub struct Stage6ProverKernelExecutor<'a, F: Field> { + pub inputs: Stage6ProverInputs<'a, F>, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage6ProverKernelExecutor<'a, F> { + pub fn new(inputs: Stage6ProverInputs<'a, F>) -> Self { + Self { + inputs, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage6CpuProgramPlan, + ) -> Result, Stage6KernelError> { + value_store_from_observations( + program, + self.inputs.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } +} + +impl Stage6KernelExecutor for Stage6ProverKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage6TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage6KernelError> { + self.challenge_vectors.push(Stage6ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage6SumcheckOutput, + ) -> Result<(), Stage6KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn opening_claim_values( + &self, + program: &'static Stage6CpuProgramPlan, + ) -> Result>, Stage6KernelError> { + self.value_store(program)?.opening_claim_values(program) + } + + fn prove_sumcheck( + &mut self, + context: Stage6KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage6KernelError> + where + T: Transcript, + { + prove_stage6_kernel( + context, + &self.inputs, + self.value_store(context.program)?, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage6KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage6KernelError> + where + T: Transcript, + { + Err(Stage6KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage6ExecutionMode::Prover, + actual: Stage6ExecutionMode::Verifier, + }) + } +} + +#[derive(Clone)] +pub struct Stage6ProofCarryingKernelExecutor<'a, F: Field> { + pub proof: &'a Stage6Proof, + pub opening_inputs: &'a [Stage6OpeningInputValue], + pub bytecode_read_raf: Option>, + pub cursor: usize, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage6ProofCarryingKernelExecutor<'a, F> { + pub fn new( + proof: &'a Stage6Proof, + opening_inputs: &'a [Stage6OpeningInputValue], + ) -> Self { + Self { + proof, + opening_inputs, + bytecode_read_raf: None, + cursor: 0, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + pub fn with_bytecode_read_raf_data( + mut self, + bytecode_read_raf: Stage6BytecodeReadRafData<'a, F>, + ) -> Self { + self.bytecode_read_raf = Some(bytecode_read_raf); + self + } + + fn value_store( + &self, + program: &'static Stage6CpuProgramPlan, + ) -> Result, Stage6KernelError> { + value_store_from_observations( + program, + self.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } + + fn next_proof( + &mut self, + driver: &'static str, + ) -> Result<&'a Stage6SumcheckOutput, Stage6KernelError> { + let proof = self + .proof + .sumchecks + .get(self.cursor) + .ok_or(Stage6KernelError::MissingProof { driver })?; + self.cursor += 1; + Ok(proof) + } +} + +impl Stage6KernelExecutor for Stage6ProofCarryingKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage6TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage6KernelError> { + self.challenge_vectors.push(Stage6ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage6SumcheckOutput, + ) -> Result<(), Stage6KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn opening_claim_values( + &self, + program: &'static Stage6CpuProgramPlan, + ) -> Result>, Stage6KernelError> { + self.value_store(program)?.opening_claim_values(program) + } + + fn prove_sumcheck( + &mut self, + context: Stage6KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage6KernelError> + where + T: Transcript, + { + let proof = self.next_proof(context.driver.symbol)?; + verify_stage6_kernel( + context, + self.value_store(context.program)?, + proof, + self.bytecode_read_raf, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage6KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage6KernelError> + where + T: Transcript, + { + let proof = self.next_proof(context.driver.symbol)?; + verify_stage6_kernel( + context, + self.value_store(context.program)?, + proof, + self.bytecode_read_raf, + transcript, + ) + } +} + +#[derive(Clone, Debug, Default)] +struct Stage6ValueStore { + scalars: Vec<(&'static str, F)>, + points: Vec<(&'static str, Vec)>, +} + +impl Stage6ValueStore { + fn with_opening_inputs(inputs: &[Stage6OpeningInputValue]) -> Self { + let mut store = Self::default(); + for input in inputs { + store.insert_scalar(input.symbol, input.eval); + store.insert_point(input.symbol, input.point.clone()); + } + store + } + + fn seed_constants(&mut self, program: &'static Stage6CpuProgramPlan) { + for constant in program.field_constants { + self.insert_scalar(constant.symbol, F::from_u64(constant.value as u64)); + } + for zero in program.point_zeros { + self.insert_point(zero.symbol, vec![F::from_u64(0); zero.arity]); + } + } + + fn observe_challenge_vector( + &mut self, + plan: &'static Stage6TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage6KernelError> { + self.insert_point(plan.symbol, values.to_vec()); + if matches!(plan.kind, "challenge_scalar" | "scalar") { + require_operand_count(plan.symbol, 1, values.len())?; + self.insert_scalar(plan.symbol, values[0]); + } + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + program: &'static Stage6CpuProgramPlan, + output: &Stage6SumcheckOutput, + ) -> Result<(), Stage6KernelError> { + self.observe_sumcheck_values(program, output.driver, &output.point, &output.evals) + } + + fn observe_sumcheck_values( + &mut self, + program: &'static Stage6CpuProgramPlan, + driver: &'static str, + point: &[F], + evals: &[Stage6NamedEval], + ) -> Result<(), Stage6KernelError> { + self.insert_point(driver, point.to_vec()); + for instance in program + .instance_results + .iter() + .filter(|instance| instance.source == driver) + { + let end = instance.round_offset + instance.point_arity; + let mut point = point + .get(instance.round_offset..end) + .ok_or(Stage6KernelError::InvalidInputLength { + input: instance.symbol, + expected: end, + actual: point.len(), + })? + .to_vec(); + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "bytecode_read_raf" => point = normalize_bytecode_read_raf_point(program, &point)?, + "stage6_booleanity" => point = normalize_stage6_booleanity_point(program, &point)?, + "instruction_read_raf" => point = normalize_instruction_read_raf_point(&point)?, + _ => { + return Err(Stage6KernelError::InvalidProof { + driver, + reason: "unsupported point order", + }); + } + } + self.insert_point(instance.symbol, point); + } + for eval in program.evals.iter().filter(|eval| eval.source == driver) { + let value = evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| evals.get(eval.index)) + .ok_or(Stage6KernelError::MissingValue { + symbol: eval.symbol, + })? + .value; + self.insert_scalar(eval.symbol, value); + self.insert_scalar(eval.name, value); + } + let _ = self.evaluate_available_points(program)?; + let _ = self.evaluate_available_field_exprs(program)?; + self.verify_opening_equalities(program)?; + Ok(()) + } + + fn evaluate_available_field_exprs( + &mut self, + program: &'static Stage6CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for expr in program.field_exprs { + if self.try_scalar(expr.symbol).is_some() { + continue; + } + let Some(operands) = self.try_expr_operands(expr) else { + continue; + }; + self.insert_scalar(expr.symbol, evaluate_stage6_field_expr(expr, &operands)?); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + fn evaluate_available_points( + &mut self, + program: &'static Stage6CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for slice in program.point_slices { + if self.try_point(slice.symbol).is_some() { + continue; + } + let Some(input) = self.try_point(slice.input) else { + continue; + }; + let end = slice.offset + slice.length; + let point = input + .get(slice.offset..end) + .ok_or(Stage6KernelError::InvalidInputLength { + input: slice.symbol, + expected: end, + actual: input.len(), + })? + .to_vec(); + self.insert_point(slice.symbol, point); + progress += 1; + } + for concat in program.point_concats { + if self.try_point(concat.symbol).is_some() { + continue; + } + let Some(point) = self.try_concat_point(concat) else { + continue; + }; + require_operand_count(concat.symbol, concat.arity, point.len())?; + self.insert_point(concat.symbol, point); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + fn verify_opening_equalities( + &self, + program: &'static Stage6CpuProgramPlan, + ) -> Result<(), Stage6KernelError> { + for equality in program.opening_equalities { + match equality.mode { + "point_and_eval" => { + if self.point(equality.lhs)? != self.point(equality.rhs)? + || self.scalar(equality.lhs)? != self.scalar(equality.rhs)? + { + return Err(Stage6KernelError::InvalidProof { + driver: equality.symbol, + reason: "opening claim equality failed", + }); + } + } + _ => { + return Err(Stage6KernelError::InvalidProof { + driver: equality.symbol, + reason: "unsupported opening equality mode", + }); + } + } + } + Ok(()) + } + + fn claim_value( + &mut self, + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + ) -> Result { + let _ = self.evaluate_available_field_exprs(program)?; + self.scalar(claim.claim_value) + } + + fn batch_claim_values( + &mut self, + program: &'static Stage6CpuProgramPlan, + batch: &Stage6SumcheckBatchPlan, + ) -> Result, Stage6KernelError> { + batch + .claim_operands + .iter() + .map(|symbol| { + let claim = program + .claim(symbol) + .ok_or(Stage6KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + self.claim_value(program, claim) + }) + .collect() + } + + fn insert_scalar(&mut self, symbol: &'static str, value: F) { + if let Some((_, existing)) = self + .scalars + .iter_mut() + .find(|(existing, _)| *existing == symbol) + { + *existing = value; + } else { + self.scalars.push((symbol, value)); + } + } + + fn insert_point(&mut self, symbol: &'static str, point: Vec) { + if let Some((_, existing)) = self + .points + .iter_mut() + .find(|(existing, _)| *existing == symbol) + { + *existing = point; + } else { + self.points.push((symbol, point)); + } + } + + fn scalar(&self, symbol: &'static str) -> Result { + self.try_scalar(symbol) + .ok_or(Stage6KernelError::MissingValue { symbol }) + } + + fn try_scalar(&self, symbol: &str) -> Option { + self.scalars + .iter() + .find(|(existing, _)| *existing == symbol) + .map(|(_, value)| *value) + } + + fn point(&self, symbol: &'static str) -> Result<&[F], Stage6KernelError> { + self.try_point(symbol) + .ok_or(Stage6KernelError::MissingValue { symbol }) + } + + fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.points + .iter() + .find(|(existing, _)| *existing == symbol) + .map(|(_, point)| point.as_slice()) + } + + fn try_expr_operands(&self, expr: &Stage6FieldExprPlan) -> Option> { + expr.operands + .iter() + .map(|operand| self.try_scalar(operand)) + .collect() + } + + fn try_concat_point(&self, concat: &Stage6PointConcatPlan) -> Option> { + let mut point = Vec::with_capacity(concat.arity); + for input in concat.inputs { + point.extend_from_slice(self.try_point(input)?); + } + Some(point) + } + + fn opening_claim_values( + mut self, + program: &'static Stage6CpuProgramPlan, + ) -> Result>, Stage6KernelError> { + let _ = self.evaluate_available_points(program)?; + let _ = self.evaluate_available_field_exprs(program)?; + program + .opening_claims + .iter() + .map(|claim| { + Ok(Stage6OpeningClaimValue { + symbol: claim.symbol, + oracle: claim.oracle, + domain: claim.domain, + claim_kind: claim.claim_kind, + point: self.point(claim.point_source)?.to_vec(), + eval: self.scalar(claim.eval_source)?, + }) + }) + .collect() + } +} + +fn value_store_from_observations( + program: &'static Stage6CpuProgramPlan, + opening_inputs: &[Stage6OpeningInputValue], + challenge_vectors: &[Stage6ChallengeVector], + completed_sumchecks: &[Stage6SumcheckOutput], +) -> Result, Stage6KernelError> { + let mut store = Stage6ValueStore::with_opening_inputs(opening_inputs); + store.seed_constants(program); + for challenge in challenge_vectors { + let plan = + find_squeeze(program, challenge.symbol).ok_or(Stage6KernelError::MissingValue { + symbol: challenge.symbol, + })?; + store.observe_challenge_vector(plan, &challenge.values)?; + } + for output in completed_sumchecks { + store.observe_sumcheck_output(program, output)?; + } + let _ = store.evaluate_available_points(program)?; + let _ = store.evaluate_available_field_exprs(program)?; + store.verify_opening_equalities(program)?; + Ok(store) +} + +fn prove_stage6_kernel( + context: Stage6KernelContext<'_>, + inputs: &Stage6ProverInputs<'_, F>, + store: Stage6ValueStore, + transcript: &mut T, +) -> Result, Stage6KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage6KernelAbi::Batched => prove_batched_stage6(context, inputs, store, transcript), + abi => Err(Stage6KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +fn verify_stage6_kernel( + context: Stage6KernelContext<'_>, + store: Stage6ValueStore, + proof: &Stage6SumcheckOutput, + bytecode_read_raf: Option>, + transcript: &mut T, +) -> Result, Stage6KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage6KernelAbi::Batched => { + verify_batched_stage6(context, store, proof, bytecode_read_raf, transcript) + } + abi => Err(Stage6KernelError::KernelNotImplemented { abi: abi.name() }), + } +} + +#[tracing::instrument(skip_all, name = "Stage6::prove_batched")] +fn prove_batched_stage6( + context: Stage6KernelContext<'_>, + inputs: &Stage6ProverInputs<'_, F>, + mut store: Stage6ValueStore, + transcript: &mut T, +) -> Result, Stage6KernelError> +where + F: Field, + T: Transcript, +{ + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let timing_enabled = std::env::var_os("JOLT_STAGE6_KERNEL_TIMINGS").is_some(); + let two_inv = F::from_u64(2) + .inverse() + .ok_or(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "field element 2 is not invertible", + })?; + let mut instances = Vec::with_capacity(claims.len()); + let mut timing_stats = Vec::with_capacity(claims.len()); + for (index, claim) in claims.iter().enumerate() { + let offset = instance_round_offset(context.program, context.driver.symbol, claim.symbol)?; + if offset + claim.num_rounds > max_rounds { + return Err(Stage6KernelError::InvalidInputLength { + input: claim.symbol, + expected: max_rounds, + actual: offset + claim.num_rounds, + }); + } + let relation = claim_relation(context.program, claim)?; + let active_scale = F::one().mul_pow_2(max_rounds - offset - claim.num_rounds); + let init_start = timing_enabled.then(std::time::Instant::now); + let state = + Stage6ProverInstanceState::new(context.program, claim, inputs, &store, active_scale)?; + let init_nanos = init_start.map_or(0, |start| start.elapsed().as_nanos()); + instances.push(Stage6BatchedInstance { + claim, + relation, + offset, + previous_claim: input_claims[index].mul_pow_2(max_rounds - claim.num_rounds), + state, + }); + timing_stats.push((relation, init_nanos, 0u128, 0u128)); + } + + let mut point = Vec::with_capacity(max_rounds); + let mut round_polynomials = Vec::with_capacity(max_rounds); + let mut batched_claim = instances + .iter() + .zip(&batching_coeffs) + .map(|(instance, &coefficient)| instance.previous_claim * coefficient) + .sum::(); + for round in 0..max_rounds { + let mut individual_polys = Vec::with_capacity(instances.len()); + for (index, instance) in instances.iter_mut().enumerate() { + let poly = if instance.is_active(round) { + let round_start = timing_enabled.then(std::time::Instant::now); + let poly = instance + .state + .round_poly(instance.previous_claim, instance.relation)?; + if let Some(start) = round_start { + timing_stats[index].2 += start.elapsed().as_nanos(); + } + poly + } else { + UnivariatePoly::new(vec![instance.previous_claim * two_inv]) + }; + individual_polys.push(poly); + } + let batched_poly = combine_univariate_polys(&individual_polys, &batching_coeffs); + if batched_poly.evaluate(F::zero()) + batched_poly.evaluate(F::one()) != batched_claim { + return Err(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round claim mismatch", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, &batched_poly); + let challenge = transcript.challenge(); + point.push(challenge); + batched_claim = batched_poly.evaluate(challenge); + for (index, (instance, poly)) in instances.iter_mut().zip(individual_polys).enumerate() { + instance.previous_claim = poly.evaluate(challenge); + if instance.is_active(round) { + let bind_start = timing_enabled.then(std::time::Instant::now); + instance.state.ingest_challenge(challenge); + if let Some(start) = bind_start { + timing_stats[index].3 += start.elapsed().as_nanos(); + } + } + } + round_polynomials.push(batched_poly); + } + + let mut evals = Vec::new(); + let mut expected = F::zero(); + for (instance, &coefficient) in instances.iter().zip(&batching_coeffs) { + let relation_claim = instance.state.final_relation_eval(instance.relation)?; + if instance.previous_claim != relation_claim { + return Err(Stage6KernelError::InvalidProof { + driver: instance.relation.symbol(), + reason: "stage6 relation output claim mismatch", + }); + } + expected += coefficient * relation_claim; + evals.extend(instance.state.final_evals(instance.relation)?); + } + if batched_claim != expected { + return Err(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + store.observe_sumcheck_values(context.program, context.driver.symbol, &point, &evals)?; + let opening_claims = append_opening_claims(context.program, &mut store, transcript, &evals)?; + if timing_enabled { + for (relation, init_nanos, round_nanos, bind_nanos) in timing_stats { + tracing::info!( + "[stage6 timings] relation={} init_ms={:.3} round_ms={:.3} bind_ms={:.3}", + relation.symbol(), + init_nanos as f64 / 1_000_000.0, + round_nanos as f64 / 1_000_000.0, + bind_nanos as f64 / 1_000_000.0, + ); + } + } + Ok(Stage6SumcheckOutput { + driver: context.driver.symbol, + point, + evals, + opening_claims, + proof: jolt_sumcheck::SumcheckProof { round_polynomials }, + }) +} + +fn verify_batched_stage6( + context: Stage6KernelContext<'_>, + mut store: Stage6ValueStore, + proof: &Stage6SumcheckOutput, + bytecode_read_raf: Option>, + transcript: &mut T, +) -> Result, Stage6KernelError> +where + F: Field, + T: Transcript, +{ + if proof.driver != context.driver.symbol { + return Err(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + if proof.proof.round_polynomials.len() != context.driver.num_rounds { + return Err(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected batched round count", + }); + } + + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let mut running_claim = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), &coefficient)| { + claim.mul_pow_2(max_rounds - plan.num_rounds) * coefficient + }) + .sum::(); + let mut point = Vec::with_capacity(max_rounds); + for poly in &proof.proof.round_polynomials { + if polynomial_degree(poly) > context.driver.degree { + return Err(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched polynomial exceeds degree bound", + }); + } + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != running_claim { + return Err(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round check failed", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, poly); + let challenge = transcript.challenge(); + running_claim = poly.evaluate(challenge); + point.push(challenge); + } + if !proof.point.is_empty() && proof.point != point { + return Err(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched point mismatch", + }); + } + if let Some(expected) = expected_batched_output_claim_if_supported( + context, + &store, + &proof.evals, + &point, + &batching_coeffs, + bytecode_read_raf, + )? { + if running_claim != expected { + return Err(Stage6KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + } + + let output = Stage6SumcheckOutput { + driver: context.driver.symbol, + point, + evals: proof.evals.clone(), + opening_claims: Vec::new(), + proof: proof.proof.clone(), + }; + store.observe_sumcheck_output(context.program, &output)?; + let opening_claims = + append_opening_claims(context.program, &mut store, transcript, &output.evals)?; + let output = Stage6SumcheckOutput { + opening_claims, + ..output + }; + Ok(output) +} + +fn expected_batched_output_claim_if_supported( + context: Stage6KernelContext<'_>, + store: &Stage6ValueStore, + evals: &[Stage6NamedEval], + point: &[F], + batching_coeffs: &[F], + bytecode_read_raf: Option>, +) -> Result, Stage6KernelError> { + let mut expected = F::zero(); + for (claim, &coefficient) in context.batch_claims()?.iter().zip(batching_coeffs) { + let Some(instance) = context.program.instance_results.iter().find(|instance| { + instance.claim == claim.symbol && instance.source == context.driver.symbol + }) else { + return Ok(None); + }; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(Stage6KernelError::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let value = match claim_relation(context.program, claim)? { + Stage6Relation::HammingBooleanity => { + expected_hamming_booleanity(store, evals, local_point)? + } + Stage6Relation::IncClaimReduction => { + expected_inc_claim_reduction(store, evals, local_point)? + } + Stage6Relation::RamRaVirtual => expected_ram_ra_virtual(store, evals, local_point)?, + Stage6Relation::InstructionRaVirtual => { + expected_instruction_ra_virtual(context.program, store, evals, local_point)? + } + Stage6Relation::Booleanity => { + expected_booleanity(context.program, store, evals, local_point)? + } + Stage6Relation::BytecodeReadRaf => { + let Some(data) = bytecode_read_raf else { + return Ok(None); + }; + expected_bytecode_read_raf(context.program, data, store, evals, local_point)? + } + Stage6Relation::Batched => return Ok(None), + }; + expected += coefficient * value; + } + Ok(Some(expected)) +} + +fn expected_bytecode_read_raf( + program: &'static Stage6CpuProgramPlan, + data: Stage6BytecodeReadRafData<'_, F>, + store: &Stage6ValueStore, + evals: &[Stage6NamedEval], + local_point: &[F], +) -> Result { + let log_t = stage6_trace_rounds(program)?; + let opening_point = normalize_bytecode_read_raf_point(program, local_point)?; + let log_k = opening_point.len() - log_t; + let (r_address_prime, r_cycle_prime) = opening_point.split_at(log_k); + + let gamma = store.scalar("stage6.bytecode_read_raf.gamma")?; + let gamma_powers = bytecode_gamma_powers(gamma); + let int_eval = identity_polynomial_eval(r_address_prime); + let stage_value_evals = bytecode_stage_value_evals(data, store, r_address_prime, log_t)?; + let stage_cycle_points = bytecode_stage_cycle_points(store, log_t)?; + let int_contrib = [ + gamma_powers[5] * int_eval, + F::zero(), + gamma_powers[4] * int_eval, + F::zero(), + F::zero(), + ]; + + let mut val = F::zero(); + for index in 0..stage_value_evals.len() { + val += (stage_value_evals[index] + int_contrib[index]) + * EqPolynomial::::mle(&stage_cycle_points[index], r_cycle_prime) + * gamma_powers[index]; + } + + let entry_bits = index_bits(data.entry_bytecode_index, log_k)?; + let zero_cycle = vec![F::zero(); r_cycle_prime.len()]; + let entry_contrib = gamma_powers[7] + * EqPolynomial::::mle(&entry_bits, r_address_prime) + * EqPolynomial::::mle(&zero_cycle, r_cycle_prime); + let bytecode_ra = + indexed_evals_by_prefix_any(evals, "stage6.bytecode_read_raf.eval.BytecodeRa_")? + .into_iter() + .product::(); + Ok((val + entry_contrib) * bytecode_ra) +} + +fn expected_booleanity( + program: &'static Stage6CpuProgramPlan, + store: &Stage6ValueStore, + evals: &[Stage6NamedEval], + local_point: &[F], +) -> Result { + let log_t = stage6_trace_rounds(program)?; + let log_k_chunk = + local_point + .len() + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.booleanity.point", + expected: log_t, + actual: local_point.len(), + })?; + let stage5_point = store.point("stage6.input.stage5.instruction_read_raf.InstructionRa_0")?; + let stage5_address_len = + stage5_point + .len() + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + expected: log_t, + actual: stage5_point.len(), + })?; + if stage5_address_len < log_k_chunk { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + expected: log_k_chunk + log_t, + actual: stage5_point.len(), + }); + } + + let mut stage5_addr = stage5_point[..stage5_address_len].to_vec(); + stage5_addr.reverse(); + let mut combined_r = stage5_addr[stage5_address_len - log_k_chunk..].to_vec(); + combined_r.extend(stage5_point[stage5_address_len..].iter().rev().copied()); + require_operand_count( + "stage6.booleanity.combined_point", + local_point.len(), + combined_r.len(), + )?; + let mut verifier_point = combined_r[..log_k_chunk].to_vec(); + verifier_point.reverse(); + verifier_point.extend(combined_r[log_k_chunk..].iter().rev().copied()); + let eq_eval = EqPolynomial::::mle(local_point, &verifier_point); + + let gamma = store.scalar("stage6.booleanity.gamma")?; + let gamma_sq = gamma.square(); + let mut gamma_power = F::one(); + let mut booleanity = F::zero(); + for ra in booleanity_evals(evals)? { + booleanity += gamma_power * (ra.square() - ra); + gamma_power *= gamma_sq; + } + Ok(eq_eval * booleanity) +} + +fn expected_hamming_booleanity( + store: &Stage6ValueStore, + evals: &[Stage6NamedEval], + local_point: &[F], +) -> Result { + let hamming = eval_by_name(evals, "stage6.hamming_booleanity.eval.HammingWeight")?; + let lookup_output_point = reverse_slice(store.point("stage6.input.stage1.LookupOutput")?); + require_operand_count( + "stage6.input.stage1.LookupOutput", + local_point.len(), + lookup_output_point.len(), + )?; + let eq_eval = EqPolynomial::::mle(local_point, &lookup_output_point); + Ok((hamming.square() - hamming) * eq_eval) +} + +fn expected_inc_claim_reduction( + store: &Stage6ValueStore, + evals: &[Stage6NamedEval], + local_point: &[F], +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let ram_inc_stage2 = suffix_point( + store.point("stage6.input.stage2.ram_read_write.RamInc")?, + r_cycle_reduced.len(), + "stage6.input.stage2.ram_read_write.RamInc", + )?; + let ram_inc_stage4 = suffix_point( + store.point("stage6.input.stage4.ram_val_check.RamInc")?, + r_cycle_reduced.len(), + "stage6.input.stage4.ram_val_check.RamInc", + )?; + let rd_inc_stage4 = suffix_point( + store.point("stage6.input.stage4.registers_read_write.RdInc")?, + r_cycle_reduced.len(), + "stage6.input.stage4.registers_read_write.RdInc", + )?; + let rd_inc_stage5 = suffix_point( + store.point("stage6.input.stage5.registers_val_evaluation.RdInc")?, + r_cycle_reduced.len(), + "stage6.input.stage5.registers_val_evaluation.RdInc", + )?; + let gamma = store.scalar("stage6.inc_claim_reduction.gamma")?; + let eq_ram_combined = EqPolynomial::::mle(ram_inc_stage2, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(ram_inc_stage4, &r_cycle_reduced); + let eq_rd_combined = EqPolynomial::::mle(rd_inc_stage4, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(rd_inc_stage5, &r_cycle_reduced); + let ram_inc = eval_by_name(evals, "stage6.inc_claim_reduction.eval.RamInc")?; + let rd_inc = eval_by_name(evals, "stage6.inc_claim_reduction.eval.RdInc")?; + Ok(ram_inc * eq_ram_combined + gamma.square() * rd_inc * eq_rd_combined) +} + +fn expected_ram_ra_virtual( + store: &Stage6ValueStore, + evals: &[Stage6NamedEval], + local_point: &[F], +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle = suffix_point( + store.point("stage6.input.stage5.ram_ra_claim_reduction.RamRa")?, + r_cycle_reduced.len(), + "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + )?; + let eq_eval = EqPolynomial::::mle(r_cycle, &r_cycle_reduced); + let ram_ra = indexed_evals_by_prefix_any(evals, "stage6.ram_ra_virtual.eval.RamRa_")? + .into_iter() + .product::(); + Ok(eq_eval * ram_ra) +} + +fn expected_instruction_ra_virtual( + program: &'static Stage6CpuProgramPlan, + store: &Stage6ValueStore, + evals: &[Stage6NamedEval], + local_point: &[F], +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle = suffix_point( + store.point("stage6.input.stage5.instruction_read_raf.InstructionRa_0")?, + r_cycle_reduced.len(), + "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + )?; + let eq_eval = EqPolynomial::::mle(r_cycle, &r_cycle_reduced); + let committed_ra = + indexed_evals_by_prefix_any(evals, "stage6.instruction_ra_virtual.eval.InstructionRa_")?; + let virtual_count = program + .opening_inputs + .iter() + .filter(|input| { + input + .symbol + .starts_with("stage6.input.stage5.instruction_read_raf.InstructionRa_") + }) + .count(); + if virtual_count == 0 || committed_ra.len() % virtual_count != 0 { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.instruction_ra_virtual.eval.InstructionRa_", + expected: virtual_count, + actual: committed_ra.len(), + }); + } + let committed_per_virtual = committed_ra.len() / virtual_count; + let gamma = store.scalar("stage6.instruction_ra_virtual.gamma")?; + let mut gamma_power = F::one(); + let mut value = F::zero(); + for chunk in committed_ra.chunks(committed_per_virtual) { + value += gamma_power * chunk.iter().copied().product::(); + gamma_power *= gamma; + } + Ok(eq_eval * value) +} + +struct Stage6BatchedInstance<'a, F: Field> { + claim: &'a Stage6SumcheckClaimPlan, + relation: Stage6Relation, + offset: usize, + previous_claim: F, + state: Stage6ProverInstanceState, +} + +impl Stage6BatchedInstance<'_, F> { + fn is_active(&self, round: usize) -> bool { + round >= self.offset && round < self.offset + self.claim.num_rounds + } +} + +enum Stage6ProverInstanceState { + Booleanity(BooleanityStage6State), + CoreBooleanity(CoreBooleanityStage6State), + BytecodeReadRaf(BytecodeReadRafStage6State), + Dense(DenseStage6State), +} + +impl Stage6ProverInstanceState { + fn new( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + inputs: &Stage6ProverInputs<'_, F>, + store: &Stage6ValueStore, + active_scale: F, + ) -> Result { + match claim_relation(program, claim)? { + Stage6Relation::BytecodeReadRaf => { + bytecode_read_raf_state(program, claim, inputs, store, active_scale) + } + Stage6Relation::Booleanity => { + booleanity_state(program, claim, inputs, store, active_scale) + } + Stage6Relation::HammingBooleanity => { + hamming_booleanity_state(program, claim, inputs, store, active_scale) + .map(Self::Dense) + } + Stage6Relation::IncClaimReduction => { + inc_claim_reduction_state(program, claim, inputs, store, active_scale) + .map(Self::Dense) + } + Stage6Relation::RamRaVirtual => { + ram_ra_virtual_state(program, claim, inputs, store, active_scale).map(Self::Dense) + } + Stage6Relation::InstructionRaVirtual => { + instruction_ra_virtual_state(program, claim, inputs, store, active_scale) + .map(Self::Dense) + } + relation @ Stage6Relation::Batched => Err(Stage6KernelError::KernelNotImplemented { + abi: relation.symbol(), + }), + } + } + + fn round_poly( + &mut self, + previous_claim: F, + relation: Stage6Relation, + ) -> Result, Stage6KernelError> { + match self { + Self::Booleanity(state) => state.round_poly(previous_claim, relation), + Self::CoreBooleanity(state) => state.round_poly(previous_claim, relation), + Self::BytecodeReadRaf(state) => state.round_poly(previous_claim, relation), + Self::Dense(state) => state.round_poly(previous_claim, relation), + } + } + + fn ingest_challenge(&mut self, challenge: F) { + match self { + Self::Booleanity(state) => state.bind(challenge), + Self::CoreBooleanity(state) => state.bind(challenge), + Self::BytecodeReadRaf(state) => state.bind(challenge), + Self::Dense(state) => state.bind(challenge), + } + } + + fn final_relation_eval(&self, relation: Stage6Relation) -> Result { + match self { + Self::Booleanity(state) => state.final_relation_eval(relation), + Self::CoreBooleanity(state) => state.final_relation_eval(relation), + Self::BytecodeReadRaf(state) => state.final_relation_eval(relation), + Self::Dense(state) => state.final_relation_eval(relation), + } + } + + fn final_evals( + &self, + relation: Stage6Relation, + ) -> Result>, Stage6KernelError> { + match self { + Self::Booleanity(state) => state.final_evals(relation), + Self::CoreBooleanity(state) => state.final_evals(relation), + Self::BytecodeReadRaf(state) => state.final_evals(relation), + Self::Dense(state) => state.final_evals(relation), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum BytecodeReadRafPhase { + Address, + Cycle, +} + +struct BytecodeReadRafStage6State { + log_k: usize, + log_t: usize, + chunk_lens: Vec, + bytecode_ra_chunks: Vec>, + bytecode_ra_indices: Option>>>, + stage_factors: [Vec; BYTECODE_READ_RAF_STAGE_COUNT], + stage_values: [Vec; BYTECODE_READ_RAF_STAGE_COUNT], + entry_trace: Vec, + entry_expected: Vec, + address_challenges: Vec, + cycle_factors: Vec>, + cycle_eqs: [Vec; BYTECODE_READ_RAF_STAGE_COUNT], + cycle_entry_eq: Vec, + bound_stage_values: Option<[F; BYTECODE_READ_RAF_STAGE_COUNT]>, + bound_entry_expected: Option, + outputs: Vec, + gamma_powers: [F; 8], + active_scale: F, + degree_bound: usize, + phase: BytecodeReadRafPhase, +} + +impl BytecodeReadRafStage6State { + #[expect(clippy::too_many_arguments)] + fn new( + data: Stage6BytecodeReadRafData<'_, F>, + bytecode_ra_chunks: &[&[F]], + bytecode_ra_index_chunks: Option<&[&[Option]]>, + bytecode_cycle_indices: Vec, + chunk_lens: Vec, + store: &Stage6ValueStore, + log_k: usize, + log_t: usize, + active_scale: F, + degree_bound: usize, + outputs: Vec, + ) -> Result { + if degree_bound < 2 || degree_bound < chunk_lens.len() + 1 { + return Err(Stage6KernelError::InvalidProof { + driver: Stage6Relation::BytecodeReadRaf.symbol(), + reason: "bytecode read RAF degree bound is too small", + }); + } + let expected_entries = + 1usize + .checked_shl(log_k as u32) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.entries", + expected: usize::BITS as usize, + actual: log_k, + })?; + require_operand_count( + "stage6.bytecode_read_raf.entries", + expected_entries, + data.entries.len(), + )?; + require_operand_count( + "stage6.bytecode_read_raf.trace", + 1usize << log_t, + bytecode_cycle_indices.len(), + )?; + if data.entry_bytecode_index >= expected_entries { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.entry_bytecode_index", + expected: expected_entries, + actual: data.entry_bytecode_index + 1, + }); + } + if bytecode_cycle_indices + .iter() + .any(|&index| index >= expected_entries) + { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRa", + expected: expected_entries, + actual: expected_entries + 1, + }); + } + let bytecode_ra_indices = match bytecode_ra_index_chunks { + Some(chunks) if !chunks.is_empty() => { + validate_bytecode_ra_index_chunks(chunks, &chunk_lens, log_t)?; + Some(chunks.iter().map(|chunk| (*chunk).to_vec()).collect()) + } + _ => None, + }; + + let gamma = store.scalar("stage6.bytecode_read_raf.gamma")?; + let gamma_powers = bytecode_gamma_powers(gamma); + let stage_cycle_points = bytecode_stage_cycle_points(store, log_t)?; + let cycle_eqs = stage_cycle_points.each_ref().map(|point| { + let eq = EqPolynomial::::evals(point, None); + debug_assert_eq!(eq.len(), 1usize << log_t); + eq + }); + + let stage1_gamma = store.scalar("stage6.bytecode_read_raf.stage1_gamma")?; + let stage2_gamma = store.scalar("stage6.bytecode_read_raf.stage2_gamma")?; + let stage3_gamma = store.scalar("stage6.bytecode_read_raf.stage3_gamma")?; + let stage4_gamma = store.scalar("stage6.bytecode_read_raf.stage4_gamma")?; + let stage5_gamma = store.scalar("stage6.bytecode_read_raf.stage5_gamma")?; + let stage1_gamma_powers = field_powers(stage1_gamma, 16); + let stage2_gamma_powers = field_powers(stage2_gamma, 4); + let stage3_gamma_powers = field_powers(stage3_gamma, 9); + let stage4_gamma_powers = field_powers(stage4_gamma, 3); + let stage5_gamma_powers = field_powers(stage5_gamma, data.num_lookup_tables + 2); + let stage4_register_point = + register_prefix_point(store, "stage6.input.stage4.Rs1Ra", log_t)?; + let stage5_register_point = register_prefix_point( + store, + "stage6.input.stage5.registers_val_evaluation.RdWa", + log_t, + )?; + + let mut stage_values: [Vec; BYTECODE_READ_RAF_STAGE_COUNT] = + std::array::from_fn(|_| vec![F::zero(); expected_entries]); + for (index, entry) in data.entries.iter().enumerate() { + let mut values = bytecode_entry_stage_values( + entry, + data.num_lookup_tables, + stage4_register_point, + stage5_register_point, + &stage1_gamma_powers, + &stage2_gamma_powers, + &stage3_gamma_powers, + &stage4_gamma_powers, + &stage5_gamma_powers, + )?; + let int_eval = F::from_u64(index as u64); + values[0] += gamma_powers[5] * int_eval; + values[2] += gamma_powers[4] * int_eval; + for stage in 0..BYTECODE_READ_RAF_STAGE_COUNT { + stage_values[stage][index] = values[stage]; + } + } + + let mut stage_factors: [Vec; BYTECODE_READ_RAF_STAGE_COUNT] = + std::array::from_fn(|_| vec![F::zero(); expected_entries]); + for (cycle, &bytecode_index) in bytecode_cycle_indices.iter().enumerate() { + for stage in 0..BYTECODE_READ_RAF_STAGE_COUNT { + stage_factors[stage][bytecode_index] += cycle_eqs[stage][cycle]; + } + } + + let mut entry_trace = vec![F::zero(); expected_entries]; + entry_trace[bytecode_cycle_indices[0]] = F::one(); + let mut entry_expected = vec![F::zero(); expected_entries]; + entry_expected[data.entry_bytecode_index] = F::one(); + + let mut cycle_entry_eq = vec![F::zero(); 1usize << log_t]; + cycle_entry_eq[0] = F::one(); + + Ok(Self { + log_k, + log_t, + chunk_lens, + bytecode_ra_chunks: bytecode_ra_chunks + .iter() + .map(|chunk| (*chunk).to_vec()) + .collect(), + bytecode_ra_indices, + stage_factors, + stage_values, + entry_trace, + entry_expected, + address_challenges: Vec::with_capacity(log_k), + cycle_factors: Vec::new(), + cycle_eqs, + cycle_entry_eq, + bound_stage_values: None, + bound_entry_expected: None, + outputs, + gamma_powers, + active_scale, + degree_bound, + phase: BytecodeReadRafPhase::Address, + }) + } + + fn round_poly( + &self, + previous_claim: F, + relation: Stage6Relation, + ) -> Result, Stage6KernelError> { + let poly = match self.phase { + BytecodeReadRafPhase::Address => self.address_round_poly(relation)?, + BytecodeReadRafPhase::Cycle => self.cycle_round_poly(relation)?, + }; + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != previous_claim { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 relation input claim mismatch", + }); + } + Ok(poly) + } + + fn address_round_poly( + &self, + relation: Stage6Relation, + ) -> Result, Stage6KernelError> { + let first_len = self.stage_values[0].len(); + if first_len == 0 || !first_len.is_power_of_two() { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF address phase has invalid length", + }); + } + let eval_count = self.degree_bound + 1; + let mut evals = if first_len / 2 >= DENSE_BIND_PAR_THRESHOLD { + (0..first_len / 2) + .into_par_iter() + .fold( + || vec![F::zero(); eval_count], + |mut row_evals, row| { + self.accumulate_address_row(row, &mut row_evals); + row_evals + }, + ) + .reduce( + || vec![F::zero(); eval_count], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + *left += right; + } + left + }, + ) + } else { + let mut evals = vec![F::zero(); eval_count]; + for row in 0..first_len / 2 { + self.accumulate_address_row(row, &mut evals); + } + evals + }; + for eval in &mut evals { + *eval *= self.active_scale; + } + Ok(UnivariatePoly::from_evals(&evals)) + } + + fn accumulate_address_row(&self, row: usize, evals: &mut [F]) { + for (point_index, eval) in evals.iter_mut().enumerate() { + let point = F::from_u64(point_index as u64); + let mut value = F::zero(); + for stage in 0..BYTECODE_READ_RAF_STAGE_COUNT { + let trace_eval = pair_linear_eval(&self.stage_factors[stage], row, point); + let value_eval = pair_linear_eval(&self.stage_values[stage], row, point); + value += self.gamma_powers[stage] * trace_eval * value_eval; + } + let entry_trace = pair_linear_eval(&self.entry_trace, row, point); + let entry_expected = pair_linear_eval(&self.entry_expected, row, point); + value += self.gamma_powers[7] * entry_trace * entry_expected; + *eval += value; + } + } + + fn cycle_round_poly( + &self, + relation: Stage6Relation, + ) -> Result, Stage6KernelError> { + let Some(bound_stage_values) = self.bound_stage_values else { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF cycle phase missing bound values", + }); + }; + let Some(bound_entry_expected) = self.bound_entry_expected else { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF cycle phase missing entry value", + }); + }; + let first_len = self.cycle_factors.first().map_or(0, Vec::len); + if first_len == 0 || !first_len.is_power_of_two() { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF cycle phase has invalid length", + }); + } + let eval_count = self.degree_bound + 1; + let mut evals = if first_len / 2 >= DENSE_BIND_PAR_THRESHOLD { + (0..first_len / 2) + .into_par_iter() + .fold( + || vec![F::zero(); eval_count], + |mut row_evals, row| { + self.accumulate_cycle_row( + row, + bound_stage_values, + bound_entry_expected, + &mut row_evals, + ); + row_evals + }, + ) + .reduce( + || vec![F::zero(); eval_count], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + *left += right; + } + left + }, + ) + } else { + let mut evals = vec![F::zero(); eval_count]; + for row in 0..first_len / 2 { + self.accumulate_cycle_row( + row, + bound_stage_values, + bound_entry_expected, + &mut evals, + ); + } + evals + }; + for eval in &mut evals { + *eval *= self.active_scale; + } + Ok(UnivariatePoly::from_evals(&evals)) + } + + fn accumulate_cycle_row( + &self, + row: usize, + bound_stage_values: [F; BYTECODE_READ_RAF_STAGE_COUNT], + bound_entry_expected: F, + evals: &mut [F], + ) { + for (point_index, eval) in evals.iter_mut().enumerate() { + let point = F::from_u64(point_index as u64); + let mut ra_product = F::one(); + for factor in &self.cycle_factors { + ra_product *= pair_linear_eval(factor, row, point); + } + let mut weighted_value = F::zero(); + for (stage, bound_stage_value) in bound_stage_values.iter().enumerate() { + weighted_value += self.gamma_powers[stage] + * *bound_stage_value + * pair_linear_eval(&self.cycle_eqs[stage], row, point); + } + weighted_value += self.gamma_powers[7] + * bound_entry_expected + * pair_linear_eval(&self.cycle_entry_eq, row, point); + *eval += ra_product * weighted_value; + } + } + + fn bind(&mut self, challenge: F) { + match self.phase { + BytecodeReadRafPhase::Address => self.bind_address(challenge), + BytecodeReadRafPhase::Cycle => self.bind_cycle(challenge), + } + } + + fn bind_address(&mut self, challenge: F) { + for stage in 0..BYTECODE_READ_RAF_STAGE_COUNT { + bind_dense_evals_reuse(&mut self.stage_factors[stage], &mut Vec::new(), challenge); + bind_dense_evals_reuse(&mut self.stage_values[stage], &mut Vec::new(), challenge); + } + bind_dense_evals_reuse(&mut self.entry_trace, &mut Vec::new(), challenge); + bind_dense_evals_reuse(&mut self.entry_expected, &mut Vec::new(), challenge); + self.address_challenges.push(challenge); + if self.address_challenges.len() == self.log_k { + self.init_cycle_phase(); + } + } + + fn init_cycle_phase(&mut self) { + let bound_stage_values = std::array::from_fn(|stage| { + self.stage_values[stage] + .first() + .copied() + .unwrap_or(F::zero()) + }); + let bound_entry_expected = self.entry_expected.first().copied().unwrap_or(F::zero()); + let mut address_point = self.address_challenges.clone(); + address_point.reverse(); + + self.cycle_factors = if let Some(bytecode_ra_indices) = &self.bytecode_ra_indices { + self.sparse_cycle_factors(bytecode_ra_indices, &address_point) + } else { + self.dense_cycle_factors(&address_point) + }; + + self.bound_stage_values = Some(bound_stage_values); + self.bound_entry_expected = Some(bound_entry_expected); + self.stage_factors = std::array::from_fn(|_| Vec::new()); + self.stage_values = std::array::from_fn(|_| Vec::new()); + self.entry_trace.clear(); + self.entry_expected.clear(); + self.phase = BytecodeReadRafPhase::Cycle; + } + + fn sparse_cycle_factors( + &self, + bytecode_ra_indices: &[Vec>], + address_point: &[F], + ) -> Vec> { + let trace_len = 1usize << self.log_t; + bytecode_ra_indices + .iter() + .zip(&self.chunk_lens) + .scan(0usize, |offset, (indices, &chunk_len)| { + let start = *offset; + *offset += chunk_len; + Some((indices, start, chunk_len)) + }) + .map(|(indices, offset, chunk_len)| { + let eq_chunk = + EqPolynomial::::evals(&address_point[offset..offset + chunk_len], None); + indices + .iter() + .take(trace_len) + .map(|index| match index { + Some(index) => eq_chunk[usize::from(*index)], + None => F::zero(), + }) + .collect() + }) + .collect() + } + + fn dense_cycle_factors(&self, address_point: &[F]) -> Vec> { + self.bytecode_ra_chunks + .iter() + .zip(&self.chunk_lens) + .scan(0usize, |offset, (chunk, &chunk_len)| { + let start = *offset; + *offset += chunk_len; + Some((chunk, start, chunk_len)) + }) + .map(|(chunk, offset, chunk_len)| { + let eq_chunk = + EqPolynomial::::evals(&address_point[offset..offset + chunk_len], None); + let trace_len = 1usize << self.log_t; + (0..trace_len) + .map(|cycle| { + eq_chunk + .iter() + .enumerate() + .map(|(chunk_value, &eq)| chunk[chunk_value * trace_len + cycle] * eq) + .sum() + }) + .collect() + }) + .collect() + } + + fn bind_cycle(&mut self, challenge: F) { + for factor in &mut self.cycle_factors { + bind_dense_evals_reuse(factor, &mut Vec::new(), challenge); + } + for eq in &mut self.cycle_eqs { + bind_dense_evals_reuse(eq, &mut Vec::new(), challenge); + } + bind_dense_evals_reuse(&mut self.cycle_entry_eq, &mut Vec::new(), challenge); + } + + fn final_relation_eval(&self, relation: Stage6Relation) -> Result { + let Some(bound_stage_values) = self.bound_stage_values else { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF final eval missing bound values", + }); + }; + let Some(bound_entry_expected) = self.bound_entry_expected else { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF final eval missing entry value", + }); + }; + let mut ra_product = F::one(); + for factor in &self.cycle_factors { + ra_product *= factor + .first() + .copied() + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF final eval missing RA factor", + })?; + } + let mut weighted_value = F::zero(); + for (stage, bound_stage_value) in bound_stage_values.iter().enumerate() { + weighted_value += self.gamma_powers[stage] + * *bound_stage_value + * self.cycle_eqs[stage].first().copied().ok_or( + Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF final eval missing cycle eq", + }, + )?; + } + weighted_value += self.gamma_powers[7] + * bound_entry_expected + * self + .cycle_entry_eq + .first() + .copied() + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF final eval missing entry eq", + })?; + Ok(ra_product * weighted_value) + } + + fn final_evals( + &self, + relation: Stage6Relation, + ) -> Result>, Stage6KernelError> { + self.outputs + .iter() + .map(|output| { + let factor = + output + .factor + .checked_sub(1) + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF output factor underflow", + })?; + let value = self + .cycle_factors + .get(factor) + .and_then(|values| values.first()) + .copied() + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "bytecode read RAF final eval missing output factor", + })?; + Ok(named_eval(output.name, output.oracle, value)) + }) + .collect() + } +} + +#[inline] +fn pair_linear_eval(values: &[F], row: usize, point: F) -> F { + let low = values[row << 1]; + let high = values[(row << 1) + 1]; + low + (high - low) * point +} + +struct BooleanityStage6State { + eq: Vec, + eq_scratch: Vec, + chunks: Vec>, + chunk_scratch: Vec>, + gamma_powers: Vec, + outputs: Vec, + active_scale: F, + degree_bound: usize, +} + +impl BooleanityStage6State { + fn new( + eq: Vec, + chunks: Vec>, + gamma_powers: Vec, + outputs: Vec, + active_scale: F, + degree_bound: usize, + ) -> Result { + if degree_bound < 3 { + return Err(Stage6KernelError::InvalidProof { + driver: Stage6Relation::Booleanity.symbol(), + reason: "booleanity degree bound is too small", + }); + } + require_operand_count("stage6.booleanity.gamma", chunks.len(), gamma_powers.len())?; + if chunks.iter().any(|chunk| chunk.len() != eq.len()) { + return Err(Stage6KernelError::InvalidProof { + driver: Stage6Relation::Booleanity.symbol(), + reason: "booleanity chunks have inconsistent lengths", + }); + } + let chunk_scratch = (0..chunks.len()).map(|_| Vec::new()).collect(); + Ok(Self { + eq, + eq_scratch: Vec::new(), + chunks, + chunk_scratch, + gamma_powers, + outputs, + active_scale, + degree_bound, + }) + } + + fn round_poly( + &self, + previous_claim: F, + relation: Stage6Relation, + ) -> Result, Stage6KernelError> { + let first_len = self.eq.len(); + if first_len == 0 || !first_len.is_power_of_two() { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "booleanity factor has invalid length", + }); + } + let mut evals = if self.degree_bound == 3 { + self.round_evals_degree3(first_len / 2) + } else { + self.round_evals_generic(first_len / 2) + }; + for eval in &mut evals { + *eval *= self.active_scale; + } + let poly = UnivariatePoly::from_evals(&evals); + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != previous_claim { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 relation input claim mismatch", + }); + } + Ok(poly) + } + + fn round_evals_degree3(&self, half_len: usize) -> Vec { + let evals = if half_len >= DENSE_BIND_PAR_THRESHOLD { + (0..half_len) + .into_par_iter() + .fold( + || [F::zero(); 4], + |mut row_evals, row| { + self.accumulate_row_degree3(row, &mut row_evals); + row_evals + }, + ) + .reduce( + || [F::zero(); 4], + |left, right| { + [ + left[0] + right[0], + left[1] + right[1], + left[2] + right[2], + left[3] + right[3], + ] + }, + ) + } else { + let mut evals = [F::zero(); 4]; + for row in 0..half_len { + self.accumulate_row_degree3(row, &mut evals); + } + evals + }; + evals.to_vec() + } + + fn accumulate_row_degree3(&self, row: usize, evals: &mut [F; 4]) { + let eq_low = self.eq[row << 1]; + let eq_high = self.eq[(row << 1) + 1]; + let delta_eq = eq_high - eq_low; + let eq_at_0 = eq_low; + let eq_at_1 = eq_high; + let eq_at_2 = eq_low + delta_eq.mul_u64(2); + let eq_at_3 = eq_low + delta_eq.mul_u64(3); + for (chunk_index, chunk) in self.chunks.iter().enumerate() { + let ra_low = chunk[row << 1]; + let ra_high = chunk[(row << 1) + 1]; + if ra_low == F::zero() && ra_high == F::zero() { + continue; + } + let delta_ra = ra_high - ra_low; + let gamma_power = self.gamma_powers[chunk_index]; + let ra_at_0 = ra_low; + let ra_at_1 = ra_high; + let ra_at_2 = ra_low + delta_ra.mul_u64(2); + let ra_at_3 = ra_low + delta_ra.mul_u64(3); + evals[0] += gamma_power * eq_at_0 * (ra_at_0.square() - ra_at_0); + evals[1] += gamma_power * eq_at_1 * (ra_at_1.square() - ra_at_1); + evals[2] += gamma_power * eq_at_2 * (ra_at_2.square() - ra_at_2); + evals[3] += gamma_power * eq_at_3 * (ra_at_3.square() - ra_at_3); + } + } + + fn round_evals_generic(&self, half_len: usize) -> Vec { + let eval_count = self.degree_bound + 1; + if half_len >= DENSE_BIND_PAR_THRESHOLD { + (0..half_len) + .into_par_iter() + .fold( + || vec![F::zero(); eval_count], + |mut row_evals, row| { + self.accumulate_row_generic(row, &mut row_evals); + row_evals + }, + ) + .reduce( + || vec![F::zero(); eval_count], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + *left += right; + } + left + }, + ) + } else { + let mut evals = vec![F::zero(); eval_count]; + for row in 0..half_len { + self.accumulate_row_generic(row, &mut evals); + } + evals + } + } + + fn accumulate_row_generic(&self, row: usize, evals: &mut [F]) { + let eq_low = self.eq[row << 1]; + let eq_high = self.eq[(row << 1) + 1]; + let delta_eq = eq_high - eq_low; + for (chunk_index, chunk) in self.chunks.iter().enumerate() { + let ra_low = chunk[row << 1]; + let ra_high = chunk[(row << 1) + 1]; + if ra_low == F::zero() && ra_high == F::zero() { + continue; + } + let delta_ra = ra_high - ra_low; + let gamma_power = self.gamma_powers[chunk_index]; + for (point_index, eval) in evals.iter_mut().enumerate() { + let point = F::from_u64(point_index as u64); + let eq_at_point = eq_low + delta_eq * point; + let ra_at_point = ra_low + delta_ra * point; + *eval += gamma_power * eq_at_point * (ra_at_point.square() - ra_at_point); + } + } + } + + fn bind(&mut self, challenge: F) { + bind_dense_evals_reuse(&mut self.eq, &mut self.eq_scratch, challenge); + if self.eq.len() >= DENSE_BIND_PAR_THRESHOLD { + self.chunks + .par_iter_mut() + .zip(self.chunk_scratch.par_iter_mut()) + .for_each(|(chunk, scratch)| { + bind_dense_evals_reuse(chunk, scratch, challenge); + }); + } else { + for (chunk, scratch) in self.chunks.iter_mut().zip(&mut self.chunk_scratch) { + bind_dense_evals_reuse(chunk, scratch, challenge); + } + } + } + + fn factor_eval(&self, index: usize, relation: Stage6Relation) -> Result { + self.chunks + .get(index) + .and_then(|values| values.first()) + .copied() + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty booleanity factor", + }) + } + + fn final_relation_eval(&self, relation: Stage6Relation) -> Result { + let eq = self + .eq + .first() + .copied() + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty booleanity eq factor", + })?; + let mut booleanity = F::zero(); + for (index, &gamma_power) in self.gamma_powers.iter().enumerate() { + let ra = self.factor_eval(index, relation)?; + booleanity += gamma_power * (ra.square() - ra); + } + Ok(eq * booleanity) + } + + fn final_evals( + &self, + relation: Stage6Relation, + ) -> Result>, Stage6KernelError> { + self.outputs + .iter() + .map(|output| { + let factor = + output + .factor + .checked_sub(1) + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "booleanity output factor underflow", + })?; + Ok(named_eval( + output.name, + output.oracle, + self.factor_eval(factor, relation)?, + )) + }) + .collect() + } +} + +struct CoreBooleanityStage6State { + log_k_chunk: usize, + address_round: usize, + b: GruenSplitEqPolynomial, + d: GruenSplitEqPolynomial, + f_table: ExpandingTable, + eq_r_r: F, + g: Vec>, + indices: Vec>>, + h: Option>>, + h_scratch: Vec>, + gamma_powers: Vec, + gamma_powers_inv: Vec, + gamma_powers_square: Vec, + outputs: Vec, + active_scale: F, +} + +impl CoreBooleanityStage6State { + fn new( + r_address: &[F], + r_cycle: &[F], + indices: Vec>>, + gamma: F, + outputs: Vec, + active_scale: F, + ) -> Result { + let log_k_chunk = r_address.len(); + let chunk_domain = 1usize << log_k_chunk; + let trace_len = 1usize << r_cycle.len(); + if indices.iter().any(|chunk| chunk.len() != trace_len) { + return Err(Stage6KernelError::InvalidProof { + driver: Stage6Relation::Booleanity.symbol(), + reason: "booleanity index chunks have inconsistent trace lengths", + }); + } + + let eq_cycle = EqPolynomial::::evals(r_cycle, None); + let mut g = (0..indices.len()) + .map(|_| vec![F::zero(); chunk_domain]) + .collect::>(); + for (chunk_index, chunk) in indices.iter().enumerate() { + for (cycle, index) in chunk.iter().enumerate() { + let Some(index) = index else { + continue; + }; + let index = usize::from(*index); + if index >= chunk_domain { + return Err(Stage6KernelError::InvalidProof { + driver: Stage6Relation::Booleanity.symbol(), + reason: "booleanity index exceeds chunk domain", + }); + } + g[chunk_index][index] += eq_cycle[cycle]; + } + } + + let mut gamma_powers = Vec::with_capacity(indices.len()); + let mut gamma_powers_inv = Vec::with_capacity(indices.len()); + let mut gamma_powers_square = Vec::with_capacity(indices.len()); + let mut gamma_power = F::one(); + let gamma_square = gamma.square(); + let mut gamma_square_power = F::one(); + for _ in 0..indices.len() { + gamma_powers.push(gamma_power); + gamma_powers_inv.push(gamma_power.inverse().ok_or( + Stage6KernelError::InvalidProof { + driver: Stage6Relation::Booleanity.symbol(), + reason: "booleanity gamma power is not invertible", + }, + )?); + gamma_powers_square.push(gamma_square_power); + gamma_power *= gamma; + gamma_square_power *= gamma_square; + } + + let mut f_table = ExpandingTable::new(chunk_domain, BindingOrder::LowToHigh); + f_table.reset(F::one()); + + Ok(Self { + log_k_chunk, + address_round: 0, + b: GruenSplitEqPolynomial::new(r_address, BindingOrder::LowToHigh), + d: GruenSplitEqPolynomial::new(r_cycle, BindingOrder::LowToHigh), + f_table, + eq_r_r: F::zero(), + g, + indices, + h: None, + h_scratch: Vec::new(), + gamma_powers, + gamma_powers_inv, + gamma_powers_square, + outputs, + active_scale, + }) + } + + fn round_poly( + &self, + previous_claim: F, + relation: Stage6Relation, + ) -> Result, Stage6KernelError> { + if relation != Stage6Relation::Booleanity { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "wrong relation for core booleanity state", + }); + } + let mut poly = if self.h.is_none() { + self.address_round_poly(previous_claim) + } else { + self.cycle_round_poly(previous_claim)? + }; + if self.active_scale != F::one() { + poly *= self.active_scale; + } + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != previous_claim { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 booleanity input claim mismatch", + }); + } + Ok(poly) + } + + fn address_round_poly(&self, previous_claim: F) -> UnivariatePoly { + let m = self.address_round + 1; + let f_values = self.f_table.values(); + let quadratic_coeffs = self.b.fold_out_in( + || [F::zero(); 2], + |inner, k_prime, _x_in, e_in| { + let block_start = k_prime << m; + let block_end = block_start + (1 << m); + for (index, g_i) in self.g.iter().enumerate() { + let mut eval_0 = F::zero(); + let mut eval_infty = F::zero(); + for (local, &g_k) in g_i[block_start..block_end].iter().enumerate() { + let k_m = local >> (m - 1); + let f_k = f_values[local & ((1 << (m - 1)) - 1)]; + let g_times_f = g_k * f_k; + let eval_inf = g_times_f * f_k; + if k_m == 0 { + eval_0 += eval_inf - g_times_f; + } + eval_infty += eval_inf; + } + inner[0] += e_in * self.gamma_powers_square[index] * eval_0; + inner[1] += e_in * self.gamma_powers_square[index] * eval_infty; + } + }, + |_x_out, e_out, inner| [e_out * inner[0], e_out * inner[1]], + |left, right| [left[0] + right[0], left[1] + right[1]], + ); + self.b + .gruen_poly_deg_3(quadratic_coeffs[0], quadratic_coeffs[1], previous_claim) + } + + fn cycle_round_poly(&self, previous_claim: F) -> Result, Stage6KernelError> { + let h = self.h.as_ref().ok_or(Stage6KernelError::InvalidProof { + driver: Stage6Relation::Booleanity.symbol(), + reason: "booleanity cycle state is missing", + })?; + let quadratic_coeffs = self.d.fold_out_in( + || [F::zero(); 2], + |inner, j_prime, _x_in, e_in| { + for (index, h_i) in h.iter().enumerate() { + let h_0 = h_i[2 * j_prime]; + let h_1 = h_i[2 * j_prime + 1]; + let delta = h_1 - h_0; + let rho = self.gamma_powers[index]; + inner[0] += e_in * h_0 * (h_0 - rho); + inner[1] += e_in * delta.square(); + } + }, + |_x_out, e_out, inner| [e_out * inner[0], e_out * inner[1]], + |left, right| [left[0] + right[0], left[1] + right[1]], + ); + let adjusted_claim = previous_claim + * self + .eq_r_r + .inverse() + .ok_or(Stage6KernelError::InvalidProof { + driver: Stage6Relation::Booleanity.symbol(), + reason: "booleanity address equality scalar is not invertible", + })?; + Ok(self + .d + .gruen_poly_deg_3(quadratic_coeffs[0], quadratic_coeffs[1], adjusted_claim) + * self.eq_r_r) + } + + fn bind(&mut self, challenge: F) { + if self.h.is_none() { + self.b.bind(challenge); + self.f_table.update(challenge); + self.address_round += 1; + if self.address_round == self.log_k_chunk { + self.eq_r_r = self.b.current_scalar(); + let base_eq = self.f_table.clone_values(); + let h = self + .indices + .iter() + .enumerate() + .map(|(chunk_index, chunk)| { + let rho = self.gamma_powers[chunk_index]; + chunk + .iter() + .map(|index| { + index.map_or(F::zero(), |index| rho * base_eq[usize::from(index)]) + }) + .collect::>() + }) + .collect::>(); + self.h_scratch = (0..h.len()).map(|_| Vec::new()).collect(); + self.h = Some(h); + } + } else { + self.d.bind(challenge); + if let Some(h) = &mut self.h { + for (chunk, scratch) in h.iter_mut().zip(&mut self.h_scratch) { + bind_dense_evals_reuse(chunk, scratch, challenge); + } + } + } + } + + fn factor_eval(&self, index: usize, relation: Stage6Relation) -> Result { + self.h + .as_ref() + .and_then(|h| h.get(index)) + .and_then(|values| values.first()) + .copied() + .map(|value| value * self.gamma_powers_inv[index]) + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty core booleanity factor", + }) + } + + fn final_relation_eval(&self, relation: Stage6Relation) -> Result { + if relation != Stage6Relation::Booleanity { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "wrong relation for core booleanity state", + }); + } + let eq = self.d.current_scalar() * self.eq_r_r; + let h = self.h.as_ref().ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "booleanity cycle state is missing", + })?; + let mut booleanity = F::zero(); + for (index, h_i) in h.iter().enumerate() { + let scaled = h_i + .first() + .copied() + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty core booleanity factor", + })?; + booleanity += scaled * (scaled - self.gamma_powers[index]); + } + Ok(eq * booleanity) + } + + fn final_evals( + &self, + relation: Stage6Relation, + ) -> Result>, Stage6KernelError> { + self.outputs + .iter() + .map(|output| { + let factor = + output + .factor + .checked_sub(1) + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "booleanity output factor underflow", + })?; + Ok(named_eval( + output.name, + output.oracle, + self.factor_eval(factor, relation)?, + )) + }) + .collect() + } +} + +struct DenseStage6State { + factors: Vec>, + factor_scratch: Vec>, + terms: Vec>, + outputs: Vec, + active_scale: F, + degree_bound: usize, +} + +#[derive(Clone)] +struct DenseTerm { + coefficient: F, + factors: Vec, +} + +#[derive(Clone, Copy)] +struct FactorOutput { + name: &'static str, + oracle: &'static str, + factor: usize, +} + +impl DenseStage6State { + fn new( + factors: Vec>, + terms: Vec>, + outputs: Vec, + active_scale: F, + degree_bound: usize, + ) -> Self { + let factor_scratch = (0..factors.len()).map(|_| Vec::new()).collect(); + Self { + factors, + factor_scratch, + terms, + outputs, + active_scale, + degree_bound, + } + } + + fn round_poly( + &self, + previous_claim: F, + relation: Stage6Relation, + ) -> Result, Stage6KernelError> { + let first_len = self.factors.first().map_or(0, Vec::len); + if first_len == 0 || !first_len.is_power_of_two() { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 dense factor has invalid length", + }); + } + if self.factors.iter().any(|factor| factor.len() != first_len) { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 dense factors have inconsistent lengths", + }); + } + let poly = round_poly_from_dense_terms( + &self.factors, + &self.terms, + self.active_scale, + self.degree_bound, + relation, + )?; + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != previous_claim { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 relation input claim mismatch", + }); + } + Ok(poly) + } + + fn bind(&mut self, challenge: F) { + if self.factors.first().map_or(0, Vec::len) / 2 >= DENSE_BIND_PAR_THRESHOLD { + self.factors + .par_iter_mut() + .zip(self.factor_scratch.par_iter_mut()) + .for_each(|(factor, scratch)| { + bind_dense_evals_reuse(factor, scratch, challenge); + }); + } else { + for (factor, scratch) in self.factors.iter_mut().zip(&mut self.factor_scratch) { + bind_dense_evals_reuse(factor, scratch, challenge); + } + } + } + + fn factor_eval(&self, index: usize, relation: Stage6Relation) -> Result { + self.factors + .get(index) + .and_then(|values| values.first()) + .copied() + .ok_or(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "empty stage6 factor", + }) + } + + fn final_relation_eval(&self, relation: Stage6Relation) -> Result { + let mut result = F::zero(); + for term in &self.terms { + let mut value = term.coefficient; + for &factor in &term.factors { + value *= self.factor_eval(factor, relation)?; + } + result += value; + } + Ok(result) + } + + fn final_evals( + &self, + relation: Stage6Relation, + ) -> Result>, Stage6KernelError> { + self.outputs + .iter() + .map(|output| { + Ok(named_eval( + output.name, + output.oracle, + self.factor_eval(output.factor, relation)?, + )) + }) + .collect() + } +} + +fn bytecode_read_raf_state( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + inputs: &Stage6ProverInputs<'_, F>, + store: &Stage6ValueStore, + active_scale: F, +) -> Result, Stage6KernelError> { + let witness = inputs + .bytecode_read_raf + .ok_or(Stage6KernelError::MissingKernelInput { + kernel: "jolt_stage6_batched", + input: "bytecode_read_raf", + })?; + if witness.bytecode_ra_chunks.is_empty() + && matches!(witness.bytecode_ra_index_chunks, None | Some([])) + { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRa", + expected: 1, + actual: 0, + }); + } + + let log_t = stage6_trace_rounds(program)?; + let log_k = + claim + .num_rounds + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.input", + expected: log_t, + actual: claim.num_rounds, + })?; + let domain_len = 1usize.checked_shl(claim.num_rounds as u32).ok_or( + Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.domain", + expected: usize::BITS as usize, + actual: claim.num_rounds, + }, + )?; + let expected_entries = + 1usize + .checked_shl(log_k as u32) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.entries", + expected: usize::BITS as usize, + actual: log_k, + })?; + require_operand_count( + "stage6.bytecode_read_raf.entries", + expected_entries, + witness.data.entries.len(), + )?; + if witness.data.entry_bytecode_index >= expected_entries { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.entry_bytecode_index", + expected: expected_entries, + actual: witness.data.entry_bytecode_index + 1, + }); + } + + let chunk_lens = if witness.bytecode_ra_chunks.is_empty() { + witness + .bytecode_ra_chunk_lens + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRa", + expected: 1, + actual: 0, + })? + .to_vec() + } else { + let mut chunk_lens = Vec::with_capacity(witness.bytecode_ra_chunks.len()); + for chunk in witness.bytecode_ra_chunks { + let rounds = log2_exact(chunk.len(), "stage6.bytecode_read_raf.BytecodeRa")?; + let chunk_len = + rounds + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRa", + expected: log_t, + actual: rounds, + })?; + chunk_lens.push(chunk_len); + } + chunk_lens + }; + let covered_address_len = chunk_lens.iter().sum::(); + require_operand_count( + "stage6.bytecode_read_raf.address_chunks", + log_k, + covered_address_len, + )?; + + let sparse_cycle_indices = match witness.bytecode_ra_index_chunks { + Some(chunks) if !chunks.is_empty() => Some(bytecode_cycle_indices_from_sparse_chunks( + chunks, + &chunk_lens, + log_t, + )?), + _ => None, + }; + + if let Some(bytecode_cycle_indices) = sparse_cycle_indices.or_else(|| { + bytecode_cycle_indices_from_one_hot(witness.bytecode_ra_chunks, &chunk_lens, log_t) + }) { + let outputs = bytecode_read_raf_output_plans(program, chunk_lens.len())?; + return BytecodeReadRafStage6State::new( + witness.data, + witness.bytecode_ra_chunks, + witness.bytecode_ra_index_chunks, + bytecode_cycle_indices, + chunk_lens, + store, + log_k, + log_t, + active_scale, + claim.degree, + outputs, + ) + .map(Stage6ProverInstanceState::BytecodeReadRaf); + } + + bytecode_read_raf_dense_state( + program, + claim, + witness, + store, + active_scale, + log_k, + log_t, + domain_len, + chunk_lens, + ) + .map(Stage6ProverInstanceState::Dense) +} + +#[expect(clippy::too_many_arguments)] +fn bytecode_read_raf_dense_state( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + witness: Stage6BytecodeReadRafWitness<'_, F>, + store: &Stage6ValueStore, + active_scale: F, + log_k: usize, + log_t: usize, + domain_len: usize, + chunk_lens: Vec, +) -> Result, Stage6KernelError> { + let mut factors = Vec::with_capacity(witness.bytecode_ra_chunks.len() + 1); + factors.push(bytecode_weighted_value_factor( + witness.data, + store, + log_k, + log_t, + domain_len, + )?); + factors.extend(expanded_bytecode_ra_factors( + witness.bytecode_ra_chunks, + &chunk_lens, + log_k, + log_t, + domain_len, + )?); + let term_factors = (0..factors.len()).collect::>(); + let outputs = bytecode_read_raf_output_plans(program, witness.bytecode_ra_chunks.len())?; + + Ok(DenseStage6State::new( + factors, + vec![DenseTerm { + coefficient: F::one(), + factors: term_factors, + }], + outputs, + active_scale, + claim.degree, + )) +} + +fn booleanity_state( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + inputs: &Stage6ProverInputs<'_, F>, + store: &Stage6ValueStore, + active_scale: F, +) -> Result, Stage6KernelError> { + let witness = inputs + .booleanity + .ok_or(Stage6KernelError::MissingKernelInput { + kernel: "jolt_stage6_batched", + input: "booleanity", + })?; + let log_t = stage6_trace_rounds(program)?; + if let Some(index_chunks) = witness.index_chunks { + if index_chunks.is_empty() { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.booleanity.index_chunks", + expected: 1, + actual: 0, + }); + } + let trace_len = 1usize << log_t; + for chunk in index_chunks { + require_operand_count("stage6.booleanity.index_chunk", trace_len, chunk.len())?; + } + let log_k_chunk = + claim + .num_rounds + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.booleanity.input", + expected: log_t, + actual: claim.num_rounds, + })?; + let combined_r = booleanity_combined_point(store, log_t, log_k_chunk)?; + require_operand_count( + "stage6.booleanity.combined_point", + claim.num_rounds, + combined_r.len(), + )?; + let r_address = &combined_r[..log_k_chunk]; + let r_cycle = &combined_r[log_k_chunk..]; + return CoreBooleanityStage6State::new( + r_address, + r_cycle, + index_chunks.iter().map(|chunk| (*chunk).to_vec()).collect(), + store.scalar("stage6.booleanity.gamma")?, + booleanity_output_plans(program, index_chunks.len())?, + active_scale, + ) + .map(Stage6ProverInstanceState::CoreBooleanity); + } + + if witness.chunks.is_empty() { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.booleanity.Ra", + expected: 1, + actual: 0, + }); + } + let domain_len = witness.chunks[0].len(); + let booleanity_rounds = log2_exact(domain_len, "stage6.booleanity.trace_len")?; + require_operand_count( + "stage6.booleanity.input", + booleanity_rounds, + claim.num_rounds, + )?; + for chunk in witness.chunks { + require_operand_count("stage6.booleanity.Ra", domain_len, chunk.len())?; + } + let log_k_chunk = + booleanity_rounds + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.booleanity.trace_len", + expected: log_t, + actual: booleanity_rounds, + })?; + let combined_r = booleanity_combined_point(store, log_t, log_k_chunk)?; + let mut eq_point = combined_r[..log_k_chunk].to_vec(); + eq_point.reverse(); + eq_point.extend(combined_r[log_k_chunk..].iter().rev().copied()); + eq_point.reverse(); + let eq = EqPolynomial::::evals(&eq_point, None); + require_operand_count("stage6.booleanity.eq", domain_len, eq.len())?; + + let gamma = store.scalar("stage6.booleanity.gamma")?; + let gamma_sq = gamma.square(); + let mut gamma_power = F::one(); + let mut gamma_powers = Vec::with_capacity(witness.chunks.len()); + for _ in 0..witness.chunks.len() { + gamma_powers.push(gamma_power); + gamma_power *= gamma_sq; + } + + BooleanityStage6State::new( + eq, + witness + .chunks + .iter() + .map(|chunk| (*chunk).to_vec()) + .collect(), + gamma_powers, + booleanity_output_plans(program, witness.chunks.len())?, + active_scale, + claim.degree, + ) + .map(Stage6ProverInstanceState::Booleanity) +} + +fn booleanity_combined_point( + store: &Stage6ValueStore, + log_t: usize, + log_k_chunk: usize, +) -> Result, Stage6KernelError> { + let stage5_point = store.point("stage6.input.stage5.instruction_read_raf.InstructionRa_0")?; + let stage5_address_len = + stage5_point + .len() + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + expected: log_t, + actual: stage5_point.len(), + })?; + if stage5_address_len < log_k_chunk { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + expected: log_k_chunk + log_t, + actual: stage5_point.len(), + }); + } + + let mut stage5_addr = stage5_point[..stage5_address_len].to_vec(); + stage5_addr.reverse(); + let mut combined_r = stage5_addr[stage5_address_len - log_k_chunk..].to_vec(); + combined_r.extend(stage5_point[stage5_address_len..].iter().rev().copied()); + require_operand_count( + "stage6.booleanity.combined_point", + log_k_chunk + log_t, + combined_r.len(), + )?; + Ok(combined_r) +} + +fn hamming_booleanity_state( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + inputs: &Stage6ProverInputs<'_, F>, + store: &Stage6ValueStore, + active_scale: F, +) -> Result, Stage6KernelError> { + let witness = inputs + .hamming_booleanity + .ok_or(Stage6KernelError::MissingKernelInput { + kernel: "jolt_stage6_batched", + input: "hamming_booleanity", + })?; + let trace_rounds = log2_exact( + witness.hamming_weight.len(), + "stage6.hamming_booleanity.trace_len", + )?; + require_operand_count( + "stage6.hamming_booleanity.input", + trace_rounds, + claim.num_rounds, + )?; + let lookup_output_point = store.point("stage6.input.stage1.LookupOutput")?.to_vec(); + require_operand_count( + "stage6.input.stage1.LookupOutput", + trace_rounds, + lookup_output_point.len(), + )?; + let eq_lookup_output = EqPolynomial::::evals(&lookup_output_point, None); + require_operand_count( + "stage6.hamming_booleanity.eq", + witness.hamming_weight.len(), + eq_lookup_output.len(), + )?; + let output = program + .evals + .iter() + .find(|eval| eval.name == "stage6.hamming_booleanity.eval.HammingWeight") + .map(|eval| FactorOutput { + name: eval.name, + oracle: eval.oracle, + factor: 1, + }) + .ok_or(Stage6KernelError::MissingValue { + symbol: "stage6.hamming_booleanity.eval.HammingWeight", + })?; + + Ok(DenseStage6State::new( + vec![eq_lookup_output, witness.hamming_weight.to_vec()], + vec![ + DenseTerm { + coefficient: F::one(), + factors: vec![0, 1, 1], + }, + DenseTerm { + coefficient: -F::one(), + factors: vec![0, 1], + }, + ], + vec![output], + active_scale, + claim.degree, + )) +} + +fn inc_claim_reduction_state( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + inputs: &Stage6ProverInputs<'_, F>, + store: &Stage6ValueStore, + active_scale: F, +) -> Result, Stage6KernelError> { + let witness = inputs + .inc_claim_reduction + .ok_or(Stage6KernelError::MissingKernelInput { + kernel: "jolt_stage6_batched", + input: "inc_claim_reduction", + })?; + let trace_rounds = log2_exact( + witness.ram_inc.len(), + "stage6.inc_claim_reduction.trace_len", + )?; + require_operand_count( + "stage6.inc_claim_reduction.RdInc", + witness.ram_inc.len(), + witness.rd_inc.len(), + )?; + require_operand_count( + "stage6.inc_claim_reduction.input", + trace_rounds, + claim.num_rounds, + )?; + + let ram_inc_stage2 = suffix_point( + store.point("stage6.input.stage2.ram_read_write.RamInc")?, + trace_rounds, + "stage6.input.stage2.ram_read_write.RamInc", + )?; + let ram_inc_stage4 = suffix_point( + store.point("stage6.input.stage4.ram_val_check.RamInc")?, + trace_rounds, + "stage6.input.stage4.ram_val_check.RamInc", + )?; + let rd_inc_stage4 = suffix_point( + store.point("stage6.input.stage4.registers_read_write.RdInc")?, + trace_rounds, + "stage6.input.stage4.registers_read_write.RdInc", + )?; + let rd_inc_stage5 = suffix_point( + store.point("stage6.input.stage5.registers_val_evaluation.RdInc")?, + trace_rounds, + "stage6.input.stage5.registers_val_evaluation.RdInc", + )?; + let gamma = store.scalar("stage6.inc_claim_reduction.gamma")?; + let gamma2 = gamma.square(); + + let mut eq_ram_combined = EqPolynomial::::evals(ram_inc_stage2, None); + let eq_ram_stage4 = EqPolynomial::::evals(ram_inc_stage4, None); + let mut eq_rd_combined = EqPolynomial::::evals(rd_inc_stage4, None); + let eq_rd_stage5 = EqPolynomial::::evals(rd_inc_stage5, None); + require_operand_count( + "stage6.inc_claim_reduction.eq_ram", + witness.ram_inc.len(), + eq_ram_combined.len(), + )?; + require_operand_count( + "stage6.inc_claim_reduction.eq_rd", + witness.rd_inc.len(), + eq_rd_combined.len(), + )?; + for (combined, stage4) in eq_ram_combined.iter_mut().zip(eq_ram_stage4) { + *combined += gamma * stage4; + } + for (combined, stage5) in eq_rd_combined.iter_mut().zip(eq_rd_stage5) { + *combined += gamma * stage5; + } + + Ok(DenseStage6State::new( + vec![ + eq_ram_combined, + witness.ram_inc.to_vec(), + eq_rd_combined, + witness.rd_inc.to_vec(), + ], + vec![ + DenseTerm { + coefficient: F::one(), + factors: vec![0, 1], + }, + DenseTerm { + coefficient: gamma2, + factors: vec![2, 3], + }, + ], + vec![ + factor_output_by_name(program, "stage6.inc_claim_reduction.eval.RamInc", 1)?, + factor_output_by_name(program, "stage6.inc_claim_reduction.eval.RdInc", 3)?, + ], + active_scale, + claim.degree, + )) +} + +fn ram_ra_virtual_state( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + inputs: &Stage6ProverInputs<'_, F>, + store: &Stage6ValueStore, + active_scale: F, +) -> Result, Stage6KernelError> { + let witness = inputs + .ram_ra_virtual + .ok_or(Stage6KernelError::MissingKernelInput { + kernel: "jolt_stage6_batched", + input: "ram_ra_virtual", + })?; + if witness.ram_ra_chunks.is_empty() { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.ram_ra_virtual.RamRa", + expected: 1, + actual: 0, + }); + } + let trace_len = witness.ram_ra_chunks[0].len(); + let trace_rounds = log2_exact(trace_len, "stage6.ram_ra_virtual.trace_len")?; + require_operand_count( + "stage6.ram_ra_virtual.input", + trace_rounds, + claim.num_rounds, + )?; + for chunk in witness.ram_ra_chunks { + require_operand_count("stage6.ram_ra_virtual.RamRa", trace_len, chunk.len())?; + } + + let input_point = store.point("stage6.input.stage5.ram_ra_claim_reduction.RamRa")?; + let r_cycle = suffix_point( + input_point, + trace_rounds, + "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + )?; + let eq_cycle = EqPolynomial::::evals(r_cycle, None); + require_operand_count("stage6.ram_ra_virtual.eq", trace_len, eq_cycle.len())?; + + let mut factors = Vec::with_capacity(witness.ram_ra_chunks.len() + 1); + factors.push(eq_cycle); + factors.extend(witness.ram_ra_chunks.iter().map(|chunk| (*chunk).to_vec())); + let term_factors = (0..factors.len()).collect::>(); + let outputs = ram_ra_virtual_output_plans(program, witness.ram_ra_chunks.len())?; + + Ok(DenseStage6State::new( + factors, + vec![DenseTerm { + coefficient: F::one(), + factors: term_factors, + }], + outputs, + active_scale, + claim.degree, + )) +} + +fn instruction_ra_virtual_state( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, + inputs: &Stage6ProverInputs<'_, F>, + store: &Stage6ValueStore, + active_scale: F, +) -> Result, Stage6KernelError> { + let witness = inputs + .instruction_ra_virtual + .ok_or(Stage6KernelError::MissingKernelInput { + kernel: "jolt_stage6_batched", + input: "instruction_ra_virtual", + })?; + if witness.instruction_ra_chunks.is_empty() + || witness.virtual_count == 0 + || witness.instruction_ra_chunks.len() % witness.virtual_count != 0 + { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.instruction_ra_virtual.InstructionRa", + expected: witness.virtual_count, + actual: witness.instruction_ra_chunks.len(), + }); + } + let trace_len = witness.instruction_ra_chunks[0].len(); + let trace_rounds = log2_exact(trace_len, "stage6.instruction_ra_virtual.trace_len")?; + require_operand_count( + "stage6.instruction_ra_virtual.input", + trace_rounds, + claim.num_rounds, + )?; + for chunk in witness.instruction_ra_chunks { + require_operand_count( + "stage6.instruction_ra_virtual.InstructionRa", + trace_len, + chunk.len(), + )?; + } + + let input_point = store.point("stage6.input.stage5.instruction_read_raf.InstructionRa_0")?; + let r_cycle = suffix_point( + input_point, + trace_rounds, + "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + )?; + let eq_cycle = EqPolynomial::::evals(r_cycle, None); + require_operand_count( + "stage6.instruction_ra_virtual.eq", + trace_len, + eq_cycle.len(), + )?; + + let mut factors = Vec::with_capacity(witness.instruction_ra_chunks.len() + 1); + factors.push(eq_cycle); + factors.extend( + witness + .instruction_ra_chunks + .iter() + .map(|chunk| (*chunk).to_vec()), + ); + + let chunks_per_virtual = witness.instruction_ra_chunks.len() / witness.virtual_count; + let gamma = store.scalar("stage6.instruction_ra_virtual.gamma")?; + let mut gamma_power = F::one(); + let mut terms = Vec::with_capacity(witness.virtual_count); + for virtual_index in 0..witness.virtual_count { + let start = 1 + virtual_index * chunks_per_virtual; + let end = start + chunks_per_virtual; + let mut factors = Vec::with_capacity(chunks_per_virtual + 1); + factors.push(0); + factors.extend(start..end); + terms.push(DenseTerm { + coefficient: gamma_power, + factors, + }); + gamma_power *= gamma; + } + let outputs = + instruction_ra_virtual_output_plans(program, witness.instruction_ra_chunks.len())?; + + Ok(DenseStage6State::new( + factors, + terms, + outputs, + active_scale, + claim.degree, + )) +} + +fn evaluate_stage6_field_expr( + expr: &Stage6FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => single_operand(expr.symbol, operands), + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + formula => { + if let Some(exponent) = formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + Stage6KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + Err(Stage6KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }) + } + } +} + +fn pow_field(base: F, mut exponent: usize) -> F { + let mut result = F::one(); + let mut power = base; + while exponent != 0 { + if exponent & 1 == 1 { + result *= power; + } + power = power.square(); + exponent >>= 1; + } + result +} + +fn single_operand(symbol: &'static str, operands: &[F]) -> Result { + require_operand_count(symbol, 1, operands.len())?; + Ok(operands[0]) +} + +fn require_operand_count( + input: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage6KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage6KernelError::InvalidInputLength { + input, + expected, + actual, + }) + } +} + +fn append_opening_claims( + program: &'static Stage6CpuProgramPlan, + store: &mut Stage6ValueStore, + transcript: &mut T, + evals: &[Stage6NamedEval], +) -> Result>, Stage6KernelError> +where + F: Field, + T: Transcript, +{ + if program.opening_batches.is_empty() { + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } + return Ok(Vec::new()); + } + let _ = store.evaluate_available_points(program)?; + let mut opening_claims = Vec::new(); + let mut seen = program + .opening_inputs + .iter() + .filter_map(|input| { + store + .try_point(input.symbol) + .map(|point| (input.claim_kind, input.oracle, point.to_vec())) + }) + .collect::>(); + for batch in program.opening_batches { + for symbol in batch.claim_operands { + let claim = + find_opening_claim(program, symbol).ok_or(Stage6KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + let point = store.point(claim.point_source)?.to_vec(); + let duplicate = seen.iter().any(|(kind, oracle, seen_point)| { + *kind == claim.claim_kind && *oracle == claim.oracle && seen_point == &point + }); + let value = store.scalar(claim.eval_source)?; + if !duplicate { + append_labeled_scalar(transcript, "opening_claim", &value); + seen.push((claim.claim_kind, claim.oracle, point.clone())); + } + opening_claims.push(Stage6OpeningClaimValue { + symbol: claim.symbol, + oracle: claim.oracle, + domain: claim.domain, + claim_kind: claim.claim_kind, + point: point.clone(), + eval: value, + }); + } + } + Ok(opening_claims) +} + +fn find_opening_claim<'a>( + program: &'a Stage6CpuProgramPlan, + symbol: &str, +) -> Option<&'a Stage6OpeningClaimPlan> { + program + .opening_claims + .iter() + .find(|claim| claim.symbol == symbol) +} + +fn stage6_trace_rounds(program: &'static Stage6CpuProgramPlan) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.relation == "jolt.stage6.hamming_booleanity") + .map(|instance| instance.num_rounds) + .ok_or(Stage6KernelError::MissingValue { + symbol: "stage6.hamming_booleanity.instance", + }) +} + +fn bytecode_gamma_powers(gamma: F) -> [F; 8] { + let mut powers = [F::one(); 8]; + for index in 1..powers.len() { + powers[index] = powers[index - 1] * gamma; + } + powers +} + +fn bytecode_stage_cycle_points( + store: &Stage6ValueStore, + log_t: usize, +) -> Result<[Vec; 5], Stage6KernelError> { + Ok([ + suffix_point( + store.point("stage6.input.stage1.Imm")?, + log_t, + "stage6.input.stage1.Imm", + )? + .to_vec(), + suffix_point( + store.point("stage6.input.stage2.OpFlagJump")?, + log_t, + "stage6.input.stage2.OpFlagJump", + )? + .to_vec(), + suffix_point( + store.point("stage6.input.stage3.spartan_shift.UnexpandedPC")?, + log_t, + "stage6.input.stage3.spartan_shift.UnexpandedPC", + )? + .to_vec(), + suffix_point( + store.point("stage6.input.stage4.Rs1Ra")?, + log_t, + "stage6.input.stage4.Rs1Ra", + )? + .to_vec(), + suffix_point( + store.point("stage6.input.stage5.registers_val_evaluation.RdWa")?, + log_t, + "stage6.input.stage5.registers_val_evaluation.RdWa", + )? + .to_vec(), + ]) +} + +fn bytecode_stage_value_evals( + data: Stage6BytecodeReadRafData<'_, F>, + store: &Stage6ValueStore, + r_address: &[F], + log_t: usize, +) -> Result<[F; 5], Stage6KernelError> { + let expected_len = 1usize.checked_shl(r_address.len() as u32).ok_or( + Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.entries", + expected: usize::BITS as usize, + actual: r_address.len(), + }, + )?; + require_operand_count( + "stage6.bytecode_read_raf.entries", + expected_len, + data.entries.len(), + )?; + if data.entry_bytecode_index >= expected_len { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.entry_bytecode_index", + expected: expected_len, + actual: data.entry_bytecode_index + 1, + }); + } + + let stage1_gamma = store.scalar("stage6.bytecode_read_raf.stage1_gamma")?; + let stage2_gamma = store.scalar("stage6.bytecode_read_raf.stage2_gamma")?; + let stage3_gamma = store.scalar("stage6.bytecode_read_raf.stage3_gamma")?; + let stage4_gamma = store.scalar("stage6.bytecode_read_raf.stage4_gamma")?; + let stage5_gamma = store.scalar("stage6.bytecode_read_raf.stage5_gamma")?; + let stage1_gamma_powers = field_powers(stage1_gamma, 16); + let stage2_gamma_powers = field_powers(stage2_gamma, 4); + let stage3_gamma_powers = field_powers(stage3_gamma, 9); + let stage4_gamma_powers = field_powers(stage4_gamma, 3); + let stage5_gamma_powers = field_powers(stage5_gamma, data.num_lookup_tables + 2); + + let stage4_register_point = register_prefix_point(store, "stage6.input.stage4.Rs1Ra", log_t)?; + let stage5_register_point = register_prefix_point( + store, + "stage6.input.stage5.registers_val_evaluation.RdWa", + log_t, + )?; + + let mut evals = [F::zero(); 5]; + for (index, entry) in data.entries.iter().enumerate() { + let eq = indexed_boolean_eq(index, r_address)?; + let values = bytecode_entry_stage_values( + entry, + data.num_lookup_tables, + stage4_register_point, + stage5_register_point, + &stage1_gamma_powers, + &stage2_gamma_powers, + &stage3_gamma_powers, + &stage4_gamma_powers, + &stage5_gamma_powers, + )?; + for stage in 0..evals.len() { + evals[stage] += eq * values[stage]; + } + } + Ok(evals) +} + +fn bytecode_weighted_value_factor( + data: Stage6BytecodeReadRafData<'_, F>, + store: &Stage6ValueStore, + log_k: usize, + log_t: usize, + domain_len: usize, +) -> Result, Stage6KernelError> { + let gamma = store.scalar("stage6.bytecode_read_raf.gamma")?; + let gamma_powers = bytecode_gamma_powers(gamma); + let stage_cycle_points = bytecode_stage_cycle_points(store, log_t)?; + + let stage1_gamma = store.scalar("stage6.bytecode_read_raf.stage1_gamma")?; + let stage2_gamma = store.scalar("stage6.bytecode_read_raf.stage2_gamma")?; + let stage3_gamma = store.scalar("stage6.bytecode_read_raf.stage3_gamma")?; + let stage4_gamma = store.scalar("stage6.bytecode_read_raf.stage4_gamma")?; + let stage5_gamma = store.scalar("stage6.bytecode_read_raf.stage5_gamma")?; + let stage1_gamma_powers = field_powers(stage1_gamma, 16); + let stage2_gamma_powers = field_powers(stage2_gamma, 4); + let stage3_gamma_powers = field_powers(stage3_gamma, 9); + let stage4_gamma_powers = field_powers(stage4_gamma, 3); + let stage5_gamma_powers = field_powers(stage5_gamma, data.num_lookup_tables + 2); + let stage4_register_point = register_prefix_point(store, "stage6.input.stage4.Rs1Ra", log_t)?; + let stage5_register_point = register_prefix_point( + store, + "stage6.input.stage5.registers_val_evaluation.RdWa", + log_t, + )?; + let stage_values = data + .entries + .iter() + .map(|entry| { + bytecode_entry_stage_values( + entry, + data.num_lookup_tables, + stage4_register_point, + stage5_register_point, + &stage1_gamma_powers, + &stage2_gamma_powers, + &stage3_gamma_powers, + &stage4_gamma_powers, + &stage5_gamma_powers, + ) + }) + .collect::, _>>()?; + + (0..domain_len) + .map(|row| { + let (address_bits, cycle_bits) = normalized_bytecode_row_bits::(row, log_k, log_t)?; + let address_index = bits_to_index(&address_bits); + let int_eval = identity_polynomial_eval(&address_bits); + let int_contrib = [ + gamma_powers[5] * int_eval, + F::zero(), + gamma_powers[4] * int_eval, + F::zero(), + F::zero(), + ]; + let mut value = F::zero(); + for stage in 0..stage_values[address_index].len() { + value += (stage_values[address_index][stage] + int_contrib[stage]) + * EqPolynomial::::mle(&stage_cycle_points[stage], &cycle_bits) + * gamma_powers[stage]; + } + if address_index == data.entry_bytecode_index + && cycle_bits.iter().all(|bit| *bit == F::zero()) + { + value += gamma_powers[7]; + } + Ok(value) + }) + .collect() +} + +fn expanded_bytecode_ra_factors( + chunks: &[&[F]], + chunk_lens: &[usize], + log_k: usize, + log_t: usize, + domain_len: usize, +) -> Result>, Stage6KernelError> { + let mut factors = Vec::with_capacity(chunks.len()); + let mut offset = 0usize; + for (chunk, &chunk_len) in chunks.iter().zip(chunk_lens) { + let factor = (0..domain_len) + .map(|row| { + let (address_bits, cycle_bits) = + normalized_bytecode_row_bits::(row, log_k, log_t)?; + let mut chunk_bits = address_bits[offset..offset + chunk_len].to_vec(); + chunk_bits.extend(cycle_bits); + let index = bits_to_index(&chunk_bits); + chunk + .get(index) + .copied() + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRa", + expected: index + 1, + actual: chunk.len(), + }) + }) + .collect::, _>>()?; + factors.push(factor); + offset += chunk_len; + } + Ok(factors) +} + +fn bytecode_cycle_indices_from_one_hot( + chunks: &[&[F]], + chunk_lens: &[usize], + log_t: usize, +) -> Option> { + let trace_len = 1usize.checked_shl(log_t as u32)?; + let mut indices = vec![0usize; trace_len]; + let mut remaining_address_bits = chunk_lens.iter().sum::(); + for (chunk, &chunk_len) in chunks.iter().zip(chunk_lens) { + remaining_address_bits = remaining_address_bits.checked_sub(chunk_len)?; + let chunk_domain = 1usize.checked_shl(chunk_len as u32)?; + if chunk.len() != chunk_domain.checked_mul(trace_len)? { + return None; + } + for cycle in 0..trace_len { + let mut selected = None; + for chunk_value in 0..chunk_domain { + let value = chunk[chunk_value * trace_len + cycle]; + if value == F::one() { + if selected.replace(chunk_value).is_some() { + return None; + } + } else if value != F::zero() { + return None; + } + } + let selected = selected?; + indices[cycle] |= selected << remaining_address_bits; + } + } + Some(indices) +} + +fn bytecode_cycle_indices_from_sparse_chunks( + chunks: &[&[Option]], + chunk_lens: &[usize], + log_t: usize, +) -> Result, Stage6KernelError> { + validate_bytecode_ra_index_chunks(chunks, chunk_lens, log_t)?; + let trace_len = + 1usize + .checked_shl(log_t as u32) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRaIndex", + expected: usize::BITS as usize, + actual: log_t, + })?; + let mut indices = vec![0usize; trace_len]; + let mut remaining_address_bits = chunk_lens.iter().sum::(); + for (chunk, &chunk_len) in chunks.iter().zip(chunk_lens) { + remaining_address_bits = remaining_address_bits.checked_sub(chunk_len).ok_or( + Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRaIndex", + expected: chunk_len, + actual: remaining_address_bits, + }, + )?; + for (cycle, index) in chunk.iter().enumerate() { + let Some(index) = *index else { + return Err(Stage6KernelError::InvalidProof { + driver: Stage6Relation::BytecodeReadRaf.symbol(), + reason: "bytecode read RAF sparse index is missing", + }); + }; + indices[cycle] |= usize::from(index) << remaining_address_bits; + } + } + Ok(indices) +} + +fn validate_bytecode_ra_index_chunks( + chunks: &[&[Option]], + chunk_lens: &[usize], + log_t: usize, +) -> Result<(), Stage6KernelError> { + require_operand_count( + "stage6.bytecode_read_raf.BytecodeRaIndex", + chunk_lens.len(), + chunks.len(), + )?; + let trace_len = + 1usize + .checked_shl(log_t as u32) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRaIndex", + expected: usize::BITS as usize, + actual: log_t, + })?; + for (chunk, &chunk_len) in chunks.iter().zip(chunk_lens) { + require_operand_count( + "stage6.bytecode_read_raf.BytecodeRaIndex", + trace_len, + chunk.len(), + )?; + let chunk_domain = + 1usize + .checked_shl(chunk_len as u32) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRaIndex", + expected: usize::BITS as usize, + actual: chunk_len, + })?; + for index in *chunk { + let Some(index) = *index else { + return Err(Stage6KernelError::InvalidProof { + driver: Stage6Relation::BytecodeReadRaf.symbol(), + reason: "bytecode read RAF sparse index is missing", + }); + }; + let index = usize::from(index); + if index >= chunk_domain { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.BytecodeRaIndex", + expected: chunk_domain, + actual: index + 1, + }); + } + } + } + Ok(()) +} + +fn normalized_bytecode_row_bits( + row: usize, + log_k: usize, + log_t: usize, +) -> Result<(Vec, Vec), Stage6KernelError> { + let mut raw_bits = index_bits(row, log_k + log_t)?; + raw_bits.reverse(); + let mut cycle_bits = raw_bits.split_off(log_k); + raw_bits.reverse(); + cycle_bits.reverse(); + Ok((raw_bits, cycle_bits)) +} + +#[expect( + clippy::too_many_arguments, + reason = "bytecode stage evaluator mirrors generated stage challenge layout" +)] +fn bytecode_entry_stage_values( + entry: &Stage6BytecodeEntry, + num_lookup_tables: usize, + stage4_register_point: &[F], + stage5_register_point: &[F], + stage1_gamma_powers: &[F], + stage2_gamma_powers: &[F], + stage3_gamma_powers: &[F], + stage4_gamma_powers: &[F], + stage5_gamma_powers: &[F], +) -> Result<[F; 5], Stage6KernelError> { + let mut stage1 = entry.address + entry.imm * stage1_gamma_powers[1]; + for (flag, gamma) in entry + .circuit_flags + .iter() + .zip(stage1_gamma_powers.iter().skip(2)) + { + if *flag { + stage1 += *gamma; + } + } + + let mut stage2 = F::zero(); + if entry.circuit_flags[5] { + stage2 += stage2_gamma_powers[0]; + } + if entry.is_branch { + stage2 += stage2_gamma_powers[1]; + } + if entry.circuit_flags[6] { + stage2 += stage2_gamma_powers[2]; + } + if entry.circuit_flags[7] { + stage2 += stage2_gamma_powers[3]; + } + + let mut stage3 = entry.imm + entry.address * stage3_gamma_powers[1]; + if entry.left_is_rs1 { + stage3 += stage3_gamma_powers[2]; + } + if entry.left_is_pc { + stage3 += stage3_gamma_powers[3]; + } + if entry.right_is_rs2 { + stage3 += stage3_gamma_powers[4]; + } + if entry.right_is_imm { + stage3 += stage3_gamma_powers[5]; + } + if entry.is_noop { + stage3 += stage3_gamma_powers[6]; + } + if entry.circuit_flags[7] { + stage3 += stage3_gamma_powers[7]; + } + if entry.circuit_flags[12] { + stage3 += stage3_gamma_powers[8]; + } + + let stage4 = register_eq(entry.rd, stage4_register_point, "stage6.bytecode.entry.rd")? + * stage4_gamma_powers[0] + + register_eq( + entry.rs1, + stage4_register_point, + "stage6.bytecode.entry.rs1", + )? * stage4_gamma_powers[1] + + register_eq( + entry.rs2, + stage4_register_point, + "stage6.bytecode.entry.rs2", + )? * stage4_gamma_powers[2]; + + let mut stage5 = register_eq(entry.rd, stage5_register_point, "stage6.bytecode.entry.rd")? + * stage5_gamma_powers[0]; + if !entry.is_interleaved { + stage5 += stage5_gamma_powers[1]; + } + if let Some(table) = entry.lookup_table { + if table >= num_lookup_tables { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode.entry.lookup_table", + expected: num_lookup_tables, + actual: table + 1, + }); + } + stage5 += stage5_gamma_powers[2 + table]; + } + + Ok([stage1, stage2, stage3, stage4, stage5]) +} + +fn register_eq( + index: Option, + point: &[F], + input: &'static str, +) -> Result { + let Some(index) = index else { + return Ok(F::zero()); + }; + let register_count = + 1usize + .checked_shl(point.len() as u32) + .ok_or(Stage6KernelError::InvalidInputLength { + input, + expected: usize::BITS as usize, + actual: point.len(), + })?; + if index >= register_count { + return Err(Stage6KernelError::InvalidInputLength { + input, + expected: register_count, + actual: index + 1, + }); + } + indexed_boolean_eq(index, point) +} + +fn indexed_boolean_eq(index: usize, point: &[F]) -> Result { + let bits = index_bits(index, point.len())?; + Ok(EqPolynomial::::mle(&bits, point)) +} + +fn index_bits(index: usize, len: usize) -> Result, Stage6KernelError> { + if len >= usize::BITS as usize { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.index_bits", + expected: usize::BITS as usize - 1, + actual: len, + }); + } + let limit = 1usize << len; + if index >= limit { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.index_bits.index", + expected: limit, + actual: index + 1, + }); + } + Ok((0..len) + .map(|bit| F::from_u64(((index >> (len - 1 - bit)) & 1) as u64)) + .collect()) +} + +fn bits_to_index(bits: &[F]) -> usize { + bits.iter().fold(0usize, |index, bit| { + let bit_value = usize::from(*bit != F::zero()); + (index << 1) | bit_value + }) +} + +fn field_powers(base: F, count: usize) -> Vec { + let mut powers = Vec::with_capacity(count); + let mut power = F::one(); + for _ in 0..count { + powers.push(power); + power *= base; + } + powers +} + +fn identity_polynomial_eval(point: &[F]) -> F { + point + .iter() + .enumerate() + .map(|(index, value)| value.mul_pow_2(point.len() - 1 - index)) + .sum() +} + +fn register_prefix_point<'a, F: Field>( + store: &'a Stage6ValueStore, + symbol: &'static str, + log_t: usize, +) -> Result<&'a [F], Stage6KernelError> { + let point = store.point(symbol)?; + let register_len = + point + .len() + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: symbol, + expected: log_t, + actual: point.len(), + })?; + prefix_point(point, register_len, symbol) +} + +fn normalize_bytecode_read_raf_point( + program: &'static Stage6CpuProgramPlan, + point: &[F], +) -> Result, Stage6KernelError> { + let log_t = stage6_trace_rounds(program)?; + let log_k = point + .len() + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.bytecode_read_raf.point", + expected: log_t, + actual: point.len(), + })?; + let mut normalized = point.to_vec(); + normalized[..log_k].reverse(); + normalized[log_k..].reverse(); + Ok(normalized) +} + +fn normalize_stage6_booleanity_point( + program: &'static Stage6CpuProgramPlan, + point: &[F], +) -> Result, Stage6KernelError> { + let log_t = stage6_trace_rounds(program)?; + let log_k = point + .len() + .checked_sub(log_t) + .ok_or(Stage6KernelError::InvalidInputLength { + input: "stage6.booleanity.point", + expected: log_t, + actual: point.len(), + })?; + let mut normalized = point.to_vec(); + normalized[..log_k].reverse(); + normalized[log_k..].reverse(); + Ok(normalized) +} + +fn normalize_instruction_read_raf_point( + point: &[F], +) -> Result, Stage6KernelError> { + const LOG_K: usize = 128; + if point.len() < LOG_K { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.instruction_read_raf.point", + expected: LOG_K, + actual: point.len(), + }); + } + let mut normalized = point.to_vec(); + normalized[LOG_K..].reverse(); + Ok(normalized) +} + +fn reverse_slice(values: &[F]) -> Vec { + values.iter().rev().copied().collect() +} + +fn prefix_point<'a, F: Field>( + point: &'a [F], + length: usize, + input: &'static str, +) -> Result<&'a [F], Stage6KernelError> { + point + .get(..length) + .filter(|prefix| prefix.len() == length) + .ok_or(Stage6KernelError::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +fn suffix_point<'a, F: Field>( + point: &'a [F], + length: usize, + input: &'static str, +) -> Result<&'a [F], Stage6KernelError> { + point + .get(point.len().saturating_sub(length)..) + .filter(|suffix| suffix.len() == length) + .ok_or(Stage6KernelError::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +fn log2_exact(value: usize, input: &'static str) -> Result { + if value != 0 && value.is_power_of_two() { + Ok(value.trailing_zeros() as usize) + } else { + Err(Stage6KernelError::InvalidInputLength { + input, + expected: value.next_power_of_two(), + actual: value, + }) + } +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients() + .iter() + .rposition(|coefficient| *coefficient != F::zero()) + .unwrap_or(0) +} + +fn append_compressed_univariate_poly( + transcript: &mut T, + label: &'static str, + poly: &UnivariatePoly, +) where + F: Field, + T: Transcript, +{ + let compressed = poly.compress(); + transcript.append(&LabelWithCount( + label.as_bytes(), + compressed.coeffs_except_linear_term().len() as u64, + )); + for coefficient in compressed.coeffs_except_linear_term() { + transcript.append(coefficient); + } +} + +fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &F) +where + F: Field, + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +fn named_eval(name: &'static str, oracle: &'static str, value: F) -> Stage6NamedEval { + Stage6NamedEval { + name, + oracle, + value, + } +} + +fn factor_output_by_name( + program: &'static Stage6CpuProgramPlan, + name: &'static str, + factor: usize, +) -> Result { + program + .evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| FactorOutput { + name: eval.name, + oracle: eval.oracle, + factor, + }) + .ok_or(Stage6KernelError::MissingValue { symbol: name }) +} + +fn ram_ra_virtual_output_plans( + program: &'static Stage6CpuProgramPlan, + chunk_count: usize, +) -> Result, Stage6KernelError> { + indexed_output_plans_by_prefix(program, "stage6.ram_ra_virtual.eval.RamRa_", chunk_count, 1) +} + +fn bytecode_read_raf_output_plans( + program: &'static Stage6CpuProgramPlan, + chunk_count: usize, +) -> Result, Stage6KernelError> { + indexed_output_plans_by_prefix( + program, + "stage6.bytecode_read_raf.eval.BytecodeRa_", + chunk_count, + 1, + ) +} + +fn booleanity_output_plans( + program: &'static Stage6CpuProgramPlan, + chunk_count: usize, +) -> Result, Stage6KernelError> { + let mut evals = program + .evals + .iter() + .filter(|eval| { + eval.name + .starts_with("stage6.booleanity.eval.InstructionRa_") + || eval.name.starts_with("stage6.booleanity.eval.BytecodeRa_") + || eval.name.starts_with("stage6.booleanity.eval.RamRa_") + }) + .collect::>(); + evals.sort_by_key(|eval| eval.index); + if evals.len() != chunk_count { + return Err(Stage6KernelError::InvalidInputLength { + input: "stage6.booleanity.eval", + expected: chunk_count, + actual: evals.len(), + }); + } + evals + .into_iter() + .enumerate() + .map(|(index, eval)| { + if eval.index != index { + return Err(Stage6KernelError::InvalidProof { + driver: "stage6.booleanity.eval", + reason: "non-contiguous indexed eval", + }); + } + Ok(FactorOutput { + name: eval.name, + oracle: eval.oracle, + factor: index + 1, + }) + }) + .collect() +} + +fn instruction_ra_virtual_output_plans( + program: &'static Stage6CpuProgramPlan, + chunk_count: usize, +) -> Result, Stage6KernelError> { + indexed_output_plans_by_prefix( + program, + "stage6.instruction_ra_virtual.eval.InstructionRa_", + chunk_count, + 1, + ) +} + +fn indexed_output_plans_by_prefix( + program: &'static Stage6CpuProgramPlan, + prefix: &'static str, + count: usize, + first_factor: usize, +) -> Result, Stage6KernelError> { + let mut outputs = vec![None; count]; + for eval in program.evals { + let Some(suffix) = eval.name.strip_prefix(prefix) else { + continue; + }; + let index = suffix + .parse::() + .map_err(|_| Stage6KernelError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval suffix", + })?; + if index >= count || outputs[index].is_some() { + return Err(Stage6KernelError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval", + }); + } + outputs[index] = Some(FactorOutput { + name: eval.name, + oracle: eval.oracle, + factor: first_factor + index, + }); + } + outputs + .into_iter() + .map(|output| output.ok_or(Stage6KernelError::MissingValue { symbol: prefix })) + .collect() +} + +fn eval_by_name( + evals: &[Stage6NamedEval], + name: &'static str, +) -> Result { + evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| eval.value) + .ok_or(Stage6KernelError::MissingValue { symbol: name }) +} + +fn indexed_evals_by_prefix_any( + evals: &[Stage6NamedEval], + prefix: &'static str, +) -> Result, Stage6KernelError> { + let mut indexed_values = Vec::new(); + for eval in evals { + let Some(suffix) = eval.name.strip_prefix(prefix) else { + continue; + }; + let index = suffix + .parse::() + .map_err(|_| Stage6KernelError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval suffix", + })?; + if indexed_values + .iter() + .any(|(existing_index, _)| *existing_index == index) + { + return Err(Stage6KernelError::InvalidProof { + driver: prefix, + reason: "duplicate indexed eval", + }); + } + indexed_values.push((index, eval.value)); + } + if indexed_values.is_empty() { + return Err(Stage6KernelError::MissingValue { symbol: prefix }); + } + indexed_values.sort_by_key(|(index, _)| *index); + for (expected, (actual, _)) in indexed_values.iter().enumerate() { + if *actual != expected { + return Err(Stage6KernelError::InvalidProof { + driver: prefix, + reason: "non-contiguous indexed eval", + }); + } + } + Ok(indexed_values.into_iter().map(|(_, value)| value).collect()) +} + +fn booleanity_evals(evals: &[Stage6NamedEval]) -> Result, Stage6KernelError> { + let mut values = indexed_evals_by_prefix_any(evals, "stage6.booleanity.eval.InstructionRa_")?; + values.extend(indexed_evals_by_prefix_any( + evals, + "stage6.booleanity.eval.BytecodeRa_", + )?); + values.extend(indexed_evals_by_prefix_any( + evals, + "stage6.booleanity.eval.RamRa_", + )?); + Ok(values) +} + +fn claim_relation( + program: &'static Stage6CpuProgramPlan, + claim: &Stage6SumcheckClaimPlan, +) -> Result { + if let Some(relation) = claim.relation { + return Stage6Relation::from_symbol(relation) + .ok_or(Stage6KernelError::UnknownRelation { relation }); + } + let kernel_symbol = claim.kernel.ok_or(Stage6KernelError::MissingKernel { + driver: claim.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or(Stage6KernelError::MissingKernel { + driver: claim.symbol, + kernel: kernel_symbol, + })?; + Stage6Relation::from_symbol(kernel.relation).ok_or(Stage6KernelError::UnknownRelation { + relation: kernel.relation, + }) +} + +fn instance_round_offset( + program: &'static Stage6CpuProgramPlan, + driver: &'static str, + claim: &'static str, +) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.source == driver && instance.claim == claim) + .map(|instance| instance.round_offset) + .ok_or(Stage6KernelError::MissingClaim { + batch: driver, + claim, + }) +} + +fn combine_univariate_polys( + polynomials: &[UnivariatePoly], + coefficients: &[F], +) -> UnivariatePoly { + let max_len = polynomials + .iter() + .map(|poly| poly.coefficients().len()) + .max() + .unwrap_or(0); + let mut combined = vec![F::zero(); max_len]; + for (poly, &coefficient) in polynomials.iter().zip(coefficients) { + for (combined, &term) in combined.iter_mut().zip(poly.coefficients()) { + *combined += term * coefficient; + } + } + trim_trailing_zero_coefficients(&mut combined); + UnivariatePoly::new(combined) +} + +fn trim_trailing_zero_coefficients(coefficients: &mut Vec) { + while coefficients.len() > 1 && coefficients.last() == Some(&F::zero()) { + let _ = coefficients.pop(); + } +} + +fn round_poly_from_dense_terms( + factors: &[Vec], + terms: &[DenseTerm], + active_scale: F, + degree_bound: usize, + relation: Stage6Relation, +) -> Result, Stage6KernelError> { + if degree_bound > 5 { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 dense degree bound is unsupported", + }); + } + let half = factors.first().map_or(0, |factor| factor.len() / 2); + for term in terms { + if term.factors.len() > degree_bound { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 dense term exceeds degree bound", + }); + } + if term.factors.iter().any(|factor| *factor >= factors.len()) { + return Err(Stage6KernelError::InvalidProof { + driver: relation.symbol(), + reason: "stage6 dense term references missing factor", + }); + } + } + + let eval_count = degree_bound + 1; + let mut evals = if half >= DENSE_BIND_PAR_THRESHOLD { + (0..half) + .into_par_iter() + .fold( + || vec![F::zero(); eval_count], + |mut row_evals, row| { + accumulate_dense_row_evaluations(factors, terms, row, &mut row_evals); + row_evals + }, + ) + .reduce( + || vec![F::zero(); eval_count], + |mut left, right| { + for (left, right) in left.iter_mut().zip(right) { + *left += right; + } + left + }, + ) + } else { + let mut total = vec![F::zero(); eval_count]; + for row in 0..half { + accumulate_dense_row_evaluations(factors, terms, row, &mut total); + } + total + }; + for eval in &mut evals { + *eval *= active_scale; + } + Ok(UnivariatePoly::interpolate_over_integers(&evals)) +} + +fn accumulate_dense_row_evaluations( + factors: &[Vec], + terms: &[DenseTerm], + row: usize, + evals: &mut [F], +) { + for (point, eval) in evals.iter_mut().enumerate() { + let point = F::from_u64(point as u64); + for term in terms { + let mut term_eval = term.coefficient; + for &factor in &term.factors { + let low = factors[factor][2 * row]; + let high = factors[factor][2 * row + 1]; + term_eval *= low + (high - low) * point; + } + *eval += term_eval; + } + } +} + +pub fn execute_stage6_program( + program: &'static Stage6CpuProgramPlan, + mode: Stage6ExecutionMode, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage6KernelError> +where + F: Field, + T: Transcript, + E: Stage6KernelExecutor, +{ + let mut artifacts = Stage6ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_squeeze(program, step.symbol).ok_or(Stage6KernelError::MissingValue { + symbol: step.symbol, + })?; + let values = transcript.challenge_vector(squeeze.count); + executor.observe_challenge_vector(squeeze, &values)?; + artifacts.challenge_vectors.push(Stage6ChallengeVector { + symbol: squeeze.symbol, + values, + }); + } + "transcript_absorb_bytes" => { + let absorb = find_absorb_bytes(program, step.symbol).ok_or( + Stage6KernelError::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage6_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_driver(program, step.symbol).ok_or(Stage6KernelError::MissingDriver { + driver: step.symbol, + })?; + let kernel_symbol = driver.kernel.ok_or(Stage6KernelError::MissingKernel { + driver: driver.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or( + Stage6KernelError::MissingKernel { + driver: driver.symbol, + kernel: kernel_symbol, + }, + )?; + let batch = + find_batch(program, driver.batch).ok_or(Stage6KernelError::MissingBatch { + driver: driver.symbol, + batch: driver.batch, + })?; + let context = Stage6KernelContext { + mode, + program, + kernel, + batch, + driver, + }; + let output = match mode { + Stage6ExecutionMode::Prover => executor.prove_sumcheck(context, transcript)?, + Stage6ExecutionMode::Verifier => { + executor.verify_sumcheck(context, transcript)? + } + }; + executor.observe_sumcheck_output(&output)?; + artifacts + .opening_claims + .extend(output.opening_claims.clone()); + artifacts.sumchecks.push(output); + } + _ => { + return Err(Stage6KernelError::InvalidProgramStep { + symbol: step.symbol, + kind: step.kind, + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + artifacts.opening_claims = executor.opening_claim_values(program)?; + Ok(artifacts) +} + +fn absorb_stage6_bytes(absorb: &'static Stage6TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn find_squeeze( + program: &'static Stage6CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage6TranscriptSqueezePlan> { + program + .transcript_squeezes + .iter() + .find(|squeeze| squeeze.symbol == symbol) +} + +fn find_absorb_bytes( + program: &'static Stage6CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage6TranscriptAbsorbBytesPlan> { + program + .transcript_absorb_bytes + .iter() + .find(|absorb| absorb.symbol == symbol) +} + +fn find_driver( + program: &'static Stage6CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage6SumcheckDriverPlan> { + program + .drivers + .iter() + .find(|driver| driver.symbol == symbol) +} + +fn find_kernel( + program: &'static Stage6CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage6KernelPlan> { + program + .kernels + .iter() + .find(|kernel| kernel.symbol == symbol) +} + +fn find_batch( + program: &'static Stage6CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage6SumcheckBatchPlan> { + program.batches.iter().find(|batch| batch.symbol == symbol) +} + +#[cfg(test)] +#[expect( + clippy::expect_used, + clippy::unwrap_used, + reason = "tests use panic-on-error helpers to keep failure context concise" +)] +mod tests { + use super::*; + use jolt_field::Fr; + use jolt_transcript::Blake2bTranscript; + + const PARAMS: Stage6Params = Stage6Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }; + const STEPS: &[Stage6ProgramStepPlan] = &[Stage6ProgramStepPlan { + kind: "sumcheck_driver", + symbol: "stage6.sumcheck", + }]; + const CLAIM_INPUT_OPENINGS: &[&str] = &["stage6.input.claim"]; + const CLAIMS: &[Stage6SumcheckClaimPlan] = &[Stage6SumcheckClaimPlan { + symbol: "stage6.claim", + stage: "stage6", + domain: "jolt.test_domain", + num_rounds: 1, + degree: 1, + claim: "stage6.claim", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + claim_value: "stage6.input.claim", + input_openings: CLAIM_INPUT_OPENINGS, + }]; + const KERNELS: &[Stage6KernelPlan] = &[Stage6KernelPlan { + symbol: "jolt.cpu.stage6.batched", + relation: "jolt.stage6.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_batched", + }]; + const BATCHES: &[Stage6SumcheckBatchPlan] = &[Stage6SumcheckBatchPlan { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + count: 0, + ordered_claims: &[], + claim_operands: &[], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[], + }]; + const DRIVERS: &[Stage6SumcheckDriverPlan] = &[Stage6SumcheckDriverPlan { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + batch: "stage6.batch", + policy: "jolt_core_stage6_aligned", + round_schedule: &[], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 0, + degree: 0, + }]; + const PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: &[], + field_constants: &[], + field_exprs: &[], + kernels: KERNELS, + claims: &[], + batches: BATCHES, + drivers: DRIVERS, + instance_results: &[], + evals: &[], + point_zeros: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + const REPLAY_BATCHES: &[Stage6SumcheckBatchPlan] = &[Stage6SumcheckBatchPlan { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + count: 1, + ordered_claims: &["stage6.claim"], + claim_operands: &["stage6.claim"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[1], + }]; + const REPLAY_DRIVERS: &[Stage6SumcheckDriverPlan] = &[Stage6SumcheckDriverPlan { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + batch: "stage6.batch", + policy: "jolt_core_stage6_aligned", + round_schedule: &[1], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 1, + degree: 1, + }]; + const REPLAY_PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: &[], + field_constants: &[], + field_exprs: &[], + kernels: KERNELS, + claims: CLAIMS, + batches: REPLAY_BATCHES, + drivers: REPLAY_DRIVERS, + instance_results: &[], + evals: &[], + point_zeros: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + const BYTECODE_FIELD_CONSTANTS: &[Stage6FieldConstantPlan] = &[ + Stage6FieldConstantPlan { + symbol: "stage6.bytecode_read_raf.gamma", + field: "bn254_fr", + value: 2, + }, + Stage6FieldConstantPlan { + symbol: "stage6.bytecode_read_raf.stage1_gamma", + field: "bn254_fr", + value: 3, + }, + Stage6FieldConstantPlan { + symbol: "stage6.bytecode_read_raf.stage2_gamma", + field: "bn254_fr", + value: 5, + }, + Stage6FieldConstantPlan { + symbol: "stage6.bytecode_read_raf.stage3_gamma", + field: "bn254_fr", + value: 7, + }, + Stage6FieldConstantPlan { + symbol: "stage6.bytecode_read_raf.stage4_gamma", + field: "bn254_fr", + value: 11, + }, + Stage6FieldConstantPlan { + symbol: "stage6.bytecode_read_raf.stage5_gamma", + field: "bn254_fr", + value: 13, + }, + ]; + const BYTECODE_CLAIM_INPUT_OPENINGS: &[&str] = &["stage6.input.bytecode_read_raf_claim"]; + const BYTECODE_CLAIMS: &[Stage6SumcheckClaimPlan] = &[Stage6SumcheckClaimPlan { + symbol: "stage6.bytecode_read_raf.input", + stage: "stage6", + domain: "jolt.stage6_bytecode_read_raf_domain", + num_rounds: 3, + degree: 3, + claim: "stage6.bytecode_read_raf.weighted_prior_stage_values", + kernel: Some("jolt.cpu.stage6.bytecode_read_raf"), + relation: None, + claim_value: "stage6.input.bytecode_read_raf_claim", + input_openings: BYTECODE_CLAIM_INPUT_OPENINGS, + }]; + const BYTECODE_KERNELS: &[Stage6KernelPlan] = &[ + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.bytecode_read_raf", + relation: "jolt.stage6.bytecode_read_raf", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_bytecode_read_raf", + }, + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.batched", + relation: "jolt.stage6.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_batched", + }, + ]; + const BYTECODE_BATCHES: &[Stage6SumcheckBatchPlan] = &[Stage6SumcheckBatchPlan { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + count: 1, + ordered_claims: &["stage6.bytecode_read_raf.input"], + claim_operands: &["stage6.bytecode_read_raf.input"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[3], + }]; + const BYTECODE_DRIVERS: &[Stage6SumcheckDriverPlan] = &[Stage6SumcheckDriverPlan { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + batch: "stage6.batch", + policy: "jolt_core_stage6_aligned", + round_schedule: &[3], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 3, + degree: 3, + }]; + const BYTECODE_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = &[ + Stage6SumcheckInstanceResultPlan { + symbol: "stage6.bytecode_read_raf.instance", + source: "stage6.sumcheck", + claim: "stage6.bytecode_read_raf.input", + relation: "jolt.stage6.bytecode_read_raf", + index: 0, + point_arity: 3, + num_rounds: 3, + round_offset: 0, + point_order: "bytecode_read_raf", + degree: 3, + }, + Stage6SumcheckInstanceResultPlan { + symbol: "stage6.hamming_booleanity.instance", + source: "stage6.sumcheck", + claim: "stage6.hamming_booleanity.input", + relation: "jolt.stage6.hamming_booleanity", + index: 1, + point_arity: 1, + num_rounds: 1, + round_offset: 0, + point_order: "reverse", + degree: 3, + }, + ]; + const BYTECODE_EVALS: &[Stage6SumcheckEvalPlan] = &[ + Stage6SumcheckEvalPlan { + symbol: "stage6.bytecode_read_raf.eval.BytecodeRa_0", + source: "stage6.sumcheck", + name: "stage6.bytecode_read_raf.eval.BytecodeRa_0", + index: 0, + oracle: "BytecodeRa_0", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.bytecode_read_raf.eval.BytecodeRa_1", + source: "stage6.sumcheck", + name: "stage6.bytecode_read_raf.eval.BytecodeRa_1", + index: 1, + oracle: "BytecodeRa_1", + }, + ]; + const BYTECODE_PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: &[], + field_constants: BYTECODE_FIELD_CONSTANTS, + field_exprs: &[], + kernels: BYTECODE_KERNELS, + claims: BYTECODE_CLAIMS, + batches: BYTECODE_BATCHES, + drivers: BYTECODE_DRIVERS, + instance_results: BYTECODE_INSTANCE_RESULTS, + evals: BYTECODE_EVALS, + point_zeros: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + const BOOLEANITY_OPENING_INPUTS: &[Stage6OpeningInputPlan] = &[Stage6OpeningInputPlan { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + source_stage: "stage5", + source_claim: "stage5.instruction_read_raf.opening.InstructionRa_0", + oracle: "InstructionRa_0", + domain: "jolt.stage5_instruction_ra_chunk_domain", + point_arity: 4, + claim_kind: "virtual", + }]; + const BOOLEANITY_FIELD_CONSTANTS: &[Stage6FieldConstantPlan] = &[ + Stage6FieldConstantPlan { + symbol: "stage6.zero", + field: "bn254_fr", + value: 0, + }, + Stage6FieldConstantPlan { + symbol: "stage6.booleanity.gamma", + field: "bn254_fr", + value: 2, + }, + ]; + const BOOLEANITY_CLAIMS: &[Stage6SumcheckClaimPlan] = &[Stage6SumcheckClaimPlan { + symbol: "stage6.booleanity.input", + stage: "stage6", + domain: "jolt.stage6_booleanity_domain", + num_rounds: 3, + degree: 3, + claim: "stage6.booleanity.zero", + kernel: Some("jolt.cpu.stage6.booleanity"), + relation: None, + claim_value: "stage6.zero", + input_openings: &[], + }]; + const BOOLEANITY_KERNELS: &[Stage6KernelPlan] = &[ + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.booleanity", + relation: "jolt.stage6.booleanity", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_booleanity", + }, + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.batched", + relation: "jolt.stage6.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_batched", + }, + ]; + const BOOLEANITY_BATCHES: &[Stage6SumcheckBatchPlan] = &[Stage6SumcheckBatchPlan { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + count: 1, + ordered_claims: &["stage6.booleanity.input"], + claim_operands: &["stage6.booleanity.input"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[3], + }]; + const BOOLEANITY_DRIVERS: &[Stage6SumcheckDriverPlan] = &[Stage6SumcheckDriverPlan { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + batch: "stage6.batch", + policy: "jolt_core_stage6_aligned", + round_schedule: &[3], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 3, + degree: 3, + }]; + const BOOLEANITY_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = &[ + Stage6SumcheckInstanceResultPlan { + symbol: "stage6.booleanity.instance", + source: "stage6.sumcheck", + claim: "stage6.booleanity.input", + relation: "jolt.stage6.booleanity", + index: 0, + point_arity: 3, + num_rounds: 3, + round_offset: 0, + point_order: "stage6_booleanity", + degree: 3, + }, + Stage6SumcheckInstanceResultPlan { + symbol: "stage6.hamming_booleanity.instance", + source: "stage6.sumcheck", + claim: "stage6.hamming_booleanity.input", + relation: "jolt.stage6.hamming_booleanity", + index: 1, + point_arity: 2, + num_rounds: 2, + round_offset: 0, + point_order: "reverse", + degree: 3, + }, + ]; + const BOOLEANITY_EVALS: &[Stage6SumcheckEvalPlan] = &[ + Stage6SumcheckEvalPlan { + symbol: "stage6.booleanity.eval.InstructionRa_0", + source: "stage6.sumcheck", + name: "stage6.booleanity.eval.InstructionRa_0", + index: 0, + oracle: "InstructionRa_0", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.booleanity.eval.BytecodeRa_0", + source: "stage6.sumcheck", + name: "stage6.booleanity.eval.BytecodeRa_0", + index: 1, + oracle: "BytecodeRa_0", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.booleanity.eval.RamRa_0", + source: "stage6.sumcheck", + name: "stage6.booleanity.eval.RamRa_0", + index: 2, + oracle: "RamRa_0", + }, + ]; + const BOOLEANITY_PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: BOOLEANITY_OPENING_INPUTS, + field_constants: BOOLEANITY_FIELD_CONSTANTS, + field_exprs: &[], + kernels: BOOLEANITY_KERNELS, + claims: BOOLEANITY_CLAIMS, + batches: BOOLEANITY_BATCHES, + drivers: BOOLEANITY_DRIVERS, + instance_results: BOOLEANITY_INSTANCE_RESULTS, + evals: BOOLEANITY_EVALS, + point_zeros: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + const HAMMING_OPENING_INPUTS: &[Stage6OpeningInputPlan] = &[Stage6OpeningInputPlan { + symbol: "stage6.input.stage1.LookupOutput", + source_stage: "stage1", + source_claim: "stage1.outer_remaining.opening.LookupOutput", + oracle: "LookupOutput", + domain: "jolt.trace_domain", + point_arity: 2, + claim_kind: "virtual", + }]; + const HAMMING_FIELD_CONSTANTS: &[Stage6FieldConstantPlan] = &[Stage6FieldConstantPlan { + symbol: "stage6.zero", + field: "bn254_fr", + value: 0, + }]; + const HAMMING_CLAIM_INPUT_OPENINGS: &[&str] = &["stage6.input.stage1.LookupOutput"]; + const HAMMING_CLAIMS: &[Stage6SumcheckClaimPlan] = &[Stage6SumcheckClaimPlan { + symbol: "stage6.hamming_booleanity.input", + stage: "stage6", + domain: "jolt.trace_domain", + num_rounds: 2, + degree: 3, + claim: "stage6.hamming_booleanity.zero", + kernel: Some("jolt.cpu.stage6.hamming_booleanity"), + relation: None, + claim_value: "stage6.zero", + input_openings: HAMMING_CLAIM_INPUT_OPENINGS, + }]; + const HAMMING_KERNELS: &[Stage6KernelPlan] = &[ + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.hamming_booleanity", + relation: "jolt.stage6.hamming_booleanity", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_hamming_booleanity", + }, + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.batched", + relation: "jolt.stage6.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_batched", + }, + ]; + const HAMMING_BATCHES: &[Stage6SumcheckBatchPlan] = &[Stage6SumcheckBatchPlan { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + count: 1, + ordered_claims: &["stage6.hamming_booleanity.input"], + claim_operands: &["stage6.hamming_booleanity.input"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[2], + }]; + const HAMMING_DRIVERS: &[Stage6SumcheckDriverPlan] = &[Stage6SumcheckDriverPlan { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + batch: "stage6.batch", + policy: "jolt_core_stage6_aligned", + round_schedule: &[2], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 2, + degree: 3, + }]; + const HAMMING_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = + &[Stage6SumcheckInstanceResultPlan { + symbol: "stage6.hamming_booleanity.instance", + source: "stage6.sumcheck", + claim: "stage6.hamming_booleanity.input", + relation: "jolt.stage6.hamming_booleanity", + index: 0, + point_arity: 2, + num_rounds: 2, + round_offset: 0, + point_order: "reverse", + degree: 3, + }]; + const HAMMING_EVALS: &[Stage6SumcheckEvalPlan] = &[Stage6SumcheckEvalPlan { + symbol: "stage6.hamming_booleanity.eval.HammingWeight", + source: "stage6.sumcheck", + name: "stage6.hamming_booleanity.eval.HammingWeight", + index: 0, + oracle: "HammingWeight", + }]; + const HAMMING_PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: HAMMING_OPENING_INPUTS, + field_constants: HAMMING_FIELD_CONSTANTS, + field_exprs: &[], + kernels: HAMMING_KERNELS, + claims: HAMMING_CLAIMS, + batches: HAMMING_BATCHES, + drivers: HAMMING_DRIVERS, + instance_results: HAMMING_INSTANCE_RESULTS, + evals: HAMMING_EVALS, + point_zeros: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + const INC_FIELD_CONSTANTS: &[Stage6FieldConstantPlan] = &[Stage6FieldConstantPlan { + symbol: "stage6.inc_claim_reduction.gamma", + field: "bn254_fr", + value: 2, + }]; + const INC_CLAIM_INPUT_OPENINGS: &[&str] = &[ + "stage6.input.stage2.ram_read_write.RamInc", + "stage6.input.stage4.ram_val_check.RamInc", + "stage6.input.stage4.registers_read_write.RdInc", + "stage6.input.stage5.registers_val_evaluation.RdInc", + ]; + const INC_CLAIMS: &[Stage6SumcheckClaimPlan] = &[Stage6SumcheckClaimPlan { + symbol: "stage6.inc_claim_reduction.input", + stage: "stage6", + domain: "jolt.trace_domain", + num_rounds: 2, + degree: 2, + claim: "stage6.inc_claim_reduction.weighted_increments", + kernel: Some("jolt.cpu.stage6.inc_claim_reduction"), + relation: None, + claim_value: "stage6.input.inc_claim", + input_openings: INC_CLAIM_INPUT_OPENINGS, + }]; + const INC_KERNELS: &[Stage6KernelPlan] = &[ + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.inc_claim_reduction", + relation: "jolt.stage6.inc_claim_reduction", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_inc_claim_reduction", + }, + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.batched", + relation: "jolt.stage6.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_batched", + }, + ]; + const INC_BATCHES: &[Stage6SumcheckBatchPlan] = &[Stage6SumcheckBatchPlan { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + count: 1, + ordered_claims: &["stage6.inc_claim_reduction.input"], + claim_operands: &["stage6.inc_claim_reduction.input"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[2], + }]; + const INC_DRIVERS: &[Stage6SumcheckDriverPlan] = &[Stage6SumcheckDriverPlan { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + batch: "stage6.batch", + policy: "jolt_core_stage6_aligned", + round_schedule: &[2], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 2, + degree: 2, + }]; + const INC_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = + &[Stage6SumcheckInstanceResultPlan { + symbol: "stage6.inc_claim_reduction.instance", + source: "stage6.sumcheck", + claim: "stage6.inc_claim_reduction.input", + relation: "jolt.stage6.inc_claim_reduction", + index: 0, + point_arity: 2, + num_rounds: 2, + round_offset: 0, + point_order: "reverse", + degree: 2, + }]; + const INC_EVALS: &[Stage6SumcheckEvalPlan] = &[ + Stage6SumcheckEvalPlan { + symbol: "stage6.inc_claim_reduction.eval.RamInc", + source: "stage6.sumcheck", + name: "stage6.inc_claim_reduction.eval.RamInc", + index: 0, + oracle: "RamInc", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.inc_claim_reduction.eval.RdInc", + source: "stage6.sumcheck", + name: "stage6.inc_claim_reduction.eval.RdInc", + index: 1, + oracle: "RdInc", + }, + ]; + const INC_PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: &[], + field_constants: INC_FIELD_CONSTANTS, + field_exprs: &[], + kernels: INC_KERNELS, + claims: INC_CLAIMS, + batches: INC_BATCHES, + drivers: INC_DRIVERS, + instance_results: INC_INSTANCE_RESULTS, + evals: INC_EVALS, + point_zeros: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + const RAM_RA_CLAIM_INPUT_OPENINGS: &[&str] = + &["stage6.input.stage5.ram_ra_claim_reduction.RamRa"]; + const RAM_RA_CLAIMS: &[Stage6SumcheckClaimPlan] = &[Stage6SumcheckClaimPlan { + symbol: "stage6.ram_ra_virtual.input", + stage: "stage6", + domain: "jolt.trace_domain", + num_rounds: 2, + degree: 5, + claim: "stage6.ram_ra_virtual.weighted_ram_ra", + kernel: Some("jolt.cpu.stage6.ram_ra_virtual"), + relation: None, + claim_value: "stage6.input.ram_ra_virtual_claim", + input_openings: RAM_RA_CLAIM_INPUT_OPENINGS, + }]; + const RAM_RA_KERNELS: &[Stage6KernelPlan] = &[ + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.ram_ra_virtual", + relation: "jolt.stage6.ram_ra_virtual", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_ram_ra_virtual", + }, + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.batched", + relation: "jolt.stage6.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_batched", + }, + ]; + const RAM_RA_BATCHES: &[Stage6SumcheckBatchPlan] = &[Stage6SumcheckBatchPlan { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + count: 1, + ordered_claims: &["stage6.ram_ra_virtual.input"], + claim_operands: &["stage6.ram_ra_virtual.input"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[2], + }]; + const RAM_RA_DRIVERS: &[Stage6SumcheckDriverPlan] = &[Stage6SumcheckDriverPlan { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + batch: "stage6.batch", + policy: "jolt_core_stage6_aligned", + round_schedule: &[2], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 2, + degree: 5, + }]; + const RAM_RA_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = + &[Stage6SumcheckInstanceResultPlan { + symbol: "stage6.ram_ra_virtual.instance", + source: "stage6.sumcheck", + claim: "stage6.ram_ra_virtual.input", + relation: "jolt.stage6.ram_ra_virtual", + index: 0, + point_arity: 2, + num_rounds: 2, + round_offset: 0, + point_order: "reverse", + degree: 5, + }]; + const RAM_RA_EVALS: &[Stage6SumcheckEvalPlan] = &[ + Stage6SumcheckEvalPlan { + symbol: "stage6.ram_ra_virtual.eval.RamRa_0", + source: "stage6.sumcheck", + name: "stage6.ram_ra_virtual.eval.RamRa_0", + index: 0, + oracle: "RamRa_0", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.ram_ra_virtual.eval.RamRa_1", + source: "stage6.sumcheck", + name: "stage6.ram_ra_virtual.eval.RamRa_1", + index: 1, + oracle: "RamRa_1", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.ram_ra_virtual.eval.RamRa_2", + source: "stage6.sumcheck", + name: "stage6.ram_ra_virtual.eval.RamRa_2", + index: 2, + oracle: "RamRa_2", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.ram_ra_virtual.eval.RamRa_3", + source: "stage6.sumcheck", + name: "stage6.ram_ra_virtual.eval.RamRa_3", + index: 3, + oracle: "RamRa_3", + }, + ]; + const RAM_RA_PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: &[], + field_constants: &[], + field_exprs: &[], + kernels: RAM_RA_KERNELS, + claims: RAM_RA_CLAIMS, + batches: RAM_RA_BATCHES, + drivers: RAM_RA_DRIVERS, + instance_results: RAM_RA_INSTANCE_RESULTS, + evals: RAM_RA_EVALS, + point_zeros: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + const INSTRUCTION_RA_OPENING_INPUTS: &[Stage6OpeningInputPlan] = &[ + Stage6OpeningInputPlan { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + source_stage: "stage5", + source_claim: "stage5.instruction_read_raf.opening.InstructionRa_0", + oracle: "InstructionRa_0", + domain: "jolt.stage5_instruction_ra_chunk_domain", + point_arity: 2, + claim_kind: "virtual", + }, + Stage6OpeningInputPlan { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", + source_stage: "stage5", + source_claim: "stage5.instruction_read_raf.opening.InstructionRa_1", + oracle: "InstructionRa_1", + domain: "jolt.stage5_instruction_ra_chunk_domain", + point_arity: 2, + claim_kind: "virtual", + }, + ]; + const INSTRUCTION_RA_FIELD_CONSTANTS: &[Stage6FieldConstantPlan] = &[Stage6FieldConstantPlan { + symbol: "stage6.instruction_ra_virtual.gamma", + field: "bn254_fr", + value: 3, + }]; + const INSTRUCTION_RA_CLAIM_INPUT_OPENINGS: &[&str] = &[ + "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + "stage6.input.stage5.instruction_read_raf.InstructionRa_1", + ]; + const INSTRUCTION_RA_CLAIMS: &[Stage6SumcheckClaimPlan] = &[Stage6SumcheckClaimPlan { + symbol: "stage6.instruction_ra_virtual.input", + stage: "stage6", + domain: "jolt.trace_domain", + num_rounds: 2, + degree: 5, + claim: "stage6.instruction_ra_virtual.weighted_instruction_ra", + kernel: Some("jolt.cpu.stage6.instruction_ra_virtual"), + relation: None, + claim_value: "stage6.input.instruction_ra_virtual_claim", + input_openings: INSTRUCTION_RA_CLAIM_INPUT_OPENINGS, + }]; + const INSTRUCTION_RA_KERNELS: &[Stage6KernelPlan] = &[ + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.instruction_ra_virtual", + relation: "jolt.stage6.instruction_ra_virtual", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_instruction_ra_virtual", + }, + Stage6KernelPlan { + symbol: "jolt.cpu.stage6.batched", + relation: "jolt.stage6.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage6_batched", + }, + ]; + const INSTRUCTION_RA_BATCHES: &[Stage6SumcheckBatchPlan] = &[Stage6SumcheckBatchPlan { + symbol: "stage6.batch", + stage: "stage6", + proof_slot: "stage6.sumcheck", + policy: "jolt_core_stage6_aligned", + count: 1, + ordered_claims: &["stage6.instruction_ra_virtual.input"], + claim_operands: &["stage6.instruction_ra_virtual.input"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[2], + }]; + const INSTRUCTION_RA_DRIVERS: &[Stage6SumcheckDriverPlan] = &[Stage6SumcheckDriverPlan { + symbol: "stage6.sumcheck", + stage: "stage6", + proof_slot: "stage6.sumcheck", + kernel: Some("jolt.cpu.stage6.batched"), + relation: None, + batch: "stage6.batch", + policy: "jolt_core_stage6_aligned", + round_schedule: &[2], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 2, + degree: 5, + }]; + const INSTRUCTION_RA_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = + &[Stage6SumcheckInstanceResultPlan { + symbol: "stage6.instruction_ra_virtual.instance", + source: "stage6.sumcheck", + claim: "stage6.instruction_ra_virtual.input", + relation: "jolt.stage6.instruction_ra_virtual", + index: 0, + point_arity: 2, + num_rounds: 2, + round_offset: 0, + point_order: "reverse", + degree: 5, + }]; + const INSTRUCTION_RA_EVALS: &[Stage6SumcheckEvalPlan] = &[ + Stage6SumcheckEvalPlan { + symbol: "stage6.instruction_ra_virtual.eval.InstructionRa_0", + source: "stage6.sumcheck", + name: "stage6.instruction_ra_virtual.eval.InstructionRa_0", + index: 0, + oracle: "InstructionRa_0", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.instruction_ra_virtual.eval.InstructionRa_1", + source: "stage6.sumcheck", + name: "stage6.instruction_ra_virtual.eval.InstructionRa_1", + index: 1, + oracle: "InstructionRa_1", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.instruction_ra_virtual.eval.InstructionRa_2", + source: "stage6.sumcheck", + name: "stage6.instruction_ra_virtual.eval.InstructionRa_2", + index: 2, + oracle: "InstructionRa_2", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.instruction_ra_virtual.eval.InstructionRa_3", + source: "stage6.sumcheck", + name: "stage6.instruction_ra_virtual.eval.InstructionRa_3", + index: 3, + oracle: "InstructionRa_3", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.instruction_ra_virtual.eval.InstructionRa_4", + source: "stage6.sumcheck", + name: "stage6.instruction_ra_virtual.eval.InstructionRa_4", + index: 4, + oracle: "InstructionRa_4", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.instruction_ra_virtual.eval.InstructionRa_5", + source: "stage6.sumcheck", + name: "stage6.instruction_ra_virtual.eval.InstructionRa_5", + index: 5, + oracle: "InstructionRa_5", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.instruction_ra_virtual.eval.InstructionRa_6", + source: "stage6.sumcheck", + name: "stage6.instruction_ra_virtual.eval.InstructionRa_6", + index: 6, + oracle: "InstructionRa_6", + }, + Stage6SumcheckEvalPlan { + symbol: "stage6.instruction_ra_virtual.eval.InstructionRa_7", + source: "stage6.sumcheck", + name: "stage6.instruction_ra_virtual.eval.InstructionRa_7", + index: 7, + oracle: "InstructionRa_7", + }, + ]; + const INSTRUCTION_RA_PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: INSTRUCTION_RA_OPENING_INPUTS, + field_constants: INSTRUCTION_RA_FIELD_CONSTANTS, + field_exprs: &[], + kernels: INSTRUCTION_RA_KERNELS, + claims: INSTRUCTION_RA_CLAIMS, + batches: INSTRUCTION_RA_BATCHES, + drivers: INSTRUCTION_RA_DRIVERS, + instance_results: INSTRUCTION_RA_INSTANCE_RESULTS, + evals: INSTRUCTION_RA_EVALS, + point_zeros: &[], + point_slices: &[], + point_concats: &[], + opening_claims: &[], + opening_equalities: &[], + opening_batches: &[], + }; + + #[test] + fn stage6_symbols_parse_as_stage6_relations_and_abis() { + assert_eq!( + Stage6Relation::from_symbol("jolt.stage6.bytecode_read_raf"), + Some(Stage6Relation::BytecodeReadRaf) + ); + assert_eq!( + Stage6Relation::from_symbol("jolt.stage6.batched"), + Some(Stage6Relation::Batched) + ); + assert_eq!( + Stage6KernelAbi::from_name("jolt_stage6_bytecode_read_raf"), + Some(Stage6KernelAbi::BytecodeReadRaf) + ); + assert_eq!( + Stage6KernelAbi::from_name("jolt_stage6_batched"), + Some(Stage6KernelAbi::Batched) + ); + assert_eq!(Stage6Relation::from_symbol("jolt.stage5.batched"), None); + assert_eq!(Stage6KernelAbi::from_name("jolt_stage5_batched"), None); + } + + #[test] + fn unsupported_executor_reaches_stage6_batched_abi() { + let mut executor = UnsupportedStage6KernelExecutor; + let mut transcript = Blake2bTranscript::::new(b"stage6_test"); + let error = execute_stage6_program( + &PROGRAM, + Stage6ExecutionMode::Prover, + &mut executor, + &mut transcript, + ) + .expect_err("stage6 kernels are not implemented yet"); + + assert_eq!( + error, + Stage6KernelError::KernelNotImplemented { + abi: "jolt_stage6_batched" + } + ); + } + + #[test] + fn proof_carrying_executor_replays_stage6_sumcheck_transcript() { + let input_claim = Fr::from_u64(3); + let (proof, opening_inputs) = replay_proof(input_claim); + let mut executor = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut transcript = Blake2bTranscript::::new(b"stage6_test"); + + let artifacts = execute_stage6_program( + &REPLAY_PROGRAM, + Stage6ExecutionMode::Prover, + &mut executor, + &mut transcript, + ) + .expect("proof-carrying Stage 6 replay succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].point, proof.sumchecks[0].point); + } + + #[test] + fn proof_carrying_executor_rejects_bad_round_polynomial() { + let input_claim = Fr::from_u64(3); + let (mut proof, opening_inputs) = replay_proof(input_claim); + let mut coefficients = proof.sumchecks[0].proof.round_polynomials[0] + .coefficients() + .to_vec(); + coefficients[0] += Fr::from_u64(1); + proof.sumchecks[0].proof.round_polynomials[0] = UnivariatePoly::new(coefficients); + let mut executor = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut transcript = Blake2bTranscript::::new(b"stage6_test"); + + let error = execute_stage6_program( + &REPLAY_PROGRAM, + Stage6ExecutionMode::Prover, + &mut executor, + &mut transcript, + ) + .expect_err("tampered Stage 6 replay is rejected"); + + assert_eq!( + error, + Stage6KernelError::InvalidProof { + driver: "stage6.sumcheck", + reason: "batched round check failed" + } + ); + } + + #[test] + fn bytecode_read_raf_prover_produces_verifiable_sumcheck() { + let entries = bytecode_entries(); + let data = Stage6BytecodeReadRafData { + entries: &entries, + entry_bytecode_index: 2, + num_lookup_tables: 2, + }; + let bytecode_ra_0 = frs(&[1, 2, 3, 4]); + let bytecode_ra_1 = frs(&[2, 1, 4, 3]); + let bytecode_ra_chunks: [&[Fr]; 2] = [&bytecode_ra_0, &bytecode_ra_1]; + let mut opening_inputs = bytecode_opening_inputs(); + let input_claim = bytecode_read_raf_claim(data, &bytecode_ra_chunks, &opening_inputs); + opening_inputs.push(Stage6OpeningInputValue { + symbol: "stage6.input.bytecode_read_raf_claim", + point: Vec::new(), + eval: input_claim, + }); + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_bytecode_read_raf( + Stage6BytecodeReadRafWitness { + data, + bytecode_ra_chunks: &bytecode_ra_chunks, + bytecode_ra_chunk_lens: None, + bytecode_ra_index_chunks: None, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let artifacts = execute_stage6_program( + &BYTECODE_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("bytecode read RAF prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 2); + + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs) + .with_bytecode_read_raf_data(data); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let verified = execute_stage6_program( + &BYTECODE_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("proof-carrying verifier accepts bytecode read RAF output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn bytecode_read_raf_prover_accepts_sparse_index_chunks() { + let entries = bytecode_entries(); + let data = Stage6BytecodeReadRafData { + entries: &entries, + entry_bytecode_index: 2, + num_lookup_tables: 2, + }; + let bytecode_ra_0 = frs(&[0, 1, 1, 0]); + let bytecode_ra_1 = frs(&[1, 0, 0, 1]); + let bytecode_ra_chunks: [&[Fr]; 2] = [&bytecode_ra_0, &bytecode_ra_1]; + let bytecode_ra_index_0 = [Some(1u8), Some(0u8)]; + let bytecode_ra_index_1 = [Some(0u8), Some(1u8)]; + let bytecode_ra_index_chunks: [&[Option]; 2] = + [&bytecode_ra_index_0, &bytecode_ra_index_1]; + let bytecode_ra_chunk_lens = [1usize, 1usize]; + let sparse_only_bytecode_ra_chunks: [&[Fr]; 0] = []; + let mut opening_inputs = bytecode_opening_inputs(); + let input_claim = bytecode_read_raf_claim(data, &bytecode_ra_chunks, &opening_inputs); + opening_inputs.push(Stage6OpeningInputValue { + symbol: "stage6.input.bytecode_read_raf_claim", + point: Vec::new(), + eval: input_claim, + }); + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_bytecode_read_raf( + Stage6BytecodeReadRafWitness { + data, + bytecode_ra_chunks: &sparse_only_bytecode_ra_chunks, + bytecode_ra_chunk_lens: Some(&bytecode_ra_chunk_lens), + bytecode_ra_index_chunks: Some(&bytecode_ra_index_chunks), + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let artifacts = execute_stage6_program( + &BYTECODE_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("bytecode read RAF prover accepts sparse index chunks"); + + assert_eq!( + bytecode_cycle_indices_from_sparse_chunks(&bytecode_ra_index_chunks, &[1, 1], 1) + .expect("sparse bytecode indices are valid"), + vec![2, 1] + ); + + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs) + .with_bytecode_read_raf_data(data); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let verified = execute_stage6_program( + &BYTECODE_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("proof-carrying verifier accepts sparse bytecode read RAF output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn bytecode_read_raf_verifier_rejects_bad_final_eval() { + let entries = bytecode_entries(); + let data = Stage6BytecodeReadRafData { + entries: &entries, + entry_bytecode_index: 2, + num_lookup_tables: 2, + }; + let bytecode_ra_0 = frs(&[1, 2, 3, 4]); + let bytecode_ra_1 = frs(&[2, 1, 4, 3]); + let bytecode_ra_chunks: [&[Fr]; 2] = [&bytecode_ra_0, &bytecode_ra_1]; + let mut opening_inputs = bytecode_opening_inputs(); + let input_claim = bytecode_read_raf_claim(data, &bytecode_ra_chunks, &opening_inputs); + opening_inputs.push(Stage6OpeningInputValue { + symbol: "stage6.input.bytecode_read_raf_claim", + point: Vec::new(), + eval: input_claim, + }); + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_bytecode_read_raf( + Stage6BytecodeReadRafWitness { + data, + bytecode_ra_chunks: &bytecode_ra_chunks, + bytecode_ra_chunk_lens: None, + bytecode_ra_index_chunks: None, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let mut artifacts = execute_stage6_program( + &BYTECODE_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("bytecode read RAF prover succeeds"); + + artifacts.sumchecks[0].evals[0].value += Fr::from_u64(1); + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks, + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs) + .with_bytecode_read_raf_data(data); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let error = execute_stage6_program( + &BYTECODE_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect_err("tampered bytecode read RAF eval is rejected"); + + assert_eq!( + error, + Stage6KernelError::InvalidProof { + driver: "stage6.sumcheck", + reason: "batched output claim mismatch" + } + ); + } + + #[test] + fn booleanity_prover_produces_verifiable_sumcheck() { + let instruction_ra = frs(&[0, 1, 0, 1, 1, 0, 1, 0]); + let bytecode_ra = frs(&[1, 0, 1, 0, 0, 1, 0, 1]); + let ram_ra = frs(&[0, 0, 1, 1, 0, 1, 1, 0]); + let chunks: [&[Fr]; 3] = [&instruction_ra, &bytecode_ra, &ram_ra]; + let stage5_point = frs(&[11, 13, 2, 3]); + let opening_inputs = vec![Stage6OpeningInputValue { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + point: stage5_point, + eval: Fr::from_u64(0), + }]; + let prover_inputs = + Stage6ProverInputs::new(&opening_inputs).with_booleanity(Stage6BooleanityWitness { + chunks: &chunks, + index_chunks: None, + }); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let artifacts = execute_stage6_program( + &BOOLEANITY_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("booleanity prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 3); + + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let verified = execute_stage6_program( + &BOOLEANITY_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("proof-carrying verifier accepts booleanity output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn booleanity_verifier_rejects_bad_final_eval() { + let instruction_ra = frs(&[0, 1, 0, 1, 1, 0, 1, 0]); + let bytecode_ra = frs(&[1, 0, 1, 0, 0, 1, 0, 1]); + let ram_ra = frs(&[0, 0, 1, 1, 0, 1, 1, 0]); + let chunks: [&[Fr]; 3] = [&instruction_ra, &bytecode_ra, &ram_ra]; + let stage5_point = frs(&[11, 13, 2, 3]); + let opening_inputs = vec![Stage6OpeningInputValue { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + point: stage5_point, + eval: Fr::from_u64(0), + }]; + let prover_inputs = + Stage6ProverInputs::new(&opening_inputs).with_booleanity(Stage6BooleanityWitness { + chunks: &chunks, + index_chunks: None, + }); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let mut artifacts = execute_stage6_program( + &BOOLEANITY_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("booleanity prover succeeds"); + + artifacts.sumchecks[0].evals[0].value += Fr::from_u64(1); + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks, + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let error = execute_stage6_program( + &BOOLEANITY_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect_err("tampered booleanity eval is rejected"); + + assert_eq!( + error, + Stage6KernelError::InvalidProof { + driver: "stage6.sumcheck", + reason: "batched output claim mismatch" + } + ); + } + + #[test] + fn hamming_booleanity_prover_produces_verifiable_sumcheck() { + let lookup_output_point = frs(&[3, 5]); + let hamming_weight = frs(&[0, 1, 1, 0]); + let opening_inputs = vec![Stage6OpeningInputValue { + symbol: "stage6.input.stage1.LookupOutput", + point: lookup_output_point, + eval: Fr::from_u64(9), + }]; + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_hamming_booleanity( + Stage6HammingBooleanityWitness { + hamming_weight: &hamming_weight, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let artifacts = execute_stage6_program( + &HAMMING_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("hamming booleanity prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 1); + + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let verified = execute_stage6_program( + &HAMMING_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("proof-carrying verifier accepts hamming output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn stage6_witness_helper_adapts_kernel_opening_inputs() { + let params = Stage6WitnessParams { + trace_len: 2, + log_k_chunk: 1, + log_k_bytecode: 1, + log_k_ram: 1, + lookups_ra_virtual_log_k_chunk: 1, + instruction_d: 1, + instruction_ra_virtual_d: 1, + bytecode_d: 1, + ram_d: 1, + }; + let cycle_inputs = [ + CycleInput { + dense: [2, 3], + one_hot: [Some(1), Some(0), Some(1)], + }, + CycleInput::PADDING, + ]; + let opening_inputs = [ + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + point: frs(&[5]), + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + point: frs(&[7]), + eval: Fr::from_u64(0), + }, + ]; + + let witness = stage6_witness_from_opening_inputs(params, &cycle_inputs, &opening_inputs); + + assert_eq!(witness.ram_ra_virtual.len(), 1); + assert_eq!(witness.instruction_ra_virtual.len(), 1); + assert_eq!(witness.rd_inc, vec![Fr::from_u64(2), Fr::from_u64(0)]); + assert_eq!(witness.ram_inc, vec![Fr::from_u64(3), Fr::from_u64(0)]); + } + + #[test] + fn hamming_booleanity_prover_requires_witness() { + let opening_inputs = vec![Stage6OpeningInputValue { + symbol: "stage6.input.stage1.LookupOutput", + point: frs(&[3, 5]), + eval: Fr::from_u64(9), + }]; + let mut prover = Stage6ProverKernelExecutor::new(Stage6ProverInputs::new(&opening_inputs)); + let mut transcript = Blake2bTranscript::::new(b"stage6_test"); + let error = execute_stage6_program( + &HAMMING_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut transcript, + ) + .expect_err("hamming booleanity witness is required"); + + assert_eq!( + error, + Stage6KernelError::MissingKernelInput { + kernel: "jolt_stage6_batched", + input: "hamming_booleanity" + } + ); + } + + #[test] + fn hamming_booleanity_verifier_rejects_bad_final_eval() { + let lookup_output_point = frs(&[3, 5]); + let hamming_weight = frs(&[0, 1, 1, 0]); + let opening_inputs = vec![Stage6OpeningInputValue { + symbol: "stage6.input.stage1.LookupOutput", + point: lookup_output_point, + eval: Fr::from_u64(9), + }]; + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_hamming_booleanity( + Stage6HammingBooleanityWitness { + hamming_weight: &hamming_weight, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let mut artifacts = execute_stage6_program( + &HAMMING_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("hamming booleanity prover succeeds"); + + artifacts.sumchecks[0].evals[0].value += Fr::from_u64(1); + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks, + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let error = execute_stage6_program( + &HAMMING_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect_err("tampered hamming eval is rejected"); + + assert_eq!( + error, + Stage6KernelError::InvalidProof { + driver: "stage6.sumcheck", + reason: "batched output claim mismatch" + } + ); + } + + #[test] + fn inc_claim_reduction_prover_produces_verifiable_sumcheck() { + let ram_inc = frs(&[1, 3, 5, 7]); + let rd_inc = frs(&[2, 4, 6, 8]); + let ram_inc_stage2_point = frs(&[2, 3]); + let ram_inc_stage4_point = frs(&[5, 7]); + let rd_inc_stage4_point = frs(&[11, 13]); + let rd_inc_stage5_point = frs(&[17, 19]); + let gamma = Fr::from_u64(2); + let input_claim = multilinear_eval(&ram_inc, &ram_inc_stage2_point) + + gamma * multilinear_eval(&ram_inc, &ram_inc_stage4_point) + + gamma.square() * multilinear_eval(&rd_inc, &rd_inc_stage4_point) + + gamma.square() * gamma * multilinear_eval(&rd_inc, &rd_inc_stage5_point); + let opening_inputs = vec![ + Stage6OpeningInputValue { + symbol: "stage6.input.stage2.ram_read_write.RamInc", + point: ram_inc_stage2_point.clone(), + eval: multilinear_eval(&ram_inc, &ram_inc_stage2_point), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage4.ram_val_check.RamInc", + point: ram_inc_stage4_point.clone(), + eval: multilinear_eval(&ram_inc, &ram_inc_stage4_point), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage4.registers_read_write.RdInc", + point: rd_inc_stage4_point.clone(), + eval: multilinear_eval(&rd_inc, &rd_inc_stage4_point), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.registers_val_evaluation.RdInc", + point: rd_inc_stage5_point.clone(), + eval: multilinear_eval(&rd_inc, &rd_inc_stage5_point), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.inc_claim", + point: Vec::new(), + eval: input_claim, + }, + ]; + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_inc_claim_reduction( + Stage6IncClaimReductionWitness { + ram_inc: &ram_inc, + rd_inc: &rd_inc, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let artifacts = execute_stage6_program( + &INC_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("increment claim-reduction prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 2); + + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let verified = execute_stage6_program( + &INC_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("proof-carrying verifier accepts increment output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn inc_claim_reduction_verifier_rejects_bad_final_eval() { + let ram_inc = frs(&[1, 3, 5, 7]); + let rd_inc = frs(&[2, 4, 6, 8]); + let ram_inc_stage2_point = frs(&[2, 3]); + let ram_inc_stage4_point = frs(&[5, 7]); + let rd_inc_stage4_point = frs(&[11, 13]); + let rd_inc_stage5_point = frs(&[17, 19]); + let gamma = Fr::from_u64(2); + let input_claim = multilinear_eval(&ram_inc, &ram_inc_stage2_point) + + gamma * multilinear_eval(&ram_inc, &ram_inc_stage4_point) + + gamma.square() * multilinear_eval(&rd_inc, &rd_inc_stage4_point) + + gamma.square() * gamma * multilinear_eval(&rd_inc, &rd_inc_stage5_point); + let opening_inputs = vec![ + Stage6OpeningInputValue { + symbol: "stage6.input.stage2.ram_read_write.RamInc", + point: ram_inc_stage2_point, + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage4.ram_val_check.RamInc", + point: ram_inc_stage4_point, + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage4.registers_read_write.RdInc", + point: rd_inc_stage4_point, + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.registers_val_evaluation.RdInc", + point: rd_inc_stage5_point, + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.inc_claim", + point: Vec::new(), + eval: input_claim, + }, + ]; + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_inc_claim_reduction( + Stage6IncClaimReductionWitness { + ram_inc: &ram_inc, + rd_inc: &rd_inc, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let mut artifacts = execute_stage6_program( + &INC_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("increment claim-reduction prover succeeds"); + + artifacts.sumchecks[0].evals[0].value += Fr::from_u64(1); + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks, + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let error = execute_stage6_program( + &INC_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect_err("tampered increment eval is rejected"); + + assert_eq!( + error, + Stage6KernelError::InvalidProof { + driver: "stage6.sumcheck", + reason: "batched output claim mismatch" + } + ); + } + + #[test] + fn ram_ra_virtual_prover_produces_verifiable_sumcheck() { + let ram_ra_0 = frs(&[1, 2, 3, 4]); + let ram_ra_1 = frs(&[2, 3, 4, 5]); + let ram_ra_2 = frs(&[3, 4, 5, 6]); + let ram_ra_3 = frs(&[4, 5, 6, 7]); + let ram_ra_chunks: [&[Fr]; 4] = [&ram_ra_0, &ram_ra_1, &ram_ra_2, &ram_ra_3]; + let input_point = frs(&[2, 3]); + let input_claim = product_virtual_claim(&ram_ra_chunks, &input_point); + let opening_inputs = vec![ + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + point: input_point, + eval: input_claim, + }, + Stage6OpeningInputValue { + symbol: "stage6.input.ram_ra_virtual_claim", + point: Vec::new(), + eval: input_claim, + }, + ]; + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_ram_ra_virtual( + Stage6RamRaVirtualWitness { + ram_ra_chunks: &ram_ra_chunks, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let artifacts = execute_stage6_program( + &RAM_RA_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("RAM RA virtualization prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 4); + + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let verified = execute_stage6_program( + &RAM_RA_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("proof-carrying verifier accepts RAM RA virtualization output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn ram_ra_virtual_verifier_rejects_bad_final_eval() { + let ram_ra_0 = frs(&[1, 2, 3, 4]); + let ram_ra_1 = frs(&[2, 3, 4, 5]); + let ram_ra_2 = frs(&[3, 4, 5, 6]); + let ram_ra_3 = frs(&[4, 5, 6, 7]); + let ram_ra_chunks: [&[Fr]; 4] = [&ram_ra_0, &ram_ra_1, &ram_ra_2, &ram_ra_3]; + let input_point = frs(&[2, 3]); + let input_claim = product_virtual_claim(&ram_ra_chunks, &input_point); + let opening_inputs = vec![ + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + point: input_point, + eval: input_claim, + }, + Stage6OpeningInputValue { + symbol: "stage6.input.ram_ra_virtual_claim", + point: Vec::new(), + eval: input_claim, + }, + ]; + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_ram_ra_virtual( + Stage6RamRaVirtualWitness { + ram_ra_chunks: &ram_ra_chunks, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let mut artifacts = execute_stage6_program( + &RAM_RA_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("RAM RA virtualization prover succeeds"); + + artifacts.sumchecks[0].evals[0].value += Fr::from_u64(1); + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks, + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let error = execute_stage6_program( + &RAM_RA_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect_err("tampered RAM RA eval is rejected"); + + assert_eq!( + error, + Stage6KernelError::InvalidProof { + driver: "stage6.sumcheck", + reason: "batched output claim mismatch" + } + ); + } + + #[test] + fn instruction_ra_virtual_prover_produces_verifiable_sumcheck() { + let instruction_ra_0 = frs(&[1, 2, 3, 4]); + let instruction_ra_1 = frs(&[2, 3, 4, 5]); + let instruction_ra_2 = frs(&[3, 4, 5, 6]); + let instruction_ra_3 = frs(&[4, 5, 6, 7]); + let instruction_ra_4 = frs(&[5, 6, 7, 8]); + let instruction_ra_5 = frs(&[6, 7, 8, 9]); + let instruction_ra_6 = frs(&[7, 8, 9, 10]); + let instruction_ra_7 = frs(&[8, 9, 10, 11]); + let instruction_ra_chunks: [&[Fr]; 8] = [ + &instruction_ra_0, + &instruction_ra_1, + &instruction_ra_2, + &instruction_ra_3, + &instruction_ra_4, + &instruction_ra_5, + &instruction_ra_6, + &instruction_ra_7, + ]; + let input_point = frs(&[2, 3]); + let gamma = Fr::from_u64(3); + let input_claim = + grouped_product_virtual_claim(&instruction_ra_chunks, 2, &input_point, gamma); + let opening_inputs = vec![ + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + point: input_point.clone(), + eval: input_claim, + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", + point: input_point, + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.instruction_ra_virtual_claim", + point: Vec::new(), + eval: input_claim, + }, + ]; + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_instruction_ra_virtual( + Stage6InstructionRaVirtualWitness { + instruction_ra_chunks: &instruction_ra_chunks, + virtual_count: 2, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let artifacts = execute_stage6_program( + &INSTRUCTION_RA_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("instruction RA virtualization prover succeeds"); + + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 8); + + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let verified = execute_stage6_program( + &INSTRUCTION_RA_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("proof-carrying verifier accepts instruction RA virtualization output"); + + assert_eq!(artifacts.sumchecks[0].point, verified.sumchecks[0].point); + assert_eq!( + named_eval_values(&artifacts.sumchecks[0].evals), + named_eval_values(&verified.sumchecks[0].evals) + ); + } + + #[test] + fn instruction_ra_virtual_verifier_rejects_bad_final_eval() { + let instruction_ra_0 = frs(&[1, 2, 3, 4]); + let instruction_ra_1 = frs(&[2, 3, 4, 5]); + let instruction_ra_2 = frs(&[3, 4, 5, 6]); + let instruction_ra_3 = frs(&[4, 5, 6, 7]); + let instruction_ra_4 = frs(&[5, 6, 7, 8]); + let instruction_ra_5 = frs(&[6, 7, 8, 9]); + let instruction_ra_6 = frs(&[7, 8, 9, 10]); + let instruction_ra_7 = frs(&[8, 9, 10, 11]); + let instruction_ra_chunks: [&[Fr]; 8] = [ + &instruction_ra_0, + &instruction_ra_1, + &instruction_ra_2, + &instruction_ra_3, + &instruction_ra_4, + &instruction_ra_5, + &instruction_ra_6, + &instruction_ra_7, + ]; + let input_point = frs(&[2, 3]); + let gamma = Fr::from_u64(3); + let input_claim = + grouped_product_virtual_claim(&instruction_ra_chunks, 2, &input_point, gamma); + let opening_inputs = vec![ + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + point: input_point.clone(), + eval: input_claim, + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", + point: input_point, + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.instruction_ra_virtual_claim", + point: Vec::new(), + eval: input_claim, + }, + ]; + let prover_inputs = Stage6ProverInputs::new(&opening_inputs).with_instruction_ra_virtual( + Stage6InstructionRaVirtualWitness { + instruction_ra_chunks: &instruction_ra_chunks, + virtual_count: 2, + }, + ); + let mut prover = Stage6ProverKernelExecutor::new(prover_inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage6_test"); + let mut artifacts = execute_stage6_program( + &INSTRUCTION_RA_PROGRAM, + Stage6ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("instruction RA virtualization prover succeeds"); + + artifacts.sumchecks[0].evals[0].value += Fr::from_u64(1); + let proof = Stage6Proof { + sumchecks: artifacts.sumchecks, + }; + let mut verifier = Stage6ProofCarryingKernelExecutor::new(&proof, &opening_inputs); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage6_test"); + let error = execute_stage6_program( + &INSTRUCTION_RA_PROGRAM, + Stage6ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect_err("tampered instruction RA eval is rejected"); + + assert_eq!( + error, + Stage6KernelError::InvalidProof { + driver: "stage6.sumcheck", + reason: "batched output claim mismatch" + } + ); + } + + fn replay_proof(input_claim: Fr) -> (Stage6Proof, Vec>) { + let opening_inputs = vec![Stage6OpeningInputValue { + symbol: "stage6.input.claim", + point: Vec::new(), + eval: input_claim, + }]; + let mut transcript = Blake2bTranscript::::new(b"stage6_test"); + append_labeled_scalar(&mut transcript, "sumcheck_claim", &input_claim); + let batching_coeff = transcript.challenge_vector(1)[0]; + let claimed_sum = input_claim * batching_coeff; + let round_poly = UnivariatePoly::new(vec![claimed_sum, -claimed_sum]); + append_compressed_univariate_poly(&mut transcript, "sumcheck_poly", &round_poly); + let point = vec![transcript.challenge()]; + let proof = Stage6Proof { + sumchecks: vec![Stage6SumcheckOutput { + driver: "stage6.sumcheck", + point, + evals: Vec::new(), + opening_claims: Vec::new(), + proof: jolt_sumcheck::SumcheckProof { + round_polynomials: vec![round_poly], + }, + }], + }; + (proof, opening_inputs) + } + + fn bytecode_entries() -> [Stage6BytecodeEntry; 4] { + [ + bytecode_entry(0, 3, &[0, 5, 12], Some(0), Some(1), Some(2), Some(0)), + bytecode_entry(1, 5, &[1, 6], Some(1), None, Some(3), Some(1)), + bytecode_entry(2, 7, &[2, 7, 10], Some(2), Some(0), None, None), + bytecode_entry(3, 11, &[3, 8, 13], None, Some(2), Some(1), Some(0)), + ] + } + + fn bytecode_entry( + address: u64, + imm: u64, + flags: &[usize], + rd: Option, + rs1: Option, + rs2: Option, + lookup_table: Option, + ) -> Stage6BytecodeEntry { + let mut circuit_flags = [false; 14]; + for &flag in flags { + circuit_flags[flag] = true; + } + Stage6BytecodeEntry { + address: Fr::from_u64(address), + imm: Fr::from_u64(imm), + circuit_flags, + rd, + rs1, + rs2, + lookup_table, + is_interleaved: address.is_multiple_of(2), + is_branch: address == 1, + left_is_rs1: rs1.is_some(), + left_is_pc: address == 2, + right_is_rs2: rs2.is_some(), + right_is_imm: address == 3, + is_noop: address == 0, + } + } + + fn bytecode_opening_inputs() -> Vec> { + vec![ + Stage6OpeningInputValue { + symbol: "stage6.input.stage1.Imm", + point: frs(&[17]), + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage2.OpFlagJump", + point: frs(&[19]), + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage3.spartan_shift.UnexpandedPC", + point: frs(&[23]), + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage4.Rs1Ra", + point: frs(&[2, 3, 29]), + eval: Fr::from_u64(0), + }, + Stage6OpeningInputValue { + symbol: "stage6.input.stage5.registers_val_evaluation.RdWa", + point: frs(&[5, 7, 31]), + eval: Fr::from_u64(0), + }, + ] + } + + fn bytecode_read_raf_claim( + data: Stage6BytecodeReadRafData<'_, Fr>, + bytecode_ra_chunks: &[&[Fr]], + opening_inputs: &[Stage6OpeningInputValue], + ) -> Fr { + let mut store = Stage6ValueStore::with_opening_inputs(opening_inputs); + store.seed_constants(&BYTECODE_PROGRAM); + let log_k = 2; + let log_t = 1; + let domain_len = 1 << (log_k + log_t); + let weighted = + bytecode_weighted_value_factor(data, &store, log_k, log_t, domain_len).unwrap(); + let ra_factors = + expanded_bytecode_ra_factors(bytecode_ra_chunks, &[1, 1], log_k, log_t, domain_len) + .unwrap(); + (0..domain_len) + .map(|row| weighted[row] * ra_factors.iter().map(|factor| factor[row]).product::()) + .sum() + } + + fn frs(values: &[u64]) -> Vec { + values.iter().map(|value| Fr::from_u64(*value)).collect() + } + + fn multilinear_eval(values: &[Fr], point: &[Fr]) -> Fr { + EqPolynomial::::evals(point, None) + .into_iter() + .zip(values) + .map(|(eq, value)| eq * *value) + .sum() + } + + fn product_virtual_claim(chunks: &[&[Fr]], point: &[Fr]) -> Fr { + let eq = EqPolynomial::::evals(point, None); + (0..eq.len()) + .map(|row| eq[row] * chunks.iter().map(|chunk| chunk[row]).product::()) + .sum() + } + + fn grouped_product_virtual_claim( + chunks: &[&[Fr]], + virtual_count: usize, + point: &[Fr], + gamma: Fr, + ) -> Fr { + let eq = EqPolynomial::::evals(point, None); + let chunks_per_virtual = chunks.len() / virtual_count; + (0..eq.len()) + .map(|row| { + let mut gamma_power = Fr::from_u64(1); + let mut row_value = Fr::from_u64(0); + for group in chunks.chunks(chunks_per_virtual) { + row_value += gamma_power * group.iter().map(|chunk| chunk[row]).product::(); + gamma_power *= gamma; + } + eq[row] * row_value + }) + .sum() + } + + fn named_eval_values(evals: &[Stage6NamedEval]) -> Vec<(&'static str, &'static str, Fr)> { + evals + .iter() + .map(|eval| (eval.name, eval.oracle, eval.value)) + .collect() + } +} diff --git a/crates/jolt-kernels/src/stage7.rs b/crates/jolt-kernels/src/stage7.rs new file mode 100644 index 0000000000..32d87cb7cf --- /dev/null +++ b/crates/jolt-kernels/src/stage7.rs @@ -0,0 +1,2586 @@ +//! Stage 7 coarse-kernel ABI used by Bolt-generated Jolt prover code. +//! +//! Stage 7 fuses hamming-weight claim reduction with the address reduction for +//! all RA one-hot polynomials. This module owns the prover-side runtime behind +//! the generated `jolt.stage7.*` CPU ABI. + +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use crate::dense::bind_dense_evals_reuse; +use jolt_field::Field; +use jolt_poly::{EqPolynomial, UnivariatePoly}; +use jolt_sumcheck::SumcheckProof; +use jolt_transcript::{Label, LabelWithCount, Transcript}; +use jolt_witness::Stage6WitnessSlices; + +pub use crate::stage6::{ + Stage6ChallengeVector as Stage7ChallengeVector, + Stage6ExecutionArtifacts as Stage7ExecutionArtifacts, + Stage6ExecutionMode as Stage7ExecutionMode, Stage6FieldConstantPlan as Stage7FieldConstantPlan, + Stage6FieldExprPlan as Stage7FieldExprPlan, Stage6NamedEval as Stage7NamedEval, + Stage6OpeningBatchPlan as Stage7OpeningBatchPlan, + Stage6OpeningClaimEqualityPlan as Stage7OpeningClaimEqualityPlan, + Stage6OpeningClaimPlan as Stage7OpeningClaimPlan, + Stage6OpeningClaimValue as Stage7OpeningClaimValue, + Stage6OpeningInputPlan as Stage7OpeningInputPlan, + Stage6OpeningInputValue as Stage7OpeningInputValue, Stage6Params as Stage7Params, + Stage6PointConcatPlan as Stage7PointConcatPlan, Stage6PointSlicePlan as Stage7PointSlicePlan, + Stage6PointZeroPlan as Stage7PointZeroPlan, Stage6ProgramStepPlan as Stage7ProgramStepPlan, + Stage6Proof as Stage7Proof, Stage6SumcheckBatchPlan as Stage7SumcheckBatchPlan, + Stage6SumcheckClaimPlan as Stage7SumcheckClaimPlan, + Stage6SumcheckDriverPlan as Stage7SumcheckDriverPlan, + Stage6SumcheckEvalPlan as Stage7SumcheckEvalPlan, + Stage6SumcheckInstanceResultPlan as Stage7SumcheckInstanceResultPlan, + Stage6SumcheckOutput as Stage7SumcheckOutput, + Stage6TranscriptAbsorbBytesPlan as Stage7TranscriptAbsorbBytesPlan, + Stage6TranscriptSqueezePlan as Stage7TranscriptSqueezePlan, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +impl Stage7KernelPlan { + pub fn relation_kind(&self) -> Result { + Stage7Relation::from_symbol(self.relation).ok_or(Stage7KernelError::UnknownRelation { + relation: self.relation, + }) + } + + pub fn abi_kind(&self) -> Result { + Stage7KernelAbi::from_name(self.abi) + .ok_or(Stage7KernelError::UnknownKernelAbi { abi: self.abi }) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage7CpuProgramPlan { + pub role: &'static str, + pub params: Stage7Params, + pub steps: &'static [Stage7ProgramStepPlan], + pub transcript_squeezes: &'static [Stage7TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [Stage7TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [Stage7OpeningInputPlan], + pub field_constants: &'static [Stage7FieldConstantPlan], + pub field_exprs: &'static [Stage7FieldExprPlan], + pub kernels: &'static [Stage7KernelPlan], + pub claims: &'static [Stage7SumcheckClaimPlan], + pub batches: &'static [Stage7SumcheckBatchPlan], + pub drivers: &'static [Stage7SumcheckDriverPlan], + pub instance_results: &'static [Stage7SumcheckInstanceResultPlan], + pub evals: &'static [Stage7SumcheckEvalPlan], + pub point_zeros: &'static [Stage7PointZeroPlan], + pub point_slices: &'static [Stage7PointSlicePlan], + pub point_concats: &'static [Stage7PointConcatPlan], + pub opening_claims: &'static [Stage7OpeningClaimPlan], + pub opening_equalities: &'static [Stage7OpeningClaimEqualityPlan], + pub opening_batches: &'static [Stage7OpeningBatchPlan], +} + +impl Stage7CpuProgramPlan { + pub fn claim(&self, symbol: &str) -> Option<&Stage7SumcheckClaimPlan> { + self.claims.iter().find(|claim| claim.symbol == symbol) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage7Relation { + HammingWeightClaimReduction, + Batched, +} + +impl Stage7Relation { + pub fn from_symbol(symbol: &str) -> Option { + match symbol { + "jolt.stage7.hamming_weight_claim_reduction" => Some(Self::HammingWeightClaimReduction), + "jolt.stage7.batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn symbol(self) -> &'static str { + match self { + Self::HammingWeightClaimReduction => "jolt.stage7.hamming_weight_claim_reduction", + Self::Batched => "jolt.stage7.batched", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage7KernelAbi { + HammingWeightClaimReduction, + Batched, +} + +impl Stage7KernelAbi { + pub fn from_name(name: &str) -> Option { + match name { + "jolt_stage7_hamming_weight_claim_reduction" => Some(Self::HammingWeightClaimReduction), + "jolt_stage7_batched" => Some(Self::Batched), + _ => None, + } + } + + pub fn name(self) -> &'static str { + match self { + Self::HammingWeightClaimReduction => "jolt_stage7_hamming_weight_claim_reduction", + Self::Batched => "jolt_stage7_batched", + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Stage7KernelError { + MissingClaim { + batch: &'static str, + claim: &'static str, + }, + MissingValue { + symbol: &'static str, + }, + MissingDynamicValue { + symbol: String, + }, + MissingDriver { + driver: &'static str, + }, + MissingKernel { + driver: &'static str, + kernel: &'static str, + }, + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + UnknownRelation { + relation: &'static str, + }, + UnknownKernelAbi { + abi: &'static str, + }, + PlanCountMismatch { + artifact: &'static str, + expected: usize, + actual: usize, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + UnsupportedFieldExpr { + symbol: &'static str, + formula: &'static str, + }, + KernelNotImplemented { + abi: &'static str, + }, + WrongExecutorMode { + driver: &'static str, + expected: Stage7ExecutionMode, + actual: Stage7ExecutionMode, + }, + MissingProof { + driver: &'static str, + }, + MissingKernelInput { + kernel: &'static str, + input: &'static str, + }, + InvalidProgramStep { + symbol: &'static str, + kind: &'static str, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, +} + +impl Display for Stage7KernelError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingClaim { batch, claim } => { + write!( + formatter, + "stage7 batch @{batch} references missing claim @{claim}" + ) + } + Self::MissingValue { symbol } => { + write!(formatter, "stage7 value @{symbol} is not available") + } + Self::MissingDynamicValue { symbol } => { + write!(formatter, "stage7 value @{symbol} is not available") + } + Self::MissingDriver { driver } => { + write!(formatter, "stage7 driver @{driver} is not available") + } + Self::MissingKernel { driver, kernel } => { + write!( + formatter, + "stage7 driver @{driver} references missing kernel @{kernel}" + ) + } + Self::MissingBatch { driver, batch } => { + write!( + formatter, + "stage7 driver @{driver} references missing batch @{batch}" + ) + } + Self::UnknownRelation { relation } => { + write!(formatter, "unknown stage7 relation `{relation}`") + } + Self::UnknownKernelAbi { abi } => { + write!(formatter, "unknown stage7 kernel ABI `{abi}`") + } + Self::PlanCountMismatch { + artifact, + expected, + actual, + } => { + write!( + formatter, + "stage7 {artifact} plan count mismatch: expected {expected}, got {actual}" + ) + } + Self::InvalidInputLength { + input, + expected, + actual, + } => { + write!( + formatter, + "stage7 input `{input}` has length {actual}, expected {expected}" + ) + } + Self::UnsupportedFieldExpr { symbol, formula } => { + write!( + formatter, + "stage7 field expr @{symbol} uses unsupported formula `{formula}`" + ) + } + Self::KernelNotImplemented { abi } => { + write!(formatter, "stage7 kernel ABI `{abi}` is not implemented") + } + Self::WrongExecutorMode { + driver, + expected, + actual, + } => { + write!( + formatter, + "stage7 driver @{driver} expected {expected:?} executor, got {actual:?}" + ) + } + Self::MissingProof { driver } => { + write!(formatter, "stage7 proof for driver @{driver} is missing") + } + Self::MissingKernelInput { kernel, input } => { + write!( + formatter, + "stage7 kernel `{kernel}` is missing required input `{input}`" + ) + } + Self::InvalidProgramStep { symbol, kind } => { + write!( + formatter, + "stage7 program step @{symbol} has invalid kind `{kind}`" + ) + } + Self::InvalidProof { driver, reason } => { + write!( + formatter, + "stage7 proof for driver @{driver} is invalid: {reason}" + ) + } + } + } +} + +impl Error for Stage7KernelError {} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Stage7RaChunkLayout { + CycleMajor, + AddressMajor, +} + +#[derive(Clone, Copy)] +pub struct Stage7RaChunks<'a, F: Field> { + pub chunks: &'a [&'a [F]], + pub layout: Stage7RaChunkLayout, +} + +#[derive(Clone, Copy)] +pub struct Stage7HammingWeightClaimReductionWitness<'a, F: Field> { + pub instruction_ra: Stage7RaChunks<'a, F>, + pub bytecode_ra: Stage7RaChunks<'a, F>, + pub ram_ra: Stage7RaChunks<'a, F>, +} + +#[derive(Clone, Copy)] +pub struct Stage7RaIndexChunks<'a> { + pub chunks: &'a [&'a [Option]], +} + +#[derive(Clone, Copy)] +pub struct Stage7HammingWeightClaimReductionIndexWitness<'a> { + pub instruction_ra: Stage7RaIndexChunks<'a>, + pub bytecode_ra: Stage7RaIndexChunks<'a>, + pub ram_ra: Stage7RaIndexChunks<'a>, +} + +#[derive(Clone, Copy)] +pub struct Stage7ProverInputs<'a, F: Field> { + pub opening_inputs: &'a [Stage7OpeningInputValue], + pub hamming_weight_claim_reduction: Option>, + pub hamming_weight_claim_reduction_indices: + Option>, +} + +impl<'a, F: Field> Stage7ProverInputs<'a, F> { + pub fn new(opening_inputs: &'a [Stage7OpeningInputValue]) -> Self { + Self { + opening_inputs, + hamming_weight_claim_reduction: None, + hamming_weight_claim_reduction_indices: None, + } + } + + pub fn empty() -> Self { + Self { + opening_inputs: &[], + hamming_weight_claim_reduction: None, + hamming_weight_claim_reduction_indices: None, + } + } + + pub fn with_hamming_weight_claim_reduction( + mut self, + witness: Stage7HammingWeightClaimReductionWitness<'a, F>, + ) -> Self { + self.hamming_weight_claim_reduction = Some(witness); + self + } + + pub fn with_hamming_weight_claim_reduction_indices( + mut self, + witness: Stage7HammingWeightClaimReductionIndexWitness<'a>, + ) -> Self { + self.hamming_weight_claim_reduction_indices = Some(witness); + self + } + + pub fn with_stage6_witness_indices(self, slices: &'a Stage6WitnessSlices<'a, F>) -> Self { + self.with_hamming_weight_claim_reduction_indices( + Stage7HammingWeightClaimReductionIndexWitness { + instruction_ra: Stage7RaIndexChunks { + chunks: &slices.instruction_ra_index_chunks, + }, + bytecode_ra: Stage7RaIndexChunks { + chunks: &slices.bytecode_ra_index_chunks, + }, + ram_ra: Stage7RaIndexChunks { + chunks: &slices.ram_ra_index_chunks, + }, + }, + ) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage7KernelContext<'a> { + pub mode: Stage7ExecutionMode, + pub program: &'static Stage7CpuProgramPlan, + pub kernel: &'a Stage7KernelPlan, + pub batch: &'a Stage7SumcheckBatchPlan, + pub driver: &'a Stage7SumcheckDriverPlan, +} + +impl Stage7KernelContext<'_> { + pub fn relation_kind(&self) -> Result { + self.kernel.relation_kind() + } + + pub fn abi_kind(&self) -> Result { + self.kernel.abi_kind() + } + + pub fn batch_claims(&self) -> Result, Stage7KernelError> { + self.batch + .claim_operands + .iter() + .map(|symbol| { + self.program + .claim(symbol) + .ok_or(Stage7KernelError::MissingClaim { + batch: self.batch.symbol, + claim: symbol, + }) + }) + .collect() + } +} + +pub trait Stage7KernelExecutor { + fn observe_challenge_vector( + &mut self, + _plan: &'static Stage7TranscriptSqueezePlan, + _values: &[F], + ) -> Result<(), Stage7KernelError> { + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + _output: &Stage7SumcheckOutput, + ) -> Result<(), Stage7KernelError> { + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage7KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage7KernelError> + where + T: Transcript; + + fn verify_sumcheck( + &mut self, + context: Stage7KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage7KernelError> + where + T: Transcript; +} + +#[derive(Clone, Debug, Default)] +pub struct UnsupportedStage7KernelExecutor; + +impl Stage7KernelExecutor for UnsupportedStage7KernelExecutor { + fn prove_sumcheck( + &mut self, + context: Stage7KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage7KernelError> + where + T: Transcript, + { + let abi = context.abi_kind()?; + let _ = context.relation_kind()?; + Err(Stage7KernelError::KernelNotImplemented { abi: abi.name() }) + } + + fn verify_sumcheck( + &mut self, + context: Stage7KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage7KernelError> + where + T: Transcript, + { + let abi = context.abi_kind()?; + let _ = context.relation_kind()?; + Err(Stage7KernelError::KernelNotImplemented { abi: abi.name() }) + } +} + +#[derive(Clone)] +pub struct Stage7ProverKernelExecutor<'a, F: Field> { + pub inputs: Stage7ProverInputs<'a, F>, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage7ProverKernelExecutor<'a, F> { + pub fn new(inputs: Stage7ProverInputs<'a, F>) -> Self { + Self { + inputs, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage7CpuProgramPlan, + ) -> Result, Stage7KernelError> { + value_store_from_observations( + program, + self.inputs.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } +} + +impl Stage7KernelExecutor for Stage7ProverKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage7TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage7KernelError> { + self.challenge_vectors.push(Stage7ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage7SumcheckOutput, + ) -> Result<(), Stage7KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage7KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage7KernelError> + where + T: Transcript, + { + prove_stage7_kernel( + context, + &self.inputs, + self.value_store(context.program)?, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage7KernelContext<'_>, + _transcript: &mut T, + ) -> Result, Stage7KernelError> + where + T: Transcript, + { + Err(Stage7KernelError::WrongExecutorMode { + driver: context.driver.symbol, + expected: Stage7ExecutionMode::Prover, + actual: Stage7ExecutionMode::Verifier, + }) + } +} + +#[derive(Clone)] +pub struct Stage7ProofCarryingKernelExecutor<'a, F: Field> { + pub proof: &'a Stage7Proof, + pub opening_inputs: &'a [Stage7OpeningInputValue], + pub cursor: usize, + challenge_vectors: Vec>, + completed_sumchecks: Vec>, +} + +impl<'a, F: Field> Stage7ProofCarryingKernelExecutor<'a, F> { + pub fn new( + proof: &'a Stage7Proof, + opening_inputs: &'a [Stage7OpeningInputValue], + ) -> Self { + Self { + proof, + opening_inputs, + cursor: 0, + challenge_vectors: Vec::new(), + completed_sumchecks: Vec::new(), + } + } + + fn value_store( + &self, + program: &'static Stage7CpuProgramPlan, + ) -> Result, Stage7KernelError> { + value_store_from_observations( + program, + self.opening_inputs, + &self.challenge_vectors, + &self.completed_sumchecks, + ) + } + + fn next_proof( + &mut self, + driver: &'static str, + ) -> Result<&'a Stage7SumcheckOutput, Stage7KernelError> { + let proof = self + .proof + .sumchecks + .get(self.cursor) + .ok_or(Stage7KernelError::MissingProof { driver })?; + self.cursor += 1; + Ok(proof) + } +} + +impl Stage7KernelExecutor for Stage7ProofCarryingKernelExecutor<'_, F> { + fn observe_challenge_vector( + &mut self, + plan: &'static Stage7TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage7KernelError> { + self.challenge_vectors.push(Stage7ChallengeVector { + symbol: plan.symbol, + values: values.to_vec(), + }); + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + output: &Stage7SumcheckOutput, + ) -> Result<(), Stage7KernelError> { + self.completed_sumchecks.push(output.clone()); + Ok(()) + } + + fn prove_sumcheck( + &mut self, + context: Stage7KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage7KernelError> + where + T: Transcript, + { + let proof = self.next_proof(context.driver.symbol)?; + verify_stage7_kernel( + context, + self.value_store(context.program)?, + proof, + transcript, + ) + } + + fn verify_sumcheck( + &mut self, + context: Stage7KernelContext<'_>, + transcript: &mut T, + ) -> Result, Stage7KernelError> + where + T: Transcript, + { + let proof = self.next_proof(context.driver.symbol)?; + verify_stage7_kernel( + context, + self.value_store(context.program)?, + proof, + transcript, + ) + } +} + +#[derive(Clone, Debug, Default)] +struct Stage7ValueStore { + scalars: Vec<(&'static str, F)>, + points: Vec<(&'static str, Vec)>, +} + +impl Stage7ValueStore { + fn with_opening_inputs(inputs: &[Stage7OpeningInputValue]) -> Self { + let mut store = Self::default(); + for input in inputs { + store.insert_scalar(input.symbol, input.eval); + store.insert_point(input.symbol, input.point.clone()); + } + store + } + + fn seed_constants(&mut self, program: &'static Stage7CpuProgramPlan) { + for constant in program.field_constants { + self.insert_scalar(constant.symbol, F::from_u64(constant.value as u64)); + } + for zero in program.point_zeros { + self.insert_point(zero.symbol, vec![F::from_u64(0); zero.arity]); + } + } + + fn observe_challenge_vector( + &mut self, + plan: &'static Stage7TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), Stage7KernelError> { + self.insert_point(plan.symbol, values.to_vec()); + if matches!(plan.kind, "challenge_scalar" | "scalar") { + require_operand_count(plan.symbol, 1, values.len())?; + self.insert_scalar(plan.symbol, values[0]); + } + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + program: &'static Stage7CpuProgramPlan, + output: &Stage7SumcheckOutput, + ) -> Result<(), Stage7KernelError> { + self.observe_sumcheck_values(program, output.driver, &output.point, &output.evals) + } + + fn observe_sumcheck_values( + &mut self, + program: &'static Stage7CpuProgramPlan, + driver: &'static str, + point: &[F], + evals: &[Stage7NamedEval], + ) -> Result<(), Stage7KernelError> { + self.insert_point(driver, point.to_vec()); + for instance in program + .instance_results + .iter() + .filter(|instance| instance.source == driver) + { + let end = instance.round_offset + instance.point_arity; + let mut point = point + .get(instance.round_offset..end) + .ok_or(Stage7KernelError::InvalidInputLength { + input: instance.symbol, + expected: end, + actual: point.len(), + })? + .to_vec(); + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + _ => { + return Err(Stage7KernelError::InvalidProof { + driver, + reason: "unsupported point order", + }); + } + } + self.insert_point(instance.symbol, point); + } + for eval in program.evals.iter().filter(|eval| eval.source == driver) { + let value = evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| evals.get(eval.index)) + .ok_or(Stage7KernelError::MissingValue { + symbol: eval.symbol, + })? + .value; + self.insert_scalar(eval.symbol, value); + self.insert_scalar(eval.name, value); + } + let _ = self.evaluate_available_points(program)?; + let _ = self.evaluate_available_field_exprs(program)?; + self.verify_opening_equalities(program)?; + Ok(()) + } + + fn evaluate_available_field_exprs( + &mut self, + program: &'static Stage7CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for expr in program.field_exprs { + if self.try_scalar(expr.symbol).is_some() { + continue; + } + let Some(operands) = self.try_expr_operands(expr) else { + continue; + }; + self.insert_scalar(expr.symbol, evaluate_stage7_field_expr(expr, &operands)?); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + fn evaluate_available_points( + &mut self, + program: &'static Stage7CpuProgramPlan, + ) -> Result { + let mut inserted = 0usize; + loop { + let mut progress = 0usize; + for slice in program.point_slices { + if self.try_point(slice.symbol).is_some() { + continue; + } + let Some(input) = self.try_point(slice.input) else { + continue; + }; + let end = slice.offset + slice.length; + let point = input + .get(slice.offset..end) + .ok_or(Stage7KernelError::InvalidInputLength { + input: slice.symbol, + expected: end, + actual: input.len(), + })? + .to_vec(); + self.insert_point(slice.symbol, point); + progress += 1; + } + for concat in program.point_concats { + if self.try_point(concat.symbol).is_some() { + continue; + } + let Some(point) = self.try_concat_point(concat) else { + continue; + }; + require_operand_count(concat.symbol, concat.arity, point.len())?; + self.insert_point(concat.symbol, point); + progress += 1; + } + inserted += progress; + if progress == 0 { + return Ok(inserted); + } + } + } + + fn verify_opening_equalities( + &self, + program: &'static Stage7CpuProgramPlan, + ) -> Result<(), Stage7KernelError> { + for equality in program.opening_equalities { + match equality.mode { + "point_and_eval" => { + if self.point(equality.lhs)? != self.point(equality.rhs)? + || self.scalar(equality.lhs)? != self.scalar(equality.rhs)? + { + return Err(Stage7KernelError::InvalidProof { + driver: equality.symbol, + reason: "opening claim equality failed", + }); + } + } + _ => { + return Err(Stage7KernelError::InvalidProof { + driver: equality.symbol, + reason: "unsupported opening equality mode", + }); + } + } + } + Ok(()) + } + + fn claim_value( + &mut self, + program: &'static Stage7CpuProgramPlan, + claim: &Stage7SumcheckClaimPlan, + ) -> Result { + let _ = self.evaluate_available_field_exprs(program)?; + self.scalar(claim.claim_value) + } + + fn batch_claim_values( + &mut self, + program: &'static Stage7CpuProgramPlan, + batch: &Stage7SumcheckBatchPlan, + ) -> Result, Stage7KernelError> { + batch + .claim_operands + .iter() + .map(|symbol| { + let claim = program + .claim(symbol) + .ok_or(Stage7KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + self.claim_value(program, claim) + }) + .collect() + } + + fn insert_scalar(&mut self, symbol: &'static str, value: F) { + if let Some((_, existing)) = self + .scalars + .iter_mut() + .find(|(existing, _)| *existing == symbol) + { + *existing = value; + } else { + self.scalars.push((symbol, value)); + } + } + + fn insert_point(&mut self, symbol: &'static str, point: Vec) { + if let Some((_, existing)) = self + .points + .iter_mut() + .find(|(existing, _)| *existing == symbol) + { + *existing = point; + } else { + self.points.push((symbol, point)); + } + } + + fn scalar(&self, symbol: &'static str) -> Result { + self.try_scalar(symbol) + .ok_or(Stage7KernelError::MissingValue { symbol }) + } + + fn try_scalar(&self, symbol: &str) -> Option { + self.scalars + .iter() + .find(|(existing, _)| *existing == symbol) + .map(|(_, value)| *value) + } + + fn point(&self, symbol: &'static str) -> Result<&[F], Stage7KernelError> { + self.try_point(symbol) + .ok_or(Stage7KernelError::MissingValue { symbol }) + } + + fn dynamic_point(&self, symbol: String) -> Result<&[F], Stage7KernelError> { + self.try_point(&symbol) + .ok_or(Stage7KernelError::MissingDynamicValue { symbol }) + } + + fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.points + .iter() + .find(|(existing, _)| *existing == symbol) + .map(|(_, point)| point.as_slice()) + } + + fn try_expr_operands(&self, expr: &Stage7FieldExprPlan) -> Option> { + expr.operands + .iter() + .map(|operand| self.try_scalar(operand)) + .collect() + } + + fn try_concat_point(&self, concat: &Stage7PointConcatPlan) -> Option> { + let mut point = Vec::with_capacity(concat.arity); + for input in concat.inputs { + point.extend_from_slice(self.try_point(input)?); + } + Some(point) + } +} + +fn value_store_from_observations( + program: &'static Stage7CpuProgramPlan, + opening_inputs: &[Stage7OpeningInputValue], + challenge_vectors: &[Stage7ChallengeVector], + completed_sumchecks: &[Stage7SumcheckOutput], +) -> Result, Stage7KernelError> { + let mut store = Stage7ValueStore::with_opening_inputs(opening_inputs); + store.seed_constants(program); + for challenge in challenge_vectors { + let plan = + find_squeeze(program, challenge.symbol).ok_or(Stage7KernelError::MissingValue { + symbol: challenge.symbol, + })?; + store.observe_challenge_vector(plan, &challenge.values)?; + } + for output in completed_sumchecks { + store.observe_sumcheck_output(program, output)?; + } + let _ = store.evaluate_available_points(program)?; + let _ = store.evaluate_available_field_exprs(program)?; + store.verify_opening_equalities(program)?; + Ok(store) +} + +fn prove_stage7_kernel( + context: Stage7KernelContext<'_>, + inputs: &Stage7ProverInputs<'_, F>, + store: Stage7ValueStore, + transcript: &mut T, +) -> Result, Stage7KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage7KernelAbi::Batched => prove_batched_stage7(context, inputs, store, transcript), + abi @ Stage7KernelAbi::HammingWeightClaimReduction => { + Err(Stage7KernelError::KernelNotImplemented { abi: abi.name() }) + } + } +} + +fn verify_stage7_kernel( + context: Stage7KernelContext<'_>, + store: Stage7ValueStore, + proof: &Stage7SumcheckOutput, + transcript: &mut T, +) -> Result, Stage7KernelError> +where + F: Field, + T: Transcript, +{ + match context.abi_kind()? { + Stage7KernelAbi::Batched => verify_batched_stage7(context, store, proof, transcript), + abi @ Stage7KernelAbi::HammingWeightClaimReduction => { + Err(Stage7KernelError::KernelNotImplemented { abi: abi.name() }) + } + } +} + +#[tracing::instrument(skip_all, name = "Stage7::prove_batched")] +fn prove_batched_stage7( + context: Stage7KernelContext<'_>, + inputs: &Stage7ProverInputs<'_, F>, + mut store: Stage7ValueStore, + transcript: &mut T, +) -> Result, Stage7KernelError> +where + F: Field, + T: Transcript, +{ + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let two_inv = F::from_u64(2) + .inverse() + .ok_or(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "field element 2 is not invertible", + })?; + let mut instances = Vec::with_capacity(claims.len()); + for (index, claim) in claims.iter().enumerate() { + let offset = instance_round_offset(context.program, context.driver.symbol, claim.symbol)?; + if offset + claim.num_rounds > max_rounds { + return Err(Stage7KernelError::InvalidInputLength { + input: claim.symbol, + expected: max_rounds, + actual: offset + claim.num_rounds, + }); + } + let relation = claim_relation(context.program, claim)?; + let active_scale = F::one().mul_pow_2(max_rounds - offset - claim.num_rounds); + let state = + Stage7ProverInstanceState::new(context.program, claim, inputs, &store, active_scale)?; + instances.push(Stage7BatchedInstance { + claim, + relation, + offset, + previous_claim: input_claims[index].mul_pow_2(max_rounds - claim.num_rounds), + state, + }); + } + + let mut point = Vec::with_capacity(max_rounds); + let mut round_polynomials = Vec::with_capacity(max_rounds); + let mut batched_claim = instances + .iter() + .zip(&batching_coeffs) + .map(|(instance, &coefficient)| instance.previous_claim * coefficient) + .sum::(); + for round in 0..max_rounds { + let mut individual_polys = Vec::with_capacity(instances.len()); + for instance in &mut instances { + let poly = if instance.is_active(round) { + instance + .state + .round_poly(instance.previous_claim, instance.relation)? + } else { + UnivariatePoly::new(vec![instance.previous_claim * two_inv]) + }; + individual_polys.push(poly); + } + let batched_poly = combine_univariate_polys(&individual_polys, &batching_coeffs); + if batched_poly.evaluate(F::zero()) + batched_poly.evaluate(F::one()) != batched_claim { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round claim mismatch", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, &batched_poly); + let challenge = transcript.challenge(); + point.push(challenge); + batched_claim = batched_poly.evaluate(challenge); + for (instance, poly) in instances.iter_mut().zip(individual_polys) { + instance.previous_claim = poly.evaluate(challenge); + if instance.is_active(round) { + instance.state.ingest_challenge(challenge); + } + } + round_polynomials.push(batched_poly); + } + + let mut evals = Vec::new(); + let mut expected = F::zero(); + for (instance, &coefficient) in instances.iter().zip(&batching_coeffs) { + let relation_claim = instance.state.final_relation_eval(instance.relation)?; + if instance.previous_claim != relation_claim { + return Err(Stage7KernelError::InvalidProof { + driver: instance.relation.symbol(), + reason: "stage7 relation output claim mismatch", + }); + } + expected += coefficient * relation_claim; + evals.extend(instance.state.final_evals(instance.relation)?); + } + if batched_claim != expected { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + store.observe_sumcheck_values(context.program, context.driver.symbol, &point, &evals)?; + let opening_claims = append_opening_claims(context.program, &mut store, transcript, &evals)?; + Ok(Stage7SumcheckOutput { + driver: context.driver.symbol, + point, + evals, + opening_claims, + proof: SumcheckProof { round_polynomials }, + }) +} + +fn verify_batched_stage7( + context: Stage7KernelContext<'_>, + mut store: Stage7ValueStore, + proof: &Stage7SumcheckOutput, + transcript: &mut T, +) -> Result, Stage7KernelError> +where + F: Field, + T: Transcript, +{ + if proof.driver != context.driver.symbol { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "driver symbol mismatch", + }); + } + if proof.proof.round_polynomials.len() != context.driver.num_rounds { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "unexpected batched round count", + }); + } + + let claims = context.batch_claims()?; + let input_claims = store.batch_claim_values(context.program, context.batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, context.batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let max_rounds = context.driver.num_rounds; + let mut running_claim = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), &coefficient)| { + claim.mul_pow_2(max_rounds - plan.num_rounds) * coefficient + }) + .sum::(); + let mut point = Vec::with_capacity(max_rounds); + for poly in &proof.proof.round_polynomials { + if polynomial_degree(poly) > context.driver.degree { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched polynomial exceeds degree bound", + }); + } + if poly.evaluate(F::zero()) + poly.evaluate(F::one()) != running_claim { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched round check failed", + }); + } + append_compressed_univariate_poly(transcript, context.driver.round_label, poly); + let challenge = transcript.challenge(); + running_claim = poly.evaluate(challenge); + point.push(challenge); + } + if !proof.point.is_empty() && proof.point != point { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched point mismatch", + }); + } + let expected = + expected_batched_output_claim(context, &store, &proof.evals, &point, &batching_coeffs)?; + if running_claim != expected { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "batched output claim mismatch", + }); + } + + let output = Stage7SumcheckOutput { + driver: context.driver.symbol, + point, + evals: proof.evals.clone(), + opening_claims: Vec::new(), + proof: proof.proof.clone(), + }; + store.observe_sumcheck_output(context.program, &output)?; + let opening_claims = + append_opening_claims(context.program, &mut store, &mut *transcript, &output.evals)?; + let output = Stage7SumcheckOutput { + opening_claims, + ..output + }; + Ok(output) +} + +fn expected_batched_output_claim( + context: Stage7KernelContext<'_>, + store: &Stage7ValueStore, + evals: &[Stage7NamedEval], + point: &[F], + batching_coeffs: &[F], +) -> Result { + let mut expected = F::zero(); + for (claim, &coefficient) in context.batch_claims()?.iter().zip(batching_coeffs) { + let Some(instance) = context.program.instance_results.iter().find(|instance| { + instance.claim == claim.symbol && instance.source == context.driver.symbol + }) else { + return Err(Stage7KernelError::MissingValue { + symbol: claim.symbol, + }); + }; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(Stage7KernelError::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let value = match claim_relation(context.program, claim)? { + Stage7Relation::HammingWeightClaimReduction => { + expected_hamming_weight_claim_reduction(context.program, store, evals, local_point)? + } + Stage7Relation::Batched => { + return Err(Stage7KernelError::InvalidProof { + driver: context.driver.symbol, + reason: "nested batched relation is unsupported", + }); + } + }; + expected += coefficient * value; + } + Ok(expected) +} + +fn expected_hamming_weight_claim_reduction( + program: &'static Stage7CpuProgramPlan, + store: &Stage7ValueStore, + evals: &[Stage7NamedEval], + local_point: &[F], +) -> Result { + let log_k_chunk = local_point.len(); + let booleanity_point = store.point("stage7.input.stage6.booleanity.InstructionRa_0")?; + if booleanity_point.len() < log_k_chunk { + return Err(Stage7KernelError::InvalidInputLength { + input: "stage7.input.stage6.booleanity.InstructionRa_0", + expected: log_k_chunk, + actual: booleanity_point.len(), + }); + } + let r_addr_bool = &booleanity_point[..log_k_chunk]; + let rho_rev = reverse_slice(local_point); + let eq_bool_eval = EqPolynomial::::mle(&rho_rev, r_addr_bool); + let gamma = store.scalar("stage7.hamming_weight_claim_reduction.gamma")?; + let gamma_powers = gamma_powers(gamma, 3 * ra_eval_plans(program).len()); + + let mut output_claim = F::zero(); + for (index, eval_plan) in ra_eval_plans(program).iter().enumerate() { + let g_i = eval_by_name(evals, eval_plan.name)?; + let virtual_point = + store.dynamic_point(stage7_virtualization_input_symbol(eval_plan.oracle)?)?; + if virtual_point.len() < log_k_chunk { + return Err(Stage7KernelError::InvalidInputLength { + input: "stage7.hamming_weight_claim_reduction.virtualization_point", + expected: log_k_chunk, + actual: virtual_point.len(), + }); + } + let eq_virt_eval = EqPolynomial::::mle(&rho_rev, &virtual_point[..log_k_chunk]); + output_claim += g_i + * (gamma_powers[3 * index] + + gamma_powers[3 * index + 1] * eq_bool_eval + + gamma_powers[3 * index + 2] * eq_virt_eval); + } + Ok(output_claim) +} + +struct Stage7BatchedInstance<'a, F: Field> { + claim: &'a Stage7SumcheckClaimPlan, + relation: Stage7Relation, + offset: usize, + previous_claim: F, + state: Stage7ProverInstanceState, +} + +impl Stage7BatchedInstance<'_, F> { + fn is_active(&self, round: usize) -> bool { + round >= self.offset && round < self.offset + self.claim.num_rounds + } +} + +enum Stage7ProverInstanceState { + HammingWeightClaimReduction(HammingWeightClaimReductionState), +} + +impl Stage7ProverInstanceState { + fn new( + program: &'static Stage7CpuProgramPlan, + claim: &Stage7SumcheckClaimPlan, + inputs: &Stage7ProverInputs<'_, F>, + store: &Stage7ValueStore, + active_scale: F, + ) -> Result { + match claim_relation(program, claim)? { + Stage7Relation::HammingWeightClaimReduction => { + hamming_weight_claim_reduction_state(program, claim, inputs, store, active_scale) + .map(Self::HammingWeightClaimReduction) + } + relation @ Stage7Relation::Batched => Err(Stage7KernelError::KernelNotImplemented { + abi: relation.symbol(), + }), + } + } + + fn round_poly( + &mut self, + previous_claim: F, + relation: Stage7Relation, + ) -> Result, Stage7KernelError> { + match self { + Self::HammingWeightClaimReduction(state) => state.round_poly(previous_claim, relation), + } + } + + fn ingest_challenge(&mut self, challenge: F) { + match self { + Self::HammingWeightClaimReduction(state) => state.bind(challenge), + } + } + + fn final_relation_eval(&self, relation: Stage7Relation) -> Result { + match self { + Self::HammingWeightClaimReduction(state) => state.final_relation_eval(relation), + } + } + + fn final_evals( + &self, + relation: Stage7Relation, + ) -> Result>, Stage7KernelError> { + match self { + Self::HammingWeightClaimReduction(state) => state.final_evals(relation), + } + } +} + +struct HammingWeightClaimReductionState { + g: Vec>, + eq_bool: Vec, + eq_virt: Vec>, + gamma_powers: Vec, + outputs: Vec, + active_scale: F, +} + +#[derive(Clone, Copy)] +struct Stage7RaOutputPlan { + name: &'static str, + oracle: &'static str, +} + +impl HammingWeightClaimReductionState { + fn round_poly( + &mut self, + previous_claim: F, + relation: Stage7Relation, + ) -> Result, Stage7KernelError> { + if relation != Stage7Relation::HammingWeightClaimReduction { + return Err(Stage7KernelError::InvalidProof { + driver: relation.symbol(), + reason: "wrong relation for hamming-weight claim-reduction state", + }); + } + let half_len = self + .g + .first() + .ok_or(Stage7KernelError::InvalidProof { + driver: relation.symbol(), + reason: "missing G polynomial", + })? + .len() + / 2; + let mut evals = [F::zero(); 2]; + for row in 0..half_len { + let eq_bool_evals = low_to_high_linear_evals(&self.eq_bool, row); + for index in 0..self.g.len() { + let g_evals = low_to_high_linear_evals(&self.g[index], row); + let eq_virt_evals = low_to_high_linear_evals(&self.eq_virt[index], row); + let gamma_hw = self.gamma_powers[3 * index]; + let gamma_bool = self.gamma_powers[3 * index + 1]; + let gamma_virt = self.gamma_powers[3 * index + 2]; + for eval_index in 0..2 { + evals[eval_index] += g_evals[eval_index] + * (gamma_hw + + gamma_bool * eq_bool_evals[eval_index] + + gamma_virt * eq_virt_evals[eval_index]); + } + } + } + for eval in &mut evals { + *eval *= self.active_scale; + } + Ok(UnivariatePoly::from_evals_and_hint(previous_claim, &evals)) + } + + fn bind(&mut self, challenge: F) { + let mut scratch = Vec::new(); + for g in &mut self.g { + bind_dense_evals_reuse(g, &mut scratch, challenge); + } + bind_dense_evals_reuse(&mut self.eq_bool, &mut scratch, challenge); + for eq in &mut self.eq_virt { + bind_dense_evals_reuse(eq, &mut scratch, challenge); + } + } + + fn final_relation_eval(&self, relation: Stage7Relation) -> Result { + if relation != Stage7Relation::HammingWeightClaimReduction { + return Err(Stage7KernelError::InvalidProof { + driver: relation.symbol(), + reason: "wrong relation for hamming-weight claim-reduction state", + }); + } + let eq_bool = single_final_eval(&self.eq_bool, "stage7.eq_bool")?; + let mut value = F::zero(); + for index in 0..self.g.len() { + let g = single_final_eval(&self.g[index], "stage7.G")?; + let eq_virt = single_final_eval(&self.eq_virt[index], "stage7.eq_virt")?; + value += g + * (self.gamma_powers[3 * index] + + self.gamma_powers[3 * index + 1] * eq_bool + + self.gamma_powers[3 * index + 2] * eq_virt); + } + Ok(value * self.active_scale) + } + + fn final_evals( + &self, + relation: Stage7Relation, + ) -> Result>, Stage7KernelError> { + if relation != Stage7Relation::HammingWeightClaimReduction { + return Err(Stage7KernelError::InvalidProof { + driver: relation.symbol(), + reason: "wrong relation for hamming-weight claim-reduction state", + }); + } + if self.outputs.len() != self.g.len() { + return Err(Stage7KernelError::PlanCountMismatch { + artifact: "hamming-weight claim-reduction eval outputs", + expected: self.g.len(), + actual: self.outputs.len(), + }); + } + self.g + .iter() + .zip(&self.outputs) + .map(|(g, output)| { + Ok(Stage7NamedEval { + name: output.name, + oracle: output.oracle, + value: single_final_eval(g, output.name)?, + }) + }) + .collect() + } +} + +fn hamming_weight_claim_reduction_state( + program: &'static Stage7CpuProgramPlan, + claim: &Stage7SumcheckClaimPlan, + inputs: &Stage7ProverInputs<'_, F>, + store: &Stage7ValueStore, + active_scale: F, +) -> Result, Stage7KernelError> { + let log_k_chunk = claim.num_rounds; + let booleanity_point = store.point("stage7.input.stage6.booleanity.InstructionRa_0")?; + if booleanity_point.len() < log_k_chunk { + return Err(Stage7KernelError::InvalidInputLength { + input: "stage7.input.stage6.booleanity.InstructionRa_0", + expected: log_k_chunk, + actual: booleanity_point.len(), + }); + } + let r_addr_bool = &booleanity_point[..log_k_chunk]; + let r_cycle = &booleanity_point[log_k_chunk..]; + let outputs = ra_eval_plans(program); + let eq_cycle = EqPolynomial::::evals(r_cycle, None); + let g = if let Some(index_witness) = inputs.hamming_weight_claim_reduction_indices { + let chunks = index_witness.ra_chunks_in_program_order(); + if chunks.len() != outputs.len() { + return Err(Stage7KernelError::PlanCountMismatch { + artifact: "stage7 RA index witness chunks", + expected: outputs.len(), + actual: chunks.len(), + }); + } + chunks + .iter() + .map(|chunk| pushforward_ra_indices(chunk.indices, log_k_chunk, &eq_cycle)) + .collect::, _>>()? + } else { + let witness = + inputs + .hamming_weight_claim_reduction + .ok_or(Stage7KernelError::MissingKernelInput { + kernel: "jolt_stage7_hamming_weight_claim_reduction", + input: "hamming_weight_claim_reduction", + })?; + let chunks = witness.ra_chunks_in_program_order(); + if chunks.len() != outputs.len() { + return Err(Stage7KernelError::PlanCountMismatch { + artifact: "stage7 RA witness chunks", + expected: outputs.len(), + actual: chunks.len(), + }); + } + chunks + .iter() + .map(|chunk| pushforward_ra_chunk(chunk.evals, chunk.layout, log_k_chunk, &eq_cycle)) + .collect::, _>>()? + }; + let eq_bool = EqPolynomial::::evals(r_addr_bool, None); + let mut eq_virt = Vec::with_capacity(outputs.len()); + for output in &outputs { + let virtual_point = + store.dynamic_point(stage7_virtualization_input_symbol(output.oracle)?)?; + if virtual_point.len() < log_k_chunk { + return Err(Stage7KernelError::InvalidInputLength { + input: "stage7.hamming_weight_claim_reduction.virtualization_point", + expected: log_k_chunk, + actual: virtual_point.len(), + }); + } + eq_virt.push(EqPolynomial::::evals( + &virtual_point[..log_k_chunk], + None, + )); + } + let gamma = store.scalar("stage7.hamming_weight_claim_reduction.gamma")?; + let gamma_powers = gamma_powers(gamma, 3 * outputs.len()); + Ok(HammingWeightClaimReductionState { + g, + eq_bool, + eq_virt, + gamma_powers, + outputs, + active_scale, + }) +} + +#[derive(Clone, Copy)] +struct Stage7RaChunk<'a, F: Field> { + evals: &'a [F], + layout: Stage7RaChunkLayout, +} + +#[derive(Clone, Copy)] +struct Stage7RaIndexChunk<'a> { + indices: &'a [Option], +} + +impl<'a, F: Field> Stage7HammingWeightClaimReductionWitness<'a, F> { + fn ra_chunks_in_program_order(&self) -> Vec> { + let mut chunks = Vec::with_capacity( + self.instruction_ra.chunks.len() + + self.bytecode_ra.chunks.len() + + self.ram_ra.chunks.len(), + ); + chunks.extend( + self.instruction_ra + .chunks + .iter() + .map(|chunk| Stage7RaChunk { + evals: chunk, + layout: self.instruction_ra.layout, + }), + ); + chunks.extend(self.bytecode_ra.chunks.iter().map(|chunk| Stage7RaChunk { + evals: chunk, + layout: self.bytecode_ra.layout, + })); + chunks.extend(self.ram_ra.chunks.iter().map(|chunk| Stage7RaChunk { + evals: chunk, + layout: self.ram_ra.layout, + })); + chunks + } +} + +impl<'a> Stage7HammingWeightClaimReductionIndexWitness<'a> { + fn ra_chunks_in_program_order(&self) -> Vec> { + let mut chunks = Vec::with_capacity( + self.instruction_ra.chunks.len() + + self.bytecode_ra.chunks.len() + + self.ram_ra.chunks.len(), + ); + chunks.extend( + self.instruction_ra + .chunks + .iter() + .map(|indices| Stage7RaIndexChunk { indices }), + ); + chunks.extend( + self.bytecode_ra + .chunks + .iter() + .map(|indices| Stage7RaIndexChunk { indices }), + ); + chunks.extend( + self.ram_ra + .chunks + .iter() + .map(|indices| Stage7RaIndexChunk { indices }), + ); + chunks + } +} + +fn pushforward_ra_chunk( + chunk: &[F], + layout: Stage7RaChunkLayout, + log_k_chunk: usize, + eq_cycle: &[F], +) -> Result, Stage7KernelError> { + let address_len = 1usize << log_k_chunk; + let cycle_len = eq_cycle.len(); + let expected = address_len * cycle_len; + if chunk.len() != expected { + return Err(Stage7KernelError::InvalidInputLength { + input: "stage7.ra_chunk", + expected, + actual: chunk.len(), + }); + } + let mut output = vec![F::zero(); address_len]; + match layout { + Stage7RaChunkLayout::CycleMajor => { + for (cycle, weight) in eq_cycle.iter().copied().enumerate().take(cycle_len) { + let row_start = cycle * address_len; + for address in 0..address_len { + output[address] += chunk[row_start + address] * weight; + } + } + } + Stage7RaChunkLayout::AddressMajor => { + for (address, output_value) in output.iter_mut().enumerate().take(address_len) { + let row_start = address * cycle_len; + for (cycle, weight) in eq_cycle.iter().copied().enumerate().take(cycle_len) { + *output_value += chunk[row_start + cycle] * weight; + } + } + } + } + Ok(output) +} + +fn pushforward_ra_indices( + indices: &[Option], + log_k_chunk: usize, + eq_cycle: &[F], +) -> Result, Stage7KernelError> { + let address_len = 1usize << log_k_chunk; + if indices.len() != eq_cycle.len() { + return Err(Stage7KernelError::InvalidInputLength { + input: "stage7.ra_index_chunk", + expected: eq_cycle.len(), + actual: indices.len(), + }); + } + let mut output = vec![F::zero(); address_len]; + for (cycle, index) in indices.iter().enumerate() { + if let Some(index) = index { + let index = usize::from(*index); + if index >= address_len { + return Err(Stage7KernelError::InvalidInputLength { + input: "stage7.ra_index", + expected: address_len, + actual: index + 1, + }); + } + output[index] += eq_cycle[cycle]; + } + } + Ok(output) +} + +fn low_to_high_linear_evals(evals: &[F], row: usize) -> [F; 2] { + let low = evals[2 * row]; + let high = evals[2 * row + 1]; + [low, low + (high - low) * F::from_u64(2)] +} + +fn single_final_eval(evals: &[F], symbol: &'static str) -> Result { + if evals.len() == 1 { + Ok(evals[0]) + } else { + Err(Stage7KernelError::InvalidInputLength { + input: symbol, + expected: 1, + actual: evals.len(), + }) + } +} + +fn gamma_powers(gamma: F, count: usize) -> Vec { + let mut powers = Vec::with_capacity(count); + let mut power = F::one(); + for _ in 0..count { + powers.push(power); + power *= gamma; + } + powers +} + +fn ra_eval_plans(program: &'static Stage7CpuProgramPlan) -> Vec { + let mut evals = program + .evals + .iter() + .filter(|eval| { + eval.name + .starts_with("stage7.hamming_weight_claim_reduction.eval.") + }) + .collect::>(); + evals.sort_by_key(|eval| eval.index); + evals + .into_iter() + .map(|eval| Stage7RaOutputPlan { + name: eval.name, + oracle: eval.oracle, + }) + .collect() +} + +fn stage7_virtualization_input_symbol(oracle: &str) -> Result { + if oracle.starts_with("InstructionRa_") { + Ok(format!( + "stage7.input.stage6.instruction_ra_virtual.{oracle}" + )) + } else if oracle.starts_with("BytecodeRa_") { + Ok(format!("stage7.input.stage6.bytecode_read_raf.{oracle}")) + } else if oracle.starts_with("RamRa_") { + Ok(format!("stage7.input.stage6.ram_ra_virtual.{oracle}")) + } else { + Err(Stage7KernelError::InvalidProof { + driver: "stage7.hamming_weight_claim_reduction.oracle", + reason: "unknown RA oracle family", + }) + } +} + +fn eval_by_name( + evals: &[Stage7NamedEval], + name: &'static str, +) -> Result { + evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| eval.value) + .ok_or(Stage7KernelError::MissingValue { symbol: name }) +} + +fn claim_relation( + program: &'static Stage7CpuProgramPlan, + claim: &Stage7SumcheckClaimPlan, +) -> Result { + if let Some(relation) = claim.relation { + return Stage7Relation::from_symbol(relation) + .ok_or(Stage7KernelError::UnknownRelation { relation }); + } + let kernel_symbol = claim.kernel.ok_or(Stage7KernelError::MissingKernel { + driver: claim.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or(Stage7KernelError::MissingKernel { + driver: claim.symbol, + kernel: kernel_symbol, + })?; + kernel.relation_kind() +} + +fn instance_round_offset( + program: &'static Stage7CpuProgramPlan, + driver: &'static str, + claim: &'static str, +) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.source == driver && instance.claim == claim) + .map(|instance| instance.round_offset) + .ok_or(Stage7KernelError::MissingValue { symbol: claim }) +} + +fn evaluate_stage7_field_expr( + expr: &Stage7FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => single_operand(expr.symbol, operands), + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + formula => { + if let Some(exponent) = formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + Stage7KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + Err(Stage7KernelError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }) + } + } +} + +fn pow_field(base: F, mut exponent: usize) -> F { + let mut result = F::one(); + let mut power = base; + while exponent != 0 { + if exponent & 1 == 1 { + result *= power; + } + power = power.square(); + exponent >>= 1; + } + result +} + +fn single_operand(symbol: &'static str, operands: &[F]) -> Result { + require_operand_count(symbol, 1, operands.len())?; + Ok(operands[0]) +} + +fn require_operand_count( + input: &'static str, + expected: usize, + actual: usize, +) -> Result<(), Stage7KernelError> { + if expected == actual { + Ok(()) + } else { + Err(Stage7KernelError::InvalidInputLength { + input, + expected, + actual, + }) + } +} + +fn combine_univariate_polys( + polynomials: &[UnivariatePoly], + coefficients: &[F], +) -> UnivariatePoly { + let max_len = polynomials + .iter() + .map(|poly| poly.coefficients().len()) + .max() + .unwrap_or(0); + let mut combined = vec![F::zero(); max_len]; + for (poly, &coefficient) in polynomials.iter().zip(coefficients) { + for (combined, &term) in combined.iter_mut().zip(poly.coefficients()) { + *combined += term * coefficient; + } + } + UnivariatePoly::new(combined) +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients() + .iter() + .rposition(|coefficient| *coefficient != F::zero()) + .unwrap_or(0) +} + +fn append_compressed_univariate_poly( + transcript: &mut T, + label: &'static str, + poly: &UnivariatePoly, +) where + F: Field, + T: Transcript, +{ + let compressed = poly.compress(); + transcript.append(&LabelWithCount( + label.as_bytes(), + compressed.coeffs_except_linear_term().len() as u64, + )); + for coefficient in compressed.coeffs_except_linear_term() { + transcript.append(coefficient); + } +} + +fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &F) +where + F: Field, + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +fn append_opening_claims( + program: &'static Stage7CpuProgramPlan, + store: &mut Stage7ValueStore, + transcript: &mut T, + evals: &[Stage7NamedEval], +) -> Result>, Stage7KernelError> +where + F: Field, + T: Transcript, +{ + if program.opening_batches.is_empty() { + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } + return Ok(Vec::new()); + } + let _ = store.evaluate_available_points(program)?; + let mut opening_claims = Vec::new(); + for batch in program.opening_batches { + for symbol in batch.claim_operands { + let claim = + find_opening_claim(program, symbol).ok_or(Stage7KernelError::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + let point = store.point(claim.point_source)?.to_vec(); + let value = store.scalar(claim.eval_source)?; + append_labeled_scalar(transcript, "opening_claim", &value); + opening_claims.push(Stage7OpeningClaimValue { + symbol: claim.symbol, + oracle: claim.oracle, + domain: claim.domain, + claim_kind: claim.claim_kind, + point, + eval: value, + }); + } + } + Ok(opening_claims) +} + +pub fn execute_stage7_program( + program: &'static Stage7CpuProgramPlan, + mode: Stage7ExecutionMode, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage7KernelError> +where + F: Field, + T: Transcript, + E: Stage7KernelExecutor, +{ + let mut artifacts = Stage7ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_squeeze(program, step.symbol).ok_or(Stage7KernelError::MissingValue { + symbol: step.symbol, + })?; + let values = transcript.challenge_vector(squeeze.count); + executor.observe_challenge_vector(squeeze, &values)?; + artifacts.challenge_vectors.push(Stage7ChallengeVector { + symbol: squeeze.symbol, + values, + }); + } + "transcript_absorb_bytes" => { + let absorb = find_absorb_bytes(program, step.symbol).ok_or( + Stage7KernelError::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage7_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_driver(program, step.symbol).ok_or(Stage7KernelError::MissingDriver { + driver: step.symbol, + })?; + let kernel_symbol = driver.kernel.ok_or(Stage7KernelError::MissingKernel { + driver: driver.symbol, + kernel: "", + })?; + let kernel = find_kernel(program, kernel_symbol).ok_or( + Stage7KernelError::MissingKernel { + driver: driver.symbol, + kernel: kernel_symbol, + }, + )?; + let batch = + find_batch(program, driver.batch).ok_or(Stage7KernelError::MissingBatch { + driver: driver.symbol, + batch: driver.batch, + })?; + let context = Stage7KernelContext { + mode, + program, + kernel, + batch, + driver, + }; + let output = match mode { + Stage7ExecutionMode::Prover => executor.prove_sumcheck(context, transcript)?, + Stage7ExecutionMode::Verifier => { + executor.verify_sumcheck(context, transcript)? + } + }; + executor.observe_sumcheck_output(&output)?; + artifacts + .opening_claims + .extend(output.opening_claims.clone()); + artifacts.sumchecks.push(output); + } + _ => { + return Err(Stage7KernelError::InvalidProgramStep { + symbol: step.symbol, + kind: step.kind, + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +fn absorb_stage7_bytes(absorb: &'static Stage7TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn find_squeeze( + program: &'static Stage7CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage7TranscriptSqueezePlan> { + program + .transcript_squeezes + .iter() + .find(|squeeze| squeeze.symbol == symbol) +} + +fn find_absorb_bytes( + program: &'static Stage7CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage7TranscriptAbsorbBytesPlan> { + program + .transcript_absorb_bytes + .iter() + .find(|absorb| absorb.symbol == symbol) +} + +fn find_driver( + program: &'static Stage7CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage7SumcheckDriverPlan> { + program + .drivers + .iter() + .find(|driver| driver.symbol == symbol) +} + +fn find_kernel( + program: &'static Stage7CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage7KernelPlan> { + program + .kernels + .iter() + .find(|kernel| kernel.symbol == symbol) +} + +fn find_batch( + program: &'static Stage7CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage7SumcheckBatchPlan> { + program.batches.iter().find(|batch| batch.symbol == symbol) +} + +fn find_opening_claim( + program: &'static Stage7CpuProgramPlan, + symbol: &str, +) -> Option<&'static Stage7OpeningClaimPlan> { + program + .opening_claims + .iter() + .find(|claim| claim.symbol == symbol) +} + +fn reverse_slice(slice: &[F]) -> Vec { + slice.iter().rev().copied().collect() +} + +#[cfg(test)] +#[expect( + clippy::expect_used, + reason = "tests use expect to keep failure context concise" +)] +mod tests { + use super::*; + use jolt_field::Fr; + use jolt_transcript::Blake2bTranscript; + + const PARAMS: Stage7Params = Stage7Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", + }; + const STEPS: &[Stage7ProgramStepPlan] = &[Stage7ProgramStepPlan { + kind: "sumcheck_driver", + symbol: "stage7.sumcheck", + }]; + const FIELD_CONSTANTS: &[Stage7FieldConstantPlan] = &[ + Stage7FieldConstantPlan { + symbol: "stage7.field.one", + field: "bn254_fr", + value: 1, + }, + Stage7FieldConstantPlan { + symbol: "stage7.hamming_weight_claim_reduction.gamma", + field: "bn254_fr", + value: 2, + }, + ]; + const CLAIM_INPUTS: &[&str] = &["stage7.input.claim"]; + const CLAIMS: &[Stage7SumcheckClaimPlan] = &[Stage7SumcheckClaimPlan { + symbol: "stage7.hamming_weight_claim_reduction.input", + stage: "stage7", + domain: "jolt.stage7_hamming_weight_claim_reduction_domain", + num_rounds: 1, + degree: 2, + claim: "stage7.hamming_weight_claim_reduction.weighted_stage6_claims", + kernel: Some("jolt.cpu.stage7.hamming_weight_claim_reduction"), + relation: None, + claim_value: "stage7.input.claim", + input_openings: CLAIM_INPUTS, + }]; + const KERNELS: &[Stage7KernelPlan] = &[ + Stage7KernelPlan { + symbol: "jolt.cpu.stage7.hamming_weight_claim_reduction", + relation: "jolt.stage7.hamming_weight_claim_reduction", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage7_hamming_weight_claim_reduction", + }, + Stage7KernelPlan { + symbol: "jolt.cpu.stage7.batched", + relation: "jolt.stage7.batched", + kind: "sumcheck", + backend: "cpu", + abi: "jolt_stage7_batched", + }, + ]; + const BATCHES: &[Stage7SumcheckBatchPlan] = &[Stage7SumcheckBatchPlan { + symbol: "stage7.batch", + stage: "stage7", + proof_slot: "stage7.sumcheck", + policy: "jolt_core_stage7_aligned", + count: 1, + ordered_claims: &["stage7.hamming_weight_claim_reduction.input"], + claim_operands: &["stage7.hamming_weight_claim_reduction.input"], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + round_schedule: &[1], + }]; + const DRIVERS: &[Stage7SumcheckDriverPlan] = &[Stage7SumcheckDriverPlan { + symbol: "stage7.sumcheck", + stage: "stage7", + proof_slot: "stage7.sumcheck", + kernel: Some("jolt.cpu.stage7.batched"), + relation: Some("jolt.stage7.batched"), + batch: "stage7.batch", + policy: "jolt_core_stage7_aligned", + round_schedule: &[1], + claim_label: "sumcheck_claim", + round_label: "sumcheck_poly", + num_rounds: 1, + degree: 2, + }]; + const INSTANCE_RESULTS: &[Stage7SumcheckInstanceResultPlan] = + &[Stage7SumcheckInstanceResultPlan { + symbol: "stage7.hamming_weight_claim_reduction.instance", + source: "stage7.sumcheck", + claim: "stage7.hamming_weight_claim_reduction.input", + relation: "jolt.stage7.hamming_weight_claim_reduction", + index: 0, + point_arity: 1, + num_rounds: 1, + round_offset: 0, + point_order: "reverse", + degree: 2, + }]; + const EVALS: &[Stage7SumcheckEvalPlan] = &[ + Stage7SumcheckEvalPlan { + symbol: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", + source: "stage7.sumcheck", + name: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", + index: 0, + oracle: "InstructionRa_0", + }, + Stage7SumcheckEvalPlan { + symbol: "stage7.hamming_weight_claim_reduction.eval.RamRa_0", + source: "stage7.sumcheck", + name: "stage7.hamming_weight_claim_reduction.eval.RamRa_0", + index: 1, + oracle: "RamRa_0", + }, + ]; + const OPENING_CLAIMS: &[Stage7OpeningClaimPlan] = &[ + Stage7OpeningClaimPlan { + symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0", + oracle: "InstructionRa_0", + domain: "jolt.main_witness_commit_domain", + point_arity: 2, + claim_kind: "committed", + point_source: "stage7.hamming_weight_claim_reduction.point", + eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", + }, + Stage7OpeningClaimPlan { + symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_0", + oracle: "RamRa_0", + domain: "jolt.main_witness_commit_domain", + point_arity: 2, + claim_kind: "committed", + point_source: "stage7.hamming_weight_claim_reduction.point", + eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_0", + }, + ]; + const OPENING_BATCHES: &[Stage7OpeningBatchPlan] = &[Stage7OpeningBatchPlan { + symbol: "stage7.openings", + stage: "stage7", + proof_slot: "stage7.openings", + policy: "jolt_stage7_output_order", + count: 2, + ordered_claims: &[ + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0", + "stage7.hamming_weight_claim_reduction.opening.RamRa_0", + ], + claim_operands: &[ + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0", + "stage7.hamming_weight_claim_reduction.opening.RamRa_0", + ], + }]; + const POINT_SLICES: &[Stage7PointSlicePlan] = &[Stage7PointSlicePlan { + symbol: "stage7.hamming_weight_claim_reduction.point.cycle", + source: "stage7.input.stage6.booleanity.InstructionRa_0", + offset: 1, + length: 1, + input: "stage7.input.stage6.booleanity.InstructionRa_0", + }]; + const POINT_CONCAT_INPUTS: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.instance", + "stage7.hamming_weight_claim_reduction.point.cycle", + ]; + const POINT_CONCATS: &[Stage7PointConcatPlan] = &[Stage7PointConcatPlan { + symbol: "stage7.hamming_weight_claim_reduction.point", + layout: "address_chunk_then_cycle", + arity: 2, + inputs: POINT_CONCAT_INPUTS, + }]; + const PROGRAM: Stage7CpuProgramPlan = Stage7CpuProgramPlan { + role: "prover", + params: PARAMS, + steps: STEPS, + transcript_squeezes: &[], + transcript_absorb_bytes: &[], + opening_inputs: &[], + field_constants: FIELD_CONSTANTS, + field_exprs: &[], + kernels: KERNELS, + claims: CLAIMS, + batches: BATCHES, + drivers: DRIVERS, + instance_results: INSTANCE_RESULTS, + evals: EVALS, + point_zeros: &[], + point_slices: POINT_SLICES, + point_concats: POINT_CONCATS, + opening_claims: OPENING_CLAIMS, + opening_equalities: &[], + opening_batches: OPENING_BATCHES, + }; + + #[test] + fn hamming_weight_claim_reduction_prover_and_replay_agree() { + let r_bool = Fr::from_u64(5); + let r_cycle = Fr::from_u64(3); + let r_instr_virt = Fr::from_u64(7); + let r_ram_virt = Fr::from_u64(11); + let instruction_ra = vec![ + Fr::from_u64(1), + Fr::from_u64(0), + Fr::from_u64(0), + Fr::from_u64(1), + ]; + let ram_ra = vec![ + Fr::from_u64(0), + Fr::from_u64(0), + Fr::from_u64(1), + Fr::from_u64(0), + ]; + let bool_point = vec![r_bool, r_cycle]; + let instr_virt_point = vec![r_instr_virt, r_cycle]; + let ram_virt_point = vec![r_ram_virt, r_cycle]; + let instr_bool = eval_full_cycle_major(&instruction_ra, &bool_point); + let ram_bool = eval_full_cycle_major(&ram_ra, &bool_point); + let instr_virt = eval_full_cycle_major(&instruction_ra, &instr_virt_point); + let ram_virt = eval_full_cycle_major(&ram_ra, &ram_virt_point); + let ram_hw = r_cycle; + let gamma = Fr::from_u64(2); + let gamma_powers = gamma_powers(gamma, 6); + let input_claim = gamma_powers[0] * Fr::from_u64(1) + + gamma_powers[1] * instr_bool + + gamma_powers[2] * instr_virt + + gamma_powers[3] * ram_hw + + gamma_powers[4] * ram_bool + + gamma_powers[5] * ram_virt; + let openings = vec![ + Stage7OpeningInputValue { + symbol: "stage7.input.claim", + point: Vec::new(), + eval: input_claim, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.hamming_booleanity.HammingWeight", + point: vec![r_cycle], + eval: ram_hw, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.booleanity.InstructionRa_0", + point: bool_point.clone(), + eval: instr_bool, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_0", + point: instr_virt_point, + eval: instr_virt, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.booleanity.RamRa_0", + point: bool_point, + eval: ram_bool, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_0", + point: ram_virt_point, + eval: ram_virt, + }, + ]; + let instruction_chunks: Vec<&[Fr]> = vec![&instruction_ra]; + let ram_chunks: Vec<&[Fr]> = vec![&ram_ra]; + let inputs = Stage7ProverInputs::new(&openings).with_hamming_weight_claim_reduction( + Stage7HammingWeightClaimReductionWitness { + instruction_ra: Stage7RaChunks { + chunks: &instruction_chunks, + layout: Stage7RaChunkLayout::CycleMajor, + }, + bytecode_ra: Stage7RaChunks { + chunks: &[], + layout: Stage7RaChunkLayout::CycleMajor, + }, + ram_ra: Stage7RaChunks { + chunks: &ram_chunks, + layout: Stage7RaChunkLayout::CycleMajor, + }, + }, + ); + let mut prover = Stage7ProverKernelExecutor::new(inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage7-test"); + let artifacts = execute_stage7_program( + &PROGRAM, + Stage7ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("stage7 prover succeeds"); + assert_eq!(artifacts.sumchecks.len(), 1); + assert_eq!(artifacts.sumchecks[0].evals.len(), 2); + + let proof = Stage7Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage7ProofCarryingKernelExecutor::new(&proof, &openings); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage7-test"); + let verified = execute_stage7_program( + &PROGRAM, + Stage7ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("stage7 replay succeeds"); + assert_eq!(verified.sumchecks[0].point, artifacts.sumchecks[0].point); + assert_eq!(verifier_transcript.state(), prover_transcript.state()); + } + + #[test] + fn hamming_weight_claim_reduction_index_witness_matches_dense_witness() { + let r_bool = Fr::from_u64(5); + let r_cycle = Fr::from_u64(3); + let r_instr_virt = Fr::from_u64(7); + let r_ram_virt = Fr::from_u64(11); + let instruction_ra = vec![ + Fr::from_u64(1), + Fr::from_u64(0), + Fr::from_u64(0), + Fr::from_u64(1), + ]; + let ram_ra = vec![ + Fr::from_u64(0), + Fr::from_u64(0), + Fr::from_u64(1), + Fr::from_u64(0), + ]; + let bool_point = vec![r_bool, r_cycle]; + let instr_virt_point = vec![r_instr_virt, r_cycle]; + let ram_virt_point = vec![r_ram_virt, r_cycle]; + let instr_bool = eval_full_cycle_major(&instruction_ra, &bool_point); + let ram_bool = eval_full_cycle_major(&ram_ra, &bool_point); + let instr_virt = eval_full_cycle_major(&instruction_ra, &instr_virt_point); + let ram_virt = eval_full_cycle_major(&ram_ra, &ram_virt_point); + let ram_hw = r_cycle; + let gamma = Fr::from_u64(2); + let gamma_powers = gamma_powers(gamma, 6); + let input_claim = gamma_powers[0] * Fr::from_u64(1) + + gamma_powers[1] * instr_bool + + gamma_powers[2] * instr_virt + + gamma_powers[3] * ram_hw + + gamma_powers[4] * ram_bool + + gamma_powers[5] * ram_virt; + let openings = vec![ + Stage7OpeningInputValue { + symbol: "stage7.input.claim", + point: Vec::new(), + eval: input_claim, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.hamming_booleanity.HammingWeight", + point: vec![r_cycle], + eval: ram_hw, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.booleanity.InstructionRa_0", + point: bool_point.clone(), + eval: instr_bool, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_0", + point: instr_virt_point, + eval: instr_virt, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.booleanity.RamRa_0", + point: bool_point, + eval: ram_bool, + }, + Stage7OpeningInputValue { + symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_0", + point: ram_virt_point, + eval: ram_virt, + }, + ]; + let instruction_indices = vec![Some(0), Some(1)]; + let ram_indices = vec![None, Some(0)]; + let instruction_chunks: Vec<&[Option]> = vec![&instruction_indices]; + let ram_chunks: Vec<&[Option]> = vec![&ram_indices]; + let inputs = Stage7ProverInputs::new(&openings) + .with_hamming_weight_claim_reduction_indices( + Stage7HammingWeightClaimReductionIndexWitness { + instruction_ra: Stage7RaIndexChunks { + chunks: &instruction_chunks, + }, + bytecode_ra: Stage7RaIndexChunks { chunks: &[] }, + ram_ra: Stage7RaIndexChunks { + chunks: &ram_chunks, + }, + }, + ); + let mut prover = Stage7ProverKernelExecutor::new(inputs); + let mut prover_transcript = Blake2bTranscript::::new(b"stage7-test"); + let artifacts = execute_stage7_program( + &PROGRAM, + Stage7ExecutionMode::Prover, + &mut prover, + &mut prover_transcript, + ) + .expect("stage7 index prover succeeds"); + assert_eq!(artifacts.sumchecks.len(), 1); + + let proof = Stage7Proof { + sumchecks: artifacts.sumchecks.clone(), + }; + let mut verifier = Stage7ProofCarryingKernelExecutor::new(&proof, &openings); + let mut verifier_transcript = Blake2bTranscript::::new(b"stage7-test"); + let verified = execute_stage7_program( + &PROGRAM, + Stage7ExecutionMode::Verifier, + &mut verifier, + &mut verifier_transcript, + ) + .expect("stage7 index replay succeeds"); + assert_eq!(verified.sumchecks[0].point, artifacts.sumchecks[0].point); + assert_eq!(verifier_transcript.state(), prover_transcript.state()); + } + + fn eval_full_cycle_major(evals: &[Fr], point: &[Fr]) -> Fr { + let address = EqPolynomial::::evals(&point[..1], None); + let cycle = EqPolynomial::::evals(&point[1..], None); + let mut value = Fr::from_u64(0); + for cycle_index in 0..2 { + for address_index in 0..2 { + value += evals[cycle_index * 2 + address_index] + * cycle[cycle_index] + * address[address_index]; + } + } + value + } +} diff --git a/crates/jolt-kernels/src/trace.rs b/crates/jolt-kernels/src/trace.rs new file mode 100644 index 0000000000..1adb568246 --- /dev/null +++ b/crates/jolt-kernels/src/trace.rs @@ -0,0 +1,397 @@ +//! Trace-to-kernel witness views for Bolt-generated Jolt stages. + +use jolt_field::signed::{S128, S64}; +use jolt_field::Field; +use jolt_trace::{ + instruction_circuit_flags, instruction_instruction_flags, BytecodePreprocessing, + CircuitFlagSet, CircuitFlags, CycleRow, Instruction, InstructionFlags, InterleavedBitsMarker, + NUM_CIRCUIT_FLAGS, +}; +use jolt_witness::Stage6BytecodeEntry; + +use crate::stage1::Stage1Rv64Cycle; +use crate::stage2::{Stage2InstructionLookupCycle, Stage2ProductVirtualCycle, Stage2RamAccess}; +use crate::stage3::Stage3Cycle; +use crate::stage4::{Stage4RegisterAccess, Stage4RegisterRead, Stage4RegisterWrite}; + +pub fn stage1_rv64_cycles( + trace: &[C], + size: usize, + bytecode: &BytecodePreprocessing, +) -> Vec { + (0..size) + .map(|cycle| stage1_rv64_cycle(trace, cycle, bytecode)) + .collect() +} + +pub fn stage2_product_virtual_cycles( + trace: &[C], + size: usize, +) -> Vec { + (0..size) + .map(|index| stage2_product_virtual_cycle(trace, index)) + .collect() +} + +pub fn stage2_instruction_lookup_cycles( + trace: &[C], + size: usize, +) -> Vec { + (0..size) + .map(|index| stage2_instruction_lookup_cycle(trace.get(index).copied())) + .collect() +} + +pub fn stage2_ram_accesses( + trace: &[C], + size: usize, + mut remap_address: R, +) -> Vec +where + C: CycleRow, + R: FnMut(u64) -> Option, +{ + (0..size) + .map(|index| { + let Some(cycle) = trace.get(index) else { + return Stage2RamAccess::noop(); + }; + let Some(address) = cycle.ram_access_address() else { + return Stage2RamAccess::noop(); + }; + Stage2RamAccess { + remapped_address: remap_address(address), + read_value: cycle.ram_read_value().unwrap_or(0), + write_value: cycle.ram_write_value().unwrap_or(0), + } + }) + .collect() +} + +pub fn stage3_cycles( + trace: &[C], + size: usize, + bytecode: &BytecodePreprocessing, +) -> Vec { + (0..size) + .map(|cycle| stage3_cycle(trace.get(cycle).copied(), bytecode)) + .collect() +} + +pub fn stage4_register_accesses( + trace: &[C], + size: usize, +) -> Vec { + (0..size) + .map(|cycle| stage4_register_access(trace.get(cycle).copied())) + .collect() +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage5LookupTrace { + pub lookup_indices: Vec, + pub lookup_table_indices: Vec>, + pub is_interleaved_operands: Vec, +} + +pub fn stage5_lookup_trace( + trace: &[C], + size: usize, + mut lookup_table_index: L, +) -> Stage5LookupTrace +where + C: CycleRow, + L: FnMut(&C) -> Option, +{ + let mut lookup_indices = Vec::with_capacity(size); + let mut lookup_table_indices = Vec::with_capacity(size); + let mut is_interleaved_operands = Vec::with_capacity(size); + for index in 0..size { + let Some(cycle) = trace.get(index) else { + // Padding cycles are conceptually NoOp. NoOp's default + // CircuitFlagSet has no operand-combination bit set, so + // is_interleaved_operands is true (see jolt-riscv flags.rs). + // Returning false here diverges from the explicit NoOp-padded + // trace path used by jolt-core's fixture, causing downstream + // sumcheck input claim mismatches in Stage 6 bytecode_read_raf. + lookup_indices.push(0); + lookup_table_indices.push(None); + is_interleaved_operands.push(true); + continue; + }; + lookup_indices.push(cycle.lookup_index()); + lookup_table_indices.push(lookup_table_index(cycle)); + is_interleaved_operands.push(cycle.circuit_flags().is_interleaved_operands()); + } + Stage5LookupTrace { + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + } +} + +pub fn stage6_bytecode_entries( + bytecode: &BytecodePreprocessing, + mut lookup_table_index: L, +) -> Vec> +where + F: Field, + L: FnMut(&Instruction) -> Option, +{ + bytecode + .bytecode + .iter() + .map(|instruction| { + let instr = instruction.normalize(); + let circuit_flags = instruction_circuit_flags(instruction); + let instruction_flags = instruction_instruction_flags(instruction); + Stage6BytecodeEntry { + address: F::from_u64(instr.address as u64), + imm: F::from_i128(instr.operands.imm), + circuit_flags: stage6_circuit_flags(circuit_flags), + rd: instr.operands.rd.map(usize::from), + rs1: instr.operands.rs1.map(usize::from), + rs2: instr.operands.rs2.map(usize::from), + lookup_table: lookup_table_index(instruction), + is_interleaved: circuit_flags.is_interleaved_operands(), + is_branch: instruction_flags[InstructionFlags::Branch], + left_is_rs1: instruction_flags[InstructionFlags::LeftOperandIsRs1Value], + left_is_pc: instruction_flags[InstructionFlags::LeftOperandIsPC], + right_is_rs2: instruction_flags[InstructionFlags::RightOperandIsRs2Value], + right_is_imm: instruction_flags[InstructionFlags::RightOperandIsImm], + is_noop: instruction_flags[InstructionFlags::IsNoop], + } + }) + .collect() +} + +fn stage2_product_virtual_cycle( + trace: &[C], + index: usize, +) -> Stage2ProductVirtualCycle { + let Some(cycle) = trace.get(index) else { + return Stage2ProductVirtualCycle::padding(); + }; + let (instruction_left_input, instruction_right_input) = instruction_inputs(cycle); + let flags = cycle.circuit_flags(); + let instruction_flags = cycle.instruction_flags(); + let not_next_noop = trace.get(index + 1).is_some_and(|next| !next.is_noop()); + Stage2ProductVirtualCycle { + instruction_left_input, + instruction_right_input, + should_branch_lookup_output: cycle.lookup_output(), + write_lookup_output_to_rd_flag: flags[CircuitFlags::WriteLookupOutputToRD], + jump_flag: flags[CircuitFlags::Jump], + should_branch_flag: instruction_flags[InstructionFlags::Branch], + not_next_noop, + virtual_instruction_flag: flags[CircuitFlags::VirtualInstruction], + } +} + +fn stage2_instruction_lookup_cycle(cycle: Option) -> Stage2InstructionLookupCycle { + let Some(cycle) = cycle else { + return Stage2InstructionLookupCycle::padding(); + }; + let (left_instruction_input, right_instruction_input) = instruction_inputs(&cycle); + let product = instruction_product(left_instruction_input, right_instruction_input); + let (left_lookup_operand, right_lookup_operand) = lookup_operands_raw( + left_instruction_input, + right_instruction_input, + product, + cycle.circuit_flags(), + cycle.lookup_output(), + ); + Stage2InstructionLookupCycle { + lookup_output: cycle.lookup_output(), + left_lookup_operand, + right_lookup_operand, + left_instruction_input, + right_instruction_input, + } +} + +fn stage4_register_access(cycle: Option) -> Stage4RegisterAccess { + let Some(cycle) = cycle else { + return Stage4RegisterAccess::default(); + }; + Stage4RegisterAccess { + rs1: cycle.rs1_read().map(|(address, value)| Stage4RegisterRead { + address: address as usize, + value, + }), + rs2: cycle.rs2_read().map(|(address, value)| Stage4RegisterRead { + address: address as usize, + value, + }), + rd: cycle + .rd_write() + .map(|(address, pre_value, post_value)| Stage4RegisterWrite { + address: address as usize, + pre_value, + post_value, + }), + } +} + +fn stage3_cycle(cycle: Option, bytecode: &BytecodePreprocessing) -> Stage3Cycle { + let Some(cycle) = cycle else { + return Stage3Cycle::padding(); + }; + let circuit_flags = cycle.circuit_flags(); + let instruction_flags = cycle.instruction_flags(); + Stage3Cycle { + unexpanded_pc: cycle.unexpanded_pc(), + pc: bytecode.get_cycle_pc(&cycle) as u64, + is_virtual: circuit_flags[CircuitFlags::VirtualInstruction], + is_first_in_sequence: circuit_flags[CircuitFlags::IsFirstInSequence], + is_noop: instruction_flags[InstructionFlags::IsNoop], + left_operand_is_rs1: instruction_flags[InstructionFlags::LeftOperandIsRs1Value], + rs1_value: cycle.rs1_read().map_or(0, |(_, value)| value), + left_operand_is_pc: instruction_flags[InstructionFlags::LeftOperandIsPC], + right_operand_is_rs2: instruction_flags[InstructionFlags::RightOperandIsRs2Value], + rs2_value: cycle.rs2_read().map_or(0, |(_, value)| value), + right_operand_is_imm: instruction_flags[InstructionFlags::RightOperandIsImm], + imm: cycle.imm(), + rd_write_value: cycle.rd_write().map_or(0, |(_, _, post)| post), + } +} + +fn stage1_rv64_cycle( + trace: &[C], + cycle_index: usize, + bytecode: &BytecodePreprocessing, +) -> Stage1Rv64Cycle { + let Some(cycle) = trace.get(cycle_index) else { + return Stage1Rv64Cycle::padding(); + }; + let next = trace.get(cycle_index + 1); + if cycle.is_noop() { + let mut row = Stage1Rv64Cycle::padding(); + fill_next_rv64_fields(&mut row, next, bytecode); + return row; + } + + let flags_set = cycle.circuit_flags(); + let instruction_flags = cycle.instruction_flags(); + let (left_input, right_i128) = instruction_inputs(cycle); + let right_input = s64_from_i128(right_i128); + let product = instruction_product(left_input, right_i128); + let lookup_output = cycle.lookup_output(); + let (left_lookup, right_lookup) = + lookup_operands_raw(left_input, right_i128, product, flags_set, lookup_output); + let next_is_noop = next.is_none_or(CycleRow::is_noop); + let flags = stage1_rv64_flags(flags_set); + + let mut row = Stage1Rv64Cycle { + left_input, + right_input, + product, + left_lookup, + right_lookup, + lookup_output, + rs1_read_value: cycle.rs1_read().map_or(0, |(_, value)| value), + rs2_read_value: cycle.rs2_read().map_or(0, |(_, value)| value), + rd_write_value: cycle.rd_write().map_or(0, |(_, _, post)| post), + ram_addr: cycle.ram_access_address().unwrap_or(0), + ram_read_value: cycle.ram_read_value().unwrap_or(0), + ram_write_value: cycle.ram_write_value().unwrap_or(0), + pc: bytecode.get_cycle_pc(cycle) as u64, + next_pc: 0, + unexpanded_pc: cycle.unexpanded_pc(), + next_unexpanded_pc: 0, + imm: s64_from_i128(cycle.imm()), + flags, + should_jump: flags_set[CircuitFlags::Jump] && !next_is_noop, + should_branch: instruction_flags[InstructionFlags::Branch] && lookup_output == 1, + next_is_virtual: false, + next_is_first_in_sequence: false, + }; + fill_next_rv64_fields(&mut row, next, bytecode); + row +} + +fn fill_next_rv64_fields( + row: &mut Stage1Rv64Cycle, + next: Option<&C>, + bytecode: &BytecodePreprocessing, +) { + if let Some(next_cycle) = next { + row.next_pc = bytecode.get_cycle_pc(next_cycle) as u64; + row.next_unexpanded_pc = next_cycle.unexpanded_pc(); + let next_flags = next_cycle.circuit_flags(); + row.next_is_virtual = next_flags[CircuitFlags::VirtualInstruction]; + row.next_is_first_in_sequence = next_flags[CircuitFlags::IsFirstInSequence]; + } +} + +fn instruction_inputs(cycle: &impl CycleRow) -> (u64, i128) { + let instruction_flags = cycle.instruction_flags(); + let left_input = if instruction_flags[InstructionFlags::LeftOperandIsPC] { + cycle.unexpanded_pc() + } else if instruction_flags[InstructionFlags::LeftOperandIsRs1Value] { + cycle.rs1_read().map_or(0, |(_, value)| value) + } else { + 0 + }; + let right_input = if instruction_flags[InstructionFlags::RightOperandIsImm] { + cycle.imm() + } else if instruction_flags[InstructionFlags::RightOperandIsRs2Value] { + cycle.rs2_read().map_or(0, |(_, value)| value as i128) + } else { + 0 + }; + (left_input, right_input) +} + +fn instruction_product(left: u64, right: i128) -> S128 { + S64::from_u64(left).mul_trunc::<2, 2>(&S128::from_i128(right)) +} + +fn stage1_rv64_flags(flags: CircuitFlagSet) -> [bool; NUM_CIRCUIT_FLAGS] { + [ + flags[CircuitFlags::AddOperands], + flags[CircuitFlags::SubtractOperands], + flags[CircuitFlags::MultiplyOperands], + flags[CircuitFlags::Load], + flags[CircuitFlags::Store], + flags[CircuitFlags::Jump], + flags[CircuitFlags::WriteLookupOutputToRD], + flags[CircuitFlags::VirtualInstruction], + flags[CircuitFlags::Assert], + flags[CircuitFlags::DoNotUpdateUnexpandedPC], + flags[CircuitFlags::Advice], + flags[CircuitFlags::IsCompressed], + flags[CircuitFlags::IsFirstInSequence], + flags[CircuitFlags::IsLastInSequence], + ] +} + +fn stage6_circuit_flags(flags: CircuitFlagSet) -> [bool; NUM_CIRCUIT_FLAGS] { + stage1_rv64_flags(flags) +} + +fn s64_from_i128(value: i128) -> S64 { + let magnitude = value.unsigned_abs(); + assert!(magnitude <= u64::MAX as u128, "S64 input overflow"); + S64::from_u64_with_sign(magnitude as u64, value >= 0) +} + +fn lookup_operands_raw( + left: u64, + right: i128, + product: S128, + flags: CircuitFlagSet, + lookup_output: u64, +) -> (u64, u128) { + if flags[CircuitFlags::AddOperands] { + (0, (left as i128 + right) as u128) + } else if flags[CircuitFlags::SubtractOperands] { + (0, (left as i128 - right + (1i128 << 64)) as u128) + } else if flags[CircuitFlags::MultiplyOperands] { + (0, product.magnitude_as_u128()) + } else if flags[CircuitFlags::Advice] { + (0, lookup_output as u128) + } else { + (left, right as u128) + } +} diff --git a/crates/jolt-lookup-tables/src/instructions/virt/assert_lte.rs b/crates/jolt-lookup-tables/src/instructions/virt/assert_lte.rs index 93bfc17dae..61ed9974d9 100644 --- a/crates/jolt-lookup-tables/src/instructions/virt/assert_lte.rs +++ b/crates/jolt-lookup-tables/src/instructions/virt/assert_lte.rs @@ -3,7 +3,7 @@ use crate::traits::LookupQuery; use jolt_trace::instructions::AssertLte; use jolt_trace::JoltCycle; -impl_lookup_table!(AssertLte, Some(UnsignedLessThanEqual)); +impl_lookup_table!(AssertLte, Some(LessThanEqual)); impl LookupQuery for AssertLte { fn to_instruction_inputs(&self) -> (u64, i128) { diff --git a/crates/jolt-lookup-tables/src/instructions/virt/movsign.rs b/crates/jolt-lookup-tables/src/instructions/virt/movsign.rs index bdc615365f..c4e3962476 100644 --- a/crates/jolt-lookup-tables/src/instructions/virt/movsign.rs +++ b/crates/jolt-lookup-tables/src/instructions/virt/movsign.rs @@ -3,7 +3,7 @@ use crate::traits::LookupQuery; use jolt_trace::instructions::MovSign; use jolt_trace::{JoltCycle, JoltInstruction}; -impl_lookup_table!(MovSign, Some(SignMask)); +impl_lookup_table!(MovSign, Some(Movsign)); impl LookupQuery for MovSign { fn to_instruction_inputs(&self) -> (u64, i128) { diff --git a/crates/jolt-lookup-tables/src/tables/mod.rs b/crates/jolt-lookup-tables/src/tables/mod.rs index d6b629cac9..43487daf2e 100644 --- a/crates/jolt-lookup-tables/src/tables/mod.rs +++ b/crates/jolt-lookup-tables/src/tables/mod.rs @@ -70,7 +70,7 @@ use range_check::RangeCheckTable; use range_check_aligned::RangeCheckAlignedTable; use shift_right_bitmask::ShiftRightBitmaskTable; use sign_extend_half_word::SignExtendHalfWordTable; -use sign_mask::SignMaskTable; +use sign_mask::SignMaskTable as MovsignTable; use signed_greater_than_equal::SignedGreaterThanEqualTable; use signed_less_than::SignedLessThanTable; use unsigned_greater_than_equal::UnsignedGreaterThanEqualTable; @@ -78,7 +78,6 @@ use unsigned_less_than::UnsignedLessThanTable; use unsigned_less_than_equal::UnsignedLessThanEqualTable; use upper_word::UpperWordTable; use valid_div0::ValidDiv0Table; -use valid_signed_remainder::ValidSignedRemainderTable; use valid_unsigned_remainder::ValidUnsignedRemainderTable; use virtual_change_divisor::VirtualChangeDivisorTable; use virtual_change_divisor_w::VirtualChangeDivisorWTable; @@ -108,32 +107,31 @@ pub enum LookupTableKind { Or(OrTable), Xor(XorTable), Equal(EqualTable), + SignedGreaterThanEqual(SignedGreaterThanEqualTable), + UnsignedGreaterThanEqual(UnsignedGreaterThanEqualTable), NotEqual(NotEqualTable), SignedLessThan(SignedLessThanTable), UnsignedLessThan(UnsignedLessThanTable), - SignedGreaterThanEqual(SignedGreaterThanEqualTable), - UnsignedGreaterThanEqual(UnsignedGreaterThanEqualTable), - UnsignedLessThanEqual(UnsignedLessThanEqualTable), + Movsign(MovsignTable), UpperWord(UpperWordTable), + LessThanEqual(UnsignedLessThanEqualTable), + ValidUnsignedRemainder(ValidUnsignedRemainderTable), + ValidDiv0(ValidDiv0Table), + HalfwordAlignment(HalfwordAlignmentTable), + WordAlignment(WordAlignmentTable), LowerHalfWord(LowerHalfWordTable), SignExtendHalfWord(SignExtendHalfWordTable), - SignMask(SignMaskTable), Pow2(Pow2Table), Pow2W(Pow2WTable), ShiftRightBitmask(ShiftRightBitmaskTable), + VirtualRev8W(VirtualRev8WTable), VirtualSRL(VirtualSRLTable), VirtualSRA(VirtualSRATable), VirtualROTR(VirtualROTRTable), VirtualROTRW(VirtualROTRWTable), - ValidDiv0(ValidDiv0Table), - ValidUnsignedRemainder(ValidUnsignedRemainderTable), - ValidSignedRemainder(ValidSignedRemainderTable), VirtualChangeDivisor(VirtualChangeDivisorTable), VirtualChangeDivisorW(VirtualChangeDivisorWTable), - HalfwordAlignment(HalfwordAlignmentTable), - WordAlignment(WordAlignmentTable), MulUNoOverflow(MulUNoOverflowTable), - VirtualRev8W(VirtualRev8WTable), VirtualXORROT32(VirtualXORROTTable), VirtualXORROT24(VirtualXORROTTable), VirtualXORROT16(VirtualXORROTTable), @@ -158,32 +156,31 @@ macro_rules! dispatch { Self::Or($t) => $expr, Self::Xor($t) => $expr, Self::Equal($t) => $expr, + Self::SignedGreaterThanEqual($t) => $expr, + Self::UnsignedGreaterThanEqual($t) => $expr, Self::NotEqual($t) => $expr, Self::SignedLessThan($t) => $expr, Self::UnsignedLessThan($t) => $expr, - Self::SignedGreaterThanEqual($t) => $expr, - Self::UnsignedGreaterThanEqual($t) => $expr, - Self::UnsignedLessThanEqual($t) => $expr, + Self::Movsign($t) => $expr, Self::UpperWord($t) => $expr, + Self::LessThanEqual($t) => $expr, + Self::ValidUnsignedRemainder($t) => $expr, + Self::ValidDiv0($t) => $expr, + Self::HalfwordAlignment($t) => $expr, + Self::WordAlignment($t) => $expr, Self::LowerHalfWord($t) => $expr, Self::SignExtendHalfWord($t) => $expr, - Self::SignMask($t) => $expr, Self::Pow2($t) => $expr, Self::Pow2W($t) => $expr, Self::ShiftRightBitmask($t) => $expr, + Self::VirtualRev8W($t) => $expr, Self::VirtualSRL($t) => $expr, Self::VirtualSRA($t) => $expr, Self::VirtualROTR($t) => $expr, Self::VirtualROTRW($t) => $expr, - Self::ValidDiv0($t) => $expr, - Self::ValidUnsignedRemainder($t) => $expr, - Self::ValidSignedRemainder($t) => $expr, Self::VirtualChangeDivisor($t) => $expr, Self::VirtualChangeDivisorW($t) => $expr, - Self::HalfwordAlignment($t) => $expr, - Self::WordAlignment($t) => $expr, Self::MulUNoOverflow($t) => $expr, - Self::VirtualRev8W($t) => $expr, Self::VirtualXORROT32($t) => $expr, Self::VirtualXORROT24($t) => $expr, Self::VirtualXORROT16($t) => $expr, @@ -197,6 +194,97 @@ macro_rules! dispatch { } impl LookupTableKind { + /// Returns the canonical Jolt lookup table list in `jolt-core` order. + pub fn all() -> [Self; 40] { + [ + Self::RangeCheck(Default::default()), + Self::RangeCheckAligned(Default::default()), + Self::And(Default::default()), + Self::Andn(Default::default()), + Self::Or(Default::default()), + Self::Xor(Default::default()), + Self::Equal(Default::default()), + Self::SignedGreaterThanEqual(Default::default()), + Self::UnsignedGreaterThanEqual(Default::default()), + Self::NotEqual(Default::default()), + Self::SignedLessThan(Default::default()), + Self::UnsignedLessThan(Default::default()), + Self::Movsign(Default::default()), + Self::UpperWord(Default::default()), + Self::LessThanEqual(Default::default()), + Self::ValidUnsignedRemainder(Default::default()), + Self::ValidDiv0(Default::default()), + Self::HalfwordAlignment(Default::default()), + Self::WordAlignment(Default::default()), + Self::LowerHalfWord(Default::default()), + Self::SignExtendHalfWord(Default::default()), + Self::Pow2(Default::default()), + Self::Pow2W(Default::default()), + Self::ShiftRightBitmask(Default::default()), + Self::VirtualRev8W(Default::default()), + Self::VirtualSRL(Default::default()), + Self::VirtualSRA(Default::default()), + Self::VirtualROTR(Default::default()), + Self::VirtualROTRW(Default::default()), + Self::VirtualChangeDivisor(Default::default()), + Self::VirtualChangeDivisorW(Default::default()), + Self::MulUNoOverflow(Default::default()), + Self::VirtualXORROT32(Default::default()), + Self::VirtualXORROT24(Default::default()), + Self::VirtualXORROT16(Default::default()), + Self::VirtualXORROT63(Default::default()), + Self::VirtualXORROTW16(Default::default()), + Self::VirtualXORROTW12(Default::default()), + Self::VirtualXORROTW8(Default::default()), + Self::VirtualXORROTW7(Default::default()), + ] + } + + pub fn name(&self) -> &'static str { + match self { + Self::RangeCheck(_) => "RangeCheck", + Self::RangeCheckAligned(_) => "RangeCheckAligned", + Self::And(_) => "And", + Self::Andn(_) => "Andn", + Self::Or(_) => "Or", + Self::Xor(_) => "Xor", + Self::Equal(_) => "Equal", + Self::SignedGreaterThanEqual(_) => "SignedGreaterThanEqual", + Self::UnsignedGreaterThanEqual(_) => "UnsignedGreaterThanEqual", + Self::NotEqual(_) => "NotEqual", + Self::SignedLessThan(_) => "SignedLessThan", + Self::UnsignedLessThan(_) => "UnsignedLessThan", + Self::Movsign(_) => "Movsign", + Self::UpperWord(_) => "UpperWord", + Self::LessThanEqual(_) => "LessThanEqual", + Self::ValidUnsignedRemainder(_) => "ValidUnsignedRemainder", + Self::ValidDiv0(_) => "ValidDiv0", + Self::HalfwordAlignment(_) => "HalfwordAlignment", + Self::WordAlignment(_) => "WordAlignment", + Self::LowerHalfWord(_) => "LowerHalfWord", + Self::SignExtendHalfWord(_) => "SignExtendHalfWord", + Self::Pow2(_) => "Pow2", + Self::Pow2W(_) => "Pow2W", + Self::ShiftRightBitmask(_) => "ShiftRightBitmask", + Self::VirtualRev8W(_) => "VirtualRev8W", + Self::VirtualSRL(_) => "VirtualSRL", + Self::VirtualSRA(_) => "VirtualSRA", + Self::VirtualROTR(_) => "VirtualROTR", + Self::VirtualROTRW(_) => "VirtualROTRW", + Self::VirtualChangeDivisor(_) => "VirtualChangeDivisor", + Self::VirtualChangeDivisorW(_) => "VirtualChangeDivisorW", + Self::MulUNoOverflow(_) => "MulUNoOverflow", + Self::VirtualXORROT32(_) => "VirtualXORROT32", + Self::VirtualXORROT24(_) => "VirtualXORROT24", + Self::VirtualXORROT16(_) => "VirtualXORROT16", + Self::VirtualXORROT63(_) => "VirtualXORROT63", + Self::VirtualXORROTW16(_) => "VirtualXORROTW16", + Self::VirtualXORROTW12(_) => "VirtualXORROTW12", + Self::VirtualXORROTW8(_) => "VirtualXORROTW8", + Self::VirtualXORROTW7(_) => "VirtualXORROTW7", + } + } + /// Returns the discriminant as a `usize`, suitable for array indexing. #[inline] pub fn index(&self) -> usize { diff --git a/crates/jolt-lookup-tables/src/tables/prefixes/mod.rs b/crates/jolt-lookup-tables/src/tables/prefixes/mod.rs index d3943afb9c..302ea13677 100644 --- a/crates/jolt-lookup-tables/src/tables/prefixes/mod.rs +++ b/crates/jolt-lookup-tables/src/tables/prefixes/mod.rs @@ -88,6 +88,12 @@ impl From for PrefixEval { } } +impl PrefixEval { + pub fn into_inner(self) -> F { + self.0 + } +} + impl Index for &[PrefixEval] { type Output = F; diff --git a/crates/jolt-lookup-tables/src/tables/suffixes/rev8w.rs b/crates/jolt-lookup-tables/src/tables/suffixes/rev8w.rs index b935b93b7e..92a8c87d53 100644 --- a/crates/jolt-lookup-tables/src/tables/suffixes/rev8w.rs +++ b/crates/jolt-lookup-tables/src/tables/suffixes/rev8w.rs @@ -5,7 +5,7 @@ pub enum Rev8WSuffix {} impl SparseDenseSuffix for Rev8WSuffix { fn suffix_mle(b: LookupBits) -> u64 { - let val = u128::from(b) as u32; - val.swap_bytes() as u64 + let val = u128::from(b) as u64; + crate::tables::virtual_rev8w::rev8w(val) } } diff --git a/crates/jolt-lookup-tables/src/tables/virtual_rev8w.rs b/crates/jolt-lookup-tables/src/tables/virtual_rev8w.rs index 2e33cfeaef..743a20cec0 100644 --- a/crates/jolt-lookup-tables/src/tables/virtual_rev8w.rs +++ b/crates/jolt-lookup-tables/src/tables/virtual_rev8w.rs @@ -62,12 +62,9 @@ impl PrefixSuffixDecomposition for VirtualRev8WTable= 64 bits, and Rev8WSuffix only - // handles the lower 32 bits. The decomposition is therefore only valid for - // lookup indices whose value fits in 32 bits (upper word = 0). #[cfg(test)] fn random_lookup_index(rng: &mut rand::rngs::StdRng) -> u128 { - rand::RngCore::next_u32(rng) as u128 + rand::RngCore::next_u64(rng) as u128 } } diff --git a/crates/jolt-openings/benches/rlc.rs b/crates/jolt-openings/benches/rlc.rs index 8cb7314e36..457050298f 100644 --- a/crates/jolt-openings/benches/rlc.rs +++ b/crates/jolt-openings/benches/rlc.rs @@ -3,7 +3,7 @@ use std::hint::black_box; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use jolt_field::{Fr, RandomSampling}; +use jolt_field::{Field, Fr}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -12,16 +12,12 @@ use jolt_openings::{rlc_combine, rlc_combine_scalars}; fn bench_rlc_combine(c: &mut Criterion) { let mut group = c.benchmark_group("rlc_combine"); let mut rng = ChaCha20Rng::seed_from_u64(0); - let rho: Fr = ::random(&mut rng); + let rho: Fr = Field::random(&mut rng); for (n_polys, num_vars) in [(8, 18), (32, 14)] { let len = 1 << num_vars; let tables: Vec> = (0..n_polys) - .map(|_| { - (0..len) - .map(|_| ::random(&mut rng)) - .collect() - }) + .map(|_| (0..len).map(|_| Fr::random(&mut rng)).collect()) .collect(); let refs: Vec<&[Fr]> = tables.iter().map(|t| t.as_slice()).collect(); @@ -39,12 +35,10 @@ fn bench_rlc_combine(c: &mut Criterion) { fn bench_rlc_combine_scalars(c: &mut Criterion) { let mut group = c.benchmark_group("rlc_combine_scalars"); let mut rng = ChaCha20Rng::seed_from_u64(1); - let rho: Fr = ::random(&mut rng); + let rho: Fr = Field::random(&mut rng); for n in [8, 32] { - let evals: Vec = (0..n) - .map(|_| ::random(&mut rng)) - .collect(); + let evals: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); group.bench_with_input(BenchmarkId::from_parameter(n), &n, |bench, _| { bench.iter(|| rlc_combine_scalars(black_box(&evals), black_box(rho))); diff --git a/crates/jolt-openings/fuzz/fuzz_targets/rlc.rs b/crates/jolt-openings/fuzz/fuzz_targets/rlc.rs index 535cf55f8b..2cc915fa5a 100644 --- a/crates/jolt-openings/fuzz/fuzz_targets/rlc.rs +++ b/crates/jolt-openings/fuzz/fuzz_targets/rlc.rs @@ -1,5 +1,5 @@ #![no_main] -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling, ReducingBytes}; +use jolt_field::{Field, Fr}; use jolt_openings::{rlc_combine, rlc_combine_scalars}; use libfuzzer_sys::fuzz_target; @@ -9,7 +9,7 @@ fuzz_target!(|data: &[u8]| { return; } - let rho = ::from_le_bytes_mod_order(&data[..32]); + let rho = ::from_bytes(&data[..32]); let remaining = &data[32..]; // Build 1-4 polynomials of length 1-8 from remaining bytes @@ -25,7 +25,7 @@ fuzz_target!(|data: &[u8]| { if end - start < 32 { Fr::from_u64(remaining[start] as u64) } else { - ::from_le_bytes_mod_order(&remaining[start..end]) + ::from_bytes(&remaining[start..end]) } }) .collect(); diff --git a/crates/jolt-openings/src/mock.rs b/crates/jolt-openings/src/mock.rs index 39d0150f8c..ec7aa70f0d 100644 --- a/crates/jolt-openings/src/mock.rs +++ b/crates/jolt-openings/src/mock.rs @@ -31,15 +31,19 @@ pub struct MockProof { impl AppendToTranscript for MockCommitment { fn append_to_transcript(&self, transcript: &mut T) { - let mut buf = Vec::with_capacity(self.evaluations.len() * F::NUM_BYTES); + let mut buf = Vec::with_capacity(self.evaluations.len() * 32); for e in &self.evaluations { - let start = buf.len(); - buf.resize(start + F::NUM_BYTES, 0); - e.to_bytes_le(&mut buf[start..]); + buf.extend_from_slice(&e.to_bytes()); } buf.reverse(); transcript.append_bytes(&buf); } + + fn serialized_len(&self) -> u64 { + u64::try_from(self.evaluations.len()) + .unwrap_or(u64::MAX / 32) + .saturating_mul(32) + } } impl Commitment for MockCommitmentScheme { @@ -160,6 +164,10 @@ impl AppendToTranscript for MockHidingCommitment { fn append_to_transcript(&self, transcript: &mut T) { self.eval.append_to_transcript(transcript); } + + fn serialized_len(&self) -> u64 { + self.eval.serialized_len() + } } impl ZkOpeningScheme for MockCommitmentScheme { @@ -194,6 +202,7 @@ impl ZkOpeningScheme for MockCommitmentScheme { actual: format!("len={}", proof.evaluations.len()), }); } + Ok(()) } } @@ -203,7 +212,8 @@ impl ZkOpeningScheme for MockCommitmentScheme { mod tests { use super::*; use crate::{reduce_prover, reduce_verifier, ProverClaim, VerifierClaim}; - use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; + use jolt_field::Field; + use jolt_field::Fr; use jolt_poly::Polynomial; use jolt_transcript::Blake2bTranscript; use rand_chacha::rand_core::SeedableRng; diff --git a/crates/jolt-openings/src/reduction.rs b/crates/jolt-openings/src/reduction.rs index 9cd5402312..d985505625 100644 --- a/crates/jolt-openings/src/reduction.rs +++ b/crates/jolt-openings/src/reduction.rs @@ -168,7 +168,7 @@ fn group_verifier_claims_by_point( #[cfg(test)] mod tests { use super::*; - use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; + use jolt_field::Fr; use jolt_poly::Polynomial; #[test] diff --git a/crates/jolt-openings/tests/reduction.rs b/crates/jolt-openings/tests/reduction.rs index 38f5e5ff09..14ec309efd 100644 --- a/crates/jolt-openings/tests/reduction.rs +++ b/crates/jolt-openings/tests/reduction.rs @@ -13,7 +13,7 @@ reason = "tests may panic on assertion failures" )] -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling, ReducingBytes}; +use jolt_field::{Field, Fr}; use jolt_openings::mock::MockCommitmentScheme; use jolt_openings::{reduce_prover, reduce_verifier, CommitmentScheme, ProverClaim, VerifierClaim}; use jolt_poly::Polynomial; diff --git a/crates/jolt-poly/benches/poly_ops.rs b/crates/jolt-poly/benches/poly_ops.rs index 51ace32b25..a6b61660d3 100644 --- a/crates/jolt-poly/benches/poly_ops.rs +++ b/crates/jolt-poly/benches/poly_ops.rs @@ -1,7 +1,7 @@ #![expect(unused_results)] use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use jolt_field::{Fr, RandomSampling}; +use jolt_field::{Field, Fr}; use jolt_poly::{EqPolynomial, Polynomial}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-poly/fuzz/fuzz_targets/dense_poly_ops.rs b/crates/jolt-poly/fuzz/fuzz_targets/dense_poly_ops.rs index 3ffb0d46d3..662c72c761 100644 --- a/crates/jolt-poly/fuzz/fuzz_targets/dense_poly_ops.rs +++ b/crates/jolt-poly/fuzz/fuzz_targets/dense_poly_ops.rs @@ -1,5 +1,5 @@ #![no_main] -use jolt_field::{Fr, ReducingBytes}; +use jolt_field::{Field, Fr}; use jolt_poly::Polynomial; use libfuzzer_sys::fuzz_target; @@ -20,18 +20,14 @@ fuzz_target!(|data: &[u8]| { // Build evaluation vector from fuzzer data let evals: Vec = (0..n) - .map(|i| ::from_le_bytes_mod_order(&data[i * 32..(i + 1) * 32])) + .map(|i| ::from_bytes(&data[i * 32..(i + 1) * 32])) .collect(); let poly = Polynomial::new(evals); // Build evaluation point from fuzzer data let point_start = n * 32; let point: Vec = (0..num_vars) - .map(|i| { - ::from_le_bytes_mod_order( - &data[point_start + i * 32..point_start + (i + 1) * 32], - ) - }) + .map(|i| ::from_bytes(&data[point_start + i * 32..point_start + (i + 1) * 32])) .collect(); // evaluate and evaluate_and_consume must agree and not panic diff --git a/crates/jolt-poly/src/compressed_univariate.rs b/crates/jolt-poly/src/compressed_univariate.rs index e013789f29..ff0c4e5b83 100644 --- a/crates/jolt-poly/src/compressed_univariate.rs +++ b/crates/jolt-poly/src/compressed_univariate.rs @@ -95,7 +95,7 @@ impl CompressedPoly { #[expect(clippy::unwrap_used)] mod tests { use super::*; - use jolt_field::{Fr, FromPrimitiveInt}; + use jolt_field::Fr; use num_traits::{One, Zero}; /// Helper: build a standard polynomial p(x) = c0 + c1*x + c2*x^2 + ... diff --git a/crates/jolt-poly/src/dense.rs b/crates/jolt-poly/src/dense.rs index 46293f2bbb..2f6b38647c 100644 --- a/crates/jolt-poly/src/dense.rs +++ b/crates/jolt-poly/src/dense.rs @@ -360,6 +360,70 @@ impl crate::MultilinearBinding for Polynomial { } } +/// Fixes the first (MSB) variable in a dense evaluation vector. +#[inline] +pub fn bind_high_to_low(evals: &mut Vec, scalar: F) { + let half = evals.len() / 2; + + #[cfg(feature = "parallel")] + { + if half >= PAR_THRESHOLD { + use rayon::prelude::*; + let (lo, hi) = evals.split_at_mut(half); + lo.par_iter_mut().zip(hi.par_iter()).for_each(|(a, b)| { + *a = *a + scalar * (*b - *a); + }); + } else { + for i in 0..half { + let lo = evals[i]; + let hi = evals[i + half]; + evals[i] = lo + scalar * (hi - lo); + } + } + } + + #[cfg(not(feature = "parallel"))] + { + for i in 0..half { + let lo = evals[i]; + let hi = evals[i + half]; + evals[i] = lo + scalar * (hi - lo); + } + } + + evals.truncate(half); +} + +/// Fixes the last (LSB) variable in a dense evaluation vector. +#[inline] +pub fn bind_low_to_high(evals: &mut Vec, scalar: F) { + let half = evals.len() / 2; + + #[cfg(feature = "parallel")] + { + if half >= PAR_THRESHOLD { + use rayon::prelude::*; + let coeffs = &*evals; + *evals = (0..half) + .into_par_iter() + .map(|i| { + let lo = coeffs[2 * i]; + let hi = coeffs[2 * i + 1]; + lo + scalar * (hi - lo) + }) + .collect(); + return; + } + } + + for i in 0..half { + let lo = evals[2 * i]; + let hi = evals[2 * i + 1]; + evals[i] = lo + scalar * (hi - lo); + } + evals.truncate(half); +} + #[inline] fn assert_matching_dims(a: &Polynomial, b: &Polynomial) -> (usize, usize) { assert_eq!( @@ -518,8 +582,8 @@ impl Neg for Polynomial { #[expect(clippy::unwrap_used)] mod tests { use super::*; + use jolt_field::Field; use jolt_field::Fr; - use jolt_field::{FromPrimitiveInt, RandomSampling}; use num_traits::{One, Zero}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-poly/src/eq.rs b/crates/jolt-poly/src/eq.rs index 94dfcf44b3..9760e430b2 100644 --- a/crates/jolt-poly/src/eq.rs +++ b/crates/jolt-poly/src/eq.rs @@ -347,8 +347,8 @@ impl crate::MultilinearEvaluation for EqPolynomial { #[cfg(test)] mod tests { use super::*; + use jolt_field::Field; use jolt_field::Fr; - use jolt_field::{FromPrimitiveInt, RandomSampling}; use num_traits::{One, Zero}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-poly/src/eq_plus_one.rs b/crates/jolt-poly/src/eq_plus_one.rs index e05cb69ded..d3eab42c5a 100644 --- a/crates/jolt-poly/src/eq_plus_one.rs +++ b/crates/jolt-poly/src/eq_plus_one.rs @@ -170,7 +170,7 @@ impl EqPlusOnePrefixSuffix { #[cfg(test)] mod tests { use super::*; - use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; + use jolt_field::{Field, Fr}; use num_traits::{One, Zero}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-poly/src/expanding_table.rs b/crates/jolt-poly/src/expanding_table.rs new file mode 100644 index 0000000000..b1d32d2e49 --- /dev/null +++ b/crates/jolt-poly/src/expanding_table.rs @@ -0,0 +1,212 @@ +//! Incrementally materialized equality tables. + +use std::ops::Index; + +use jolt_field::Field; + +use crate::{thread::unsafe_allocate_zero_vec, BindingOrder}; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +/// Table containing the evaluations of `eq(x, r)` as challenges are streamed in. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ExpandingTable { + binding_order: BindingOrder, + len: usize, + values: Vec, + scratch_space: Vec, +} + +impl ExpandingTable { + #[tracing::instrument(skip_all, name = "ExpandingTable::new")] + pub fn new(capacity: usize, binding_order: BindingOrder) -> Self { + assert!(capacity > 0, "expanding table capacity must be positive"); + let (values, scratch_space) = join_or_serial( + || unsafe_allocate_zero_vec(capacity), + || match binding_order { + BindingOrder::LowToHigh => Vec::new(), + BindingOrder::HighToLow => unsafe_allocate_zero_vec(capacity), + }, + ); + Self { + binding_order, + len: 0, + values, + scratch_space, + } + } + + #[inline] + pub fn len(&self) -> usize { + self.len + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + #[inline] + pub fn order(&self) -> BindingOrder { + self.binding_order + } + + #[inline] + pub fn values(&self) -> &[F] { + &self.values[..self.len] + } + + pub fn reset(&mut self, value: F) { + assert!(!self.values.is_empty(), "expanding table has zero capacity"); + self.values[0] = value; + self.len = 1; + } + + pub fn clone_values(&self) -> Vec { + self.values().to_vec() + } + + #[tracing::instrument(skip_all, name = "ExpandingTable::update")] + pub fn update(&mut self, challenge: F) { + assert!(self.len > 0, "expanding table must be reset before update"); + assert!( + self.len * 2 <= self.values.len(), + "expanding table capacity exceeded" + ); + match self.binding_order { + BindingOrder::LowToHigh => self.update_low_to_high(challenge), + BindingOrder::HighToLow => self.update_high_to_low(challenge), + } + self.len *= 2; + } + + fn update_low_to_high(&mut self, challenge: F) { + #[cfg(feature = "parallel")] + { + let (left, right) = self.values.split_at_mut(self.len); + left.par_iter_mut() + .zip(right.par_iter_mut()) + .for_each(|(left, right)| { + *right = *left * challenge; + *left -= *right; + }); + } + + #[cfg(not(feature = "parallel"))] + { + let (left, right) = self.values.split_at_mut(self.len); + for (left, right) in left.iter_mut().zip(right.iter_mut()) { + *right = *left * challenge; + *left -= *right; + } + } + } + + fn update_high_to_low(&mut self, challenge: F) { + #[cfg(feature = "parallel")] + { + self.values[..self.len] + .par_iter() + .zip(self.scratch_space.par_chunks_mut(2)) + .for_each(|(&value, dest)| { + let eval_1 = value * challenge; + dest[0] = value - eval_1; + dest[1] = eval_1; + }); + std::mem::swap(&mut self.values, &mut self.scratch_space); + } + + #[cfg(not(feature = "parallel"))] + { + for (index, &value) in self.values[..self.len].iter().enumerate() { + let eval_1 = value * challenge; + self.scratch_space[2 * index] = value - eval_1; + self.scratch_space[2 * index + 1] = eval_1; + } + std::mem::swap(&mut self.values, &mut self.scratch_space); + } + } +} + +impl Index for ExpandingTable { + type Output = F; + + fn index(&self, index: usize) -> &Self::Output { + assert!( + index < self.len, + "expanding table index {index} out of bounds for len {}", + self.len + ); + &self.values[index] + } +} + +#[cfg(feature = "parallel")] +fn join_or_serial( + left: impl FnOnce() -> A + Send, + right: impl FnOnce() -> B + Send, +) -> (A, B) { + rayon::join(left, right) +} + +#[cfg(not(feature = "parallel"))] +fn join_or_serial(left: impl FnOnce() -> A, right: impl FnOnce() -> B) -> (A, B) { + (left(), right()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::EqPolynomial; + use jolt_field::{Field, Fr}; + use num_traits::One; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + #[test] + fn high_to_low_matches_eq_table_prefixes() { + let mut rng = ChaCha20Rng::seed_from_u64(710); + let point: Vec = (0..8).map(|_| Fr::random(&mut rng)).collect(); + let mut table = ExpandingTable::new(1 << point.len(), BindingOrder::HighToLow); + table.reset(Fr::one()); + + for prefix_len in 0..=point.len() { + let expected = EqPolynomial::::evals(&point[..prefix_len], None); + assert_eq!(table.values(), expected); + if prefix_len < point.len() { + table.update(point[prefix_len]); + } + } + } + + #[test] + fn low_to_high_matches_reversed_eq_prefixes() { + let mut rng = ChaCha20Rng::seed_from_u64(711); + let point: Vec = (0..8).map(|_| Fr::random(&mut rng)).collect(); + let mut reversed_prefix = Vec::new(); + let mut table = ExpandingTable::new(1 << point.len(), BindingOrder::LowToHigh); + table.reset(Fr::one()); + + for (prefix_len, &challenge) in point.iter().enumerate() { + let expected = EqPolynomial::::evals(&reversed_prefix, None); + assert_eq!(table.values(), expected, "prefix_len={prefix_len}"); + reversed_prefix.insert(0, challenge); + table.update(challenge); + } + let expected = EqPolynomial::::evals(&reversed_prefix, None); + assert_eq!(table.values(), expected); + } + + #[test] + fn clone_and_index_expose_active_prefix_only() { + let mut table = ExpandingTable::new(8, BindingOrder::HighToLow); + table.reset(Fr::from_u64(3)); + table.update(Fr::from_u64(5)); + + assert_eq!(table.len(), 2); + assert_eq!(table[0], Fr::from_u64(3) - Fr::from_u64(15)); + assert_eq!(table[1], Fr::from_u64(15)); + assert_eq!(table.clone_values(), table.values()); + } +} diff --git a/crates/jolt-poly/src/identity.rs b/crates/jolt-poly/src/identity.rs index 2cc61284b5..f050fccec7 100644 --- a/crates/jolt-poly/src/identity.rs +++ b/crates/jolt-poly/src/identity.rs @@ -59,8 +59,8 @@ impl crate::MultilinearEvaluation for IdentityPolynomial { #[cfg(test)] mod tests { use super::*; + use jolt_field::Field; use jolt_field::Fr; - use jolt_field::FromPrimitiveInt; use num_traits::{One, Zero}; #[test] diff --git a/crates/jolt-poly/src/lagrange.rs b/crates/jolt-poly/src/lagrange.rs index 485e0742f8..c28005ea0a 100644 --- a/crates/jolt-poly/src/lagrange.rs +++ b/crates/jolt-poly/src/lagrange.rs @@ -154,10 +154,35 @@ pub fn interpolate_to_coeffs(domain_start: i64, values: &[F]) -> Vec(domain_start: i64, domain_size: usize, k: usize, r: F) -> F { + let mut numer = F::one(); + let mut denom = F::one(); + for j in 0..domain_size { + if j == k { + continue; + } + numer *= r - F::from_i64(domain_start + j as i64); + denom *= F::from_i128(k as i128 - j as i128); + } + numer / denom +} + +/// Evaluates `sum_k L_k(tau) * L_k(r)` over a consecutive integer domain. +pub fn lagrange_kernel_eval(domain_start: i64, domain_size: usize, tau: F, r: F) -> F { + let tau_evals = lagrange_evals(domain_start, domain_size, tau); + let r_evals = lagrange_evals(domain_start, domain_size, r); + tau_evals + .iter() + .zip(r_evals.iter()) + .map(|(&a, &b)| a * b) + .fold(F::zero(), |acc, value| acc + value) +} + #[cfg(test)] mod tests { use super::*; - use jolt_field::{Fr, FromPrimitiveInt}; + use jolt_field::Fr; use num_traits::{One, Zero}; #[test] diff --git a/crates/jolt-poly/src/lib.rs b/crates/jolt-poly/src/lib.rs index 88c2d27055..a67085c12e 100644 --- a/crates/jolt-poly/src/lib.rs +++ b/crates/jolt-poly/src/lib.rs @@ -48,22 +48,26 @@ mod compressed_univariate; mod dense; mod eq; mod eq_plus_one; +mod expanding_table; mod identity; pub mod lagrange; mod lt; pub mod math; mod multilinear; mod one_hot; +mod split_eq; pub mod thread; mod univariate; pub use binding::BindingOrder; pub use compressed_univariate::CompressedPoly; -pub use dense::Polynomial; +pub use dense::{bind_high_to_low, bind_low_to_high, Polynomial}; pub use eq::EqPolynomial; pub use eq_plus_one::{EqPlusOnePolynomial, EqPlusOnePrefixSuffix}; +pub use expanding_table::ExpandingTable; pub use identity::IdentityPolynomial; pub use lt::LtPolynomial; pub use multilinear::{MultilinearBinding, MultilinearEvaluation, MultilinearPoly, RlcSource}; pub use one_hot::OneHotPolynomial; +pub use split_eq::GruenSplitEqPolynomial; pub use univariate::{UnivariatePoly, UnivariatePolynomial}; diff --git a/crates/jolt-poly/src/lt.rs b/crates/jolt-poly/src/lt.rs index 1b50547463..ea473618cf 100644 --- a/crates/jolt-poly/src/lt.rs +++ b/crates/jolt-poly/src/lt.rs @@ -169,7 +169,7 @@ fn bind_in_place(v: &mut Vec, challenge: F) { #[cfg(test)] mod tests { use super::*; - use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; + use jolt_field::{Field, Fr}; use num_traits::{One, Zero}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-poly/src/multilinear.rs b/crates/jolt-poly/src/multilinear.rs index b2b49906eb..e3e740034c 100644 --- a/crates/jolt-poly/src/multilinear.rs +++ b/crates/jolt-poly/src/multilinear.rs @@ -69,6 +69,8 @@ pub trait MultilinearBinding: Send + Sync { /// - [`fold_rows`](Self::fold_rows): matrix-vector product $v \cdot M$ (opening protocols) /// - [`is_one_hot`](Self::is_one_hot) / [`for_each_one`](Self::for_each_one): unit-entry /// one-hot hints for PCS commit optimization (e.g., batch addition instead of MSM) +/// - [`is_sparse`](Self::is_sparse) / [`for_each_nonzero`](Self::for_each_nonzero): +/// weighted sparse-entry hints for PCS commit optimization pub trait MultilinearPoly: Send + Sync { /// Number of variables $n$. The polynomial has $2^n$ evaluations. fn num_vars(&self) -> usize; @@ -130,6 +132,25 @@ pub trait MultilinearPoly: Send + Sync { /// should override this method. The default is a no-op for non-one-hot /// polynomials. fn for_each_one(&self, _f: &mut dyn FnMut(usize)) {} + + /// Whether this polynomial exposes sparse nonzero entries. + /// + /// This is a more general contract than [`is_one_hot`](Self::is_one_hot): + /// entries may carry arbitrary field weights. One-hot implementors get a + /// default sparse view through [`for_each_one`](Self::for_each_one). + fn is_sparse(&self) -> bool { + self.is_one_hot() + } + + /// Iterates over `(position, value)` pairs for nonzero entries. + /// + /// Implementations that return true from [`is_sparse`](Self::is_sparse) + /// should override this when entries are not unit-valued. + fn for_each_nonzero(&self, f: &mut dyn FnMut(usize, F)) { + if self.is_one_hot() { + self.for_each_one(&mut |idx| f(idx, F::one())); + } + } } // --------------------------------------------------------------------------- @@ -355,7 +376,7 @@ impl> MultilinearPoly for RlcSource { #[cfg(test)] mod tests { use super::*; - use jolt_field::{Fr, RandomSampling}; + use jolt_field::Fr; use num_traits::Zero; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/crates/jolt-poly/src/one_hot.rs b/crates/jolt-poly/src/one_hot.rs index da9f097a34..fa0d96fdcc 100644 --- a/crates/jolt-poly/src/one_hot.rs +++ b/crates/jolt-poly/src/one_hot.rs @@ -150,7 +150,7 @@ impl MultilinearPoly for OneHotPolynomial { mod tests { use super::*; use crate::Polynomial; - use jolt_field::{Fr, RandomSampling}; + use jolt_field::Fr; use num_traits::Zero; use rand_chacha::ChaCha20Rng; use rand_core::{RngCore, SeedableRng}; diff --git a/crates/jolt-poly/src/polynomial.rs b/crates/jolt-poly/src/polynomial.rs new file mode 100644 index 0000000000..548ea2bd04 --- /dev/null +++ b/crates/jolt-poly/src/polynomial.rs @@ -0,0 +1,1015 @@ +//! Polynomial stored as evaluations over the Boolean hypercube. + +use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}; + +use jolt_field::Field; +use rand_core::RngCore; +use serde::{Deserialize, Serialize}; + +use crate::eq::EqPolynomial; + +/// Minimum number of evaluations before parallelizing bind/evaluate. +/// +/// Below this threshold the overhead of Rayon work-stealing exceeds the +/// benefit. 1024 field elements is roughly one L1 cache line's worth of +/// useful work per core, keeping synchronization cost negligible. +pub(crate) const PAR_THRESHOLD: usize = 1024; + +/// Multilinear polynomial stored as evaluations over the Boolean hypercube $\{0,1\}^n$. +/// +/// Generic over the scalar type `T`: +/// - When `T` is a [`Field`] type: full polynomial with in-place [`bind`](Polynomial::bind), +/// [`evaluate`](Polynomial::evaluate), and arithmetic operators. +/// - When `T` is a small type (`u8`, `bool`, `i64`, etc.): compact storage with +/// [`bind_to_field`](Polynomial::bind_to_field) for on-demand field promotion. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(bound(serialize = "T: Serialize", deserialize = "T: for<'a> Deserialize<'a>",))] +pub struct Polynomial { + evals: Vec, + num_vars: usize, +} + +impl Polynomial { + /// Creates a polynomial from its evaluations over the Boolean hypercube. + /// + /// # Panics + /// Panics if `evals.len()` is not a power of two (or zero). + pub fn new(evals: Vec) -> Self { + let len = evals.len(); + if len == 0 { + return Self { evals, num_vars: 0 }; + } + assert!( + len.is_power_of_two(), + "evaluation count must be a power of two, got {len}" + ); + let num_vars = len.trailing_zeros() as usize; + Self { evals, num_vars } + } + + /// Number of variables `n`. The polynomial has `2^n` evaluations. + #[inline] + pub fn num_vars(&self) -> usize { + self.num_vars + } + + #[inline] + pub fn len(&self) -> usize { + self.evals.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.evals.is_empty() + } + + /// The raw evaluation table over the Boolean hypercube. + #[inline] + pub fn evals(&self) -> &[T] { + &self.evals + } +} + +impl Polynomial { + /// Fixes the first variable to `scalar`, promoting all evaluations to field elements. + /// + /// Produces a `Polynomial` with `n - 1` variables: + /// $$g(x_2, \ldots, x_n) = (1 - s) \cdot f(0, x_2, \ldots) + s \cdot f(1, x_2, \ldots)$$ + /// + /// When `T = F`, the `From` conversion is the identity and the compiler + /// eliminates it, making this equivalent to an allocating bind. + pub fn bind_to_field>(&self, scalar: F) -> Polynomial { + let half = self.evals.len() / 2; + let mut result = Vec::with_capacity(half); + for i in 0..half { + let lo: F = self.evals[i].into(); + let hi: F = self.evals[i + half].into(); + result.push(lo + scalar * (hi - lo)); + } + Polynomial { + evals: result, + num_vars: self.num_vars - 1, + } + } +} + +impl Polynomial { + /// Creates the zero polynomial with $2^n$ evaluations all equal to zero. + pub fn zeros(num_vars: usize) -> Self { + Self { + evals: vec![F::zero(); 1 << num_vars], + num_vars, + } + } + + /// Creates a polynomial with random evaluations. + pub fn random(num_vars: usize, rng: &mut impl RngCore) -> Self { + let evals = (0..(1 << num_vars)).map(|_| F::random(rng)).collect(); + Self { evals, num_vars } + } + + /// Fixes the first (MSB) variable to `scalar` in place, halving the evaluations. + /// + /// The evaluations table is laid out so that the first variable controls the + /// upper/lower half split: indices `0..half` have $x_1 = 0$ and indices + /// `half..2*half` have $x_1 = 1$. The result is: + /// $$g(x_2, \ldots, x_n) = f(0, x_2, \ldots) + s \cdot (f(1, x_2, \ldots) - f(0, x_2, \ldots))$$ + /// + /// Equivalent to `bind_with_order(scalar, BindingOrder::HighToLow)`. + #[inline] + pub fn bind(&mut self, scalar: F) { + self.bind_high_to_low(scalar); + } + + /// Binds with the specified variable ordering. + /// + /// - `BindingOrder::HighToLow`: binds the MSB (first variable, index `0`). + /// Pairs `evals[i]` with `evals[i + half]`. + /// - `BindingOrder::LowToHigh`: binds the LSB (last variable, index `n-1`). + /// Pairs `evals[2*i]` with `evals[2*i + 1]`. + #[inline] + pub fn bind_with_order(&mut self, scalar: F, order: crate::BindingOrder) { + match order { + crate::BindingOrder::HighToLow => self.bind_high_to_low(scalar), + crate::BindingOrder::LowToHigh => self.bind_low_to_high(scalar), + } + } + + #[inline] + fn bind_high_to_low(&mut self, scalar: F) { + bind_high_to_low(&mut self.evals, scalar); + self.num_vars -= 1; + } + + #[inline] + fn bind_low_to_high(&mut self, scalar: F) { + bind_low_to_high(&mut self.evals, scalar); + self.num_vars -= 1; + } + + /// Returns the `(lo, hi)` pair for the given index and binding order. + /// + /// For sumcheck round polynomial evaluation at index `j`: + /// - `HighToLow`: `lo = evals[j]`, `hi = evals[j + half]` + /// - `LowToHigh`: `lo = evals[2*j]`, `hi = evals[2*j + 1]` + #[inline] + pub fn sumcheck_eval_pair(&self, index: usize, order: crate::BindingOrder) -> (F, F) { + match order { + crate::BindingOrder::HighToLow => { + let half = self.evals.len() / 2; + (self.evals[index], self.evals[index + half]) + } + crate::BindingOrder::LowToHigh => (self.evals[2 * index], self.evals[2 * index + 1]), + } + } + + /// Evaluates the polynomial at `point` using the multilinear extension formula: + /// $$f(r) = \sum_{x \in \{0,1\}^n} f(x) \cdot \widetilde{eq}(x, r)$$ + pub fn evaluate(&self, point: &[F]) -> F { + assert_eq!( + point.len(), + self.num_vars, + "point dimension must match num_vars" + ); + let eq_evals = EqPolynomial::new(point.to_vec()).evaluations(); + + #[cfg(feature = "parallel")] + { + if self.evals.len() >= PAR_THRESHOLD { + use rayon::prelude::*; + return self + .evals + .par_iter() + .zip(eq_evals.par_iter()) + .map(|(&f, &e)| f * e) + .sum(); + } + } + + self.evals + .iter() + .zip(eq_evals.iter()) + .map(|(&f, &e)| f * e) + .sum() + } + + /// Evaluates by sequentially binding each variable, consuming `self`. + /// + /// More memory-efficient than `evaluate` when the polynomial is no longer needed, + /// as it avoids materializing the full eq table. + pub fn evaluate_and_consume(mut self, point: &[F]) -> F { + assert_eq!( + point.len(), + self.num_vars, + "point dimension must match num_vars" + ); + for &r in point { + self.bind(r); + } + debug_assert_eq!(self.evals.len(), 1); + self.evals[0] + } + + #[inline] + pub fn evaluations(&self) -> &[F] { + &self.evals + } + + #[inline] + pub fn evaluations_mut(&mut self) -> &mut [F] { + &mut self.evals + } +} + +impl From> for Polynomial { + fn from(evaluations: Vec) -> Self { + Self::new(evaluations) + } +} + +impl crate::MultilinearEvaluation for Polynomial { + #[inline] + fn num_vars(&self) -> usize { + self.num_vars + } + + #[inline] + fn len(&self) -> usize { + self.evals.len() + } + + fn evaluate(&self, point: &[F]) -> F { + Polynomial::evaluate(self, point) + } +} + +impl crate::MultilinearBinding for Polynomial { + fn bind(&mut self, scalar: F) { + Polynomial::bind(self, scalar); + } +} + +#[inline] +fn assert_matching_dims(a: &Polynomial, b: &Polynomial) -> (usize, usize) { + assert_eq!( + a.num_vars, b.num_vars, + "num_vars mismatch: {} vs {}", + a.num_vars, b.num_vars + ); + (a.num_vars, a.evals.len()) +} + +impl Add for Polynomial { + type Output = Self; + + fn add(mut self, rhs: Self) -> Self { + self += &rhs; + self + } +} + +impl Add<&Self> for Polynomial { + type Output = Self; + + fn add(mut self, rhs: &Self) -> Self { + self += rhs; + self + } +} + +impl AddAssign for Polynomial { + fn add_assign(&mut self, rhs: Self) { + *self += &rhs; + } +} + +impl AddAssign<&Self> for Polynomial { + fn add_assign(&mut self, rhs: &Self) { + let (_nv, len) = assert_matching_dims(self, rhs); + + #[cfg(feature = "parallel")] + { + if len >= PAR_THRESHOLD { + use rayon::prelude::*; + self.evals + .par_iter_mut() + .zip(rhs.evals.par_iter()) + .for_each(|(a, b)| *a += *b); + return; + } + } + + for i in 0..len { + self.evals[i] += rhs.evals[i]; + } + } +} + +impl Sub for Polynomial { + type Output = Self; + + fn sub(mut self, rhs: Self) -> Self { + self -= &rhs; + self + } +} + +impl Sub<&Self> for Polynomial { + type Output = Self; + + fn sub(mut self, rhs: &Self) -> Self { + self -= rhs; + self + } +} + +impl SubAssign for Polynomial { + fn sub_assign(&mut self, rhs: Self) { + *self -= &rhs; + } +} + +impl SubAssign<&Self> for Polynomial { + fn sub_assign(&mut self, rhs: &Self) { + let (_nv, len) = assert_matching_dims(self, rhs); + + #[cfg(feature = "parallel")] + { + if len >= PAR_THRESHOLD { + use rayon::prelude::*; + self.evals + .par_iter_mut() + .zip(rhs.evals.par_iter()) + .for_each(|(a, b)| *a -= *b); + return; + } + } + + for i in 0..len { + self.evals[i] -= rhs.evals[i]; + } + } +} + +impl Mul for Polynomial { + type Output = Self; + + fn mul(mut self, rhs: F) -> Self { + let len = self.evals.len(); + + #[cfg(feature = "parallel")] + { + if len >= PAR_THRESHOLD { + use rayon::prelude::*; + self.evals.par_iter_mut().for_each(|a| *a *= rhs); + return self; + } + } + + for i in 0..len { + self.evals[i] *= rhs; + } + self + } +} + +impl Mul for &Polynomial { + type Output = Polynomial; + + fn mul(self, rhs: F) -> Polynomial { + self.clone() * rhs + } +} + +impl Neg for Polynomial { + type Output = Self; + + fn neg(mut self) -> Self { + let len = self.evals.len(); + + #[cfg(feature = "parallel")] + { + if len >= PAR_THRESHOLD { + use rayon::prelude::*; + self.evals.par_iter_mut().for_each(|a| *a = -*a); + return self; + } + } + + for i in 0..len { + self.evals[i] = -self.evals[i]; + } + self + } +} + +/// Fixes the MSB variable of an evaluation table to `scalar`, halving the buffer. +/// +/// Pairs `evals[i]` with `evals[i + half]`: +/// $$g(x_2, \ldots) = f(0, x_2, \ldots) + s \cdot (f(1, x_2, \ldots) - f(0, x_2, \ldots))$$ +/// +/// Operates directly on a `Vec` without tracking `num_vars`. Used by +/// [`Polynomial::bind`] internally and by compute backends that manage +/// raw buffers. +#[inline] +pub fn bind_high_to_low(evals: &mut Vec, scalar: F) { + let half = evals.len() / 2; + + #[cfg(feature = "parallel")] + { + if half >= PAR_THRESHOLD { + use rayon::prelude::*; + let (lo, hi) = evals.split_at_mut(half); + lo.par_iter_mut().zip(hi.par_iter()).for_each(|(a, b)| { + *a = *a + scalar * (*b - *a); + }); + evals.truncate(half); + return; + } + } + + for i in 0..half { + let lo = evals[i]; + let hi = evals[i + half]; + evals[i] = lo + scalar * (hi - lo); + } + evals.truncate(half); +} + +/// Fixes the LSB variable of an evaluation table to `scalar`, halving the buffer. +/// +/// Pairs `evals[2*i]` with `evals[2*i + 1]`: +/// $$g(\ldots, x_{n-1}) = f(\ldots, 0) + s \cdot (f(\ldots, 1) - f(\ldots, 0))$$ +/// +/// Operates directly on a `Vec` without tracking `num_vars`. Used by +/// [`Polynomial::bind_with_order`] internally and by compute backends that +/// manage raw buffers. +#[inline] +pub fn bind_low_to_high(evals: &mut Vec, scalar: F) { + let half = evals.len() / 2; + + #[cfg(feature = "parallel")] + { + if half >= PAR_THRESHOLD { + use rayon::prelude::*; + // Interleaved read pattern (2i, 2i+1) → write (i) aliases across + // iterations: iteration j reads evals[2j] which iteration i=2j + // writes. A separate output buffer avoids the data race. + let src: &[F] = evals; + let result: Vec = (0..half) + .into_par_iter() + .map(|i| { + let lo = src[2 * i]; + let hi = src[2 * i + 1]; + lo + scalar * (hi - lo) + }) + .collect(); + *evals = result; + return; + } + } + + for i in 0..half { + let lo = evals[2 * i]; + let hi = evals[2 * i + 1]; + evals[i] = lo + scalar * (hi - lo); + } + evals.truncate(half); +} + +#[cfg(test)] +mod tests { + use super::*; + use jolt_field::Field; + use jolt_field::Fr; + use num_traits::Zero; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + #[test] + fn bind_to_field_then_evaluate_equals_direct_evaluate() { + let mut rng = ChaCha20Rng::seed_from_u64(1); + let n = 5; + let poly = Polynomial::::random(n, &mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + let direct = poly.evaluate(&point); + + let bound = poly.bind_to_field(point[0]); + let via_bind = bound.evaluate(&point[1..]); + + assert_eq!(direct, via_bind); + } + + #[test] + fn zeros_evaluates_to_zero() { + let mut rng = ChaCha20Rng::seed_from_u64(2); + let n = 4; + let poly = Polynomial::::zeros(n); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + assert!(poly.evaluate(&point).is_zero()); + } + + #[test] + fn bind_matches_bind_to_field() { + let mut rng = ChaCha20Rng::seed_from_u64(3); + let n = 6; + let poly = Polynomial::::random(n, &mut rng); + let scalar = Fr::random(&mut rng); + + let bound = poly.bind_to_field(scalar); + + let mut poly_mut = poly; + poly_mut.bind(scalar); + + assert_eq!(bound.evaluations(), poly_mut.evaluations()); + } + + #[test] + fn evaluate_and_consume_matches_evaluate() { + let mut rng = ChaCha20Rng::seed_from_u64(4); + let n = 4; + let poly = Polynomial::::random(n, &mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + let expected = poly.evaluate(&point); + let consumed = poly.clone().evaluate_and_consume(&point); + assert_eq!(expected, consumed); + } + + #[test] + fn empty_polynomial() { + let poly = Polynomial::::new(vec![]); + assert_eq!(poly.num_vars(), 0); + assert!(poly.is_empty()); + } + + #[test] + fn single_evaluation() { + let val = Fr::from_u64(42); + let poly = Polynomial::new(vec![val]); + assert_eq!(poly.num_vars(), 0); + assert_eq!(poly.evaluate(&[]), val); + } + + #[test] + fn sequential_bind_equals_full_evaluate() { + let mut rng = ChaCha20Rng::seed_from_u64(5); + let n = 4; + let poly = Polynomial::::random(n, &mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + let mut p = poly.clone(); + for &r in &point { + p.bind(r); + } + assert_eq!(p.evals.len(), 1); + assert_eq!(p.evals[0], poly.evaluate(&point)); + } + + #[test] + fn serde_round_trip() { + let mut rng = ChaCha20Rng::seed_from_u64(100); + let poly = Polynomial::::random(4, &mut rng); + let bytes = bincode::serde::encode_to_vec(&poly, bincode::config::standard()).unwrap(); + let recovered: Polynomial = + bincode::serde::decode_from_slice(&bytes, bincode::config::standard()) + .unwrap() + .0; + assert_eq!(poly, recovered); + } + + #[test] + fn serde_round_trip_empty() { + let poly = Polynomial::::new(vec![]); + let bytes = bincode::serde::encode_to_vec(&poly, bincode::config::standard()).unwrap(); + let recovered: Polynomial = + bincode::serde::decode_from_slice(&bytes, bincode::config::standard()) + .unwrap() + .0; + assert_eq!(poly, recovered); + } + + #[test] + fn serde_round_trip_single() { + let poly = Polynomial::new(vec![Fr::from_u64(99)]); + let bytes = bincode::serde::encode_to_vec(&poly, bincode::config::standard()).unwrap(); + let recovered: Polynomial = + bincode::serde::decode_from_slice(&bytes, bincode::config::standard()) + .unwrap() + .0; + assert_eq!(poly, recovered); + } + + #[test] + fn parallel_bind_matches_bind_to_field() { + // n=11 -> 2048 evaluations, above PAR_THRESHOLD=1024 + let mut rng = ChaCha20Rng::seed_from_u64(201); + let n = 11; + let poly = Polynomial::::random(n, &mut rng); + let scalar = Fr::random(&mut rng); + + let bound = poly.bind_to_field(scalar); + + let mut poly_mut = poly; + poly_mut.bind(scalar); + + assert_eq!(bound.evaluations(), poly_mut.evaluations()); + } + + #[test] + fn parallel_bind_equals_evaluate_and_consume() { + let mut rng = ChaCha20Rng::seed_from_u64(202); + let n = 11; + let poly = Polynomial::::random(n, &mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + let consumed = poly.clone().evaluate_and_consume(&point); + + let mut p = poly; + for &r in &point { + p.bind(r); + } + assert_eq!(p.evals.len(), 1); + assert_eq!(p.evals[0], consumed); + } + + #[test] + fn parallel_bind_then_evaluate_and_consume() { + let mut rng = ChaCha20Rng::seed_from_u64(203); + let n = 11; + let poly = Polynomial::::random(n, &mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + let expected = poly.clone().evaluate_and_consume(&point); + + let bound = poly.bind_to_field(point[0]); + let via_bind = bound.evaluate_and_consume(&point[1..]); + assert_eq!(expected, via_bind); + } + + #[test] + fn add_element_wise() { + let mut rng = ChaCha20Rng::seed_from_u64(500); + let n = 4; + let a = Polynomial::::random(n, &mut rng); + let b = Polynomial::::random(n, &mut rng); + + let sum = a.clone() + &b; + for i in 0..sum.evaluations().len() { + assert_eq!( + sum.evaluations()[i], + a.evaluations()[i] + b.evaluations()[i] + ); + } + } + + #[test] + fn sub_element_wise() { + let mut rng = ChaCha20Rng::seed_from_u64(501); + let n = 4; + let a = Polynomial::::random(n, &mut rng); + let b = Polynomial::::random(n, &mut rng); + + let diff = a.clone() - &b; + for i in 0..diff.evaluations().len() { + assert_eq!( + diff.evaluations()[i], + a.evaluations()[i] - b.evaluations()[i] + ); + } + } + + #[test] + fn scalar_mul() { + let mut rng = ChaCha20Rng::seed_from_u64(502); + let n = 4; + let poly = Polynomial::::random(n, &mut rng); + let s = Fr::random(&mut rng); + + let scaled = poly.clone() * s; + for i in 0..scaled.evaluations().len() { + assert_eq!(scaled.evaluations()[i], poly.evaluations()[i] * s); + } + } + + #[test] + fn negation() { + let mut rng = ChaCha20Rng::seed_from_u64(503); + let n = 4; + let poly = Polynomial::::random(n, &mut rng); + + let neg = -poly.clone(); + for i in 0..neg.evaluations().len() { + assert_eq!(neg.evaluations()[i], -poly.evaluations()[i]); + } + } + + #[test] + fn add_preserves_evaluation() { + let mut rng = ChaCha20Rng::seed_from_u64(504); + let n = 5; + let a = Polynomial::::random(n, &mut rng); + let b = Polynomial::::random(n, &mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + let sum = a.clone() + &b; + assert_eq!( + sum.evaluate(&point), + a.evaluate(&point) + b.evaluate(&point) + ); + } + + #[test] + fn sub_preserves_evaluation() { + let mut rng = ChaCha20Rng::seed_from_u64(505); + let n = 5; + let a = Polynomial::::random(n, &mut rng); + let b = Polynomial::::random(n, &mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + let diff = a.clone() - &b; + assert_eq!( + diff.evaluate(&point), + a.evaluate(&point) - b.evaluate(&point) + ); + } + + #[test] + fn scalar_mul_preserves_evaluation() { + let mut rng = ChaCha20Rng::seed_from_u64(506); + let n = 5; + let poly = Polynomial::::random(n, &mut rng); + let s = Fr::random(&mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + let scaled = poly.clone() * s; + assert_eq!(scaled.evaluate(&point), poly.evaluate(&point) * s); + } + + #[test] + #[should_panic(expected = "num_vars mismatch")] + fn add_mismatched_num_vars_panics() { + let mut rng = ChaCha20Rng::seed_from_u64(510); + let a = Polynomial::::random(3, &mut rng); + let b = Polynomial::::random(4, &mut rng); + let _ = a + b; + } + + #[test] + fn add_assign_accumulation() { + let mut rng = ChaCha20Rng::seed_from_u64(511); + let n = 4; + let a = Polynomial::::random(n, &mut rng); + let b = Polynomial::::random(n, &mut rng); + let c = Polynomial::::random(n, &mut rng); + + let mut acc = a.clone(); + acc += &b; + acc += &c; + + let expected = a.clone() + &b + &c; + assert_eq!(acc, expected); + } + + #[test] + fn neg_double_is_identity() { + let mut rng = ChaCha20Rng::seed_from_u64(512); + let poly = Polynomial::::random(4, &mut rng); + assert_eq!(-(-poly.clone()), poly); + } + + #[test] + fn add_sub_inverse() { + let mut rng = ChaCha20Rng::seed_from_u64(513); + let n = 4; + let a = Polynomial::::random(n, &mut rng); + let b = Polynomial::::random(n, &mut rng); + + let result = (a.clone() + &b) - &b; + assert_eq!(result, a); + } + + #[test] + fn ref_scalar_mul() { + let mut rng = ChaCha20Rng::seed_from_u64(514); + let n = 4; + let poly = Polynomial::::random(n, &mut rng); + let s = Fr::random(&mut rng); + + let owned_result = poly.clone() * s; + let ref_result = &poly * s; + assert_eq!(owned_result, ref_result); + } + + #[test] + fn compact_u8_bind_to_field_matches_dense() { + let scalars: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let compact = Polynomial::new(scalars.clone()); + let dense_evals: Vec = scalars.iter().map(|&s| Fr::from(s)).collect(); + let dense = Polynomial::new(dense_evals); + + let mut rng = ChaCha20Rng::seed_from_u64(10); + let scalar = Fr::random(&mut rng); + + assert_eq!( + compact.bind_to_field::(scalar), + dense.bind_to_field(scalar) + ); + } + + #[test] + fn compact_u8_sequential_bind_matches_evaluate() { + let scalars: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let compact = Polynomial::new(scalars.clone()); + let dense_evals: Vec = scalars.iter().map(|&s| Fr::from(s)).collect(); + let dense = Polynomial::new(dense_evals); + + let mut rng = ChaCha20Rng::seed_from_u64(10); + let point: Vec = (0..3).map(|_| Fr::random(&mut rng)).collect(); + + let mut bound = compact.bind_to_field::(point[0]); + for &r in &point[1..] { + bound.bind(r); + } + assert_eq!(bound.evals[0], dense.evaluate(&point)); + } + + #[test] + fn compact_bool_bind_to_field_matches_dense() { + let scalars: Vec = vec![true, false, false, true]; + let compact = Polynomial::new(scalars.clone()); + let dense_evals: Vec = scalars.iter().map(|&s| Fr::from(s)).collect(); + let dense = Polynomial::new(dense_evals); + + let mut rng = ChaCha20Rng::seed_from_u64(20); + let scalar = Fr::random(&mut rng); + + assert_eq!( + compact.bind_to_field::(scalar), + dense.bind_to_field(scalar) + ); + } + + #[test] + fn compact_u16_bind_to_field_matches_dense() { + let scalars: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8]; + let compact = Polynomial::new(scalars.clone()); + let dense_evals: Vec = scalars.iter().map(|&s| Fr::from(s)).collect(); + let dense = Polynomial::new(dense_evals); + + let mut rng = ChaCha20Rng::seed_from_u64(30); + let scalar = Fr::random(&mut rng); + + assert_eq!( + compact.bind_to_field::(scalar), + dense.bind_to_field(scalar) + ); + } + + #[test] + fn compact_i64_bind_to_field_matches_dense() { + let scalars: Vec = vec![-1, 0, 1, -100, i64::MIN, i64::MAX, -42, 42]; + let compact = Polynomial::new(scalars.clone()); + let dense_evals: Vec = scalars.iter().map(|&s| Fr::from(s)).collect(); + let dense = Polynomial::new(dense_evals); + + let mut rng = ChaCha20Rng::seed_from_u64(50); + let scalar = Fr::random(&mut rng); + + assert_eq!( + compact.bind_to_field::(scalar), + dense.bind_to_field(scalar) + ); + } + + #[test] + fn compact_i128_bind_to_field_matches_dense() { + let scalars: Vec = vec![-1, 0, 1, -999, i128::MIN, i128::MAX, -7, 7]; + let compact = Polynomial::new(scalars.clone()); + let dense_evals: Vec = scalars.iter().map(|&s| Fr::from(s)).collect(); + let dense = Polynomial::new(dense_evals); + + let mut rng = ChaCha20Rng::seed_from_u64(60); + let scalar = Fr::random(&mut rng); + + assert_eq!( + compact.bind_to_field::(scalar), + dense.bind_to_field(scalar) + ); + } + + #[test] + fn compact_u128_bind_to_field_matches_dense() { + let scalars: Vec = vec![u128::MAX, u128::MAX - 1, 0, 1]; + let compact = Polynomial::new(scalars.clone()); + let dense_evals: Vec = scalars.iter().map(|&s| Fr::from(s)).collect(); + let dense = Polynomial::new(dense_evals); + + let mut rng = ChaCha20Rng::seed_from_u64(70); + let scalar = Fr::random(&mut rng); + + assert_eq!( + compact.bind_to_field::(scalar), + dense.bind_to_field(scalar) + ); + } + + #[test] + fn compact_bind_chain_consistency() { + let scalars: Vec = vec![10, 20, 30, 40, 50, 60, 70, 80]; + let compact = Polynomial::new(scalars.clone()); + let dense_evals: Vec = scalars.iter().map(|&s| Fr::from(s)).collect(); + let dense = Polynomial::new(dense_evals); + + let mut rng = ChaCha20Rng::seed_from_u64(80); + let r1 = Fr::random(&mut rng); + let r2 = Fr::random(&mut rng); + let remaining: Vec = (0..1).map(|_| Fr::random(&mut rng)).collect(); + + // bind_to_field(r1) then bind(r2) should match dense evaluate + let mut bound = compact.bind_to_field::(r1); + bound.bind(r2); + let result = bound.evaluate(&remaining); + + let mut full_point = vec![r1, r2]; + full_point.extend_from_slice(&remaining); + assert_eq!(result, dense.evaluate(&full_point)); + } + + #[test] + fn compact_empty() { + let compact = Polynomial::::new(vec![]); + assert_eq!(compact.num_vars(), 0); + assert!(compact.is_empty()); + } + + #[test] + fn compact_single_element() { + let compact = Polynomial::::new(vec![42]); + assert_eq!(compact.num_vars(), 0); + assert_eq!(compact.evals(), &[42u64]); + } + + #[test] + fn serde_round_trip_compact_u8() { + let scalars: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let compact = Polynomial::new(scalars); + let bytes = bincode::serde::encode_to_vec(&compact, bincode::config::standard()).unwrap(); + let recovered: Polynomial = + bincode::serde::decode_from_slice(&bytes, bincode::config::standard()) + .unwrap() + .0; + + let mut rng = ChaCha20Rng::seed_from_u64(40); + let scalar = Fr::random(&mut rng); + assert_eq!( + compact.bind_to_field::(scalar), + recovered.bind_to_field::(scalar) + ); + } + + #[test] + fn serde_round_trip_compact_bool() { + let scalars: Vec = vec![true, false, true, false]; + let compact = Polynomial::new(scalars); + let bytes = bincode::serde::encode_to_vec(&compact, bincode::config::standard()).unwrap(); + let recovered: Polynomial = + bincode::serde::decode_from_slice(&bytes, bincode::config::standard()) + .unwrap() + .0; + + let mut rng = ChaCha20Rng::seed_from_u64(41); + let scalar = Fr::random(&mut rng); + assert_eq!( + compact.bind_to_field::(scalar), + recovered.bind_to_field::(scalar) + ); + } + + #[test] + fn low_to_high_binding_produces_correct_evaluation() { + let mut rng = ChaCha20Rng::seed_from_u64(900); + let n = 5; + let poly = Polynomial::::random(n, &mut rng); + let point: Vec = (0..n).map(|_| Fr::random(&mut rng)).collect(); + + // HighToLow binds point[0] first (MSB), so binding sequentially + // with point[0], point[1], ... should yield evaluate(point). + let mut hi_to_lo = poly.clone(); + for &r in &point { + hi_to_lo.bind_with_order(r, crate::BindingOrder::HighToLow); + } + assert_eq!(hi_to_lo.len(), 1); + assert_eq!(hi_to_lo.evaluations()[0], poly.evaluate(&point)); + + // LowToHigh binds point[n-1] first (LSB), so to get the same + // evaluation we must reverse the order of challenges. + let mut lo_to_hi = poly.clone(); + for &r in point.iter().rev() { + lo_to_hi.bind_with_order(r, crate::BindingOrder::LowToHigh); + } + assert_eq!(lo_to_hi.len(), 1); + assert_eq!(lo_to_hi.evaluations()[0], poly.evaluate(&point)); + } +} diff --git a/crates/jolt-poly/src/source.rs b/crates/jolt-poly/src/source.rs new file mode 100644 index 0000000000..2556f7f92b --- /dev/null +++ b/crates/jolt-poly/src/source.rs @@ -0,0 +1,553 @@ +//! Abstract multilinear polynomial trait and compositions. +//! +//! [`MultilinearPoly`] is the core abstraction over multilinear polynomials +//! in evaluation form. Implementations range from dense evaluation tables +//! ([`Polynomial`](crate::Polynomial)) to structured sparse representations +//! ([`OneHotPolynomial`](crate::OneHotPolynomial)) to lazy compositions ([`RlcSource`]). The trait +//! decouples polynomial *access* from *storage*, enabling streaming opening +//! proofs where the full $2^n$ table never resides in memory simultaneously. +//! +//! [`RlcSource`] composes multiple polynomials via random linear combination +//! without materializing the combined table. Its [`fold_rows`](MultilinearPoly::fold_rows) +//! distributes across constituents, avoiding allocation of the combined table. + +use jolt_field::Field; + +use crate::Polynomial; + +/// A multilinear polynomial $f : \{0,1\}^n \to \mathbb{F}$ in evaluation form. +/// +/// The evaluation table can be viewed as a $(2^\nu \times 2^\sigma)$ matrix +/// where $\nu + \sigma = n$. Implementations range from dense evaluation +/// tables ([`Polynomial`](crate::Polynomial)) to structured sparse forms +/// ([`OneHotPolynomial`](crate::OneHotPolynomial)) to lazy compositions ([`RlcSource`]). +/// +/// Core operations: +/// - [`num_vars`](Self::num_vars) / [`evaluate`](Self::evaluate): metadata and point evaluation +/// - [`for_each_row`](Self::for_each_row): row-wise iteration (streaming commit, row-based MSM) +/// - [`fold_rows`](Self::fold_rows): matrix-vector product $v \cdot M$ (opening protocols) +/// - [`is_sparse`](Self::is_sparse) / [`for_each_nonzero`](Self::for_each_nonzero): sparsity +/// hints for PCS commit optimization (e.g., batch addition instead of MSM) +pub trait MultilinearPoly: Send + Sync { + /// Number of variables $n$. The polynomial has $2^n$ evaluations. + fn num_vars(&self) -> usize; + + /// Evaluates $f(r)$ at an arbitrary point $r \in \mathbb{F}^n$. + fn evaluate(&self, point: &[F]) -> F; + + /// Iterates over the evaluation table in row-major order. + /// + /// The table is interpreted as a $(2^\nu \times 2^\sigma)$ matrix where + /// $\sigma$ is the number of column variables and $\nu = n - \sigma$. + /// The closure receives `(row_index, row_data)` pairs in order. + /// + /// For in-memory polynomials, rows are borrowed slices (zero-copy). + /// For lazy sources, each row may be computed on-the-fly. + fn for_each_row(&self, sigma: usize, f: &mut dyn FnMut(usize, &[F])); + + /// Folds a left vector against the $(2^\nu \times 2^\sigma)$ matrix form. + /// + /// Computes: + /// $$\text{result}\[c\] = \sum_{r=0}^{2^\nu - 1} \text{left}\[r\] \cdot M\[r\]\[c\]$$ + /// + /// where $M\[r\]\[c\] = f(\text{bits}(r \cdot 2^\sigma + c))$ and + /// $\nu = n - \sigma$. + /// + /// The default implementation iterates rows via [`for_each_row`](Self::for_each_row). + /// Implementations with distributable structure (e.g., [`RlcSource`]) or + /// sparse representations (e.g., one-hot polynomials) should override + /// for better performance. + /// + /// # Panics + /// + /// Panics if `left.len() != 2^(num_vars - sigma)`. + fn fold_rows(&self, left: &[F], sigma: usize) -> Vec { + let num_cols = 1usize << sigma; + let mut result = vec![F::zero(); num_cols]; + self.for_each_row(sigma, &mut |row_idx, row| { + let l = left[row_idx]; + for (r, &val) in result.iter_mut().zip(row.iter()) { + *r += l * val; + } + }); + result + } + + /// Whether this polynomial has sparse structure that allows more efficient + /// commitment (e.g., batch affine addition instead of full MSM). + /// + /// When true, PCS backends should use [`for_each_nonzero`](Self::for_each_nonzero) + /// to access only the nonzero entries. + fn is_sparse(&self) -> bool { + false + } + + /// Iterates over nonzero entries as `(flat_index, value)` pairs. + /// + /// For dense polynomials, the default scans the full table. Structured + /// sparse types (e.g., [`OneHotPolynomial`](crate::OneHotPolynomial)) yield only O(T) entries. + fn for_each_nonzero(&self, f: &mut dyn FnMut(usize, F)) { + let n = self.num_vars(); + let total = 1usize << n; + self.for_each_row(n, &mut |_, row| { + for (i, &val) in row.iter().take(total).enumerate() { + if !val.is_zero() { + f(i, val); + } + } + }); + } +} + +impl MultilinearPoly for Polynomial { + #[inline] + fn num_vars(&self) -> usize { + Polynomial::num_vars(self) + } + + fn evaluate(&self, point: &[F]) -> F { + Polynomial::evaluate(self, point) + } + + fn for_each_row(&self, sigma: usize, f: &mut dyn FnMut(usize, &[F])) { + let num_cols = 1usize << sigma; + for (i, row) in self.evaluations().chunks(num_cols).enumerate() { + f(i, row); + } + } + + fn fold_rows(&self, left: &[F], sigma: usize) -> Vec { + let num_cols = 1usize << sigma; + let evals = self.evaluations(); + debug_assert_eq!( + left.len(), + evals.len() / num_cols, + "left vector length must equal number of rows" + ); + + let mut result = vec![F::zero(); num_cols]; + for (row_idx, row) in evals.chunks(num_cols).enumerate() { + let l = left[row_idx]; + for (r, &val) in result.iter_mut().zip(row.iter()) { + *r += l * val; + } + } + result + } +} + +impl MultilinearPoly for [F] { + #[inline] + fn num_vars(&self) -> usize { + if self.is_empty() { + return 0; + } + assert!( + self.len().is_power_of_two(), + "slice length must be a power of two, got {}", + self.len() + ); + self.len().trailing_zeros() as usize + } + + fn evaluate(&self, point: &[F]) -> F { + let eq_evals = crate::EqPolynomial::new(point.to_vec()).evaluations(); + self.iter().zip(eq_evals.iter()).map(|(&f, &e)| f * e).sum() + } + + fn for_each_row(&self, sigma: usize, f: &mut dyn FnMut(usize, &[F])) { + let num_cols = 1usize << sigma; + for (i, row) in self.chunks(num_cols).enumerate() { + f(i, row); + } + } + + fn fold_rows(&self, left: &[F], sigma: usize) -> Vec { + let num_cols = 1usize << sigma; + let mut result = vec![F::zero(); num_cols]; + for (row_idx, row) in self.chunks(num_cols).enumerate() { + let l = left[row_idx]; + for (r, &val) in result.iter_mut().zip(row.iter()) { + *r += l * val; + } + } + result + } +} + +impl MultilinearPoly for Vec { + #[inline] + fn num_vars(&self) -> usize { + self.as_slice().num_vars() + } + + fn evaluate(&self, point: &[F]) -> F { + self.as_slice().evaluate(point) + } + + fn for_each_row(&self, sigma: usize, f: &mut dyn FnMut(usize, &[F])) { + self.as_slice().for_each_row(sigma, f); + } + + fn fold_rows(&self, left: &[F], sigma: usize) -> Vec { + self.as_slice().fold_rows(left, sigma) + } +} + +/// Lazy RLC composition of multilinear polynomials. +/// +/// Represents $f(x) = \sum_{i=0}^{k-1} s_i \cdot f_i(x)$ without +/// materializing the combined evaluation table. Operations distribute +/// over the constituents: +/// +/// - [`evaluate`](MultilinearPoly::evaluate): $\sum_i s_i \cdot f_i(r)$ +/// - [`fold_rows`](MultilinearPoly::fold_rows): $\sum_i s_i \cdot (v \cdot M_i)$ — +/// each polynomial computes its own fold, results are combined with scalars. +/// No evaluation table is ever materialized. +pub struct RlcSource> { + sources: Vec, + scalars: Vec, + num_vars: usize, +} + +impl> RlcSource { + /// Creates a lazy RLC composition. + /// + /// # Panics + /// + /// Panics if `sources` and `scalars` have different lengths, or if + /// sources have inconsistent `num_vars`. + pub fn new(sources: Vec, scalars: Vec) -> Self { + assert_eq!(sources.len(), scalars.len()); + let num_vars = sources.first().map_or(0, |s| s.num_vars()); + debug_assert!( + sources.iter().all(|s| s.num_vars() == num_vars), + "all sources must have the same num_vars" + ); + Self { + sources, + scalars, + num_vars, + } + } + + pub fn sources(&self) -> &[S] { + &self.sources + } + + pub fn scalars(&self) -> &[F] { + &self.scalars + } +} + +impl> MultilinearPoly for RlcSource { + fn num_vars(&self) -> usize { + self.num_vars + } + + fn evaluate(&self, point: &[F]) -> F { + self.sources + .iter() + .zip(&self.scalars) + .map(|(source, &scalar)| scalar * source.evaluate(point)) + .fold(F::zero(), |acc, x| acc + x) + } + + /// Iterates over combined rows by collecting each source's rows and combining. + /// + /// Memory: O(k × 2^σ) where k = number of sources. + /// For streaming-critical paths, prefer [`fold_rows`](Self::fold_rows) which + /// distributes without materializing any rows. + fn for_each_row(&self, sigma: usize, f: &mut dyn FnMut(usize, &[F])) { + if self.sources.is_empty() { + return; + } + + let num_cols = 1usize << sigma; + let nu = self.num_vars.saturating_sub(sigma); + let num_rows = 1usize << nu; + + // Collect all rows from all sources. + // Each inner vec has num_rows entries, each of length num_cols. + let all_rows: Vec>> = self + .sources + .iter() + .map(|source| { + let mut rows = Vec::with_capacity(num_rows); + source.for_each_row(sigma, &mut |_idx, row| { + rows.push(row.to_vec()); + }); + rows + }) + .collect(); + + let mut combined = vec![F::zero(); num_cols]; + for row_idx in 0..num_rows { + combined.fill(F::zero()); + for (source_rows, &scalar) in all_rows.iter().zip(&self.scalars) { + for (dst, &val) in combined.iter_mut().zip(source_rows[row_idx].iter()) { + *dst += scalar * val; + } + } + f(row_idx, &combined); + } + } + + /// Distributes fold_rows across constituent sources. + /// + /// Computes $\sum_i s_i \cdot (v \cdot M_i)$ by having each source + /// independently compute its own fold. No evaluation table is + /// ever materialized — this is the key streaming win. + fn fold_rows(&self, left: &[F], sigma: usize) -> Vec { + let num_cols = 1usize << sigma; + let mut result = vec![F::zero(); num_cols]; + for (source, &scalar) in self.sources.iter().zip(&self.scalars) { + let contribution = source.fold_rows(left, sigma); + for (r, &c) in result.iter_mut().zip(contribution.iter()) { + *r += scalar * c; + } + } + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + use jolt_field::Fr; + use num_traits::Zero; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + #[test] + fn polynomial_for_each_row_matches_chunks() { + let mut rng = ChaCha20Rng::seed_from_u64(1); + let poly = Polynomial::::random(4, &mut rng); + let sigma = 2; + let num_cols = 1usize << sigma; + + let mut rows = Vec::new(); + poly.for_each_row(sigma, &mut |_idx, row| { + rows.push(row.to_vec()); + }); + + assert_eq!(rows.len(), poly.len() / num_cols); + for (i, row) in rows.iter().enumerate() { + let start = i * num_cols; + assert_eq!(row.as_slice(), &poly.evaluations()[start..start + num_cols]); + } + } + + #[test] + fn polynomial_fold_rows_matches_manual_vmp() { + let mut rng = ChaCha20Rng::seed_from_u64(2); + let num_vars = 4; + let sigma = 2; + let nu = num_vars - sigma; + let num_cols = 1usize << sigma; + let num_rows = 1usize << nu; + + let poly = Polynomial::::random(num_vars, &mut rng); + let left: Vec = (0..num_rows).map(|_| Fr::random(&mut rng)).collect(); + + let result = poly.fold_rows(&left, sigma); + + // Manual VMP + let mut expected = vec![Fr::zero(); num_cols]; + for (row, &l) in left.iter().enumerate() { + for (col, dest) in expected.iter_mut().enumerate() { + *dest += l * poly.evaluations()[row * num_cols + col]; + } + } + + assert_eq!(result, expected); + } + + #[test] + fn rlc_source_evaluate_matches_manual() { + let mut rng = ChaCha20Rng::seed_from_u64(10); + let num_vars = 3; + + let p1 = Polynomial::::random(num_vars, &mut rng); + let p2 = Polynomial::::random(num_vars, &mut rng); + let s1 = Fr::random(&mut rng); + let s2 = Fr::random(&mut rng); + + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); + + let rlc = RlcSource::new(vec![p1.clone(), p2.clone()], vec![s1, s2]); + let result = rlc.evaluate(&point); + let expected = s1 * p1.evaluate(&point) + s2 * p2.evaluate(&point); + + assert_eq!(result, expected); + } + + #[test] + fn rlc_source_fold_rows_matches_materialized() { + let mut rng = ChaCha20Rng::seed_from_u64(20); + let num_vars = 4; + let sigma = 2; + let nu = num_vars - sigma; + let num_rows = 1usize << nu; + + let p1 = Polynomial::::random(num_vars, &mut rng); + let p2 = Polynomial::::random(num_vars, &mut rng); + let s1 = Fr::random(&mut rng); + let s2 = Fr::random(&mut rng); + let left: Vec = (0..num_rows).map(|_| Fr::random(&mut rng)).collect(); + + // Lazy fold + let rlc = RlcSource::new(vec![p1.clone(), p2.clone()], vec![s1, s2]); + let lazy_result = rlc.fold_rows(&left, sigma); + + // Materialized fold + let combined_evals: Vec = p1 + .evaluations() + .iter() + .zip(p2.evaluations().iter()) + .map(|(&a, &b)| s1 * a + s2 * b) + .collect(); + let combined = Polynomial::new(combined_evals); + let materialized_result = combined.fold_rows(&left, sigma); + + assert_eq!(lazy_result, materialized_result); + } + + #[test] + fn rlc_source_for_each_row_matches_materialized() { + let mut rng = ChaCha20Rng::seed_from_u64(30); + let num_vars = 3; + let sigma = 1; + + let p1 = Polynomial::::random(num_vars, &mut rng); + let p2 = Polynomial::::random(num_vars, &mut rng); + let s1 = Fr::random(&mut rng); + let s2 = Fr::random(&mut rng); + + let rlc = RlcSource::new(vec![p1.clone(), p2.clone()], vec![s1, s2]); + + let mut lazy_rows = Vec::new(); + rlc.for_each_row(sigma, &mut |_idx, row| { + lazy_rows.push(row.to_vec()); + }); + + let combined_evals: Vec = p1 + .evaluations() + .iter() + .zip(p2.evaluations().iter()) + .map(|(&a, &b)| s1 * a + s2 * b) + .collect(); + let combined = Polynomial::new(combined_evals); + let mut materialized_rows = Vec::new(); + combined.for_each_row(sigma, &mut |_idx, row| { + materialized_rows.push(row.to_vec()); + }); + + assert_eq!(lazy_rows, materialized_rows); + } + + #[test] + fn rlc_source_fold_equals_evaluate_at_point() { + use crate::eq::EqPolynomial; + + let mut rng = ChaCha20Rng::seed_from_u64(40); + let num_vars = 4; + let sigma = 2; + let nu = num_vars - sigma; + + let p1 = Polynomial::::random(num_vars, &mut rng); + let p2 = Polynomial::::random(num_vars, &mut rng); + let p3 = Polynomial::::random(num_vars, &mut rng); + let s1 = Fr::random(&mut rng); + let s2 = Fr::random(&mut rng); + let s3 = Fr::random(&mut rng); + + let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); + + // Split point into row-point (first nu vars) and col-point (last sigma vars) + let row_point = &point[..nu]; + let col_point = &point[nu..]; + + let rlc = RlcSource::new(vec![p1.clone(), p2.clone(), p3.clone()], vec![s1, s2, s3]); + + // fold_rows with eq(row_point) as left vector, then dot with eq(col_point) + let eq_rows = EqPolynomial::new(row_point.to_vec()).evaluations(); + let folded = rlc.fold_rows(&eq_rows, sigma); + let eq_cols = EqPolynomial::new(col_point.to_vec()).evaluations(); + let via_fold: Fr = folded + .iter() + .zip(eq_cols.iter()) + .map(|(&a, &b)| a * b) + .sum(); + + // Direct evaluation + let via_eval = rlc.evaluate(&point); + + assert_eq!(via_fold, via_eval); + } + + #[test] + fn default_fold_rows_matches_override() { + let mut rng = ChaCha20Rng::seed_from_u64(50); + let num_vars = 4; + let sigma = 2; + let nu = num_vars - sigma; + let num_rows = 1usize << nu; + + let poly = Polynomial::::random(num_vars, &mut rng); + let left: Vec = (0..num_rows).map(|_| Fr::random(&mut rng)).collect(); + + // Use the default impl (via for_each_row) + let default_result = default_fold_rows(&poly, &left, sigma); + + // Use the overridden impl + let override_result = poly.fold_rows(&left, sigma); + + assert_eq!(default_result, override_result); + } + + /// Calls the default `fold_rows` implementation (via `for_each_row`). + fn default_fold_rows( + source: &impl MultilinearPoly, + left: &[F], + sigma: usize, + ) -> Vec { + let num_cols = 1usize << sigma; + let mut result = vec![F::zero(); num_cols]; + source.for_each_row(sigma, &mut |row_idx, row| { + let l = left[row_idx]; + for (r, &val) in result.iter_mut().zip(row.iter()) { + *r += l * val; + } + }); + result + } + + #[test] + fn empty_rlc_source() { + let rlc: RlcSource> = RlcSource::new(vec![], vec![]); + assert_eq!(rlc.num_vars(), 0); + } + + #[test] + fn single_source_rlc_is_scaled_original() { + let mut rng = ChaCha20Rng::seed_from_u64(60); + let num_vars = 3; + let sigma = 1; + let nu = num_vars - sigma; + let num_rows = 1usize << nu; + + let poly = Polynomial::::random(num_vars, &mut rng); + let scalar = Fr::random(&mut rng); + let left: Vec = (0..num_rows).map(|_| Fr::random(&mut rng)).collect(); + + let rlc = RlcSource::new(vec![poly.clone()], vec![scalar]); + let rlc_result = rlc.fold_rows(&left, sigma); + + // Manually scale the polynomial fold + let direct_result = poly.fold_rows(&left, sigma); + let scaled: Vec = direct_result.iter().map(|&v| scalar * v).collect(); + + assert_eq!(rlc_result, scaled); + } +} diff --git a/crates/jolt-poly/src/split_eq.rs b/crates/jolt-poly/src/split_eq.rs new file mode 100644 index 0000000000..501cb537c9 --- /dev/null +++ b/crates/jolt-poly/src/split_eq.rs @@ -0,0 +1,438 @@ +//! Split equality polynomial used by sumcheck provers. +//! +//! This implements the Dao-Thaler/Gruen factorization used by Jolt's larger +//! sumchecks. It stores prefix tables for two halves of the remaining equality +//! polynomial and tracks already-bound variables in a scalar. + +use jolt_field::Field; + +use crate::{BindingOrder, EqPolynomial, Polynomial, UnivariatePoly}; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GruenSplitEqPolynomial { + current_index: usize, + current_scalar: F, + w: Vec, + e_in_vec: Vec>, + e_out_vec: Vec>, + binding_order: BindingOrder, +} + +impl GruenSplitEqPolynomial { + #[tracing::instrument(skip_all, name = "GruenSplitEqPolynomial::new_with_scaling")] + pub fn new_with_scaling( + w: &[F], + binding_order: BindingOrder, + scaling_factor: Option, + ) -> Self { + assert!(!w.is_empty(), "split eq requires at least one variable"); + match binding_order { + BindingOrder::LowToHigh => { + let split = w.len() / 2; + let w_prime = &w[..w.len() - 1]; + let (w_out, w_in) = w_prime.split_at(split); + let (e_out_vec, e_in_vec) = join_or_serial( + || EqPolynomial::evals_cached(w_out, None), + || EqPolynomial::evals_cached(w_in, None), + ); + Self { + current_index: w.len(), + current_scalar: scaling_factor.unwrap_or_else(F::one), + w: w.to_vec(), + e_in_vec, + e_out_vec, + binding_order, + } + } + BindingOrder::HighToLow => { + let w_prime = &w[1..]; + let split = w.len() / 2; + let (w_in, w_out) = w_prime.split_at(split); + let (e_in_vec, e_out_vec) = join_or_serial( + || EqPolynomial::evals_cached_rev(w_in, None), + || EqPolynomial::evals_cached_rev(w_out, None), + ); + Self { + current_index: 0, + current_scalar: scaling_factor.unwrap_or_else(F::one), + w: w.to_vec(), + e_in_vec, + e_out_vec, + binding_order, + } + } + } + } + + #[tracing::instrument(skip_all, name = "GruenSplitEqPolynomial::new")] + pub fn new(w: &[F], binding_order: BindingOrder) -> Self { + Self::new_with_scaling(w, binding_order, None) + } + + #[inline] + pub fn num_vars(&self) -> usize { + self.w.len() + } + + #[inline] + pub fn len(&self) -> usize { + match self.binding_order { + BindingOrder::LowToHigh => 1 << self.current_index, + BindingOrder::HighToLow => 1 << (self.w.len() - self.current_index), + } + } + + #[inline] + pub fn is_empty(&self) -> bool { + false + } + + #[inline] + pub fn num_bound_vars(&self) -> usize { + match self.binding_order { + BindingOrder::LowToHigh => self.w.len() - self.current_index, + BindingOrder::HighToLow => self.current_index, + } + } + + #[inline] + pub fn e_in_current_len(&self) -> usize { + self.e_in_current().len() + } + + #[inline] + pub fn e_out_current_len(&self) -> usize { + self.e_out_current().len() + } + + #[inline] + pub fn e_in_current(&self) -> &[F] { + &self.e_in_vec[self.e_in_vec.len() - 1] + } + + #[inline] + pub fn e_out_current(&self) -> &[F] { + &self.e_out_vec[self.e_out_vec.len() - 1] + } + + pub fn e_out_in_for_window(&self, window_size: usize) -> (&[F], &[F]) { + assert_eq!( + self.binding_order, + BindingOrder::LowToHigh, + "streaming windows are not defined for high-to-low split eq" + ); + let num_unbound = self.current_index; + let window_size = window_size.min(num_unbound); + let head_len = num_unbound.saturating_sub(window_size); + let split = self.w.len() / 2; + let head_out_bits = head_len.min(split); + let head_in_bits = head_len.saturating_sub(head_out_bits); + (&self.e_out_vec[head_out_bits], &self.e_in_vec[head_in_bits]) + } + + pub fn e_active_for_window(&self, window_size: usize) -> Vec { + if window_size <= 1 { + return vec![F::one()]; + } + assert_eq!( + self.binding_order, + BindingOrder::LowToHigh, + "streaming windows are not defined for high-to-low split eq" + ); + let num_unbound = self.current_index; + if window_size > num_unbound { + return vec![F::one()]; + } + let remaining_w = &self.w[..num_unbound]; + let window_start = remaining_w.len() - window_size; + let (_head, w_window) = remaining_w.split_at(window_start); + let (w_active, _w_current) = w_window.split_at(window_size - 1); + EqPolynomial::::evals(w_active, None) + } + + #[tracing::instrument(skip_all, name = "GruenSplitEqPolynomial::bind")] + pub fn bind(&mut self, r: F) { + match self.binding_order { + BindingOrder::LowToHigh => { + let w = self.w[self.current_index - 1]; + let prod = w * r; + self.current_scalar *= F::one() - w - r + prod + prod; + self.current_index -= 1; + if self.w.len() / 2 < self.current_index && self.e_in_vec.len() > 1 { + let _ = self.e_in_vec.pop(); + } else if 0 < self.current_index && self.e_out_vec.len() > 1 { + let _ = self.e_out_vec.pop(); + } + } + BindingOrder::HighToLow => { + let w = self.w[self.current_index]; + let prod = w * r; + self.current_scalar *= F::one() - w - r + prod + prod; + self.current_index += 1; + if self.current_index <= self.w.len() / 2 && self.e_in_vec.len() > 1 { + let _ = self.e_in_vec.pop(); + } else if self.current_index <= self.w.len() && self.e_out_vec.len() > 1 { + let _ = self.e_out_vec.pop(); + } + } + } + } + + pub fn gruen_poly_deg_3( + &self, + q_constant: F, + q_quadratic_coeff: F, + s_0_plus_s_1: F, + ) -> UnivariatePoly { + let eq_eval_1 = self.current_scalar * self.current_w(); + let eq_eval_0 = self.current_scalar - eq_eval_1; + let eq_slope = eq_eval_1 - eq_eval_0; + let eq_eval_2 = eq_eval_1 + eq_slope; + let eq_eval_3 = eq_eval_2 + eq_slope; + + let quadratic_eval_0 = q_constant; + let cubic_eval_0 = eq_eval_0 * quadratic_eval_0; + let cubic_eval_1 = s_0_plus_s_1 - cubic_eval_0; + let quadratic_eval_1 = cubic_eval_1 / eq_eval_1; + let e_times_2 = q_quadratic_coeff + q_quadratic_coeff; + let quadratic_eval_2 = quadratic_eval_1 + quadratic_eval_1 - quadratic_eval_0 + e_times_2; + let quadratic_eval_3 = + quadratic_eval_2 + quadratic_eval_1 - quadratic_eval_0 + e_times_2 + e_times_2; + + UnivariatePoly::from_evals(&[ + cubic_eval_0, + cubic_eval_1, + eq_eval_2 * quadratic_eval_2, + eq_eval_3 * quadratic_eval_3, + ]) + } + + pub fn gruen_poly_deg_2(&self, q_0: F, previous_claim: F) -> UnivariatePoly { + let eq_eval_1 = self.current_scalar * self.current_w(); + let eq_eval_0 = self.current_scalar - eq_eval_1; + let eq_slope = eq_eval_1 - eq_eval_0; + let eq_eval_2 = eq_eval_1 + eq_slope; + + let quadratic_eval_0 = eq_eval_0 * q_0; + let quadratic_eval_1 = previous_claim - quadratic_eval_0; + let linear_eval_1 = quadratic_eval_1 / eq_eval_1; + let linear_eval_2 = linear_eval_1 + linear_eval_1 - q_0; + + UnivariatePoly::from_evals(&[ + quadratic_eval_0, + quadratic_eval_1, + eq_eval_2 * linear_eval_2, + ]) + } + + pub fn gruen_poly_from_evals(&self, q_evals: &[F], s_0_plus_s_1: F) -> UnivariatePoly { + assert!(!q_evals.is_empty(), "q_evals must be non-empty"); + let r_round = self.current_w(); + let l_at_0 = self.current_scalar * EqPolynomial::::mle(&[F::zero()], &[r_round]); + let l_at_1 = self.current_scalar * EqPolynomial::::mle(&[F::one()], &[r_round]); + let q_at_0 = (s_0_plus_s_1 - l_at_1 * q_evals[0]) / l_at_0; + + let mut full_q_evals = Vec::with_capacity(q_evals.len() + 1); + full_q_evals.push(q_at_0); + full_q_evals.extend_from_slice(q_evals); + let q = UnivariatePoly::from_evals_toom(&full_q_evals); + + let l_c0 = l_at_0; + let l_c1 = l_at_1 - l_at_0; + let q_coeffs = q.into_coefficients(); + let mut s_coeffs = vec![F::zero(); q_coeffs.len() + 1]; + for (index, q_coeff) in q_coeffs.into_iter().enumerate() { + s_coeffs[index] += q_coeff * l_c0; + s_coeffs[index + 1] += q_coeff * l_c1; + } + UnivariatePoly::new(s_coeffs) + } + + pub fn merge(&self) -> Polynomial { + let evals = match self.binding_order { + BindingOrder::LowToHigh => { + EqPolynomial::evals(&self.w[..self.current_index], Some(self.current_scalar)) + } + BindingOrder::HighToLow => { + EqPolynomial::evals(&self.w[self.current_index..], Some(self.current_scalar)) + } + }; + Polynomial::new(evals) + } + + #[inline] + pub fn current_scalar(&self) -> F { + self.current_scalar + } + + #[inline] + pub fn current_w(&self) -> F { + match self.binding_order { + BindingOrder::LowToHigh => self.w[self.current_index - 1], + BindingOrder::HighToLow => self.w[self.current_index], + } + } + + #[inline] + pub fn group_index(&self, x_out: usize, x_in: usize) -> usize { + let num_x_in_bits = self.e_in_current_len().trailing_zeros() as usize; + (x_out << num_x_in_bits) | x_in + } + + pub fn fold_out_in< + OuterAcc: Send, + InnerAcc: Send, + MakeInner: Fn() -> InnerAcc + Sync + Send, + InnerStep: Fn(&mut InnerAcc, usize, usize, F) + Sync + Send, + OuterStep: Fn(usize, F, InnerAcc) -> OuterAcc + Sync + Send, + Merge: Fn(OuterAcc, OuterAcc) -> OuterAcc + Sync + Send, + >( + &self, + make_inner: MakeInner, + inner_step: InnerStep, + outer_step: OuterStep, + merge: Merge, + ) -> OuterAcc { + let e_out = self.e_out_current(); + let e_in = self.e_in_current(); + + #[cfg(feature = "parallel")] + { + let result = (0..e_out.len()) + .into_par_iter() + .map(|x_out| { + let mut inner_acc = make_inner(); + for (x_in, &e_in) in e_in.iter().enumerate() { + let group = self.group_index(x_out, x_in); + inner_step(&mut inner_acc, group, x_in, e_in); + } + outer_step(x_out, e_out[x_out], inner_acc) + }) + .reduce_with(merge); + if let Some(result) = result { + result + } else { + assert!(!e_out.is_empty(), "split eq e_out invariant"); + std::process::abort(); + } + } + + #[cfg(not(feature = "parallel"))] + { + let mut iter = (0..e_out.len()).map(|x_out| { + let mut inner_acc = make_inner(); + for (x_in, &e_in) in e_in.iter().enumerate() { + let group = self.group_index(x_out, x_in); + inner_step(&mut inner_acc, group, x_in, e_in); + } + outer_step(x_out, e_out[x_out], inner_acc) + }); + let first = iter.next().expect("split eq e_out invariant"); + iter.fold(first, merge) + } + } +} + +#[cfg(feature = "parallel")] +fn join_or_serial( + left: impl FnOnce() -> A + Send, + right: impl FnOnce() -> B + Send, +) -> (A, B) { + rayon::join(left, right) +} + +#[cfg(not(feature = "parallel"))] +fn join_or_serial(left: impl FnOnce() -> A, right: impl FnOnce() -> B) -> (A, B) { + (left(), right()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::math::Math; + use jolt_field::{Field, Fr}; + use num_traits::{One, Zero}; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + #[test] + fn bind_low_to_high_matches_dense_eq() { + let mut rng = ChaCha20Rng::seed_from_u64(700); + let point: Vec = (0..10).map(|_| Fr::random(&mut rng)).collect(); + let mut dense = Polynomial::new(EqPolynomial::::evals(&point, None)); + let mut split = GruenSplitEqPolynomial::new(&point, BindingOrder::LowToHigh); + assert_eq!(dense, split.merge()); + + for _ in 0..point.len() { + let r = Fr::random(&mut rng); + dense.bind_with_order(r, BindingOrder::LowToHigh); + split.bind(r); + assert_eq!(dense, split.merge()); + } + } + + #[test] + fn bind_high_to_low_matches_dense_eq() { + let mut rng = ChaCha20Rng::seed_from_u64(701); + let point: Vec = (0..10).map(|_| Fr::random(&mut rng)).collect(); + let mut dense = Polynomial::new(EqPolynomial::::evals(&point, None)); + let mut split = GruenSplitEqPolynomial::new(&point, BindingOrder::HighToLow); + assert_eq!(dense, split.merge()); + + for _ in 0..point.len() { + let r = Fr::random(&mut rng); + dense.bind_with_order(r, BindingOrder::HighToLow); + split.bind(r); + assert_eq!(dense, split.merge()); + } + } + + #[test] + fn window_size_one_factors_current_head() { + let mut rng = ChaCha20Rng::seed_from_u64(702); + let point: Vec = (0..10).map(|_| Fr::random(&mut rng)).collect(); + let mut split = GruenSplitEqPolynomial::new(&point, BindingOrder::LowToHigh); + + for _round in 0..point.len() { + let num_unbound = split.current_index; + if num_unbound <= 1 { + break; + } + let (e_out, e_in) = split.e_out_in_for_window(1); + let head = EqPolynomial::::evals(&split.w[..num_unbound - 1], None); + assert_eq!(e_out.len() * e_in.len(), head.len()); + + let x_in_bits = e_in.len().log_2(); + for (x_out, &e_out) in e_out.iter().enumerate() { + for (x_in, &e_in) in e_in.iter().enumerate() { + let index = (x_out << x_in_bits) | x_in; + assert_eq!(e_out * e_in, head[index]); + } + } + + split.bind(Fr::random(&mut rng)); + } + } + + #[test] + fn gruen_degree_two_matches_direct_interpolation() { + let mut rng = ChaCha20Rng::seed_from_u64(703); + let point: Vec = (0..5).map(|_| Fr::random(&mut rng)).collect(); + let split = GruenSplitEqPolynomial::new(&point, BindingOrder::LowToHigh); + let q0 = Fr::from_u64(11); + let q1 = Fr::from_u64(29); + let l1 = split.current_scalar() * split.current_w(); + let l0 = split.current_scalar() - l1; + let previous_claim = l0 * q0 + l1 * q1; + + let poly = split.gruen_poly_deg_2(q0, previous_claim); + let q2 = q1 + q1 - q0; + let l2 = l1 + (l1 - l0); + assert_eq!(poly.evaluate(Fr::zero()), l0 * q0); + assert_eq!(poly.evaluate(Fr::one()), l1 * q1); + assert_eq!(poly.evaluate(Fr::from_u64(2)), l2 * q2); + } +} diff --git a/crates/jolt-poly/src/univariate.rs b/crates/jolt-poly/src/univariate.rs index 47cc890e80..ffccf355b5 100644 --- a/crates/jolt-poly/src/univariate.rs +++ b/crates/jolt-poly/src/univariate.rs @@ -195,8 +195,12 @@ impl UnivariatePoly { /// on the Vandermonde system. Equivalent to `interpolate_over_integers` but uses a /// direct matrix solve instead of the Lagrange formula. pub fn from_evals(evals: &[F]) -> Self { - Self { - coefficients: gaussian_elimination_vandermonde(evals), + match evals { + [e0, e1, e2] => Self::from_evals_degree2(*e0, *e1, *e2), + [e0, e1, e2, e3] => Self::from_evals_degree3(*e0, *e1, *e2, *e3), + _ => Self { + coefficients: gaussian_elimination_vandermonde(evals), + }, } } @@ -204,10 +208,16 @@ impl UnivariatePoly { /// /// Recovers `p(1) = hint - p(0)` and then interpolates over the full set `{0, 1, ..., n-1}`. pub fn from_evals_and_hint(hint: F, evals: &[F]) -> Self { - let mut full = evals.to_vec(); - let eval_at_1 = hint - full[0]; - full.insert(1, eval_at_1); - Self::from_evals(&full) + match evals { + [e0, e2] => Self::from_evals_degree2(*e0, hint - *e0, *e2), + [e0, e2, e3] => Self::from_evals_degree3(*e0, hint - *e0, *e2, *e3), + _ => { + let mut full = evals.to_vec(); + let eval_at_1 = hint - full[0]; + full.insert(1, eval_at_1); + Self::from_evals(&full) + } + } } /// Interpolates from evaluations at `[0, 1, ..., degree-1, ∞]`. @@ -252,7 +262,6 @@ impl UnivariatePoly { /// - `hint = s(0) + s(1)` /// /// Used by the split-eq evaluator to construct round polynomials. - #[expect(clippy::expect_used)] pub fn from_linear_times_quadratic_with_hint( linear_coeffs: [F; 2], quadratic_coeff_0: F, @@ -270,11 +279,8 @@ impl UnivariatePoly { !linear_eval_one.is_zero(), "linear polynomial vanishes at x=1" ); - let linear_eval_one_inv = linear_eval_one - .inverse() - .expect("nonzero linear_eval_one has an inverse"); let quadratic_coeff_1 = - (hint - cubic_coeff_0) * linear_eval_one_inv - quadratic_coeff_0 - quadratic_coeff_2; + (hint - cubic_coeff_0) / linear_eval_one - quadratic_coeff_0 - quadratic_coeff_2; // s(X) = (a + bX)(c + dX + eX^2) = ac + (ad+bc)X + (ae+bd)X^2 + beX^3 let coefficients = vec![ @@ -286,6 +292,25 @@ impl UnivariatePoly { Self { coefficients } } + fn from_evals_degree2(e0: F, e1: F, e2: F) -> Self { + let c0 = e0; + let c2 = (e0 - e1 - e1 + e2) / F::from_u64(2); + let c1 = e1 - e0 - c2; + Self { + coefficients: vec![c0, c1, c2], + } + } + + fn from_evals_degree3(e0: F, e1: F, e2: F, e3: F) -> Self { + let c0 = e0; + let c3 = (e3 - e0 + (e1 - e2) * F::from_u64(3)) / F::from_u64(6); + let c2 = (e0 - e1 - e1 + e2) / F::from_u64(2) - c3 - c3 - c3; + let c1 = e1 - e0 - c2 - c3; + Self { + coefficients: vec![c0, c1, c2, c3], + } + } + /// Returns `true` if all coefficients are zero (or the vector is empty). pub fn is_zero(&self) -> bool { self.coefficients.is_empty() || self.coefficients.iter().all(|c| *c == F::zero()) @@ -499,10 +524,7 @@ fn gaussian_elimination_augmented(matrix: &mut [Vec]) -> Vec { } for j in (i + 1)..size { - let pivot_inv = matrix[i][i] - .inverse() - .expect("nonzero pivot has an inverse"); - let factor = matrix[j][i] * pivot_inv; + let factor = matrix[j][i] / matrix[i][i]; #[expect(clippy::needless_range_loop)] for k in i..=size { let tmp = matrix[i][k]; @@ -518,10 +540,7 @@ fn gaussian_elimination_augmented(matrix: &mut [Vec]) -> Vec { "singular matrix in gaussian_elimination_augmented" ); for j in (0..i).rev() { - let pivot_inv = matrix[i][i] - .inverse() - .expect("nonzero pivot has an inverse"); - let factor = matrix[j][i] * pivot_inv; + let factor = matrix[j][i] / matrix[i][i]; for k in (0..=size).rev() { let tmp = matrix[i][k]; matrix[j][k] -= factor * tmp; @@ -531,10 +550,7 @@ fn gaussian_elimination_augmented(matrix: &mut [Vec]) -> Vec { let mut result = vec![F::zero(); size]; for i in 0..size { - let pivot_inv = matrix[i][i] - .inverse() - .expect("nonzero pivot has an inverse"); - result[i] = matrix[i][size] * pivot_inv; + result[i] = matrix[i][size] / matrix[i][i]; } result } @@ -543,8 +559,8 @@ fn gaussian_elimination_augmented(matrix: &mut [Vec]) -> Vec { #[expect(clippy::unwrap_used)] mod tests { use super::*; + use jolt_field::Field; use jolt_field::Fr; - use jolt_field::FromPrimitiveInt; use num_traits::{One, Zero}; #[test] diff --git a/crates/jolt-poly/tests/integration.rs b/crates/jolt-poly/tests/integration.rs index 5ffa348a39..bdad95ff50 100644 --- a/crates/jolt-poly/tests/integration.rs +++ b/crates/jolt-poly/tests/integration.rs @@ -5,7 +5,7 @@ //! (Polynomial, EqPolynomial, UnivariatePoly, IdentityPolynomial, RlcSource) //! that are used throughout the proving system. -use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; +use jolt_field::{Field, Fr}; use jolt_poly::{ EqPolynomial, IdentityPolynomial, MultilinearPoly, Polynomial, RlcSource, UnivariatePoly, }; diff --git a/crates/jolt-prover/Cargo.toml b/crates/jolt-prover/Cargo.toml new file mode 100644 index 0000000000..c323cdb4c2 --- /dev/null +++ b/crates/jolt-prover/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "jolt-prover" +version = "0.0.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Bolt-generated Jolt prover role crate" +repository = "https://github.com/a16z/jolt" + +[lints] +workspace = true + +[dependencies] +jolt-dory.workspace = true +jolt-field.workspace = true +jolt-kernels.workspace = true +jolt-openings.workspace = true +jolt-poly.workspace = true +jolt-transcript.workspace = true +jolt-verifier.workspace = true +jolt-witness.workspace = true +rayon.workspace = true +tracing.workspace = true diff --git a/crates/jolt-prover/src/lib.rs b/crates/jolt-prover/src/lib.rs new file mode 100644 index 0000000000..a6ced62faa --- /dev/null +++ b/crates/jolt-prover/src/lib.rs @@ -0,0 +1,100 @@ +#[rustfmt::skip] +pub mod prover; +pub mod stages; + +pub use prover::{ + default_prover_programs, jolt_proof_through_stage5, jolt_proof_through_stage6, + jolt_proof_through_stage7, prove_jolt, prove_jolt_evaluation_proof, prove_jolt_with_programs, + prove_jolt_with_stage_inputs, prove_jolt_with_witness_inputs, + prove_stage1_outer_inputs_with_program, prove_stage2_inputs_with_program, + prove_stage3_inputs_with_program, prove_stage4_inputs_with_program, + prove_stage5_inputs_with_program, prove_stage6_inputs_with_program, + prove_stage7_inputs_with_program, replay_stage1_outer_proof_with_program, + replay_stage2_proof_with_program, replay_stage3_proof_with_program, + replay_stage4_proof_with_program, replay_stage5_proof_with_program, + replay_stage6_proof_with_program, replay_stage7_proof_with_program, stage1_outer_proof, + stage1_outer_proof_from_kernel_proof, stage1_outer_prover_inputs, + stage2_opening_inputs_from_artifacts, stage2_proof, stage2_prover_inputs, + stage2_verifier_ram_data, stage3_opening_inputs_from_artifacts, stage3_proof, + stage3_prover_inputs, stage4_opening_inputs_from_artifacts, stage4_proof, stage4_prover_inputs, + stage5_kernel_proof, stage5_opening_inputs_from_artifacts, stage5_proof, stage5_prover_inputs, + stage6_bytecode_read_raf_data_from_witness_entries, stage6_execution_artifacts, + stage6_kernel_proof, stage6_opening_inputs_from_artifacts, stage6_proof, stage6_prover_inputs, + stage6_witness_from_opening_inputs, stage7_execution_artifacts, stage7_kernel_proof, + stage7_opening_inputs_from_stage6_artifacts, + stage7_opening_inputs_from_stage6_artifacts_with_program, stage7_proof, stage7_prover_inputs, + verifier_opening_inputs_from_kernel, DefaultJoltTranscript, JoltEvaluationProveError, + JoltKernelOpeningInput, JoltOpeningInputError, JoltProveError, JoltProverArtifacts, + JoltProverInputs, JoltProverPrograms, JoltProverStageInputs, JoltProverWitnessInputs, + JoltStage2RamDataStorage, +}; + +pub use prover::{ + prove_stage1_outer_with_witness_inputs, prove_stage2_with_witness_inputs, + prove_stage3_with_witness_inputs, prove_stage4_with_trace_witness_inputs, + prove_stage4_with_witness_inputs, prove_stage5_with_trace_witness_inputs, + prove_stage5_with_witness_inputs, prove_stage6_with_trace_witness_inputs, + prove_stage6_with_witness_inputs, prove_stage7_with_trace_witness_inputs, + prove_stage7_with_witness_inputs, stage6_verifier_data_from_witness_entries, +}; + +pub const TRANSCRIPT_LABEL: &[u8] = b"Jolt"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct GeneratedStage { + pub name: &'static str, + pub module: &'static str, + pub ordinal: usize, +} + +pub const GENERATED_STAGES: &[GeneratedStage] = &[ + GeneratedStage { + name: "commitment", + module: "commitment", + ordinal: 0, + }, + GeneratedStage { + name: "stage1_outer", + module: "stage1_outer", + ordinal: 1, + }, + GeneratedStage { + name: "stage2", + module: "stage2", + ordinal: 2, + }, + GeneratedStage { + name: "stage3", + module: "stage3", + ordinal: 3, + }, + GeneratedStage { + name: "stage4", + module: "stage4", + ordinal: 4, + }, + GeneratedStage { + name: "stage5", + module: "stage5", + ordinal: 5, + }, + GeneratedStage { + name: "stage6", + module: "stage6", + ordinal: 6, + }, + GeneratedStage { + name: "stage7", + module: "stage7", + ordinal: 7, + }, + GeneratedStage { + name: "stage8", + module: "stage8", + ordinal: 8, + }, +]; + +pub fn generated_stage_names() -> impl Iterator { + GENERATED_STAGES.iter().map(|stage| stage.name) +} diff --git a/crates/jolt-prover/src/prover.rs b/crates/jolt-prover/src/prover.rs new file mode 100644 index 0000000000..6a5b0bcec6 --- /dev/null +++ b/crates/jolt-prover/src/prover.rs @@ -0,0 +1,2091 @@ +#![expect( + clippy::too_many_arguments, + reason = "generated prover helpers mirror staged protocol ABIs" +)] + +use jolt_dory::{DoryCommitment, DoryHint, DoryProverSetup, DoryScheme}; +use jolt_field::{Field, Fr}; +use jolt_kernels::{stage1, stage2, stage3, stage4, stage5, stage6, stage7}; +use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme}; +use jolt_poly::{EqPolynomial, Polynomial}; +use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript}; +use jolt_verifier::{JoltEvaluationProof, JoltNamedEval, JoltProof, JoltStage2RamAccess, JoltStage2RamData, JoltStage2RamOutputLayout, JoltStage6BytecodeEntry, JoltStage6BytecodeReadRafData, JoltStage6VerifierData, JoltStageChallengeVector, JoltStageExecutionArtifacts, JoltStageOpeningInputValue, JoltStageProof, JoltSumcheckOutput}; +use jolt_witness::{stage4_ram_val_init_opening, CycleInput, Stage45SparseTraceWitness, Stage6BytecodeEntry as WitnessStage6BytecodeEntry, Stage6WitnessParams, Stage6WitnessPolynomials, Stage6WitnessSlices}; +use rayon::prelude::*; + +use crate::stages::{commitment as commitment_stage, stage1_outer as stage1_outer_stage, stage2 as stage2_stage, stage3 as stage3_stage, stage4 as stage4_stage, stage5 as stage5_stage, stage6 as stage6_stage, stage7 as stage7_stage, stage8 as stage8_stage}; + +pub type DefaultJoltTranscript = Blake2bTranscript; + +pub struct JoltProverInputs<'a, CommitmentInputs, Stage1OuterExecutor, Stage2Executor, Stage3Executor, Stage4Executor, Stage5Executor, Stage6Executor, Stage7Executor> { + pub commitment_inputs: &'a mut CommitmentInputs, + pub prover_setup: &'a DoryProverSetup, + pub stage1_outer_executor: &'a mut Stage1OuterExecutor, + pub stage2_executor: &'a mut Stage2Executor, + pub stage3_executor: &'a mut Stage3Executor, + pub stage4_executor: &'a mut Stage4Executor, + pub stage5_executor: &'a mut Stage5Executor, + pub stage6_executor: &'a mut Stage6Executor, + pub stage7_executor: &'a mut Stage7Executor, + pub stage7_openings: Option<&'a [stage7::Stage7OpeningInputValue]>, +} + +#[derive(Clone, Copy, Debug)] +pub struct JoltProverPrograms { + pub commitment: &'static commitment_stage::CommitmentProverProgramPlan, + pub stage1_outer: &'static stage1::Stage1CpuProgramPlan, + pub stage2: &'static stage2::Stage2CpuProgramPlan, + pub stage3: &'static stage3::Stage3CpuProgramPlan, + pub stage4: &'static stage4::Stage4CpuProgramPlan, + pub stage5: &'static stage5::Stage5CpuProgramPlan, + pub stage6: &'static stage6::Stage6CpuProgramPlan, + pub stage7: &'static stage7::Stage7CpuProgramPlan, + pub stage8: &'static stage8_stage::Stage8EvaluationProgramPlan, +} + +pub fn default_prover_programs() -> JoltProverPrograms { + JoltProverPrograms { + commitment: &commitment_stage::COMMITMENT_PROGRAM, + stage1_outer: &stage1_outer_stage::STAGE1_PROGRAM, + stage2: &stage2_stage::STAGE2_PROGRAM, + stage3: &stage3_stage::STAGE3_PROGRAM, + stage4: &stage4_stage::STAGE4_PROGRAM, + stage5: &stage5_stage::STAGE5_PROGRAM, + stage6: &stage6_stage::STAGE6_PROGRAM, + stage7: &stage7_stage::STAGE7_PROGRAM, + stage8: &stage8_stage::STAGE8_PROGRAM, + } +} + +#[derive(Clone, Debug)] +pub struct JoltProverArtifacts { + pub commitment: commitment_stage::CommitmentArtifacts, + pub stage1_outer: stage1::Stage1ExecutionArtifacts, + pub stage2: stage2::Stage2ExecutionArtifacts, + pub stage3: stage3::Stage3ExecutionArtifacts, + pub stage4: stage4::Stage4ExecutionArtifacts, + pub stage5: stage5::Stage5ExecutionArtifacts, + pub stage6: stage6::Stage6ExecutionArtifacts, + pub stage7: stage7::Stage7ExecutionArtifacts, +} + +#[derive(Debug)] +pub enum JoltProveError { + Commitment(commitment_stage::CommitmentPhaseError), + Stage1Outer(stage1::Stage1KernelError), + Stage2(stage2::Stage2KernelError), + Stage3(stage3::Stage3KernelError), + Stage4(stage4::Stage4KernelError), + Stage5(stage5::Stage5KernelError), + Stage6(stage6::Stage6KernelError), + Stage7(stage7::Stage7KernelError), + Evaluation(JoltEvaluationProveError), +} + +#[derive(Debug)] +pub enum JoltEvaluationProveError { + MissingOracle { oracle: &'static str }, + MissingOpeningHint { oracle: &'static str }, + MissingStageEval { stage: &'static str, eval: &'static str }, + MissingStage7RaEval, + MissingStage7EvaluationPoint, + InvalidPointLength { + artifact: &'static str, + expected: usize, + actual: usize, + }, + TargetSizeOverflow { num_vars: usize }, +} + +#[derive(Debug)] +pub enum JoltOpeningInputError { + MissingOpeningClaim { stage: &'static str, source_claim: &'static str }, + MissingStage6OpeningClaim { source_claim: &'static str }, + UnsupportedOpeningInputSource { stage: &'static str, symbol: &'static str, source_stage: &'static str }, + UnsupportedStage7InputSource { symbol: &'static str, source_stage: &'static str }, + InvalidPointLength { + symbol: &'static str, + expected: usize, + actual: usize, + }, +} + +impl From for JoltProveError { + fn from(error: commitment_stage::CommitmentPhaseError) -> Self { + Self::Commitment(error) + } +} + +impl From for JoltProveError { + fn from(error: stage1::Stage1KernelError) -> Self { + Self::Stage1Outer(error) + } +} + +impl From for JoltProveError { + fn from(error: stage2::Stage2KernelError) -> Self { + Self::Stage2(error) + } +} + +impl From for JoltProveError { + fn from(error: stage3::Stage3KernelError) -> Self { + Self::Stage3(error) + } +} + +impl From for JoltProveError { + fn from(error: stage4::Stage4KernelError) -> Self { + Self::Stage4(error) + } +} + +impl From for JoltProveError { + fn from(error: stage5::Stage5KernelError) -> Self { + Self::Stage5(error) + } +} + +impl From for JoltProveError { + fn from(error: stage6::Stage6KernelError) -> Self { + Self::Stage6(error) + } +} + +impl From for JoltProveError { + fn from(error: stage7::Stage7KernelError) -> Self { + Self::Stage7(error) + } +} + +impl From for JoltProveError { + fn from(error: JoltEvaluationProveError) -> Self { + Self::Evaluation(error) + } +} + +pub fn prove_jolt( + inputs: JoltProverInputs<'_, CommitmentInputs, Stage1OuterExecutor, Stage2Executor, Stage3Executor, Stage4Executor, Stage5Executor, Stage6Executor, Stage7Executor>, + transcript: &mut T, +) -> Result<(JoltProof, JoltProverArtifacts), JoltProveError> +where + CommitmentInputs: commitment_stage::CommitmentInputProvider, + Stage1OuterExecutor: stage1::Stage1KernelExecutor, + Stage2Executor: stage2::Stage2KernelExecutor, + Stage3Executor: stage3::Stage3KernelExecutor, + Stage4Executor: stage4::Stage4KernelExecutor, + Stage5Executor: stage5::Stage5KernelExecutor, + Stage6Executor: stage6::Stage6KernelExecutor, + Stage7Executor: stage7::Stage7KernelExecutor, + T: Transcript, +{ + prove_jolt_with_programs(inputs, default_prover_programs(), transcript) +} + +pub fn prove_jolt_with_programs( + inputs: JoltProverInputs<'_, CommitmentInputs, Stage1OuterExecutor, Stage2Executor, Stage3Executor, Stage4Executor, Stage5Executor, Stage6Executor, Stage7Executor>, + programs: JoltProverPrograms, + transcript: &mut T, +) -> Result<(JoltProof, JoltProverArtifacts), JoltProveError> +where + CommitmentInputs: commitment_stage::CommitmentInputProvider, + Stage1OuterExecutor: stage1::Stage1KernelExecutor, + Stage2Executor: stage2::Stage2KernelExecutor, + Stage3Executor: stage3::Stage3KernelExecutor, + Stage4Executor: stage4::Stage4KernelExecutor, + Stage5Executor: stage5::Stage5KernelExecutor, + Stage6Executor: stage6::Stage6KernelExecutor, + Stage7Executor: stage7::Stage7KernelExecutor, + T: Transcript, +{ + let _prove_span = tracing::info_span!("bolt.prove").entered(); + let _commitment_span = tracing::info_span!("bolt.commitment").entered(); + let commitment = commitment_stage::prove_commitment_phase_with_program( + programs.commitment, inputs.commitment_inputs, + inputs.prover_setup, + transcript, + )?; + drop(_commitment_span); + let _stage1_outer_span = tracing::info_span!("bolt.stage1").entered(); + let stage1_outer = stage1_outer_stage::prove_stage1_outer_with_program(programs.stage1_outer, inputs.stage1_outer_executor, transcript)?; + drop(_stage1_outer_span); + let _stage2_span = tracing::info_span!("bolt.stage2").entered(); + let stage2 = stage2_stage::execute_stage2_prover_with_program(programs.stage2, inputs.stage2_executor, transcript)?; + drop(_stage2_span); + let _stage3_span = tracing::info_span!("bolt.stage3").entered(); + let stage3 = stage3_stage::execute_stage3_prover_with_program(programs.stage3, inputs.stage3_executor, transcript)?; + drop(_stage3_span); + let _stage4_span = tracing::info_span!("bolt.stage4").entered(); + let stage4 = stage4_stage::execute_stage4_prover_with_program(programs.stage4, inputs.stage4_executor, transcript)?; + drop(_stage4_span); + let _stage5_span = tracing::info_span!("bolt.stage5").entered(); + let stage5 = stage5_stage::execute_stage5_prover_with_program(programs.stage5, inputs.stage5_executor, transcript)?; + drop(_stage5_span); + let _stage6_span = tracing::info_span!("bolt.stage6").entered(); + let stage6 = stage6_stage::execute_stage6_prover_with_program(programs.stage6, inputs.stage6_executor, transcript)?; + drop(_stage6_span); + let _stage7_span = tracing::info_span!("bolt.stage7").entered(); + let stage7 = stage7_stage::execute_stage7_prover_with_program(programs.stage7, inputs.stage7_executor, transcript)?; + drop(_stage7_span); + let evaluation = if let Some(stage7_openings) = inputs.stage7_openings { + let _stage8_span = tracing::info_span!("bolt.stage8").entered(); + let _evaluate_span = tracing::info_span!("bolt.evaluate").entered(); + Some(prove_jolt_evaluation_proof( + programs.stage8, + inputs.commitment_inputs, + inputs.prover_setup, + &commitment, + &stage6, + &stage7, + stage7_openings, + transcript, + )?) + } else { + None + }; + + let proof = JoltProof { + commitments: commitment.commitments.clone(), + stage1_outer: stage1_outer_proof(&stage1_outer), + stage2: stage2_proof(&stage2), + stage3: stage3_proof(&stage3), + stage4: stage4_proof(&stage4), + stage5: stage5_proof(&stage5), + stage6: stage6_proof(&stage6), + stage7: stage7_proof(&stage7), + evaluation, + }; + let artifacts = JoltProverArtifacts { + commitment, + stage1_outer, + stage2, + stage3, + stage4, + stage5, + stage6, + stage7, + }; + Ok((proof, artifacts)) +} + +pub fn prove_jolt_evaluation_proof( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + commitment_inputs: &mut I, + prover_setup: &DoryProverSetup, + commitments: &commitment_stage::CommitmentArtifacts, + stage6: &stage6::Stage6ExecutionArtifacts, + stage7: &stage7::Stage7ExecutionArtifacts, + stage7_openings: &[stage7::Stage7OpeningInputValue], + transcript: &mut T, +) -> Result +where + I: commitment_stage::CommitmentInputProvider, + T: Transcript, +{ + let _claims_span = tracing::info_span!("bolt.evaluate.claims").entered(); + let (sumcheck_address_point, stage7_values) = stage7_claim_values(program, stage7)?; + let address_point = reverse_point(&sumcheck_address_point); + let (opening_point, log_t) = + stage7_evaluation_opening_point(program, &address_point, stage7_openings)?; + let lagrange_factor = EqPolynomial::::zero_selector(&address_point); + let claims = evaluation_claims(program, stage6, &stage7_values, lagrange_factor)?; + drop(_claims_span); + + let _rlc_span = tracing::info_span!("bolt.evaluate.rlc_claims").entered(); + append_rlc_claims(transcript, &claims); + let gamma_powers = gamma_powers(transcript, claims.len()); + let joint_claim = claims + .iter() + .zip(&gamma_powers) + .map(|(claim, gamma)| claim.value * *gamma) + .sum(); + drop(_rlc_span); + let _materialize_span = + tracing::info_span!("bolt.evaluate.materialize_joint_polynomial").entered(); + let joint_evals = materialize_joint_polynomial( + commitment_inputs, + &claims, + &gamma_powers, + log_t, + opening_point.len(), + )?; + drop(_materialize_span); + let joint_poly = Polynomial::new(joint_evals); + let _hint_span = tracing::info_span!("bolt.evaluate.joint_opening_hint").entered(); + let joint_hint = joint_opening_hint(commitments, &claims, &gamma_powers)?; + drop(_hint_span); + let _dory_open_span = tracing::info_span!("bolt.evaluate.dory_open").entered(); + let joint_opening_proof = ::open( + &joint_poly, + &opening_point, + joint_claim, + prover_setup, + Some(joint_hint), + transcript, + ); + drop(_dory_open_span); + let _bind_span = tracing::info_span!("bolt.evaluate.bind_opening_inputs").entered(); + ::bind_opening_inputs( + transcript, + &opening_point, + &joint_claim, + ); + drop(_bind_span); + Ok(JoltEvaluationProof { joint_opening_proof }) +} + +struct EvaluationClaim { + oracle: &'static str, + source_stage: &'static str, + value: Fr, +} + +fn stage6_eval_claim( + artifacts: &stage6::Stage6ExecutionArtifacts, + eval_name: &'static str, +) -> Result { + for output in &artifacts.sumchecks { + if let Some(eval) = output.evals.iter().find(|eval| eval.name == eval_name) { + return Ok(eval.value); + } + } + Err(JoltEvaluationProveError::MissingStageEval { + stage: "stage6", + eval: eval_name, + }) +} + +fn evaluation_claims( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + stage6: &stage6::Stage6ExecutionArtifacts, + stage7_values: &std::collections::BTreeMap<&'static str, Fr>, + lagrange_factor: Fr, +) -> Result, JoltEvaluationProveError> { + let mut claims = Vec::with_capacity(program.opening_claims.len()); + for plan in program.opening_claims { + let value = match plan.source_stage { + "stage6" => stage6_eval_claim(stage6, plan.source_claim)? * lagrange_factor, + "stage7" => *stage7_values.get(plan.source_claim).ok_or( + JoltEvaluationProveError::MissingStageEval { + stage: plan.source_stage, + eval: plan.source_claim, + }, + )?, + _ => { + return Err(JoltEvaluationProveError::MissingStageEval { + stage: plan.source_stage, + eval: plan.source_claim, + }); + } + }; + claims.push(EvaluationClaim { + oracle: plan.oracle, + source_stage: plan.source_stage, + value, + }); + } + Ok(claims) +} + +fn stage7_claim_values( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + artifacts: &stage7::Stage7ExecutionArtifacts, +) -> Result<(Vec, std::collections::BTreeMap<&'static str, Fr>), JoltEvaluationProveError> { + let stage7_plans = program + .opening_claims + .iter() + .filter(|plan| plan.source_stage == "stage7") + .collect::>(); + for output in &artifacts.sumchecks { + let mut values = std::collections::BTreeMap::new(); + for plan in &stage7_plans { + if let Some(eval) = output.evals.iter().find(|eval| eval.name == plan.source_claim) { + let _ = values.insert(plan.source_claim, eval.value); + } + } + if values.len() == stage7_plans.len() { + return Ok((output.point.clone(), values)); + } + } + Err(JoltEvaluationProveError::MissingStage7RaEval) +} + +fn reverse_point(point: &[Fr]) -> Vec { + point.iter().rev().copied().collect() +} + +fn stage7_evaluation_opening_point( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + address_point: &[Fr], + stage7_openings: &[stage7::Stage7OpeningInputValue], +) -> Result<(Vec, usize), JoltEvaluationProveError> { + let cycle_source_symbol = program.evaluation_point_source.source_claim; + let cycle_source = stage7_openings + .iter() + .find(|input| input.symbol == cycle_source_symbol) + .ok_or(JoltEvaluationProveError::MissingStage7EvaluationPoint)?; + if cycle_source.point.len() < address_point.len() { + return Err(JoltEvaluationProveError::InvalidPointLength { + artifact: cycle_source_symbol, + expected: address_point.len(), + actual: cycle_source.point.len(), + }); + } + let cycle_len = cycle_source.point.len() - address_point.len(); + let mut point = Vec::with_capacity(cycle_source.point.len()); + point.extend_from_slice(address_point); + point.extend_from_slice(&cycle_source.point[address_point.len()..]); + Ok((point, cycle_len)) +} + +fn append_rlc_claims(transcript: &mut T, claims: &[EvaluationClaim]) +where + T: Transcript, +{ + transcript.append(&LabelWithCount(b"rlc_claims", claims.len() as u64)); + for claim in claims { + claim.value.append_to_transcript(transcript); + } +} + +fn gamma_powers(transcript: &mut T, count: usize) -> Vec +where + T: Transcript, +{ + let gamma = transcript.challenge(); + let mut powers = Vec::with_capacity(count); + let mut power = Fr::from_u64(1); + for _ in 0..count { + powers.push(power); + power *= gamma; + } + powers +} + +fn materialize_joint_polynomial( + commitment_inputs: &mut I, + claims: &[EvaluationClaim], + gamma_powers: &[Fr], + log_t: usize, + main_num_vars: usize, +) -> Result, JoltEvaluationProveError> +where + I: commitment_stage::CommitmentInputProvider, +{ + let trace_len = target_len(log_t)?; + let main_len = target_len(main_num_vars)?; + let mut joint = vec![Fr::from_u64(0); main_len]; + for (claim, gamma) in claims.iter().zip(gamma_powers) { + if claim.source_stage == "stage6" { + add_oracle_scaled(commitment_inputs, &mut joint, claim.oracle, log_t, trace_len, *gamma)?; + } else { + add_oracle_scaled( + commitment_inputs, + &mut joint, + claim.oracle, + main_num_vars, + main_len, + *gamma, + )?; + } + } + Ok(joint) +} + +fn add_oracle_scaled( + commitment_inputs: &mut I, + joint: &mut [Fr], + oracle: &'static str, + num_vars: usize, + limit: usize, + scalar: Fr, +) -> Result<(), JoltEvaluationProveError> +where + I: commitment_stage::CommitmentInputProvider, +{ + if commitment_inputs.add_scaled_to_joint(oracle, joint, num_vars, limit, scalar) { + return Ok(()); + } + let target_len = target_len(num_vars)?; + let data = commitment_inputs + .materialize_with_num_vars(oracle, num_vars) + .ok_or(JoltEvaluationProveError::MissingOracle { oracle })?; + if data.len() > target_len { + return Err(JoltEvaluationProveError::InvalidPointLength { + artifact: oracle, + expected: target_len, + actual: data.len(), + }); + } + let zero = Fr::from_u64(0); + let one = Fr::from_u64(1); + let len = limit.min(joint.len()).min(data.len()); + if len >= 1 << 15 { + joint[..len] + .par_iter_mut() + .zip(data[..len].par_iter()) + .for_each(|(dst, value)| { + if *value == zero { + return; + } + if *value == one { + *dst += scalar; + } else { + *dst += *value * scalar; + } + }); + } else { + for (dst, value) in joint.iter_mut().take(len).zip(data.iter()) { + if *value == zero { + continue; + } + if *value == one { + *dst += scalar; + } else { + *dst += *value * scalar; + } + } + } + Ok(()) +} + +fn joint_opening_hint( + commitments: &commitment_stage::CommitmentArtifacts, + claims: &[EvaluationClaim], + gamma_powers: &[Fr], +) -> Result { + let mut coefficients = std::collections::BTreeMap::<&'static str, Fr>::new(); + for (claim, gamma) in claims.iter().zip(gamma_powers) { + let coefficient = coefficients.entry(claim.oracle).or_insert(Fr::from_u64(0)); + *coefficient += *gamma; + } + + let mut hints = Vec::with_capacity(coefficients.len()); + let mut scalars = Vec::with_capacity(coefficients.len()); + for (oracle, coefficient) in coefficients { + hints.push(opening_hint_for_oracle(commitments, oracle)?); + scalars.push(coefficient); + } + + Ok(::combine_hints( + hints, &scalars, + )) +} + +fn opening_hint_for_oracle( + commitments: &commitment_stage::CommitmentArtifacts, + oracle: &'static str, +) -> Result { + commitments + .hints + .iter() + .find(|hint| hint.oracle == oracle) + .map(|hint| hint.hint.clone()) + .ok_or(JoltEvaluationProveError::MissingOpeningHint { oracle }) +} + +fn target_len(num_vars: usize) -> Result { + if num_vars >= usize::BITS as usize { + return Err(JoltEvaluationProveError::TargetSizeOverflow { num_vars }); + } + Ok(1usize << num_vars) +} + +pub struct JoltProverStageInputs<'a, CommitmentInputs> { + pub commitment_inputs: &'a mut CommitmentInputs, + pub prover_setup: &'a DoryProverSetup, + pub stage1_outer: stage1::Stage1ProverInputs<'a, Fr>, + pub stage2: stage2::Stage2ProverInputs<'a, Fr>, + pub stage3: stage3::Stage3ProverInputs<'a, Fr>, + pub stage4: stage4::Stage4ProverInputs<'a, Fr>, + pub stage5: stage5::Stage5ProverInputs<'a, Fr>, + pub stage6: stage6::Stage6ProverInputs<'a, Fr>, + pub stage7: stage7::Stage7ProverInputs<'a, Fr>, + pub stage7_openings: Option<&'a [stage7::Stage7OpeningInputValue]>, +} + +pub fn prove_jolt_with_stage_inputs( + inputs: JoltProverStageInputs<'_, CommitmentInputs>, + programs: JoltProverPrograms, + transcript: &mut T, +) -> Result<(JoltProof, JoltProverArtifacts), JoltProveError> +where + CommitmentInputs: commitment_stage::CommitmentInputProvider, + T: Transcript, +{ + let JoltProverStageInputs { + commitment_inputs, + prover_setup, + stage1_outer, + stage2, + stage3, + stage4, + stage5, + stage6, + stage7, + stage7_openings, + } = inputs; + let mut stage1_outer_executor = stage1::Stage1ProverKernelExecutor::new(stage1_outer); + let mut stage2_executor = stage2::Stage2ProverKernelExecutor::new(stage2); + let mut stage3_executor = stage3::Stage3ProverKernelExecutor::new(stage3); + let mut stage4_executor = stage4::Stage4ProverKernelExecutor::new(stage4); + let mut stage5_executor = stage5::Stage5ProverKernelExecutor::new(stage5); + let mut stage6_executor = stage6::Stage6ProverKernelExecutor::new(stage6); + let mut stage7_executor = stage7::Stage7ProverKernelExecutor::new(stage7); + prove_jolt_with_programs( + JoltProverInputs { + commitment_inputs, + prover_setup, + stage1_outer_executor: &mut stage1_outer_executor, + stage2_executor: &mut stage2_executor, + stage3_executor: &mut stage3_executor, + stage4_executor: &mut stage4_executor, + stage5_executor: &mut stage5_executor, + stage6_executor: &mut stage6_executor, + stage7_executor: &mut stage7_executor, + stage7_openings, + }, + programs, + transcript, + ) +} + +pub struct JoltProverWitnessInputs<'a, CommitmentInputs> { + pub commitment_inputs: &'a mut CommitmentInputs, + pub prover_setup: &'a DoryProverSetup, + pub stage1_trace_num_vars: usize, + pub stage1_outer_evaluator: &'a dyn stage1::Stage1OuterRemainingEvaluator, + pub stage2_openings: &'a [stage2::Stage2OpeningInputValue], + pub product_virtual_cycles: &'a [stage2::Stage2ProductVirtualCycle], + pub instruction_lookup_cycles: &'a [stage2::Stage2InstructionLookupCycle], + pub ram: &'a stage2::Stage2RamData<'a>, + pub stage3_openings: &'a [stage3::Stage3OpeningInputValue], + pub stage3_cycles: &'a [stage3::Stage3Cycle], + pub stage4_openings: &'a [stage4::Stage4OpeningInputValue], + pub register_count: usize, + pub trace_len: usize, + pub ram_k: usize, + pub register_accesses: &'a [stage4::Stage4RegisterAccess], + pub stage5_openings: &'a [stage5::Stage5OpeningInputValue], + pub lookup_indices: &'a [u128], + pub lookup_table_indices: &'a [Option], + pub is_interleaved_operands: &'a [bool], + pub ra_virtual_log_k_chunk: usize, + pub stage6_openings: &'a [stage6::Stage6OpeningInputValue], + pub stage6_bytecode_data: stage6::Stage6BytecodeReadRafData<'a, Fr>, + pub stage6_witness_params: Stage6WitnessParams, + pub cycle_inputs: &'a [CycleInput], + pub instruction_ra_virtual_d: usize, + pub stage7_openings: &'a [stage7::Stage7OpeningInputValue], + pub evaluation_openings: Option<&'a [stage7::Stage7OpeningInputValue]>, +} + +pub fn prove_jolt_with_witness_inputs( + inputs: JoltProverWitnessInputs<'_, CommitmentInputs>, + programs: JoltProverPrograms, + transcript: &mut T, +) -> Result<(JoltProof, JoltProverArtifacts), JoltProveError> +where + CommitmentInputs: commitment_stage::CommitmentInputProvider, + T: Transcript, +{ + let _input_span = tracing::info_span!("bolt.prove.inputs").entered(); + let _stage1_input_span = tracing::info_span!("bolt.prove.inputs.stage1").entered(); + let stage1_outer = + stage1_outer_prover_inputs(inputs.stage1_trace_num_vars, inputs.stage1_outer_evaluator); + drop(_stage1_input_span); + let _stage2_input_span = tracing::info_span!("bolt.prove.inputs.stage2").entered(); + let stage2 = stage2_prover_inputs( + inputs.stage2_openings, + inputs.product_virtual_cycles, + inputs.instruction_lookup_cycles, + inputs.ram, + )?; + drop(_stage2_input_span); + let _stage3_input_span = tracing::info_span!("bolt.prove.inputs.stage3").entered(); + let stage3 = stage3_prover_inputs(inputs.stage3_openings, inputs.stage3_cycles); + drop(_stage3_input_span); + let _stage45_witness_span = tracing::info_span!("bolt.prove.inputs.stage45_witness").entered(); + let stage45_witness = stage4::stage4_5_sparse_trace_witness_from_accesses( + inputs.register_accesses, + inputs.ram.accesses, + ); + drop(_stage45_witness_span); + let _stage4_input_span = tracing::info_span!("bolt.prove.inputs.stage4").entered(); + let stage4 = stage4_prover_inputs( + inputs.stage4_openings, + inputs.register_count, + inputs.trace_len, + inputs.ram_k, + inputs.register_accesses, + &stage45_witness, + ); + drop(_stage4_input_span); + let _stage5_input_span = tracing::info_span!("bolt.prove.inputs.stage5").entered(); + let stage5 = stage5_prover_inputs( + inputs.stage5_openings, + inputs.trace_len, + inputs.ram_k, + inputs.register_count, + inputs.lookup_indices, + inputs.lookup_table_indices, + inputs.is_interleaved_operands, + inputs.ra_virtual_log_k_chunk, + &stage45_witness, + ); + drop(_stage5_input_span); + let _stage6_witness_span = tracing::info_span!("bolt.prove.inputs.stage6_witness").entered(); + let stage6_witness = stage6_witness_from_opening_inputs( + inputs.stage6_witness_params, + inputs.cycle_inputs, + inputs.stage6_openings, + ); + let stage6_witness_slices = stage6_witness.slices(); + drop(_stage6_witness_span); + let _stage6_input_span = tracing::info_span!("bolt.prove.inputs.stage6").entered(); + let stage6 = stage6_prover_inputs( + inputs.stage6_openings, + inputs.stage6_bytecode_data, + &stage6_witness, + &stage6_witness_slices, + inputs.instruction_ra_virtual_d, + ); + drop(_stage6_input_span); + let _stage7_input_span = tracing::info_span!("bolt.prove.inputs.stage7").entered(); + let stage7 = stage7_prover_inputs(inputs.stage7_openings, &stage6_witness_slices); + drop(_stage7_input_span); + drop(_input_span); + prove_jolt_with_stage_inputs( + JoltProverStageInputs { + commitment_inputs: inputs.commitment_inputs, + prover_setup: inputs.prover_setup, + stage1_outer, + stage2, + stage3, + stage4, + stage5, + stage6, + stage7, + stage7_openings: inputs.evaluation_openings, + }, + programs, + transcript, + ) +} + +pub fn stage1_outer_prover_inputs( + trace_num_vars: usize, + evaluator: &dyn stage1::Stage1OuterRemainingEvaluator, +) -> stage1::Stage1ProverInputs<'_, Fr> { + stage1::Stage1ProverInputs::empty(trace_num_vars).with_outer_remaining_evaluator(evaluator) +} + +pub fn prove_stage1_outer_inputs_with_program( + program: &'static stage1::Stage1CpuProgramPlan, + inputs: stage1::Stage1ProverInputs<'_, Fr>, + transcript: &mut T, +) -> Result, stage1::Stage1KernelError> +where + T: Transcript, +{ + let mut executor = stage1::Stage1ProverKernelExecutor::new(inputs); + stage1_outer_stage::prove_stage1_outer_with_program(program, &mut executor, transcript) +} + +pub fn prove_stage1_outer_with_witness_inputs( + program: &'static stage1::Stage1CpuProgramPlan, + trace_num_vars: usize, + evaluator: &dyn stage1::Stage1OuterRemainingEvaluator, + transcript: &mut T, +) -> Result, stage1::Stage1KernelError> +where + T: Transcript, +{ + let inputs = stage1_outer_prover_inputs(trace_num_vars, evaluator); + prove_stage1_outer_inputs_with_program(program, inputs, transcript) +} + +pub fn replay_stage1_outer_proof_with_program( + program: &'static stage1::Stage1CpuProgramPlan, + proof: &stage1::Stage1Proof, + transcript: &mut T, +) -> Result, stage1::Stage1KernelError> +where + T: Transcript, +{ + let mut executor = stage1::Stage1VerifierKernelExecutor::new(proof); + stage1::execute_stage1_program( + program, + stage1::Stage1ExecutionMode::Verifier, + &mut executor, + transcript, + ) +} + +pub fn stage1_outer_proof_from_kernel_proof( + proof: &stage1::Stage1Proof, +) -> JoltStageProof { + JoltStageProof { + sumchecks: proof + .sumchecks + .iter() + .map(stage1_outer_sumcheck) + .collect(), + } +} + +pub fn stage2_prover_inputs<'a>( + opening_inputs: &'a [stage2::Stage2OpeningInputValue], + product_virtual_cycles: &'a [stage2::Stage2ProductVirtualCycle], + instruction_lookup_cycles: &'a [stage2::Stage2InstructionLookupCycle], + ram: &'a stage2::Stage2RamData<'a>, +) -> Result, stage2::Stage2KernelError> { + Ok(stage2::Stage2ProverInputs::new(opening_inputs) + .with_product_virtual_witness(product_virtual_cycles)? + .with_instruction_lookup_cycles(instruction_lookup_cycles) + .with_ram_data(ram)) +} + +pub struct JoltStage2RamDataStorage<'a> { + log_k: usize, + start_address: u64, + initial_ram: &'a [u64], + final_ram: &'a [u64], + accesses: Vec, + output_layout: Option, +} + +impl<'a> JoltStage2RamDataStorage<'a> { + pub fn from_kernel(ram: &stage2::Stage2RamData<'a>) -> Self { + Self { + log_k: ram.log_k, + start_address: ram.start_address, + initial_ram: ram.initial_ram, + final_ram: ram.final_ram, + accesses: ram + .accesses + .iter() + .map(|access| JoltStage2RamAccess { + remapped_address: access.remapped_address, + read_value: access.read_value, + write_value: access.write_value, + }) + .collect(), + output_layout: ram.output_layout.map(|layout| JoltStage2RamOutputLayout { + io_start: layout.io_start, + io_end: layout.io_end, + }), + } + } + + pub fn as_input(&self) -> JoltStage2RamData<'_> { + JoltStage2RamData { + log_k: self.log_k, + start_address: self.start_address, + initial_ram: self.initial_ram, + final_ram: self.final_ram, + accesses: &self.accesses, + output_layout: self.output_layout, + } + } +} + +pub fn stage2_verifier_ram_data<'a>( + ram: &stage2::Stage2RamData<'a>, +) -> JoltStage2RamDataStorage<'a> { + JoltStage2RamDataStorage::from_kernel(ram) +} + +pub trait JoltKernelOpeningInput { + fn symbol(&self) -> &'static str; + fn point(&self) -> &[Fr]; + fn eval(&self) -> Fr; +} + +macro_rules! impl_jolt_kernel_opening_input { + ($opening:ty) => { + impl JoltKernelOpeningInput for $opening { + fn symbol(&self) -> &'static str { + self.symbol + } + + fn point(&self) -> &[Fr] { + &self.point + } + + fn eval(&self) -> Fr { + self.eval + } + } + }; +} + +impl_jolt_kernel_opening_input!(stage2::Stage2OpeningInputValue); +impl_jolt_kernel_opening_input!(stage3::Stage3OpeningInputValue); +impl_jolt_kernel_opening_input!(stage4::Stage4OpeningInputValue); + +pub fn verifier_opening_inputs_from_kernel(inputs: &[I]) -> Vec +where + I: JoltKernelOpeningInput, +{ + inputs + .iter() + .map(|input| JoltStageOpeningInputValue { + symbol: input.symbol(), + point: input.point().to_vec(), + eval: input.eval(), + }) + .collect() +} + +pub fn prove_stage2_inputs_with_program( + program: &'static stage2::Stage2CpuProgramPlan, + inputs: stage2::Stage2ProverInputs<'_, Fr>, + transcript: &mut T, +) -> Result, stage2::Stage2KernelError> +where + T: Transcript, +{ + let mut executor = stage2::Stage2ProverKernelExecutor::new(inputs); + stage2_stage::execute_stage2_prover_with_program(program, &mut executor, transcript) +} + +pub fn prove_stage2_with_witness_inputs<'a, T>( + program: &'static stage2::Stage2CpuProgramPlan, + opening_inputs: &'a [stage2::Stage2OpeningInputValue], + product_virtual_cycles: &'a [stage2::Stage2ProductVirtualCycle], + instruction_lookup_cycles: &'a [stage2::Stage2InstructionLookupCycle], + ram: &'a stage2::Stage2RamData<'a>, + transcript: &mut T, +) -> Result, stage2::Stage2KernelError> +where + T: Transcript, +{ + let inputs = stage2_prover_inputs( + opening_inputs, + product_virtual_cycles, + instruction_lookup_cycles, + ram, + )?; + prove_stage2_inputs_with_program(program, inputs, transcript) +} + +pub fn stage2_opening_inputs_from_artifacts( + program: &'static stage2::Stage2CpuProgramPlan, + stage1_artifacts: &stage1::Stage1ExecutionArtifacts, +) -> Result>, JoltOpeningInputError> { + program + .opening_inputs + .iter() + .map(|input| { + let (point, eval) = match input.source_stage { + "stage1" => stage1_opening_claim(stage1_artifacts, input.source_claim)?, + source_stage => { + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource { + stage: "stage2", + symbol: input.symbol, + source_stage, + }); + } + }; + validate_point_len(input.symbol, input.point_arity, point.len())?; + Ok(stage2::Stage2OpeningInputValue { + symbol: input.symbol, + point, + eval, + }) + }) + .collect() +} + +pub fn replay_stage2_proof_with_program<'a, T>( + program: &'static stage2::Stage2CpuProgramPlan, + proof: &'a stage2::Stage2Proof, + opening_inputs: &'a [stage2::Stage2OpeningInputValue], + ram: Option<&'a stage2::Stage2RamData<'a>>, + transcript: &mut T, +) -> Result, stage2::Stage2KernelError> +where + T: Transcript, +{ + let mut executor = stage2::Stage2VerifierKernelExecutor::new(proof, opening_inputs); + if let Some(ram) = ram { + executor = executor.with_ram_data(ram); + } + stage2::execute_stage2_program( + program, + stage2::Stage2ExecutionMode::Verifier, + &mut executor, + transcript, + ) +} + +pub fn stage3_prover_inputs<'a>( + opening_inputs: &'a [stage3::Stage3OpeningInputValue], + cycles: &'a [stage3::Stage3Cycle], +) -> stage3::Stage3ProverInputs<'a, Fr> { + stage3::Stage3ProverInputs::new(opening_inputs).with_cycles(cycles) +} + +pub fn prove_stage3_inputs_with_program( + program: &'static stage3::Stage3CpuProgramPlan, + inputs: stage3::Stage3ProverInputs<'_, Fr>, + transcript: &mut T, +) -> Result, stage3::Stage3KernelError> +where + T: Transcript, +{ + let mut executor = stage3::Stage3ProverKernelExecutor::new(inputs); + stage3_stage::execute_stage3_prover_with_program(program, &mut executor, transcript) +} + +pub fn prove_stage3_with_witness_inputs( + program: &'static stage3::Stage3CpuProgramPlan, + opening_inputs: &[stage3::Stage3OpeningInputValue], + cycles: &[stage3::Stage3Cycle], + transcript: &mut T, +) -> Result, stage3::Stage3KernelError> +where + T: Transcript, +{ + let inputs = stage3_prover_inputs(opening_inputs, cycles); + prove_stage3_inputs_with_program(program, inputs, transcript) +} + +pub fn stage3_opening_inputs_from_artifacts( + program: &'static stage3::Stage3CpuProgramPlan, + stage1_artifacts: &stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts, +) -> Result>, JoltOpeningInputError> { + program + .opening_inputs + .iter() + .map(|input| { + let (point, eval) = match input.source_stage { + "stage1" => stage1_opening_claim(stage1_artifacts, input.source_claim)?, + "stage2" => stage2_opening_claim(stage2_artifacts, input.source_claim)?, + source_stage => { + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource { + stage: "stage3", + symbol: input.symbol, + source_stage, + }); + } + }; + validate_point_len(input.symbol, input.point_arity, point.len())?; + Ok(stage3::Stage3OpeningInputValue { + symbol: input.symbol, + point, + eval, + }) + }) + .collect() +} + +pub fn replay_stage3_proof_with_program( + program: &'static stage3::Stage3CpuProgramPlan, + proof: &stage3::Stage3Proof, + opening_inputs: &[stage3::Stage3OpeningInputValue], + transcript: &mut T, +) -> Result, stage3::Stage3KernelError> +where + T: Transcript, +{ + let mut executor = stage3::Stage3VerifierKernelExecutor::new(proof, opening_inputs); + stage3::execute_stage3_program( + program, + stage3::Stage3ExecutionMode::Verifier, + &mut executor, + transcript, + ) +} + +pub fn stage4_prover_inputs<'a>( + opening_inputs: &'a [stage4::Stage4OpeningInputValue], + register_count: usize, + trace_len: usize, + ram_k: usize, + register_accesses: &'a [stage4::Stage4RegisterAccess], + witness: &'a Stage45SparseTraceWitness, +) -> stage4::Stage4ProverInputs<'a, Fr> { + stage4::Stage4ProverInputs::new(opening_inputs).with_stage45_sparse_trace_witness( + register_count, + trace_len, + ram_k, + register_accesses, + witness, + ) +} + +pub fn prove_stage4_inputs_with_program( + program: &'static stage4::Stage4CpuProgramPlan, + inputs: stage4::Stage4ProverInputs<'_, Fr>, + transcript: &mut T, +) -> Result, stage4::Stage4KernelError> +where + T: Transcript, +{ + let mut executor = stage4::Stage4ProverKernelExecutor::new(inputs); + stage4_stage::execute_stage4_prover_with_program(program, &mut executor, transcript) +} + +pub fn prove_stage4_with_witness_inputs( + program: &'static stage4::Stage4CpuProgramPlan, + opening_inputs: &[stage4::Stage4OpeningInputValue], + register_count: usize, + trace_len: usize, + ram_k: usize, + register_accesses: &[stage4::Stage4RegisterAccess], + witness: &Stage45SparseTraceWitness, + transcript: &mut T, +) -> Result, stage4::Stage4KernelError> +where + T: Transcript, +{ + let inputs = stage4_prover_inputs( + opening_inputs, + register_count, + trace_len, + ram_k, + register_accesses, + witness, + ); + prove_stage4_inputs_with_program(program, inputs, transcript) +} + +pub fn prove_stage4_with_trace_witness_inputs( + program: &'static stage4::Stage4CpuProgramPlan, + opening_inputs: &[stage4::Stage4OpeningInputValue], + register_count: usize, + trace_len: usize, + ram_k: usize, + register_accesses: &[stage4::Stage4RegisterAccess], + ram_accesses: &[stage2::Stage2RamAccess], + transcript: &mut T, +) -> Result, stage4::Stage4KernelError> +where + T: Transcript, +{ + let witness = stage4::stage4_5_sparse_trace_witness_from_accesses( + register_accesses, + ram_accesses, + ); + prove_stage4_with_witness_inputs( + program, + opening_inputs, + register_count, + trace_len, + ram_k, + register_accesses, + &witness, + transcript, + ) +} + +pub fn stage4_opening_inputs_from_artifacts( + program: &'static stage4::Stage4CpuProgramPlan, + initial_ram_state: &[u64], + stage2_artifacts: &stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts, +) -> Result>, JoltOpeningInputError> { + program + .opening_inputs + .iter() + .map(|input| { + let (point, eval) = match input.source_stage { + "stage2" => stage2_opening_claim(stage2_artifacts, input.source_claim)?, + "stage3" => stage3_opening_claim(stage3_artifacts, input.source_claim)?, + "stage4_precomputed" => { + let (point, _) = stage2_opening_claim( + stage2_artifacts, + "stage2.ram_output.opening.RamValFinal", + )?; + stage4_ram_val_init_opening(initial_ram_state, &point) + } + source_stage => { + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource { + stage: "stage4", + symbol: input.symbol, + source_stage, + }); + } + }; + opening_input_value(input.symbol, input.point_arity, point, eval) + }) + .collect() +} + +pub fn replay_stage4_proof_with_program( + program: &'static stage4::Stage4CpuProgramPlan, + proof: &stage4::Stage4Proof, + opening_inputs: &[stage4::Stage4OpeningInputValue], + transcript: &mut T, +) -> Result, stage4::Stage4KernelError> +where + T: Transcript, +{ + let mut executor = stage4::Stage4VerifierKernelExecutor::new(proof, opening_inputs); + stage4::execute_stage4_program( + program, + stage4::Stage4ExecutionMode::Verifier, + &mut executor, + transcript, + ) +} + +pub fn stage5_prover_inputs<'a>( + opening_inputs: &'a [stage5::Stage5OpeningInputValue], + trace_len: usize, + ram_k: usize, + register_count: usize, + lookup_indices: &'a [u128], + lookup_table_indices: &'a [Option], + is_interleaved_operands: &'a [bool], + ra_virtual_log_k_chunk: usize, + witness: &'a Stage45SparseTraceWitness, +) -> stage5::Stage5ProverInputs<'a, Fr> { + stage5::Stage5ProverInputs::new(opening_inputs).with_stage45_sparse_trace_witness( + trace_len, + ram_k, + register_count, + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + ra_virtual_log_k_chunk, + witness, + ) +} + +pub fn prove_stage5_inputs_with_program( + program: &'static stage5::Stage5CpuProgramPlan, + inputs: stage5::Stage5ProverInputs<'_, Fr>, + transcript: &mut T, +) -> Result, stage5::Stage5KernelError> +where + T: Transcript, +{ + let mut executor = stage5::Stage5ProverKernelExecutor::new(inputs); + stage5_stage::execute_stage5_prover_with_program(program, &mut executor, transcript) +} + +pub fn prove_stage5_with_witness_inputs( + program: &'static stage5::Stage5CpuProgramPlan, + opening_inputs: &[stage5::Stage5OpeningInputValue], + trace_len: usize, + ram_k: usize, + register_count: usize, + lookup_indices: &[u128], + lookup_table_indices: &[Option], + is_interleaved_operands: &[bool], + ra_virtual_log_k_chunk: usize, + witness: &Stage45SparseTraceWitness, + transcript: &mut T, +) -> Result, stage5::Stage5KernelError> +where + T: Transcript, +{ + let inputs = stage5_prover_inputs( + opening_inputs, + trace_len, + ram_k, + register_count, + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + ra_virtual_log_k_chunk, + witness, + ); + prove_stage5_inputs_with_program(program, inputs, transcript) +} + +pub fn prove_stage5_with_trace_witness_inputs( + program: &'static stage5::Stage5CpuProgramPlan, + opening_inputs: &[stage5::Stage5OpeningInputValue], + trace_len: usize, + ram_k: usize, + register_count: usize, + lookup_indices: &[u128], + lookup_table_indices: &[Option], + is_interleaved_operands: &[bool], + ra_virtual_log_k_chunk: usize, + register_accesses: &[stage4::Stage4RegisterAccess], + ram_accesses: &[stage2::Stage2RamAccess], + transcript: &mut T, +) -> Result, stage5::Stage5KernelError> +where + T: Transcript, +{ + let witness = stage4::stage4_5_sparse_trace_witness_from_accesses( + register_accesses, + ram_accesses, + ); + prove_stage5_with_witness_inputs( + program, + opening_inputs, + trace_len, + ram_k, + register_count, + lookup_indices, + lookup_table_indices, + is_interleaved_operands, + ra_virtual_log_k_chunk, + &witness, + transcript, + ) +} + +pub fn stage5_opening_inputs_from_artifacts( + program: &'static stage5::Stage5CpuProgramPlan, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts, +) -> Result>, JoltOpeningInputError> { + program + .opening_inputs + .iter() + .map(|input| { + let (point, eval) = match input.source_stage { + "stage2" => stage2_opening_claim(stage2_artifacts, input.source_claim)?, + "stage4" => stage4_opening_claim(stage4_artifacts, input.source_claim)?, + source_stage => { + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource { + stage: "stage5", + symbol: input.symbol, + source_stage, + }); + } + }; + opening_input_value(input.symbol, input.point_arity, point, eval) + }) + .collect() +} + +pub fn stage5_kernel_proof( + artifacts: &stage5::Stage5ExecutionArtifacts, +) -> stage5::Stage5Proof { + stage5::Stage5Proof { + sumchecks: artifacts.sumchecks.clone(), + } +} + +pub fn jolt_proof_through_stage5( + commitments: &[Option], + stage1_artifacts: &stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, +) -> JoltProof { + JoltProof { + commitments: commitments.to_vec(), + stage1_outer: stage1_outer_proof(stage1_artifacts), + stage2: stage2_proof(stage2_artifacts), + stage3: stage3_proof(stage3_artifacts), + stage4: stage4_proof(stage4_artifacts), + stage5: stage5_proof.clone(), + stage6: JoltStageProof::default(), + stage7: JoltStageProof::default(), + evaluation: None, + } +} + +pub fn jolt_proof_through_stage6( + commitments: &[Option], + stage1_artifacts: &stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, + stage6_proof: &JoltStageProof, +) -> JoltProof { + let mut proof = jolt_proof_through_stage5( + commitments, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + ); + proof.stage6 = stage6_proof.clone(); + proof +} + +pub fn jolt_proof_through_stage7( + commitments: &[Option], + stage1_artifacts: &stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts, + stage5_proof: &JoltStageProof, + stage6_proof: &JoltStageProof, + stage7_proof: &JoltStageProof, +) -> JoltProof { + let mut proof = jolt_proof_through_stage6( + commitments, + stage1_artifacts, + stage2_artifacts, + stage3_artifacts, + stage4_artifacts, + stage5_proof, + stage6_proof, + ); + proof.stage7 = stage7_proof.clone(); + proof +} + +pub fn replay_stage5_proof_with_program( + program: &'static stage5::Stage5CpuProgramPlan, + proof: &stage5::Stage5Proof, + opening_inputs: &[stage5::Stage5OpeningInputValue], + transcript: &mut T, +) -> Result, stage5::Stage5KernelError> +where + T: Transcript, +{ + let mut executor = stage5::Stage5ProofCarryingKernelExecutor::new(proof, opening_inputs); + stage5_stage::execute_stage5_prover_with_program(program, &mut executor, transcript) +} + +pub fn stage6_witness_from_opening_inputs( + params: Stage6WitnessParams, + cycle_inputs: &[CycleInput], + opening_inputs: &[stage6::Stage6OpeningInputValue], +) -> Stage6WitnessPolynomials { + stage6::stage6_witness_from_opening_inputs(params, cycle_inputs, opening_inputs) +} + +pub fn stage6_bytecode_read_raf_data_from_witness_entries( + entries: &[WitnessStage6BytecodeEntry], + entry_bytecode_index: usize, + num_lookup_tables: usize, +) -> stage6::Stage6BytecodeReadRafDataStorage { + stage6::Stage6BytecodeReadRafDataStorage::from_witness_entries( + entries, + entry_bytecode_index, + num_lookup_tables, + ) +} + +pub fn stage6_verifier_data_from_witness_entries( + entries: &[WitnessStage6BytecodeEntry], + entry_bytecode_index: usize, + num_lookup_tables: usize, +) -> JoltStage6VerifierData { + JoltStage6VerifierData { + bytecode_read_raf: Some(JoltStage6BytecodeReadRafData { + entries: entries + .iter() + .map(|entry| JoltStage6BytecodeEntry { + address: entry.address, + imm: entry.imm, + circuit_flags: entry.circuit_flags, + rd: entry.rd, + rs1: entry.rs1, + rs2: entry.rs2, + lookup_table: entry.lookup_table, + is_interleaved: entry.is_interleaved, + is_branch: entry.is_branch, + left_is_rs1: entry.left_is_rs1, + left_is_pc: entry.left_is_pc, + right_is_rs2: entry.right_is_rs2, + right_is_imm: entry.right_is_imm, + is_noop: entry.is_noop, + }) + .collect(), + entry_bytecode_index, + num_lookup_tables, + }), + } +} + +pub fn stage6_prover_inputs<'a>( + opening_inputs: &'a [stage6::Stage6OpeningInputValue], + bytecode_data: stage6::Stage6BytecodeReadRafData<'a, Fr>, + witness: &'a Stage6WitnessPolynomials, + slices: &'a Stage6WitnessSlices<'a, Fr>, + instruction_ra_virtual_d: usize, +) -> stage6::Stage6ProverInputs<'a, Fr> { + stage6::Stage6ProverInputs::new(opening_inputs).with_stage6_witness( + bytecode_data, + witness, + slices, + instruction_ra_virtual_d, + ) +} + +pub fn prove_stage6_inputs_with_program( + program: &'static stage6::Stage6CpuProgramPlan, + inputs: stage6::Stage6ProverInputs<'_, Fr>, + transcript: &mut T, +) -> Result, stage6::Stage6KernelError> +where + T: Transcript, +{ + let mut executor = stage6::Stage6ProverKernelExecutor::new(inputs); + stage6_stage::execute_stage6_prover_with_program(program, &mut executor, transcript) +} + +pub fn prove_stage6_with_witness_inputs( + program: &'static stage6::Stage6CpuProgramPlan, + opening_inputs: &[stage6::Stage6OpeningInputValue], + bytecode_data: stage6::Stage6BytecodeReadRafData<'_, Fr>, + witness: &Stage6WitnessPolynomials, + slices: &Stage6WitnessSlices<'_, Fr>, + instruction_ra_virtual_d: usize, + transcript: &mut T, +) -> Result, stage6::Stage6KernelError> +where + T: Transcript, +{ + let inputs = stage6_prover_inputs( + opening_inputs, + bytecode_data, + witness, + slices, + instruction_ra_virtual_d, + ); + prove_stage6_inputs_with_program(program, inputs, transcript) +} + +pub fn prove_stage6_with_trace_witness_inputs( + program: &'static stage6::Stage6CpuProgramPlan, + opening_inputs: &[stage6::Stage6OpeningInputValue], + bytecode_data: stage6::Stage6BytecodeReadRafData<'_, Fr>, + witness_params: Stage6WitnessParams, + cycle_inputs: &[CycleInput], + instruction_ra_virtual_d: usize, + transcript: &mut T, +) -> Result, stage6::Stage6KernelError> +where + T: Transcript, +{ + let witness = stage6_witness_from_opening_inputs(witness_params, cycle_inputs, opening_inputs); + let slices = witness.slices(); + prove_stage6_with_witness_inputs( + program, + opening_inputs, + bytecode_data, + &witness, + &slices, + instruction_ra_virtual_d, + transcript, + ) +} + +pub fn stage6_opening_inputs_from_artifacts( + program: &'static stage6::Stage6CpuProgramPlan, + stage1_artifacts: &stage1::Stage1ExecutionArtifacts, + stage2_artifacts: &stage2::Stage2ExecutionArtifacts, + stage3_artifacts: &stage3::Stage3ExecutionArtifacts, + stage4_artifacts: &stage4::Stage4ExecutionArtifacts, + stage5_artifacts: &stage5::Stage5ExecutionArtifacts, +) -> Result>, JoltOpeningInputError> { + program + .opening_inputs + .iter() + .map(|input| { + let (point, eval) = match input.source_stage { + "stage1" => stage1_opening_claim(stage1_artifacts, input.source_claim)?, + "stage2" => stage2_opening_claim(stage2_artifacts, input.source_claim)?, + "stage3" => stage3_opening_claim(stage3_artifacts, input.source_claim)?, + "stage4" => stage4_opening_claim(stage4_artifacts, input.source_claim)?, + "stage5" => stage5_opening_claim(stage5_artifacts, input.source_claim)?, + source_stage => { + return Err(JoltOpeningInputError::UnsupportedOpeningInputSource { + stage: "stage6", + symbol: input.symbol, + source_stage, + }); + } + }; + opening_input_value(input.symbol, input.point_arity, point, eval) + }) + .collect() +} + +pub fn stage6_kernel_proof(proof: &JoltStageProof) -> stage6::Stage6Proof { + stage6::Stage6Proof { + sumchecks: proof + .sumchecks + .iter() + .map(stage6_kernel_sumcheck_output) + .collect(), + } +} + +fn stage6_kernel_sumcheck_output( + output: &JoltSumcheckOutput, +) -> stage6::Stage6SumcheckOutput { + stage6::Stage6SumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage6_kernel_eval).collect(), + opening_claims: Vec::new(), + proof: output.proof.clone(), + } +} + +fn stage6_kernel_eval(eval: &JoltNamedEval) -> stage6::Stage6NamedEval { + stage6::Stage6NamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + +pub fn stage6_execution_artifacts( + artifacts: &stage6::Stage6ExecutionArtifacts, +) -> JoltStageExecutionArtifacts { + JoltStageExecutionArtifacts { + challenge_vectors: artifacts + .challenge_vectors + .iter() + .map(|challenge| JoltStageChallengeVector { + symbol: challenge.symbol, + values: challenge.values.clone(), + }) + .collect(), + sumchecks: stage6_proof(artifacts).sumchecks, + opening_batches: Vec::new(), + } +} + +pub fn replay_stage6_proof_with_program<'a, T>( + program: &'static stage6::Stage6CpuProgramPlan, + proof: &'a stage6::Stage6Proof, + opening_inputs: &'a [stage6::Stage6OpeningInputValue], + bytecode_data: Option>, + transcript: &mut T, +) -> Result, stage6::Stage6KernelError> +where + T: Transcript, +{ + let mut executor = stage6::Stage6ProofCarryingKernelExecutor::new(proof, opening_inputs); + if let Some(bytecode_data) = bytecode_data { + executor = executor.with_bytecode_read_raf_data(bytecode_data); + } + stage6_stage::execute_stage6_prover_with_program(program, &mut executor, transcript) +} + +pub fn stage7_prover_inputs<'a>( + opening_inputs: &'a [stage7::Stage7OpeningInputValue], + slices: &'a Stage6WitnessSlices<'a, Fr>, +) -> stage7::Stage7ProverInputs<'a, Fr> { + stage7::Stage7ProverInputs::new(opening_inputs).with_stage6_witness_indices(slices) +} + +pub fn prove_stage7_inputs_with_program( + program: &'static stage7::Stage7CpuProgramPlan, + inputs: stage7::Stage7ProverInputs<'_, Fr>, + transcript: &mut T, +) -> Result, stage7::Stage7KernelError> +where + T: Transcript, +{ + let mut executor = stage7::Stage7ProverKernelExecutor::new(inputs); + stage7_stage::execute_stage7_prover_with_program(program, &mut executor, transcript) +} + +pub fn prove_stage7_with_witness_inputs( + program: &'static stage7::Stage7CpuProgramPlan, + opening_inputs: &[stage7::Stage7OpeningInputValue], + slices: &Stage6WitnessSlices<'_, Fr>, + transcript: &mut T, +) -> Result, stage7::Stage7KernelError> +where + T: Transcript, +{ + let inputs = stage7_prover_inputs(opening_inputs, slices); + prove_stage7_inputs_with_program(program, inputs, transcript) +} + +pub fn prove_stage7_with_trace_witness_inputs( + program: &'static stage7::Stage7CpuProgramPlan, + opening_inputs: &[stage7::Stage7OpeningInputValue], + witness_params: Stage6WitnessParams, + cycle_inputs: &[CycleInput], + stage6_openings: &[stage6::Stage6OpeningInputValue], + transcript: &mut T, +) -> Result, stage7::Stage7KernelError> +where + T: Transcript, +{ + let witness = stage6_witness_from_opening_inputs(witness_params, cycle_inputs, stage6_openings); + let slices = witness.slices(); + prove_stage7_with_witness_inputs(program, opening_inputs, &slices, transcript) +} + +pub fn stage7_kernel_proof(proof: &JoltStageProof) -> stage7::Stage7Proof { + stage7::Stage7Proof { + sumchecks: proof + .sumchecks + .iter() + .map(stage7_kernel_sumcheck_output) + .collect(), + } +} + +fn stage7_kernel_sumcheck_output( + output: &JoltSumcheckOutput, +) -> stage7::Stage7SumcheckOutput { + stage7::Stage7SumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage7_kernel_eval).collect(), + opening_claims: Vec::new(), + proof: output.proof.clone(), + } +} + +fn stage7_kernel_eval(eval: &JoltNamedEval) -> stage7::Stage7NamedEval { + stage7::Stage7NamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + +pub fn stage7_execution_artifacts( + artifacts: &stage7::Stage7ExecutionArtifacts, +) -> JoltStageExecutionArtifacts { + JoltStageExecutionArtifacts { + challenge_vectors: artifacts + .challenge_vectors + .iter() + .map(|challenge| JoltStageChallengeVector { + symbol: challenge.symbol, + values: challenge.values.clone(), + }) + .collect(), + sumchecks: stage7_proof(artifacts).sumchecks, + opening_batches: Vec::new(), + } +} + +pub fn replay_stage7_proof_with_program( + program: &'static stage7::Stage7CpuProgramPlan, + proof: &stage7::Stage7Proof, + opening_inputs: &[stage7::Stage7OpeningInputValue], + transcript: &mut T, +) -> Result, stage7::Stage7KernelError> +where + T: Transcript, +{ + let mut executor = stage7::Stage7ProofCarryingKernelExecutor::new(proof, opening_inputs); + stage7_stage::execute_stage7_prover_with_program(program, &mut executor, transcript) +} + +pub fn stage7_opening_inputs_from_stage6_artifacts( + artifacts: &stage6::Stage6ExecutionArtifacts, +) -> Result>, JoltOpeningInputError> { + stage7_opening_inputs_from_stage6_artifacts_with_program(&stage7_stage::STAGE7_PROGRAM, artifacts) +} + +pub fn stage7_opening_inputs_from_stage6_artifacts_with_program( + program: &'static stage7::Stage7CpuProgramPlan, + artifacts: &stage6::Stage6ExecutionArtifacts, +) -> Result>, JoltOpeningInputError> { + program + .opening_inputs + .iter() + .map(|input| { + let (point, eval) = stage6_opening_claim(artifacts, input.symbol, input.source_stage, input.source_claim, input.point_arity)?; + Ok(stage7::Stage7OpeningInputValue { + symbol: input.symbol, + point, + eval, + }) + }) + .collect() +} + +fn stage6_opening_claim( + artifacts: &stage6::Stage6ExecutionArtifacts, + symbol: &'static str, + source_stage: &'static str, + source_claim: &'static str, + point_arity: usize, +) -> Result<(Vec, Fr), JoltOpeningInputError> { + if source_stage != "stage6" { + return Err(JoltOpeningInputError::UnsupportedStage7InputSource { + symbol, + source_stage, + }); + } + let opening = artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .ok_or(JoltOpeningInputError::MissingStage6OpeningClaim { source_claim })?; + if opening.point.len() != point_arity { + return Err(JoltOpeningInputError::InvalidPointLength { + symbol, + expected: point_arity, + actual: opening.point.len(), + }); + } + Ok((opening.point.clone(), opening.eval)) +} + +fn opening_input_value( + symbol: &'static str, + point_arity: usize, + point: Vec, + eval: Fr, +) -> Result, JoltOpeningInputError> { + validate_point_len(symbol, point_arity, point.len())?; + Ok(stage4::Stage4OpeningInputValue { + symbol, + point, + eval, + }) +} + +fn validate_point_len( + symbol: &'static str, + expected: usize, + actual: usize, +) -> Result<(), JoltOpeningInputError> { + if actual != expected { + return Err(JoltOpeningInputError::InvalidPointLength { + symbol, + expected, + actual, + }); + } + Ok(()) +} + +fn stage1_opening_claim( + artifacts: &stage1::Stage1ExecutionArtifacts, + source_claim: &'static str, +) -> Result<(Vec, Fr), JoltOpeningInputError> { + let opening = artifacts.opening_value(source_claim).ok_or( + JoltOpeningInputError::MissingOpeningClaim { + stage: "stage1", + source_claim, + }, + )?; + Ok((opening.point.clone(), opening.eval)) +} + +fn stage2_opening_claim( + artifacts: &stage2::Stage2ExecutionArtifacts, + source_claim: &'static str, +) -> Result<(Vec, Fr), JoltOpeningInputError> { + artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .map(|opening| (opening.point.clone(), opening.eval)) + .ok_or(JoltOpeningInputError::MissingOpeningClaim { + stage: "stage2", + source_claim, + }) +} + +fn stage3_opening_claim( + artifacts: &stage3::Stage3ExecutionArtifacts, + source_claim: &'static str, +) -> Result<(Vec, Fr), JoltOpeningInputError> { + artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .map(|opening| (opening.point.clone(), opening.eval)) + .ok_or(JoltOpeningInputError::MissingOpeningClaim { + stage: "stage3", + source_claim, + }) +} + +fn stage4_opening_claim( + artifacts: &stage4::Stage4ExecutionArtifacts, + source_claim: &'static str, +) -> Result<(Vec, Fr), JoltOpeningInputError> { + artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .map(|opening| (opening.point.clone(), opening.eval)) + .ok_or(JoltOpeningInputError::MissingOpeningClaim { + stage: "stage4", + source_claim, + }) +} + +fn stage5_opening_claim( + artifacts: &stage5::Stage5ExecutionArtifacts, + source_claim: &'static str, +) -> Result<(Vec, Fr), JoltOpeningInputError> { + artifacts + .opening_claims + .iter() + .find(|opening| opening.symbol == source_claim) + .map(|opening| (opening.point.clone(), opening.eval)) + .ok_or(JoltOpeningInputError::MissingOpeningClaim { + stage: "stage5", + source_claim, + }) +} + +pub fn stage1_outer_proof(artifacts: &stage1::Stage1ExecutionArtifacts) -> JoltStageProof { + JoltStageProof { + sumchecks: artifacts.sumchecks.iter().map(stage1_outer_sumcheck).collect(), + } +} + +fn stage1_outer_sumcheck(output: &stage1::Stage1SumcheckOutput) -> JoltSumcheckOutput { + JoltSumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage1_outer_eval).collect(), + proof: output.proof.clone(), + } +} + +fn stage1_outer_eval(eval: &stage1::Stage1NamedEval) -> JoltNamedEval { + JoltNamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + +pub fn stage2_proof(artifacts: &stage2::Stage2ExecutionArtifacts) -> JoltStageProof { + JoltStageProof { + sumchecks: artifacts.sumchecks.iter().map(stage2_sumcheck).collect(), + } +} + +fn stage2_sumcheck(output: &stage2::Stage2SumcheckOutput) -> JoltSumcheckOutput { + JoltSumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage2_eval).collect(), + proof: output.proof.clone(), + } +} + +fn stage2_eval(eval: &stage2::Stage2NamedEval) -> JoltNamedEval { + JoltNamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + +pub fn stage3_proof(artifacts: &stage3::Stage3ExecutionArtifacts) -> JoltStageProof { + JoltStageProof { + sumchecks: artifacts.sumchecks.iter().map(stage3_sumcheck).collect(), + } +} + +fn stage3_sumcheck(output: &stage3::Stage3SumcheckOutput) -> JoltSumcheckOutput { + JoltSumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage3_eval).collect(), + proof: output.proof.clone(), + } +} + +fn stage3_eval(eval: &stage3::Stage3NamedEval) -> JoltNamedEval { + JoltNamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + +pub fn stage4_proof(artifacts: &stage4::Stage4ExecutionArtifacts) -> JoltStageProof { + JoltStageProof { + sumchecks: artifacts.sumchecks.iter().map(stage4_sumcheck).collect(), + } +} + +fn stage4_sumcheck(output: &stage4::Stage4SumcheckOutput) -> JoltSumcheckOutput { + JoltSumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage4_eval).collect(), + proof: output.proof.clone(), + } +} + +fn stage4_eval(eval: &stage4::Stage4NamedEval) -> JoltNamedEval { + JoltNamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + +pub fn stage5_proof(artifacts: &stage5::Stage5ExecutionArtifacts) -> JoltStageProof { + JoltStageProof { + sumchecks: artifacts.sumchecks.iter().map(stage5_sumcheck).collect(), + } +} + +fn stage5_sumcheck(output: &stage5::Stage5SumcheckOutput) -> JoltSumcheckOutput { + JoltSumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage5_eval).collect(), + proof: output.proof.clone(), + } +} + +fn stage5_eval(eval: &stage5::Stage5NamedEval) -> JoltNamedEval { + JoltNamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + +pub fn stage6_proof(artifacts: &stage6::Stage6ExecutionArtifacts) -> JoltStageProof { + JoltStageProof { + sumchecks: artifacts.sumchecks.iter().map(stage6_sumcheck).collect(), + } +} + +fn stage6_sumcheck(output: &stage6::Stage6SumcheckOutput) -> JoltSumcheckOutput { + JoltSumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage6_eval).collect(), + proof: output.proof.clone(), + } +} + +fn stage6_eval(eval: &stage6::Stage6NamedEval) -> JoltNamedEval { + JoltNamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + +pub fn stage7_proof(artifacts: &stage7::Stage7ExecutionArtifacts) -> JoltStageProof { + JoltStageProof { + sumchecks: artifacts.sumchecks.iter().map(stage7_sumcheck).collect(), + } +} + +fn stage7_sumcheck(output: &stage7::Stage7SumcheckOutput) -> JoltSumcheckOutput { + JoltSumcheckOutput { + driver: output.driver, + point: output.point.clone(), + evals: output.evals.iter().map(stage7_eval).collect(), + proof: output.proof.clone(), + } +} + +fn stage7_eval(eval: &stage7::Stage7NamedEval) -> JoltNamedEval { + JoltNamedEval { + name: eval.name, + oracle: eval.oracle, + value: eval.value, + } +} + diff --git a/crates/jolt-prover/src/stages/commitment.rs b/crates/jolt-prover/src/stages/commitment.rs new file mode 100644 index 0000000000..4eaf31aafe --- /dev/null +++ b/crates/jolt-prover/src/stages/commitment.rs @@ -0,0 +1,1084 @@ +#![allow(dead_code)] + +use std::borrow::Cow; + +use jolt_dory::{DoryCommitment, DoryHint, DoryProverSetup, DoryScheme}; +use jolt_field::{Field, Fr}; +use jolt_openings::CommitmentScheme as _; +use jolt_poly::{EqPolynomial, MultilinearPoly}; +use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript}; +use jolt_witness::{dense_i128_column_to_field, one_hot_chunk_address_major, one_hot_chunk_indices, optional_field_oracle, CommitmentTraceSources}; +use rayon::prelude::*; + +pub type DefaultCommitmentTranscript = Blake2bTranscript; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentParams { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OraclePlan { + pub oracle: &'static str, + pub domain: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentBatchPlan { + pub artifact: &'static str, + pub pcs: &'static str, + pub oracle_family: &'static str, + pub label: &'static str, + pub oracles: &'static [&'static str], + pub count: usize, + pub domain: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OptionalSkipPolicy { + MissingOrZero, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OptionalCommitmentPlan { + pub artifact: &'static str, + pub pcs: &'static str, + pub oracle: &'static str, + pub label: &'static str, + pub domain: &'static str, + pub num_vars: usize, + pub skip_policy: OptionalSkipPolicy, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TranscriptStep { + pub label: &'static str, + pub source: &'static str, + pub optional: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentProverProgramPlan { + pub params: CommitmentParams, + pub oracle_plans: &'static [OraclePlan], + pub batch_plans: &'static [CommitmentBatchPlan], + pub optional_plans: &'static [OptionalCommitmentPlan], + pub transcript_steps: &'static [TranscriptStep], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentRecord { + pub artifact: &'static str, + pub oracle: &'static str, + pub label: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Debug)] +pub struct OracleOpeningHint { + pub oracle: &'static str, + pub hint: DoryHint, +} + +#[derive(Clone, Debug)] +pub struct CommittedOracle { + pub commitment: Option, + pub record: CommitmentRecord, + pub hint: Option, +} + +#[derive(Clone, Debug, Default)] +pub struct CommitmentArtifacts { + pub commitments: Vec>, + pub records: Vec, + pub hints: Vec, +} + +pub trait CommitmentInputProvider { + fn materialize(&mut self, oracle: &'static str) -> Option>; + + fn materialize_with_num_vars( + &mut self, + oracle: &'static str, + _num_vars: usize, + ) -> Option> { + self.materialize(oracle) + } + + fn commit_batch( + &mut self, + _program: &CommitmentProverProgramPlan, + _plan: &CommitmentBatchPlan, + _prover_setup: &DoryProverSetup, + ) -> Option, CommitmentPhaseError>> { + None + } + + fn add_scaled_to_joint( + &mut self, + _oracle: &'static str, + _joint: &mut [Fr], + _num_vars: usize, + _limit: usize, + _scalar: Fr, + ) -> bool { + false + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CommitmentPhaseError { + MissingOracle { oracle: &'static str }, + MissingTranscriptSource { source: &'static str }, + PlanCountMismatch { artifact: &'static str, expected: usize, actual: usize }, + OracleTooLarge { oracle: &'static str, len: usize, target_len: usize }, + TargetSizeOverflow { num_vars: usize }, +} + +pub struct CommitmentOracleInputs<'a> { + pub rd_inc: &'a [i128], + pub ram_inc: &'a [i128], + pub instruction_keys: &'a [Option], + pub ram_addresses: &'a [Option], + pub bytecode_indices: &'a [Option], + pub untrusted_advice: Option<&'a [Fr]>, + pub trusted_advice: Option<&'a [Fr]>, +} + +impl<'a> CommitmentOracleInputs<'a> { + pub fn from_trace_sources( + sources: &'a CommitmentTraceSources, + untrusted_advice: Option<&'a [Fr]>, + trusted_advice: Option<&'a [Fr]>, + ) -> Self { + Self { + rd_inc: &sources.rd_inc, + ram_inc: &sources.ram_inc, + instruction_keys: &sources.instruction_keys, + ram_addresses: &sources.ram_addresses, + bytecode_indices: &sources.bytecode_indices, + untrusted_advice, + trusted_advice, + } + } +} + + +struct AddressMajorOneHotPolynomial { + trace_len: usize, + chunk_domain: usize, + indices: Vec>, + num_vars: usize, +} + +impl AddressMajorOneHotPolynomial { + fn new( + trace_len: usize, + chunk_domain: usize, + indices: Vec>, + num_vars: usize, + ) -> Result { + let active_len = trace_len + .checked_mul(chunk_domain) + .ok_or(CommitmentPhaseError::TargetSizeOverflow { num_vars })?; + let target_len = target_len(num_vars)?; + if active_len > target_len { + return Err(CommitmentPhaseError::OracleTooLarge { + oracle: "one_hot", + len: active_len, + target_len, + }); + } + Ok(Self { + trace_len, + chunk_domain, + indices, + num_vars, + }) + } + + fn nonzero_flat_indices(&self) -> impl Iterator + '_ { + self.indices + .iter() + .enumerate() + .filter_map(|(cycle, &index)| { + index.map(|index| { + let index = index as usize; + assert!( + index < self.chunk_domain, + "one-hot index {index} exceeds domain {}", + self.chunk_domain + ); + index * self.trace_len + cycle + }) + }) + } +} + +impl MultilinearPoly for AddressMajorOneHotPolynomial { + fn num_vars(&self) -> usize { + self.num_vars + } + + fn evaluate(&self, point: &[Fr]) -> Fr { + assert_eq!(point.len(), self.num_vars); + let eq_evals = EqPolynomial::new(point.to_vec()).evaluations(); + self.nonzero_flat_indices() + .fold(Fr::from_u64(0), |acc, flat| acc + eq_evals[flat]) + } + + fn for_each_row(&self, sigma: usize, f: &mut dyn FnMut(usize, &[Fr])) { + let num_cols = 1usize << sigma; + let num_rows = 1usize << (self.num_vars - sigma); + let mut entries = Vec::with_capacity(self.indices.len()); + for flat in self.nonzero_flat_indices() { + entries.push((flat / num_cols, flat % num_cols)); + } + entries.sort_unstable_by_key(|(row, _)| *row); + + let mut cursor = 0; + let mut row = vec![Fr::from_u64(0); num_cols]; + for row_index in 0..num_rows { + row.fill(Fr::from_u64(0)); + while cursor < entries.len() && entries[cursor].0 == row_index { + row[entries[cursor].1] = Fr::from_u64(1); + cursor += 1; + } + f(row_index, &row); + } + } + + fn fold_rows(&self, left: &[Fr], sigma: usize) -> Vec { + let num_cols = 1usize << sigma; + let num_rows = 1usize << (self.num_vars - sigma); + assert_eq!(left.len(), num_rows); + let mut result = vec![Fr::from_u64(0); num_cols]; + for flat in self.nonzero_flat_indices() { + result[flat % num_cols] += left[flat / num_cols]; + } + result + } + + fn is_sparse(&self) -> bool { + true + } + + fn for_each_nonzero(&self, f: &mut dyn FnMut(usize, Fr)) { + for flat in self.nonzero_flat_indices() { + f(flat, Fr::from_u64(1)); + } + } +} + +pub struct SparseCommitmentInputs<'a> { + pub inputs: CommitmentOracleInputs<'a>, + cache: std::collections::BTreeMap<(&'static str, usize), Option>>, + chunk_counts: OneHotChunkCounts, +} + +impl<'a> SparseCommitmentInputs<'a> { + pub fn new(inputs: CommitmentOracleInputs<'a>) -> Self { + Self { + inputs, + cache: std::collections::BTreeMap::new(), + chunk_counts: OneHotChunkCounts::default(), + } + } + + fn update_chunk_counts(&mut self, program: &CommitmentProverProgramPlan) { + let mut counts = OneHotChunkCounts::default(); + let mut instruction = 0; + let mut ram = 0; + let mut bytecode = 0; + for plan in program.oracle_plans { + if plan.oracle.strip_prefix("InstructionRa_").is_some() { + instruction += 1; + } else if plan.oracle.strip_prefix("RamRa_").is_some() { + ram += 1; + } else if plan.oracle.strip_prefix("BytecodeRa_").is_some() { + bytecode += 1; + } + } + if instruction > 0 { + counts.instruction = instruction; + } + if ram > 0 { + counts.ram = ram; + } + if bytecode > 0 { + counts.bytecode = bytecode; + } + self.chunk_counts = counts; + } + + fn one_hot_spec(&self, oracle: &'static str) -> Option { + let (prefix, num_chunks, values, padding) = + if let Some(suffix) = oracle.strip_prefix("InstructionRa_") { + ( + suffix, + self.chunk_counts.instruction, + OneHotSource::InstructionKeys, + Some(0), + ) + } else if let Some(suffix) = oracle.strip_prefix("RamRa_") { + ( + suffix, + self.chunk_counts.ram, + OneHotSource::RamAddresses, + None, + ) + } else if let Some(suffix) = oracle.strip_prefix("BytecodeRa_") { + ( + suffix, + self.chunk_counts.bytecode, + OneHotSource::BytecodeIndices, + Some(0), + ) + } else { + return None; + }; + let chunk = prefix.parse::().ok()?; + if chunk >= num_chunks { + return None; + } + Some(OneHotSpec { + source: values, + chunk, + num_chunks, + chunk_bits: 4, + padding, + }) + } + + fn source_values(&self, source: OneHotSource) -> &'a [Option] { + match source { + OneHotSource::InstructionKeys => self.inputs.instruction_keys, + OneHotSource::RamAddresses => self.inputs.ram_addresses, + OneHotSource::BytecodeIndices => self.inputs.bytecode_indices, + } + } + + fn one_hot_indices( + &self, + oracle: &'static str, + trace_len: usize, + ) -> Option>> { + let spec = self.one_hot_spec(oracle)?; + let values = self.source_values(spec.source); + Some(one_hot_chunk_indices( + values, + spec.chunk, + spec.num_chunks, + spec.chunk_bits, + trace_len, + spec.padding, + )) + } + + #[expect( + clippy::option_option, + reason = "distinguishes missing oracle from present optional oracle" + )] + fn materialize_oracle( + &self, + oracle: &'static str, + num_vars: usize, + ) -> Option>> { + let materialized = match oracle { + "RdInc" => Some(dense_i128_column_to_field( + self.inputs.rd_inc, + target_len(num_vars).ok()?, + )), + "RamInc" => Some(dense_i128_column_to_field( + self.inputs.ram_inc, + target_len(num_vars).ok()?, + )), + "UntrustedAdvice" => optional_field_oracle( + self.inputs.untrusted_advice, + target_len(num_vars).ok()?, + ), + "TrustedAdvice" => { + optional_field_oracle(self.inputs.trusted_advice, target_len(num_vars).ok()?) + } + _ => { + let spec = self.one_hot_spec(oracle)?; + let trace_len = target_len(num_vars.checked_sub(spec.chunk_bits)?).ok()?; + let values = self.source_values(spec.source); + Some(one_hot_chunk_address_major( + values, + spec.chunk, + spec.num_chunks, + spec.chunk_bits, + trace_len, + spec.padding, + )) + } + }; + Some(materialized) + } + + fn commit_oracle( + &self, + program: &CommitmentProverProgramPlan, + oracle: &'static str, + layout_num_vars: usize, + prover_setup: &DoryProverSetup, + ) -> Result<(DoryCommitment, DoryHint), CommitmentPhaseError> { + let oracle_num_vars = oracle_num_vars(program, oracle, layout_num_vars); + if let Some(spec) = self.one_hot_spec(oracle) { + let trace_len = target_len(oracle_num_vars - spec.chunk_bits)?; + let chunk_domain = target_len(spec.chunk_bits)?; + let indices = self + .one_hot_indices(oracle, trace_len) + .ok_or(CommitmentPhaseError::MissingOracle { oracle })?; + let poly = AddressMajorOneHotPolynomial::new( + trace_len, + chunk_domain, + indices, + layout_num_vars, + )?; + let _dory_commit_span = tracing::info_span!("bolt.commitment.dory_commit").entered(); + Ok(DoryScheme::commit(&poly, prover_setup)) + } else { + let data = self + .materialize_oracle(oracle, oracle_num_vars) + .flatten() + .ok_or(CommitmentPhaseError::MissingOracle { oracle })?; + // Pad to layout_num_vars (not oracle_num_vars) so the row-chunked + // commitment has uniform row-count across all oracles in the batch. + // Required for `joint_opening_hint`'s `combine_hints` to produce a + // valid aggregate commitment. + let data = into_padded_oracle(oracle, layout_num_vars, Cow::Owned(data))?; + commit_with_layout(&data, layout_num_vars, prover_setup) + } + } +} + +impl CommitmentInputProvider for SparseCommitmentInputs<'_> { + fn materialize(&mut self, oracle: &'static str) -> Option> { + let num_vars = match oracle { + "RdInc" | "RamInc" | "UntrustedAdvice" | "TrustedAdvice" => 16, + _ if self.one_hot_spec(oracle).is_some() => 20, + _ => return None, + }; + self.materialize_with_num_vars(oracle, num_vars) + } + + fn materialize_with_num_vars( + &mut self, + oracle: &'static str, + num_vars: usize, + ) -> Option> { + if !self.cache.contains_key(&(oracle, num_vars)) { + let materialized = self.materialize_oracle(oracle, num_vars).flatten(); + let _ = self.cache.insert((oracle, num_vars), materialized); + } + self.cache + .get(&(oracle, num_vars)) + .and_then(|values| values.as_ref()) + .map(|values| Cow::Borrowed(values.as_slice())) + } + + fn commit_batch( + &mut self, + program: &CommitmentProverProgramPlan, + plan: &CommitmentBatchPlan, + prover_setup: &DoryProverSetup, + ) -> Option, CommitmentPhaseError>> { + self.update_chunk_counts(program); + Some( + plan.oracles + .par_iter() + .map(|&oracle| { + let oracle_num_vars = oracle_num_vars(program, oracle, plan.num_vars); + let (commitment, hint) = + self.commit_oracle(program, oracle, plan.num_vars, prover_setup)?; + Ok(CommittedOracle { + commitment: Some(commitment), + record: CommitmentRecord { + artifact: plan.artifact, + oracle, + label: plan.label, + num_vars: oracle_num_vars, + }, + hint: Some(OracleOpeningHint { oracle, hint }), + }) + }) + .collect(), + ) + } + + fn add_scaled_to_joint( + &mut self, + oracle: &'static str, + joint: &mut [Fr], + num_vars: usize, + limit: usize, + scalar: Fr, + ) -> bool { + let dense = match oracle { + "RdInc" => Some(self.inputs.rd_inc), + "RamInc" => Some(self.inputs.ram_inc), + _ => None, + }; + if let Some(values) = dense { + let Ok(target_len) = target_len(num_vars) else { + return false; + }; + let len = limit.min(joint.len()).min(values.len()).min(target_len); + for (dst, &value) in joint.iter_mut().take(len).zip(values.iter()) { + if value != 0 { + *dst += Fr::from_i128(value) * scalar; + } + } + return true; + } + + let Some(spec) = self.one_hot_spec(oracle) else { + return false; + }; + let Some(trace_num_vars) = num_vars.checked_sub(spec.chunk_bits) else { + return false; + }; + let Ok(trace_len) = target_len(trace_num_vars) else { + return false; + }; + let Ok(chunk_domain) = target_len(spec.chunk_bits) else { + return false; + }; + let Some(active_len) = trace_len.checked_mul(chunk_domain) else { + return false; + }; + let max_flat = limit.min(joint.len()).min(active_len); + let Some(indices) = self.one_hot_indices(oracle, trace_len) else { + return false; + }; + for (cycle, index) in indices.into_iter().enumerate() { + let Some(index) = index else { + continue; + }; + let flat = index as usize * trace_len + cycle; + if flat < max_flat { + joint[flat] += scalar; + } + } + true + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum OneHotSource { + InstructionKeys, + RamAddresses, + BytecodeIndices, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct OneHotSpec { + source: OneHotSource, + chunk: usize, + num_chunks: usize, + chunk_bits: usize, + padding: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct OneHotChunkCounts { + instruction: usize, + ram: usize, + bytecode: usize, +} + +impl Default for OneHotChunkCounts { + fn default() -> Self { + Self { + instruction: 32, + ram: 4, + bytecode: 3, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct CommitmentOracles { + pub rd_inc: Vec, + pub ram_inc: Vec, + pub instruction_ra_0: Vec, + pub instruction_ra_1: Vec, + pub instruction_ra_2: Vec, + pub instruction_ra_3: Vec, + pub instruction_ra_4: Vec, + pub instruction_ra_5: Vec, + pub instruction_ra_6: Vec, + pub instruction_ra_7: Vec, + pub instruction_ra_8: Vec, + pub instruction_ra_9: Vec, + pub instruction_ra_10: Vec, + pub instruction_ra_11: Vec, + pub instruction_ra_12: Vec, + pub instruction_ra_13: Vec, + pub instruction_ra_14: Vec, + pub instruction_ra_15: Vec, + pub instruction_ra_16: Vec, + pub instruction_ra_17: Vec, + pub instruction_ra_18: Vec, + pub instruction_ra_19: Vec, + pub instruction_ra_20: Vec, + pub instruction_ra_21: Vec, + pub instruction_ra_22: Vec, + pub instruction_ra_23: Vec, + pub instruction_ra_24: Vec, + pub instruction_ra_25: Vec, + pub instruction_ra_26: Vec, + pub instruction_ra_27: Vec, + pub instruction_ra_28: Vec, + pub instruction_ra_29: Vec, + pub instruction_ra_30: Vec, + pub instruction_ra_31: Vec, + pub ram_ra_0: Vec, + pub ram_ra_1: Vec, + pub ram_ra_2: Vec, + pub ram_ra_3: Vec, + pub bytecode_ra_0: Vec, + pub bytecode_ra_1: Vec, + pub bytecode_ra_2: Vec, + pub bytecode_ra_3: Vec, + pub untrusted_advice: Option>, + pub trusted_advice: Option>, +} + +impl CommitmentInputProvider for CommitmentOracles { + fn materialize(&mut self, oracle: &'static str) -> Option> { + match oracle { + "RdInc" => Some(Cow::Borrowed(&self.rd_inc)), + "RamInc" => Some(Cow::Borrowed(&self.ram_inc)), + "InstructionRa_0" => Some(Cow::Borrowed(&self.instruction_ra_0)), + "InstructionRa_1" => Some(Cow::Borrowed(&self.instruction_ra_1)), + "InstructionRa_2" => Some(Cow::Borrowed(&self.instruction_ra_2)), + "InstructionRa_3" => Some(Cow::Borrowed(&self.instruction_ra_3)), + "InstructionRa_4" => Some(Cow::Borrowed(&self.instruction_ra_4)), + "InstructionRa_5" => Some(Cow::Borrowed(&self.instruction_ra_5)), + "InstructionRa_6" => Some(Cow::Borrowed(&self.instruction_ra_6)), + "InstructionRa_7" => Some(Cow::Borrowed(&self.instruction_ra_7)), + "InstructionRa_8" => Some(Cow::Borrowed(&self.instruction_ra_8)), + "InstructionRa_9" => Some(Cow::Borrowed(&self.instruction_ra_9)), + "InstructionRa_10" => Some(Cow::Borrowed(&self.instruction_ra_10)), + "InstructionRa_11" => Some(Cow::Borrowed(&self.instruction_ra_11)), + "InstructionRa_12" => Some(Cow::Borrowed(&self.instruction_ra_12)), + "InstructionRa_13" => Some(Cow::Borrowed(&self.instruction_ra_13)), + "InstructionRa_14" => Some(Cow::Borrowed(&self.instruction_ra_14)), + "InstructionRa_15" => Some(Cow::Borrowed(&self.instruction_ra_15)), + "InstructionRa_16" => Some(Cow::Borrowed(&self.instruction_ra_16)), + "InstructionRa_17" => Some(Cow::Borrowed(&self.instruction_ra_17)), + "InstructionRa_18" => Some(Cow::Borrowed(&self.instruction_ra_18)), + "InstructionRa_19" => Some(Cow::Borrowed(&self.instruction_ra_19)), + "InstructionRa_20" => Some(Cow::Borrowed(&self.instruction_ra_20)), + "InstructionRa_21" => Some(Cow::Borrowed(&self.instruction_ra_21)), + "InstructionRa_22" => Some(Cow::Borrowed(&self.instruction_ra_22)), + "InstructionRa_23" => Some(Cow::Borrowed(&self.instruction_ra_23)), + "InstructionRa_24" => Some(Cow::Borrowed(&self.instruction_ra_24)), + "InstructionRa_25" => Some(Cow::Borrowed(&self.instruction_ra_25)), + "InstructionRa_26" => Some(Cow::Borrowed(&self.instruction_ra_26)), + "InstructionRa_27" => Some(Cow::Borrowed(&self.instruction_ra_27)), + "InstructionRa_28" => Some(Cow::Borrowed(&self.instruction_ra_28)), + "InstructionRa_29" => Some(Cow::Borrowed(&self.instruction_ra_29)), + "InstructionRa_30" => Some(Cow::Borrowed(&self.instruction_ra_30)), + "InstructionRa_31" => Some(Cow::Borrowed(&self.instruction_ra_31)), + "RamRa_0" => Some(Cow::Borrowed(&self.ram_ra_0)), + "RamRa_1" => Some(Cow::Borrowed(&self.ram_ra_1)), + "RamRa_2" => Some(Cow::Borrowed(&self.ram_ra_2)), + "RamRa_3" => Some(Cow::Borrowed(&self.ram_ra_3)), + "BytecodeRa_0" => Some(Cow::Borrowed(&self.bytecode_ra_0)), + "BytecodeRa_1" => Some(Cow::Borrowed(&self.bytecode_ra_1)), + "BytecodeRa_2" => Some(Cow::Borrowed(&self.bytecode_ra_2)), + "BytecodeRa_3" => Some(Cow::Borrowed(&self.bytecode_ra_3)), + "UntrustedAdvice" => self.untrusted_advice.as_deref().map(Cow::Borrowed), + "TrustedAdvice" => self.trusted_advice.as_deref().map(Cow::Borrowed), + _ => None, + } + } +} + +pub fn build_commitment_oracles( + inputs: &CommitmentOracleInputs<'_>, +) -> Result { + Ok(CommitmentOracles { + rd_inc: dense_i128_column_to_field(inputs.rd_inc, target_len(18)?), + ram_inc: dense_i128_column_to_field(inputs.ram_inc, target_len(18)?), + instruction_ra_0: one_hot_chunk_address_major(inputs.instruction_keys, 0, 32, 4, target_len(18)?, Some(0)), + instruction_ra_1: one_hot_chunk_address_major(inputs.instruction_keys, 1, 32, 4, target_len(18)?, Some(0)), + instruction_ra_2: one_hot_chunk_address_major(inputs.instruction_keys, 2, 32, 4, target_len(18)?, Some(0)), + instruction_ra_3: one_hot_chunk_address_major(inputs.instruction_keys, 3, 32, 4, target_len(18)?, Some(0)), + instruction_ra_4: one_hot_chunk_address_major(inputs.instruction_keys, 4, 32, 4, target_len(18)?, Some(0)), + instruction_ra_5: one_hot_chunk_address_major(inputs.instruction_keys, 5, 32, 4, target_len(18)?, Some(0)), + instruction_ra_6: one_hot_chunk_address_major(inputs.instruction_keys, 6, 32, 4, target_len(18)?, Some(0)), + instruction_ra_7: one_hot_chunk_address_major(inputs.instruction_keys, 7, 32, 4, target_len(18)?, Some(0)), + instruction_ra_8: one_hot_chunk_address_major(inputs.instruction_keys, 8, 32, 4, target_len(18)?, Some(0)), + instruction_ra_9: one_hot_chunk_address_major(inputs.instruction_keys, 9, 32, 4, target_len(18)?, Some(0)), + instruction_ra_10: one_hot_chunk_address_major(inputs.instruction_keys, 10, 32, 4, target_len(18)?, Some(0)), + instruction_ra_11: one_hot_chunk_address_major(inputs.instruction_keys, 11, 32, 4, target_len(18)?, Some(0)), + instruction_ra_12: one_hot_chunk_address_major(inputs.instruction_keys, 12, 32, 4, target_len(18)?, Some(0)), + instruction_ra_13: one_hot_chunk_address_major(inputs.instruction_keys, 13, 32, 4, target_len(18)?, Some(0)), + instruction_ra_14: one_hot_chunk_address_major(inputs.instruction_keys, 14, 32, 4, target_len(18)?, Some(0)), + instruction_ra_15: one_hot_chunk_address_major(inputs.instruction_keys, 15, 32, 4, target_len(18)?, Some(0)), + instruction_ra_16: one_hot_chunk_address_major(inputs.instruction_keys, 16, 32, 4, target_len(18)?, Some(0)), + instruction_ra_17: one_hot_chunk_address_major(inputs.instruction_keys, 17, 32, 4, target_len(18)?, Some(0)), + instruction_ra_18: one_hot_chunk_address_major(inputs.instruction_keys, 18, 32, 4, target_len(18)?, Some(0)), + instruction_ra_19: one_hot_chunk_address_major(inputs.instruction_keys, 19, 32, 4, target_len(18)?, Some(0)), + instruction_ra_20: one_hot_chunk_address_major(inputs.instruction_keys, 20, 32, 4, target_len(18)?, Some(0)), + instruction_ra_21: one_hot_chunk_address_major(inputs.instruction_keys, 21, 32, 4, target_len(18)?, Some(0)), + instruction_ra_22: one_hot_chunk_address_major(inputs.instruction_keys, 22, 32, 4, target_len(18)?, Some(0)), + instruction_ra_23: one_hot_chunk_address_major(inputs.instruction_keys, 23, 32, 4, target_len(18)?, Some(0)), + instruction_ra_24: one_hot_chunk_address_major(inputs.instruction_keys, 24, 32, 4, target_len(18)?, Some(0)), + instruction_ra_25: one_hot_chunk_address_major(inputs.instruction_keys, 25, 32, 4, target_len(18)?, Some(0)), + instruction_ra_26: one_hot_chunk_address_major(inputs.instruction_keys, 26, 32, 4, target_len(18)?, Some(0)), + instruction_ra_27: one_hot_chunk_address_major(inputs.instruction_keys, 27, 32, 4, target_len(18)?, Some(0)), + instruction_ra_28: one_hot_chunk_address_major(inputs.instruction_keys, 28, 32, 4, target_len(18)?, Some(0)), + instruction_ra_29: one_hot_chunk_address_major(inputs.instruction_keys, 29, 32, 4, target_len(18)?, Some(0)), + instruction_ra_30: one_hot_chunk_address_major(inputs.instruction_keys, 30, 32, 4, target_len(18)?, Some(0)), + instruction_ra_31: one_hot_chunk_address_major(inputs.instruction_keys, 31, 32, 4, target_len(18)?, Some(0)), + ram_ra_0: one_hot_chunk_address_major(inputs.ram_addresses, 0, 4, 4, target_len(18)?, None), + ram_ra_1: one_hot_chunk_address_major(inputs.ram_addresses, 1, 4, 4, target_len(18)?, None), + ram_ra_2: one_hot_chunk_address_major(inputs.ram_addresses, 2, 4, 4, target_len(18)?, None), + ram_ra_3: one_hot_chunk_address_major(inputs.ram_addresses, 3, 4, 4, target_len(18)?, None), + bytecode_ra_0: one_hot_chunk_address_major(inputs.bytecode_indices, 0, 4, 4, target_len(18)?, Some(0)), + bytecode_ra_1: one_hot_chunk_address_major(inputs.bytecode_indices, 1, 4, 4, target_len(18)?, Some(0)), + bytecode_ra_2: one_hot_chunk_address_major(inputs.bytecode_indices, 2, 4, 4, target_len(18)?, Some(0)), + bytecode_ra_3: one_hot_chunk_address_major(inputs.bytecode_indices, 3, 4, 4, target_len(18)?, Some(0)), + untrusted_advice: optional_field_oracle(inputs.untrusted_advice, target_len(18)?), + trusted_advice: optional_field_oracle(inputs.trusted_advice, target_len(18)?), + }) +} + +pub const COMMITMENT_PARAMS: CommitmentParams = CommitmentParams { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const ORACLE_PLANS: &[OraclePlan] = &[ + OraclePlan { oracle: "RdInc", domain: "jolt.trace_domain", num_vars: 18 }, + OraclePlan { oracle: "RamInc", domain: "jolt.trace_domain", num_vars: 18 }, + OraclePlan { oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "UntrustedAdvice", domain: "jolt.trace_domain", num_vars: 18 }, + OraclePlan { oracle: "TrustedAdvice", domain: "jolt.trace_domain", num_vars: 18 }, +]; +pub const COMMITMENT_BATCH_0_ORACLES: &[&str] = &[ + "RdInc", + "RamInc", + "InstructionRa_0", + "InstructionRa_1", + "InstructionRa_2", + "InstructionRa_3", + "InstructionRa_4", + "InstructionRa_5", + "InstructionRa_6", + "InstructionRa_7", + "InstructionRa_8", + "InstructionRa_9", + "InstructionRa_10", + "InstructionRa_11", + "InstructionRa_12", + "InstructionRa_13", + "InstructionRa_14", + "InstructionRa_15", + "InstructionRa_16", + "InstructionRa_17", + "InstructionRa_18", + "InstructionRa_19", + "InstructionRa_20", + "InstructionRa_21", + "InstructionRa_22", + "InstructionRa_23", + "InstructionRa_24", + "InstructionRa_25", + "InstructionRa_26", + "InstructionRa_27", + "InstructionRa_28", + "InstructionRa_29", + "InstructionRa_30", + "InstructionRa_31", + "RamRa_0", + "RamRa_1", + "RamRa_2", + "RamRa_3", + "BytecodeRa_0", + "BytecodeRa_1", + "BytecodeRa_2", + "BytecodeRa_3", +]; +pub const COMMITMENT_BATCH_PLANS: &[CommitmentBatchPlan] = &[ + CommitmentBatchPlan { artifact: "jolt.main_witness_commitments", pcs: "dory", oracle_family: "jolt.main_witness_polys", label: "commitment", oracles: COMMITMENT_BATCH_0_ORACLES, count: 42, domain: "jolt.main_witness_commit_domain", num_vars: 22 }, +]; +pub const OPTIONAL_COMMITMENT_PLANS: &[OptionalCommitmentPlan] = &[ + OptionalCommitmentPlan { artifact: "jolt.untrusted_advice_commitment", pcs: "dory", oracle: "UntrustedAdvice", label: "untrusted_advice", domain: "jolt.trace_domain", num_vars: 18, skip_policy: OptionalSkipPolicy::MissingOrZero }, + OptionalCommitmentPlan { artifact: "jolt.trusted_advice_commitment", pcs: "dory", oracle: "TrustedAdvice", label: "trusted_advice", domain: "jolt.trace_domain", num_vars: 18, skip_policy: OptionalSkipPolicy::MissingOrZero }, +]; +pub const TRANSCRIPT_PLAN: &[TranscriptStep] = &[ + TranscriptStep { label: "commitment", source: "jolt.main_witness_commitments", optional: false }, + TranscriptStep { label: "untrusted_advice", source: "jolt.untrusted_advice_commitment", optional: true }, + TranscriptStep { label: "trusted_advice", source: "jolt.trusted_advice_commitment", optional: true }, +]; +pub const COMMITMENT_PROGRAM: CommitmentProverProgramPlan = CommitmentProverProgramPlan { + params: COMMITMENT_PARAMS, + oracle_plans: ORACLE_PLANS, + batch_plans: COMMITMENT_BATCH_PLANS, + optional_plans: OPTIONAL_COMMITMENT_PLANS, + transcript_steps: TRANSCRIPT_PLAN, +}; + +pub fn prove_commitment_phase( + inputs: &mut I, + prover_setup: &DoryProverSetup, + transcript: &mut T, +) -> Result +where + I: CommitmentInputProvider, + T: Transcript, +{ + prove_commitment_phase_with_program(&COMMITMENT_PROGRAM, inputs, prover_setup, transcript) +} + +pub fn prove_commitment_phase_with_program( + program: &'static CommitmentProverProgramPlan, + inputs: &mut I, + prover_setup: &DoryProverSetup, + transcript: &mut T, +) -> Result +where + I: CommitmentInputProvider, + T: Transcript, +{ + let mut artifacts = CommitmentArtifacts::default(); + for plan in program.batch_plans { + let _batch_span = tracing::info_span!("bolt.commitment.batch").entered(); + commit_batch(program, inputs, prover_setup, &mut artifacts, plan)?; + } + for plan in program.optional_plans { + let _optional_span = tracing::info_span!("bolt.commitment.optional").entered(); + commit_optional(program, inputs, prover_setup, &mut artifacts, plan)?; + } + absorb_transcript(program, &artifacts, transcript)?; + Ok(artifacts) +} + +fn commit_batch( + program: &CommitmentProverProgramPlan, + inputs: &mut I, + prover_setup: &DoryProverSetup, + artifacts: &mut CommitmentArtifacts, + plan: &CommitmentBatchPlan, +) -> Result<(), CommitmentPhaseError> +where + I: CommitmentInputProvider, +{ + if plan.count != plan.oracles.len() { + return Err(CommitmentPhaseError::PlanCountMismatch { + artifact: plan.artifact, + expected: plan.count, + actual: plan.oracles.len(), + }); + } + if let Some(committed) = inputs.commit_batch(program, plan, prover_setup) { + for committed in committed? { + artifacts.records.push(committed.record); + artifacts.commitments.push(committed.commitment); + if let Some(hint) = committed.hint { + artifacts.hints.push(hint); + } + } + return Ok(()); + } + for &oracle in plan.oracles { + let data = inputs + .materialize_with_num_vars(oracle, oracle_num_vars(program, oracle, plan.num_vars)) + .ok_or(CommitmentPhaseError::MissingOracle { oracle })?; + let oracle_num_vars = oracle_num_vars(program, oracle, plan.num_vars); + let data = into_padded_oracle(oracle, plan.num_vars, data)?; + let (commitment, hint) = commit_with_layout(&data, plan.num_vars, prover_setup)?; + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle, + label: plan.label, + num_vars: oracle_num_vars, + }); + artifacts.commitments.push(Some(commitment)); + artifacts.hints.push(OracleOpeningHint { oracle, hint }); + } + Ok(()) +} + +fn commit_optional( + program: &CommitmentProverProgramPlan, + inputs: &mut I, + prover_setup: &DoryProverSetup, + artifacts: &mut CommitmentArtifacts, + plan: &OptionalCommitmentPlan, +) -> Result<(), CommitmentPhaseError> +where + I: CommitmentInputProvider, +{ + let Some(data) = inputs.materialize_with_num_vars(plan.oracle, plan.num_vars) else { + return push_skipped_optional(program, artifacts, plan); + }; + if should_skip_optional(plan.skip_policy, data.as_ref()) { + return push_skipped_optional(program, artifacts, plan); + } + let data = into_padded_oracle(plan.oracle, plan.num_vars, data)?; + let (commitment, hint) = commit_with_layout(&data, plan.num_vars, prover_setup)?; + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle: plan.oracle, + label: plan.label, + num_vars: oracle_num_vars(program, plan.oracle, plan.num_vars), + }); + artifacts.commitments.push(Some(commitment)); + artifacts.hints.push(OracleOpeningHint { + oracle: plan.oracle, + hint, + }); + Ok(()) +} + +fn push_skipped_optional( + program: &CommitmentProverProgramPlan, + artifacts: &mut CommitmentArtifacts, + plan: &OptionalCommitmentPlan, +) -> Result<(), CommitmentPhaseError> { + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle: plan.oracle, + label: plan.label, + num_vars: oracle_num_vars(program, plan.oracle, plan.num_vars), + }); + artifacts.commitments.push(None); + Ok(()) +} + +fn should_skip_optional(policy: OptionalSkipPolicy, data: &[Fr]) -> bool { + match policy { + OptionalSkipPolicy::MissingOrZero => data.iter().all(|value| *value == Fr::from_u64(0)), + } +} + +fn into_padded_oracle( + oracle: &'static str, + num_vars: usize, + data: Cow<'_, [Fr]>, +) -> Result, CommitmentPhaseError> { + let target_len = target_len(num_vars)?; + if data.len() > target_len { + return Err(CommitmentPhaseError::OracleTooLarge { + oracle, + len: data.len(), + target_len, + }); + } + let mut data = data.into_owned(); + data.resize(target_len, Fr::from_u64(0)); + Ok(data) +} + +fn oracle_num_vars( + program: &CommitmentProverProgramPlan, + oracle: &'static str, + fallback: usize, +) -> usize { + program + .oracle_plans + .iter() + .find(|plan| plan.oracle == oracle) + .map_or(fallback, |plan| plan.num_vars) +} + +fn commit_with_layout( + data: &[Fr], + layout_num_vars: usize, + prover_setup: &DoryProverSetup, +) -> Result<(DoryCommitment, DoryHint), CommitmentPhaseError> { + let row_len = target_len(layout_num_vars.div_ceil(2))?; + let _dory_commit_span = tracing::info_span!("bolt.commitment.dory_commit").entered(); + Ok(DoryScheme::commit_evaluations_with_row_len( + data, + row_len, + prover_setup, + )) +} + +fn target_len(num_vars: usize) -> Result { + if num_vars >= usize::BITS as usize { + return Err(CommitmentPhaseError::TargetSizeOverflow { num_vars }); + } + Ok(1usize << num_vars) +} + +fn absorb_transcript( + program: &CommitmentProverProgramPlan, + artifacts: &CommitmentArtifacts, + transcript: &mut T, +) -> Result<(), CommitmentPhaseError> +where + T: Transcript, +{ + for step in program.transcript_steps { + let mut appended = false; + for (record, commitment) in artifacts.records.iter().zip(&artifacts.commitments) { + if record.artifact != step.source { + continue; + } + if let Some(commitment) = commitment { + transcript.append(&LabelWithCount(step.label.as_bytes(), commitment.serialized_len())); + commitment.append_to_transcript(transcript); + appended = true; + } + } + if !step.optional && !appended { + return Err(CommitmentPhaseError::MissingTranscriptSource { + source: step.source, + }); + } + } + Ok(()) +} diff --git a/crates/jolt-prover/src/stages/mod.rs b/crates/jolt-prover/src/stages/mod.rs new file mode 100644 index 0000000000..c8242a4f47 --- /dev/null +++ b/crates/jolt-prover/src/stages/mod.rs @@ -0,0 +1,18 @@ +#[rustfmt::skip] +pub mod commitment; +#[rustfmt::skip] +pub mod stage1_outer; +#[rustfmt::skip] +pub mod stage2; +#[rustfmt::skip] +pub mod stage3; +#[rustfmt::skip] +pub mod stage4; +#[rustfmt::skip] +pub mod stage5; +#[rustfmt::skip] +pub mod stage6; +#[rustfmt::skip] +pub mod stage7; +#[rustfmt::skip] +pub mod stage8; diff --git a/crates/jolt-prover/src/stages/stage1_outer.rs b/crates/jolt-prover/src/stages/stage1_outer.rs new file mode 100644 index 0000000000..6117af09dd --- /dev/null +++ b/crates/jolt-prover/src/stages/stage1_outer.rs @@ -0,0 +1,264 @@ +#![allow(dead_code)] + +use jolt_field::Fr; +use jolt_kernels::stage1::{execute_stage1_program, Stage1CpuProgramPlan, Stage1ExecutionArtifacts, Stage1ExecutionMode, Stage1KernelError, Stage1KernelExecutor, Stage1KernelPlan, Stage1OpeningBatchPlan, Stage1OpeningClaimPlan, Stage1Params, Stage1SumcheckBatchPlan, Stage1SumcheckClaimPlan, Stage1SumcheckDriverPlan, Stage1SumcheckEvalPlan, Stage1SumcheckInstanceResultPlan, Stage1TranscriptSqueezePlan}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage1Transcript = Blake2bTranscript; + +pub const STAGE1_PARAMS: Stage1Params = Stage1Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE1_TRANSCRIPT_SQUEEZES: &[Stage1TranscriptSqueezePlan] = &[ + Stage1TranscriptSqueezePlan { symbol: "stage1.tau", label: "outer_tau", kind: "challenge_vector", count: 20 }, +]; + +pub const STAGE1_KERNELS: &[Stage1KernelPlan] = &[ + Stage1KernelPlan { symbol: "jolt.cpu.stage1.outer.uniskip", relation: "jolt.stage1.outer.uniskip", kind: "sumcheck", backend: "cpu", abi: "jolt_stage1_outer_uniskip" }, + Stage1KernelPlan { symbol: "jolt.cpu.stage1.outer.remaining", relation: "jolt.stage1.outer.remaining", kind: "sumcheck", backend: "cpu", abi: "jolt_stage1_outer_remaining" }, +]; + +pub const STAGE1_SUMCHECK_CLAIM_0_INPUT_OPENINGS: &[&str] = &[]; + +pub const STAGE1_SUMCHECK_CLAIM_1_INPUT_OPENINGS: &[&str] = &["stage1.uniskip.opening"]; + +pub const STAGE1_SUMCHECK_CLAIMS: &[Stage1SumcheckClaimPlan] = &[ + Stage1SumcheckClaimPlan { symbol: "stage1.uniskip.input", stage: "stage1", domain: "jolt.stage1_uniskip_domain", num_rounds: 1, degree: 27, claim: "stage1.zero", kernel: Some("jolt.cpu.stage1.outer.uniskip"), relation: None, claim_value: "stage1.zero", input_openings: STAGE1_SUMCHECK_CLAIM_0_INPUT_OPENINGS }, + Stage1SumcheckClaimPlan { symbol: "stage1.outer_remaining.input", stage: "stage1", domain: "jolt.trace_domain", num_rounds: 19, degree: 3, claim: "stage1.uniskip.eval", kernel: Some("jolt.cpu.stage1.outer.remaining"), relation: None, claim_value: "stage1.uniskip.eval", input_openings: STAGE1_SUMCHECK_CLAIM_1_INPUT_OPENINGS }, +]; +pub const STAGE1_SUMCHECK_BATCH_0_ORDERED_CLAIMS: &[&str] = &["stage1.uniskip.input"]; + +pub const STAGE1_SUMCHECK_BATCH_0_CLAIM_OPERANDS: &[&str] = &["stage1.uniskip.input"]; + +pub const STAGE1_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 1, +]; + +pub const STAGE1_SUMCHECK_BATCH_1_ORDERED_CLAIMS: &[&str] = &["stage1.outer_remaining.input"]; + +pub const STAGE1_SUMCHECK_BATCH_1_CLAIM_OPERANDS: &[&str] = &["stage1.outer_remaining.input"]; + +pub const STAGE1_SUMCHECK_BATCH_1_ROUND_SCHEDULE: &[usize] = &[ + 19, +]; + +pub const STAGE1_SUMCHECK_BATCHES: &[Stage1SumcheckBatchPlan] = &[ + Stage1SumcheckBatchPlan { symbol: "stage1.uniskip.batch", stage: "stage1", proof_slot: "stage1.uni_skip_first_round", policy: "single_instance", count: 1, ordered_claims: STAGE1_SUMCHECK_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE1_SUMCHECK_BATCH_0_CLAIM_OPERANDS, claim_label: "uniskip_claim", round_label: "uniskip_poly", round_schedule: STAGE1_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, + Stage1SumcheckBatchPlan { symbol: "stage1.outer_remaining.batch", stage: "stage1", proof_slot: "stage1.sumcheck", policy: "jolt_core_front_loaded", count: 1, ordered_claims: STAGE1_SUMCHECK_BATCH_1_ORDERED_CLAIMS, claim_operands: STAGE1_SUMCHECK_BATCH_1_CLAIM_OPERANDS, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE1_SUMCHECK_BATCH_1_ROUND_SCHEDULE }, +]; +pub const STAGE1_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 1, +]; + +pub const STAGE1_SUMCHECK_DRIVER_1_ROUND_SCHEDULE: &[usize] = &[ + 19, +]; + +pub const STAGE1_SUMCHECK_DRIVERS: &[Stage1SumcheckDriverPlan] = &[ + Stage1SumcheckDriverPlan { symbol: "stage1.uniskip.sumcheck", stage: "stage1", proof_slot: "stage1.uni_skip_first_round", kernel: Some("jolt.cpu.stage1.outer.uniskip"), relation: None, batch: "stage1.uniskip.batch", policy: "univariate_skip", round_schedule: STAGE1_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "uniskip_claim", round_label: "uniskip_poly", num_rounds: 1, degree: 27 }, + Stage1SumcheckDriverPlan { symbol: "stage1.outer_remaining.sumcheck", stage: "stage1", proof_slot: "stage1.sumcheck", kernel: Some("jolt.cpu.stage1.outer.remaining"), relation: None, batch: "stage1.outer_remaining.batch", policy: "jolt_core_front_loaded", round_schedule: STAGE1_SUMCHECK_DRIVER_1_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 19, degree: 3 }, +]; +pub const STAGE1_SUMCHECK_INSTANCE_RESULTS: &[Stage1SumcheckInstanceResultPlan] = &[ + Stage1SumcheckInstanceResultPlan { symbol: "stage1.uniskip.instance", source: "stage1.uniskip.sumcheck", claim: "stage1.uniskip.input", relation: "jolt.stage1.outer.uniskip", index: 0, point_arity: 1, num_rounds: 1, round_offset: 0, point_order: "as_is", degree: 27 }, + Stage1SumcheckInstanceResultPlan { symbol: "stage1.outer_remaining.instance", source: "stage1.outer_remaining.sumcheck", claim: "stage1.outer_remaining.input", relation: "jolt.stage1.outer.remaining", index: 0, point_arity: 18, num_rounds: 19, round_offset: 1, point_order: "reverse", degree: 3 }, +]; + +pub const STAGE1_SUMCHECK_EVALS: &[Stage1SumcheckEvalPlan] = &[ + Stage1SumcheckEvalPlan { symbol: "stage1.uniskip.eval", source: "stage1.uniskip.sumcheck", name: "stage1.uniskip.eval", index: 0, oracle: "UnivariateSkip" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.LeftInstructionInput", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.LeftInstructionInput", index: 0, oracle: "LeftInstructionInput" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RightInstructionInput", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RightInstructionInput", index: 1, oracle: "RightInstructionInput" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.Product", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.Product", index: 2, oracle: "Product" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.ShouldBranch", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.ShouldBranch", index: 3, oracle: "ShouldBranch" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.PC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.PC", index: 4, oracle: "PC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.UnexpandedPC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.UnexpandedPC", index: 5, oracle: "UnexpandedPC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.Imm", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.Imm", index: 6, oracle: "Imm" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RamAddress", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RamAddress", index: 7, oracle: "RamAddress" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.Rs1Value", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.Rs1Value", index: 8, oracle: "Rs1Value" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.Rs2Value", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.Rs2Value", index: 9, oracle: "Rs2Value" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RdWriteValue", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RdWriteValue", index: 10, oracle: "RdWriteValue" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RamReadValue", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RamReadValue", index: 11, oracle: "RamReadValue" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RamWriteValue", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RamWriteValue", index: 12, oracle: "RamWriteValue" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.LeftLookupOperand", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.LeftLookupOperand", index: 13, oracle: "LeftLookupOperand" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RightLookupOperand", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RightLookupOperand", index: 14, oracle: "RightLookupOperand" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.NextUnexpandedPC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.NextUnexpandedPC", index: 15, oracle: "NextUnexpandedPC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.NextPC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.NextPC", index: 16, oracle: "NextPC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.NextIsVirtual", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.NextIsVirtual", index: 17, oracle: "NextIsVirtual" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.NextIsFirstInSequence", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.NextIsFirstInSequence", index: 18, oracle: "NextIsFirstInSequence" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.LookupOutput", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.LookupOutput", index: 19, oracle: "LookupOutput" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.ShouldJump", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.ShouldJump", index: 20, oracle: "ShouldJump" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagAddOperands", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagAddOperands", index: 21, oracle: "OpFlagAddOperands" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagSubtractOperands", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagSubtractOperands", index: 22, oracle: "OpFlagSubtractOperands" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagMultiplyOperands", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagMultiplyOperands", index: 23, oracle: "OpFlagMultiplyOperands" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagLoad", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagLoad", index: 24, oracle: "OpFlagLoad" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagStore", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagStore", index: 25, oracle: "OpFlagStore" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagJump", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagJump", index: 26, oracle: "OpFlagJump" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagWriteLookupOutputToRD", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagWriteLookupOutputToRD", index: 27, oracle: "OpFlagWriteLookupOutputToRD" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagVirtualInstruction", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagVirtualInstruction", index: 28, oracle: "OpFlagVirtualInstruction" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagAssert", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagAssert", index: 29, oracle: "OpFlagAssert" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagDoNotUpdateUnexpandedPC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagDoNotUpdateUnexpandedPC", index: 30, oracle: "OpFlagDoNotUpdateUnexpandedPC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagAdvice", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagAdvice", index: 31, oracle: "OpFlagAdvice" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagIsCompressed", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagIsCompressed", index: 32, oracle: "OpFlagIsCompressed" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagIsFirstInSequence", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagIsFirstInSequence", index: 33, oracle: "OpFlagIsFirstInSequence" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagIsLastInSequence", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagIsLastInSequence", index: 34, oracle: "OpFlagIsLastInSequence" }, +]; + +pub const STAGE1_OPENING_CLAIMS: &[Stage1OpeningClaimPlan] = &[ + Stage1OpeningClaimPlan { symbol: "stage1.uniskip.opening", oracle: "UnivariateSkip", domain: "jolt.stage1_uniskip_domain", point_arity: 1, claim_kind: "virtual", point_source: "stage1.uniskip.instance", eval_source: "stage1.uniskip.eval" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.LeftInstructionInput" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RightInstructionInput" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.Product", oracle: "Product", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.Product" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.ShouldBranch", oracle: "ShouldBranch", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.ShouldBranch" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.PC", oracle: "PC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.PC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.UnexpandedPC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.Imm", oracle: "Imm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.Imm" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RamAddress", oracle: "RamAddress", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RamAddress" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.Rs1Value" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.Rs2Value" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RdWriteValue", oracle: "RdWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RdWriteValue" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RamReadValue", oracle: "RamReadValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RamReadValue" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RamWriteValue", oracle: "RamWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RamWriteValue" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.LeftLookupOperand", oracle: "LeftLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.LeftLookupOperand" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RightLookupOperand", oracle: "RightLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RightLookupOperand" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.NextUnexpandedPC", oracle: "NextUnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.NextUnexpandedPC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.NextPC", oracle: "NextPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.NextPC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.NextIsVirtual", oracle: "NextIsVirtual", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.NextIsVirtual" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.NextIsFirstInSequence", oracle: "NextIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.NextIsFirstInSequence" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.LookupOutput" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.ShouldJump", oracle: "ShouldJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.ShouldJump" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagAddOperands", oracle: "OpFlagAddOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagAddOperands" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagSubtractOperands", oracle: "OpFlagSubtractOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagSubtractOperands" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagMultiplyOperands", oracle: "OpFlagMultiplyOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagMultiplyOperands" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagLoad", oracle: "OpFlagLoad", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagLoad" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagStore", oracle: "OpFlagStore", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagStore" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagJump", oracle: "OpFlagJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagJump" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagWriteLookupOutputToRD", oracle: "OpFlagWriteLookupOutputToRD", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagWriteLookupOutputToRD" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagVirtualInstruction" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagAssert", oracle: "OpFlagAssert", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagAssert" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagDoNotUpdateUnexpandedPC", oracle: "OpFlagDoNotUpdateUnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagDoNotUpdateUnexpandedPC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagAdvice", oracle: "OpFlagAdvice", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagAdvice" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagIsCompressed", oracle: "OpFlagIsCompressed", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagIsCompressed" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagIsFirstInSequence", oracle: "OpFlagIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagIsFirstInSequence" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagIsLastInSequence", oracle: "OpFlagIsLastInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagIsLastInSequence" }, +]; + +pub const STAGE1_OPENING_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage1.outer_remaining.opening.LeftInstructionInput", + "stage1.outer_remaining.opening.RightInstructionInput", + "stage1.outer_remaining.opening.Product", + "stage1.outer_remaining.opening.ShouldBranch", + "stage1.outer_remaining.opening.PC", + "stage1.outer_remaining.opening.UnexpandedPC", + "stage1.outer_remaining.opening.Imm", + "stage1.outer_remaining.opening.RamAddress", + "stage1.outer_remaining.opening.Rs1Value", + "stage1.outer_remaining.opening.Rs2Value", + "stage1.outer_remaining.opening.RdWriteValue", + "stage1.outer_remaining.opening.RamReadValue", + "stage1.outer_remaining.opening.RamWriteValue", + "stage1.outer_remaining.opening.LeftLookupOperand", + "stage1.outer_remaining.opening.RightLookupOperand", + "stage1.outer_remaining.opening.NextUnexpandedPC", + "stage1.outer_remaining.opening.NextPC", + "stage1.outer_remaining.opening.NextIsVirtual", + "stage1.outer_remaining.opening.NextIsFirstInSequence", + "stage1.outer_remaining.opening.LookupOutput", + "stage1.outer_remaining.opening.ShouldJump", + "stage1.outer_remaining.opening.OpFlagAddOperands", + "stage1.outer_remaining.opening.OpFlagSubtractOperands", + "stage1.outer_remaining.opening.OpFlagMultiplyOperands", + "stage1.outer_remaining.opening.OpFlagLoad", + "stage1.outer_remaining.opening.OpFlagStore", + "stage1.outer_remaining.opening.OpFlagJump", + "stage1.outer_remaining.opening.OpFlagWriteLookupOutputToRD", + "stage1.outer_remaining.opening.OpFlagVirtualInstruction", + "stage1.outer_remaining.opening.OpFlagAssert", + "stage1.outer_remaining.opening.OpFlagDoNotUpdateUnexpandedPC", + "stage1.outer_remaining.opening.OpFlagAdvice", + "stage1.outer_remaining.opening.OpFlagIsCompressed", + "stage1.outer_remaining.opening.OpFlagIsFirstInSequence", + "stage1.outer_remaining.opening.OpFlagIsLastInSequence", +]; + +pub const STAGE1_OPENING_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage1.outer_remaining.opening.LeftInstructionInput", + "stage1.outer_remaining.opening.RightInstructionInput", + "stage1.outer_remaining.opening.Product", + "stage1.outer_remaining.opening.ShouldBranch", + "stage1.outer_remaining.opening.PC", + "stage1.outer_remaining.opening.UnexpandedPC", + "stage1.outer_remaining.opening.Imm", + "stage1.outer_remaining.opening.RamAddress", + "stage1.outer_remaining.opening.Rs1Value", + "stage1.outer_remaining.opening.Rs2Value", + "stage1.outer_remaining.opening.RdWriteValue", + "stage1.outer_remaining.opening.RamReadValue", + "stage1.outer_remaining.opening.RamWriteValue", + "stage1.outer_remaining.opening.LeftLookupOperand", + "stage1.outer_remaining.opening.RightLookupOperand", + "stage1.outer_remaining.opening.NextUnexpandedPC", + "stage1.outer_remaining.opening.NextPC", + "stage1.outer_remaining.opening.NextIsVirtual", + "stage1.outer_remaining.opening.NextIsFirstInSequence", + "stage1.outer_remaining.opening.LookupOutput", + "stage1.outer_remaining.opening.ShouldJump", + "stage1.outer_remaining.opening.OpFlagAddOperands", + "stage1.outer_remaining.opening.OpFlagSubtractOperands", + "stage1.outer_remaining.opening.OpFlagMultiplyOperands", + "stage1.outer_remaining.opening.OpFlagLoad", + "stage1.outer_remaining.opening.OpFlagStore", + "stage1.outer_remaining.opening.OpFlagJump", + "stage1.outer_remaining.opening.OpFlagWriteLookupOutputToRD", + "stage1.outer_remaining.opening.OpFlagVirtualInstruction", + "stage1.outer_remaining.opening.OpFlagAssert", + "stage1.outer_remaining.opening.OpFlagDoNotUpdateUnexpandedPC", + "stage1.outer_remaining.opening.OpFlagAdvice", + "stage1.outer_remaining.opening.OpFlagIsCompressed", + "stage1.outer_remaining.opening.OpFlagIsFirstInSequence", + "stage1.outer_remaining.opening.OpFlagIsLastInSequence", +]; + +pub const STAGE1_OPENING_BATCHES: &[Stage1OpeningBatchPlan] = &[ + Stage1OpeningBatchPlan { symbol: "stage1.outer_remaining.openings", stage: "stage1", proof_slot: "stage1.virtual_openings", policy: "jolt_r1cs_input_order", count: 35, ordered_claims: STAGE1_OPENING_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE1_OPENING_BATCH_0_CLAIM_OPERANDS }, +]; +pub const STAGE1_PROGRAM: Stage1CpuProgramPlan = Stage1CpuProgramPlan { + params: STAGE1_PARAMS, + transcript_squeezes: STAGE1_TRANSCRIPT_SQUEEZES, + kernels: STAGE1_KERNELS, + claims: STAGE1_SUMCHECK_CLAIMS, + batches: STAGE1_SUMCHECK_BATCHES, + drivers: STAGE1_SUMCHECK_DRIVERS, + instance_results: STAGE1_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE1_SUMCHECK_EVALS, + opening_claims: STAGE1_OPENING_CLAIMS, + opening_batches: STAGE1_OPENING_BATCHES, +}; + +pub fn prove_stage1_outer( + executor: &mut E, + transcript: &mut T, +) -> Result, Stage1KernelError> +where + E: Stage1KernelExecutor, + T: Transcript, +{ + prove_stage1_outer_with_program(&STAGE1_PROGRAM, executor, transcript) +} + +pub fn prove_stage1_outer_with_program( + program: &'static Stage1CpuProgramPlan, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage1KernelError> +where + E: Stage1KernelExecutor, + T: Transcript, +{ + execute_stage1_program( + program, + Stage1ExecutionMode::Prover, + executor, + transcript, + ) +} diff --git a/crates/jolt-prover/src/stages/stage2.rs b/crates/jolt-prover/src/stages/stage2.rs new file mode 100644 index 0000000000..c71c070f18 --- /dev/null +++ b/crates/jolt-prover/src/stages/stage2.rs @@ -0,0 +1,402 @@ +#![allow(dead_code)] + +use jolt_field::Fr; +use jolt_kernels::stage2::{execute_stage2_program, Stage2CpuProgramPlan, Stage2ExecutionArtifacts, Stage2ExecutionMode, Stage2FieldConstantPlan, Stage2FieldExprPlan, Stage2KernelError, Stage2KernelExecutor, Stage2KernelPlan, Stage2OpeningBatchPlan, Stage2OpeningClaimPlan, Stage2OpeningInputPlan, Stage2Params, Stage2PointConcatPlan, Stage2PointSlicePlan, Stage2ProgramStepPlan, Stage2SumcheckBatchPlan, Stage2SumcheckClaimPlan, Stage2SumcheckDriverPlan, Stage2SumcheckEvalPlan, Stage2SumcheckInstanceResultPlan, Stage2TranscriptSqueezePlan}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage2Transcript = Blake2bTranscript; + +pub const STAGE2_PARAMS: Stage2Params = Stage2Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE2_PROGRAM_STEPS: &[Stage2ProgramStepPlan] = &[ + Stage2ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage2.product_virtual.tau_high" }, + Stage2ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage2.product_virtual.uniskip.sumcheck" }, + Stage2ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage2.ram_read_write.gamma" }, + Stage2ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage2.instruction_lookup.gamma" }, + Stage2ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage2.ram_output.r_address" }, + Stage2ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage2.sumcheck" }, +]; + +pub const STAGE2_TRANSCRIPT_SQUEEZES: &[Stage2TranscriptSqueezePlan] = &[ + Stage2TranscriptSqueezePlan { symbol: "stage2.product_virtual.tau_high", label: "product_virtual_tau_high", kind: "challenge_scalar", count: 1 }, + Stage2TranscriptSqueezePlan { symbol: "stage2.ram_read_write.gamma", label: "ram_read_write_gamma", kind: "challenge_scalar", count: 1 }, + Stage2TranscriptSqueezePlan { symbol: "stage2.instruction_lookup.gamma", label: "instruction_lookup_gamma", kind: "challenge_scalar", count: 1 }, + Stage2TranscriptSqueezePlan { symbol: "stage2.ram_output.r_address", label: "ram_output_r_address", kind: "challenge_vector", count: 14 }, +]; + +pub const STAGE2_OPENING_INPUTS: &[Stage2OpeningInputPlan] = &[ + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.Product", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.Product", oracle: "Product", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.ShouldBranch", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.ShouldBranch", oracle: "ShouldBranch", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.ShouldJump", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.ShouldJump", oracle: "ShouldJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RamReadValue", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RamReadValue", oracle: "RamReadValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RamWriteValue", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RamWriteValue", oracle: "RamWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.LookupOutput", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.LeftLookupOperand", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.LeftLookupOperand", oracle: "LeftLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RightLookupOperand", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RightLookupOperand", oracle: "RightLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.LeftInstructionInput", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RightInstructionInput", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RamAddress", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RamAddress", oracle: "RamAddress", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, +]; + +pub const STAGE2_FIELD_CONSTANTS: &[Stage2FieldConstantPlan] = &[ + Stage2FieldConstantPlan { symbol: "stage2.ram_output.zero", field: "bn254_fr", value: 0 }, +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_0: &[&str] = &["stage2.product_virtual.tau_high"]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_1: &[&str] = &[ + "stage2.product_virtual.uniskip.weight.Product", + "stage2.input.stage1.Product", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_2: &[&str] = &[ + "stage2.product_virtual.uniskip.weight.ShouldBranch", + "stage2.input.stage1.ShouldBranch", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_3: &[&str] = &[ + "stage2.product_virtual.uniskip.weight.ShouldJump", + "stage2.input.stage1.ShouldJump", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_4: &[&str] = &[ + "stage2.product_virtual.uniskip.term.Product", + "stage2.product_virtual.uniskip.term.ShouldBranch", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_5: &[&str] = &[ + "stage2.product_virtual.uniskip.partial.ProductShouldBranch", + "stage2.product_virtual.uniskip.term.ShouldJump", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_6: &[&str] = &[ + "stage2.ram_read_write.gamma", + "stage2.input.stage1.RamWriteValue", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_7: &[&str] = &[ + "stage2.input.stage1.RamReadValue", + "stage2.ram_read_write.term.RamWriteValue", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_8: &[&str] = &[ + "stage2.instruction_lookup.gamma", + "stage2.instruction_lookup.gamma", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_9: &[&str] = &[ + "stage2.instruction_lookup.gamma2", + "stage2.instruction_lookup.gamma", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_10: &[&str] = &[ + "stage2.instruction_lookup.gamma2", + "stage2.instruction_lookup.gamma2", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_11: &[&str] = &[ + "stage2.instruction_lookup.gamma", + "stage2.input.stage1.LeftLookupOperand", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_12: &[&str] = &[ + "stage2.instruction_lookup.gamma2", + "stage2.input.stage1.RightLookupOperand", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_13: &[&str] = &[ + "stage2.instruction_lookup.gamma3", + "stage2.input.stage1.LeftInstructionInput", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_14: &[&str] = &[ + "stage2.instruction_lookup.gamma4", + "stage2.input.stage1.RightInstructionInput", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_15: &[&str] = &[ + "stage2.input.stage1.LookupOutput", + "stage2.instruction_lookup.term.LeftLookupOperand", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_16: &[&str] = &[ + "stage2.instruction_lookup.partial.LookupOutputLeftOperand", + "stage2.instruction_lookup.term.RightLookupOperand", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_17: &[&str] = &[ + "stage2.instruction_lookup.partial.RightOperand", + "stage2.instruction_lookup.term.LeftInstructionInput", +]; + +pub const STAGE2_FIELD_EXPR_OPERANDS_18: &[&str] = &[ + "stage2.instruction_lookup.partial.LeftInstructionInput", + "stage2.instruction_lookup.term.RightInstructionInput", +]; + +pub const STAGE2_FIELD_EXPRS: &[Stage2FieldExprPlan] = &[ + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.weight.Product", kind: "op", formula: "poly.lagrange_basis_eval:-1:3:0", operand_names: STAGE2_FIELD_EXPR_OPERANDS_0, operands: STAGE2_FIELD_EXPR_OPERANDS_0 }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.weight.ShouldBranch", kind: "op", formula: "poly.lagrange_basis_eval:-1:3:1", operand_names: STAGE2_FIELD_EXPR_OPERANDS_0, operands: STAGE2_FIELD_EXPR_OPERANDS_0 }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.weight.ShouldJump", kind: "op", formula: "poly.lagrange_basis_eval:-1:3:2", operand_names: STAGE2_FIELD_EXPR_OPERANDS_0, operands: STAGE2_FIELD_EXPR_OPERANDS_0 }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.term.Product", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_1, operands: STAGE2_FIELD_EXPR_OPERANDS_1 }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.term.ShouldBranch", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_2, operands: STAGE2_FIELD_EXPR_OPERANDS_2 }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.term.ShouldJump", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_3, operands: STAGE2_FIELD_EXPR_OPERANDS_3 }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.partial.ProductShouldBranch", kind: "op", formula: "field.add", operand_names: STAGE2_FIELD_EXPR_OPERANDS_4, operands: STAGE2_FIELD_EXPR_OPERANDS_4 }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE2_FIELD_EXPR_OPERANDS_5, operands: STAGE2_FIELD_EXPR_OPERANDS_5 }, + Stage2FieldExprPlan { symbol: "stage2.ram_read_write.term.RamWriteValue", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_6, operands: STAGE2_FIELD_EXPR_OPERANDS_6 }, + Stage2FieldExprPlan { symbol: "stage2.ram_read_write.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE2_FIELD_EXPR_OPERANDS_7, operands: STAGE2_FIELD_EXPR_OPERANDS_7 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.gamma2", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_8, operands: STAGE2_FIELD_EXPR_OPERANDS_8 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.gamma3", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_9, operands: STAGE2_FIELD_EXPR_OPERANDS_9 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.gamma4", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_10, operands: STAGE2_FIELD_EXPR_OPERANDS_10 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.term.LeftLookupOperand", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_11, operands: STAGE2_FIELD_EXPR_OPERANDS_11 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.term.RightLookupOperand", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_12, operands: STAGE2_FIELD_EXPR_OPERANDS_12 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.term.LeftInstructionInput", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_13, operands: STAGE2_FIELD_EXPR_OPERANDS_13 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.term.RightInstructionInput", kind: "op", formula: "field.mul", operand_names: STAGE2_FIELD_EXPR_OPERANDS_14, operands: STAGE2_FIELD_EXPR_OPERANDS_14 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.partial.LookupOutputLeftOperand", kind: "op", formula: "field.add", operand_names: STAGE2_FIELD_EXPR_OPERANDS_15, operands: STAGE2_FIELD_EXPR_OPERANDS_15 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.partial.RightOperand", kind: "op", formula: "field.add", operand_names: STAGE2_FIELD_EXPR_OPERANDS_16, operands: STAGE2_FIELD_EXPR_OPERANDS_16 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.partial.LeftInstructionInput", kind: "op", formula: "field.add", operand_names: STAGE2_FIELD_EXPR_OPERANDS_17, operands: STAGE2_FIELD_EXPR_OPERANDS_17 }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.claim_reduction.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE2_FIELD_EXPR_OPERANDS_18, operands: STAGE2_FIELD_EXPR_OPERANDS_18 }, +]; +pub const STAGE2_KERNELS: &[Stage2KernelPlan] = &[ + Stage2KernelPlan { symbol: "jolt.cpu.stage2.product_virtual.uniskip", relation: "jolt.stage2.product_virtual.uniskip", kind: "sumcheck", backend: "cpu", abi: "jolt_stage2_product_virtual_uniskip" }, + Stage2KernelPlan { symbol: "jolt.cpu.stage2.ram.read_write", relation: "jolt.stage2.ram.read_write", kind: "sumcheck", backend: "cpu", abi: "jolt_stage2_ram_read_write" }, + Stage2KernelPlan { symbol: "jolt.cpu.stage2.product_virtual.remainder", relation: "jolt.stage2.product_virtual.remainder", kind: "sumcheck", backend: "cpu", abi: "jolt_stage2_product_virtual_remainder" }, + Stage2KernelPlan { symbol: "jolt.cpu.stage2.instruction_lookup.claim_reduction", relation: "jolt.stage2.instruction_lookup.claim_reduction", kind: "sumcheck", backend: "cpu", abi: "jolt_stage2_instruction_lookup_claim_reduction" }, + Stage2KernelPlan { symbol: "jolt.cpu.stage2.ram.raf_evaluation", relation: "jolt.stage2.ram.raf_evaluation", kind: "sumcheck", backend: "cpu", abi: "jolt_stage2_ram_raf_evaluation" }, + Stage2KernelPlan { symbol: "jolt.cpu.stage2.ram.output_check", relation: "jolt.stage2.ram.output_check", kind: "sumcheck", backend: "cpu", abi: "jolt_stage2_ram_output_check" }, + Stage2KernelPlan { symbol: "jolt.cpu.stage2.batched", relation: "jolt.stage2.batched", kind: "sumcheck", backend: "cpu", abi: "jolt_stage2_batched" }, +]; + +pub const STAGE2_SUMCHECK_CLAIM_0_INPUT_OPENINGS: &[&str] = &[ + "stage2.input.stage1.Product", + "stage2.input.stage1.ShouldBranch", + "stage2.input.stage1.ShouldJump", +]; + +pub const STAGE2_SUMCHECK_CLAIM_1_INPUT_OPENINGS: &[&str] = &[ + "stage2.input.stage1.RamReadValue", + "stage2.input.stage1.RamWriteValue", +]; + +pub const STAGE2_SUMCHECK_CLAIM_2_INPUT_OPENINGS: &[&str] = &["stage2.product_virtual.uniskip.opening.UnivariateSkip"]; + +pub const STAGE2_SUMCHECK_CLAIM_3_INPUT_OPENINGS: &[&str] = &[ + "stage2.input.stage1.LookupOutput", + "stage2.input.stage1.LeftLookupOperand", + "stage2.input.stage1.RightLookupOperand", + "stage2.input.stage1.LeftInstructionInput", + "stage2.input.stage1.RightInstructionInput", +]; + +pub const STAGE2_SUMCHECK_CLAIM_4_INPUT_OPENINGS: &[&str] = &["stage2.input.stage1.RamAddress"]; + +pub const STAGE2_SUMCHECK_CLAIM_5_INPUT_OPENINGS: &[&str] = &[]; + +pub const STAGE2_SUMCHECK_CLAIMS: &[Stage2SumcheckClaimPlan] = &[ + Stage2SumcheckClaimPlan { symbol: "stage2.product_virtual.uniskip.input", stage: "stage2", domain: "jolt.stage2_uniskip_domain", num_rounds: 1, degree: 6, claim: "stage2.product_virtual.weighted_stage1_outputs", kernel: Some("jolt.cpu.stage2.product_virtual.uniskip"), relation: None, claim_value: "stage2.product_virtual.uniskip.claim_expr", input_openings: STAGE2_SUMCHECK_CLAIM_0_INPUT_OPENINGS }, + Stage2SumcheckClaimPlan { symbol: "stage2.ram_read_write.input", stage: "stage2", domain: "jolt.stage2_ram_rw_domain", num_rounds: 32, degree: 3, claim: "stage2.ram_read_write.weighted_values", kernel: Some("jolt.cpu.stage2.ram.read_write"), relation: None, claim_value: "stage2.ram_read_write.claim_expr", input_openings: STAGE2_SUMCHECK_CLAIM_1_INPUT_OPENINGS }, + Stage2SumcheckClaimPlan { symbol: "stage2.product_virtual.remainder.input", stage: "stage2", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage2.product_virtual.uniskip.opening", kernel: Some("jolt.cpu.stage2.product_virtual.remainder"), relation: None, claim_value: "stage2.product_virtual.uniskip.eval.UnivariateSkip", input_openings: STAGE2_SUMCHECK_CLAIM_2_INPUT_OPENINGS }, + Stage2SumcheckClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.input", stage: "stage2", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage2.instruction_lookup.weighted_operands", kernel: Some("jolt.cpu.stage2.instruction_lookup.claim_reduction"), relation: None, claim_value: "stage2.instruction_lookup.claim_reduction.claim_expr", input_openings: STAGE2_SUMCHECK_CLAIM_3_INPUT_OPENINGS }, + Stage2SumcheckClaimPlan { symbol: "stage2.ram_raf.input", stage: "stage2", domain: "jolt.ram_address_domain", num_rounds: 14, degree: 2, claim: "stage2.ram_raf.ram_address", kernel: Some("jolt.cpu.stage2.ram.raf_evaluation"), relation: None, claim_value: "stage2.input.stage1.RamAddress", input_openings: STAGE2_SUMCHECK_CLAIM_4_INPUT_OPENINGS }, + Stage2SumcheckClaimPlan { symbol: "stage2.ram_output.input", stage: "stage2", domain: "jolt.ram_address_domain", num_rounds: 14, degree: 3, claim: "zero", kernel: Some("jolt.cpu.stage2.ram.output_check"), relation: None, claim_value: "stage2.ram_output.zero", input_openings: STAGE2_SUMCHECK_CLAIM_5_INPUT_OPENINGS }, +]; +pub const STAGE2_SUMCHECK_BATCH_0_ORDERED_CLAIMS: &[&str] = &["stage2.product_virtual.uniskip.input"]; + +pub const STAGE2_SUMCHECK_BATCH_0_CLAIM_OPERANDS: &[&str] = &["stage2.product_virtual.uniskip.input"]; + +pub const STAGE2_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 1, +]; + +pub const STAGE2_SUMCHECK_BATCH_1_ORDERED_CLAIMS: &[&str] = &[ + "stage2.ram_read_write.input", + "stage2.product_virtual.remainder.input", + "stage2.instruction_lookup.claim_reduction.input", + "stage2.ram_raf.input", + "stage2.ram_output.input", +]; + +pub const STAGE2_SUMCHECK_BATCH_1_CLAIM_OPERANDS: &[&str] = &[ + "stage2.ram_read_write.input", + "stage2.product_virtual.remainder.input", + "stage2.instruction_lookup.claim_reduction.input", + "stage2.ram_raf.input", + "stage2.ram_output.input", +]; + +pub const STAGE2_SUMCHECK_BATCH_1_ROUND_SCHEDULE: &[usize] = &[ + 18, + 14, +]; + +pub const STAGE2_SUMCHECK_BATCHES: &[Stage2SumcheckBatchPlan] = &[ + Stage2SumcheckBatchPlan { symbol: "stage2.product_virtual.uniskip.batch", stage: "stage2", proof_slot: "stage2.product_virtual.uni_skip_first_round", policy: "single_instance", count: 1, ordered_claims: STAGE2_SUMCHECK_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE2_SUMCHECK_BATCH_0_CLAIM_OPERANDS, claim_label: "uniskip_claim", round_label: "uniskip_poly", round_schedule: STAGE2_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, + Stage2SumcheckBatchPlan { symbol: "stage2.batch", stage: "stage2", proof_slot: "stage2.sumcheck", policy: "jolt_core_stage2_aligned", count: 5, ordered_claims: STAGE2_SUMCHECK_BATCH_1_ORDERED_CLAIMS, claim_operands: STAGE2_SUMCHECK_BATCH_1_CLAIM_OPERANDS, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE2_SUMCHECK_BATCH_1_ROUND_SCHEDULE }, +]; +pub const STAGE2_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 1, +]; + +pub const STAGE2_SUMCHECK_DRIVER_1_ROUND_SCHEDULE: &[usize] = &[ + 18, + 14, +]; + +pub const STAGE2_SUMCHECK_DRIVERS: &[Stage2SumcheckDriverPlan] = &[ + Stage2SumcheckDriverPlan { symbol: "stage2.product_virtual.uniskip.sumcheck", stage: "stage2", proof_slot: "stage2.product_virtual.uni_skip_first_round", kernel: Some("jolt.cpu.stage2.product_virtual.uniskip"), relation: None, batch: "stage2.product_virtual.uniskip.batch", policy: "univariate_skip", round_schedule: STAGE2_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "uniskip_claim", round_label: "uniskip_poly", num_rounds: 1, degree: 6 }, + Stage2SumcheckDriverPlan { symbol: "stage2.sumcheck", stage: "stage2", proof_slot: "stage2.sumcheck", kernel: Some("jolt.cpu.stage2.batched"), relation: None, batch: "stage2.batch", policy: "jolt_core_stage2_aligned", round_schedule: STAGE2_SUMCHECK_DRIVER_1_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 32, degree: 3 }, +]; +pub const STAGE2_SUMCHECK_INSTANCE_RESULTS: &[Stage2SumcheckInstanceResultPlan] = &[ + Stage2SumcheckInstanceResultPlan { symbol: "stage2.product_virtual.uniskip.instance", source: "stage2.product_virtual.uniskip.sumcheck", claim: "stage2.product_virtual.uniskip.input", relation: "jolt.stage2.product_virtual.uniskip", index: 0, point_arity: 1, num_rounds: 1, round_offset: 0, point_order: "as_is", degree: 6 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.ram_read_write.instance", source: "stage2.sumcheck", claim: "stage2.ram_read_write.input", relation: "jolt.stage2.ram.read_write", index: 0, point_arity: 32, num_rounds: 32, round_offset: 0, point_order: "reverse", degree: 3 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.product_virtual.remainder.instance", source: "stage2.sumcheck", claim: "stage2.product_virtual.remainder.input", relation: "jolt.stage2.product_virtual.remainder", index: 1, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 3 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.instruction_lookup.claim_reduction.instance", source: "stage2.sumcheck", claim: "stage2.instruction_lookup.claim_reduction.input", relation: "jolt.stage2.instruction_lookup.claim_reduction", index: 2, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 2 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.ram_raf.instance", source: "stage2.sumcheck", claim: "stage2.ram_raf.input", relation: "jolt.stage2.ram.raf_evaluation", index: 3, point_arity: 14, num_rounds: 14, round_offset: 18, point_order: "reverse", degree: 2 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.ram_output.instance", source: "stage2.sumcheck", claim: "stage2.ram_output.input", relation: "jolt.stage2.ram.output_check", index: 4, point_arity: 14, num_rounds: 14, round_offset: 18, point_order: "reverse", degree: 3 }, +]; + +pub const STAGE2_SUMCHECK_EVALS: &[Stage2SumcheckEvalPlan] = &[ + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.uniskip.eval.UnivariateSkip", source: "stage2.product_virtual.uniskip.sumcheck", name: "stage2.product_virtual.uniskip.eval.UnivariateSkip", index: 0, oracle: "UnivariateSkip" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_read_write.eval.RamVal", source: "stage2.sumcheck", name: "stage2.ram_read_write.eval.RamVal", index: 0, oracle: "RamVal" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_read_write.eval.RamRa", source: "stage2.sumcheck", name: "stage2.ram_read_write.eval.RamRa", index: 1, oracle: "RamRa" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_read_write.eval.RamInc", source: "stage2.sumcheck", name: "stage2.ram_read_write.eval.RamInc", index: 2, oracle: "RamInc" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.LeftInstructionInput", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.LeftInstructionInput", index: 0, oracle: "LeftInstructionInput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.RightInstructionInput", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.RightInstructionInput", index: 1, oracle: "RightInstructionInput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.OpFlagJump", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.OpFlagJump", index: 2, oracle: "OpFlagJump" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.OpFlagWriteLookupOutputToRD", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.OpFlagWriteLookupOutputToRD", index: 3, oracle: "OpFlagWriteLookupOutputToRD" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.LookupOutput", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.LookupOutput", index: 4, oracle: "LookupOutput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.InstructionFlagBranch", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.InstructionFlagBranch", index: 5, oracle: "InstructionFlagBranch" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.NextIsNoop", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.NextIsNoop", index: 6, oracle: "NextIsNoop" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.OpFlagVirtualInstruction", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.OpFlagVirtualInstruction", index: 7, oracle: "OpFlagVirtualInstruction" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.LookupOutput", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.LookupOutput", index: 0, oracle: "LookupOutput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand", index: 1, oracle: "LeftLookupOperand" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand", index: 2, oracle: "RightLookupOperand" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput", index: 3, oracle: "LeftInstructionInput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput", index: 4, oracle: "RightInstructionInput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_raf.eval.RamRa", source: "stage2.sumcheck", name: "stage2.ram_raf.eval.RamRa", index: 0, oracle: "RamRa" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_output.eval.RamValFinal", source: "stage2.sumcheck", name: "stage2.ram_output.eval.RamValFinal", index: 0, oracle: "RamValFinal" }, +]; + +pub const STAGE2_POINT_SLICES: &[Stage2PointSlicePlan] = &[ + Stage2PointSlicePlan { symbol: "stage2.ram_read_write.point.RamInc", source: "stage2.ram_read_write.instance", offset: 14, length: 18, input: "stage2.ram_read_write.instance" }, +]; + +pub const STAGE2_POINT_CONCAT_0_INPUTS: &[&str] = &[ + "stage2.ram_raf.instance", + "stage2.input.stage1.RamAddress", +]; + +pub const STAGE2_POINT_CONCATS: &[Stage2PointConcatPlan] = &[ + Stage2PointConcatPlan { symbol: "stage2.ram_raf.point.RamRa", layout: "address_then_cycle", arity: 32, inputs: STAGE2_POINT_CONCAT_0_INPUTS }, +]; +pub const STAGE2_OPENING_CLAIMS: &[Stage2OpeningClaimPlan] = &[ + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.uniskip.opening.UnivariateSkip", oracle: "UnivariateSkip", domain: "jolt.stage2_uniskip_domain", point_arity: 1, claim_kind: "virtual", point_source: "stage2.product_virtual.uniskip.instance", eval_source: "stage2.product_virtual.uniskip.eval.UnivariateSkip" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_read_write.opening.RamVal", oracle: "RamVal", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage2.ram_read_write.instance", eval_source: "stage2.ram_read_write.eval.RamVal" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_read_write.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage2.ram_read_write.instance", eval_source: "stage2.ram_read_write.eval.RamRa" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_read_write.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage2.ram_read_write.point.RamInc", eval_source: "stage2.ram_read_write.eval.RamInc" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.LeftInstructionInput" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.RightInstructionInput" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.OpFlagJump", oracle: "OpFlagJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.OpFlagJump" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.OpFlagWriteLookupOutputToRD", oracle: "OpFlagWriteLookupOutputToRD", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.OpFlagWriteLookupOutputToRD" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.LookupOutput" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.InstructionFlagBranch", oracle: "InstructionFlagBranch", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.InstructionFlagBranch" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.NextIsNoop", oracle: "NextIsNoop", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.NextIsNoop" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.OpFlagVirtualInstruction" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.LookupOutput" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand", oracle: "LeftLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand", oracle: "RightLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_raf.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage2.ram_raf.point.RamRa", eval_source: "stage2.ram_raf.eval.RamRa" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_output.opening.RamValFinal", oracle: "RamValFinal", domain: "jolt.ram_address_domain", point_arity: 14, claim_kind: "virtual", point_source: "stage2.ram_output.instance", eval_source: "stage2.ram_output.eval.RamValFinal" }, +]; + +pub const STAGE2_OPENING_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage2.ram_read_write.opening.RamVal", + "stage2.ram_read_write.opening.RamRa", + "stage2.ram_read_write.opening.RamInc", + "stage2.product_virtual.remainder.opening.LeftInstructionInput", + "stage2.product_virtual.remainder.opening.RightInstructionInput", + "stage2.product_virtual.remainder.opening.OpFlagJump", + "stage2.product_virtual.remainder.opening.OpFlagWriteLookupOutputToRD", + "stage2.product_virtual.remainder.opening.LookupOutput", + "stage2.product_virtual.remainder.opening.InstructionFlagBranch", + "stage2.product_virtual.remainder.opening.NextIsNoop", + "stage2.product_virtual.remainder.opening.OpFlagVirtualInstruction", + "stage2.instruction_lookup.claim_reduction.opening.LookupOutput", + "stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand", + "stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand", + "stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput", + "stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput", + "stage2.ram_raf.opening.RamRa", + "stage2.ram_output.opening.RamValFinal", +]; + +pub const STAGE2_OPENING_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage2.ram_read_write.opening.RamVal", + "stage2.ram_read_write.opening.RamRa", + "stage2.ram_read_write.opening.RamInc", + "stage2.product_virtual.remainder.opening.LeftInstructionInput", + "stage2.product_virtual.remainder.opening.RightInstructionInput", + "stage2.product_virtual.remainder.opening.OpFlagJump", + "stage2.product_virtual.remainder.opening.OpFlagWriteLookupOutputToRD", + "stage2.product_virtual.remainder.opening.LookupOutput", + "stage2.product_virtual.remainder.opening.InstructionFlagBranch", + "stage2.product_virtual.remainder.opening.NextIsNoop", + "stage2.product_virtual.remainder.opening.OpFlagVirtualInstruction", + "stage2.instruction_lookup.claim_reduction.opening.LookupOutput", + "stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand", + "stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand", + "stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput", + "stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput", + "stage2.ram_raf.opening.RamRa", + "stage2.ram_output.opening.RamValFinal", +]; + +pub const STAGE2_OPENING_BATCHES: &[Stage2OpeningBatchPlan] = &[ + Stage2OpeningBatchPlan { symbol: "stage2.openings", stage: "stage2", proof_slot: "stage2.openings", policy: "jolt_stage2_output_order", count: 18, ordered_claims: STAGE2_OPENING_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE2_OPENING_BATCH_0_CLAIM_OPERANDS }, +]; +pub const STAGE2_PROGRAM: Stage2CpuProgramPlan = Stage2CpuProgramPlan { + params: STAGE2_PARAMS, + steps: STAGE2_PROGRAM_STEPS, + transcript_squeezes: STAGE2_TRANSCRIPT_SQUEEZES, + opening_inputs: STAGE2_OPENING_INPUTS, + field_constants: STAGE2_FIELD_CONSTANTS, + field_exprs: STAGE2_FIELD_EXPRS, + kernels: STAGE2_KERNELS, + claims: STAGE2_SUMCHECK_CLAIMS, + batches: STAGE2_SUMCHECK_BATCHES, + drivers: STAGE2_SUMCHECK_DRIVERS, + instance_results: STAGE2_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE2_SUMCHECK_EVALS, + point_slices: STAGE2_POINT_SLICES, + point_concats: STAGE2_POINT_CONCATS, + opening_claims: STAGE2_OPENING_CLAIMS, + opening_batches: STAGE2_OPENING_BATCHES, +}; + +pub fn execute_stage2_prover( + executor: &mut E, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + E: Stage2KernelExecutor, + T: Transcript, +{ + execute_stage2_prover_with_program(&STAGE2_PROGRAM, executor, transcript) +} + +pub fn execute_stage2_prover_with_program( + program: &'static Stage2CpuProgramPlan, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage2KernelError> +where + E: Stage2KernelExecutor, + T: Transcript, +{ + execute_stage2_program(program, Stage2ExecutionMode::Prover, executor, transcript) +} diff --git a/crates/jolt-prover/src/stages/stage3.rs b/crates/jolt-prover/src/stages/stage3.rs new file mode 100644 index 0000000000..a9d50ccbdd --- /dev/null +++ b/crates/jolt-prover/src/stages/stage3.rs @@ -0,0 +1,351 @@ +#![allow(dead_code)] + +use jolt_field::Fr; +use jolt_kernels::stage3::{execute_stage3_program, Stage3CpuProgramPlan, Stage3ExecutionArtifacts, Stage3ExecutionMode, Stage3FieldConstantPlan, Stage3FieldExprPlan, Stage3KernelError, Stage3KernelExecutor, Stage3KernelPlan, Stage3OpeningBatchPlan, Stage3OpeningClaimEqualityPlan, Stage3OpeningClaimPlan, Stage3OpeningInputPlan, Stage3Params, Stage3PointConcatPlan, Stage3PointSlicePlan, Stage3ProgramStepPlan, Stage3SumcheckBatchPlan, Stage3SumcheckClaimPlan, Stage3SumcheckDriverPlan, Stage3SumcheckEvalPlan, Stage3SumcheckInstanceResultPlan, Stage3TranscriptSqueezePlan}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage3Transcript = Blake2bTranscript; + +pub const STAGE3_PARAMS: Stage3Params = Stage3Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE3_PROGRAM_STEPS: &[Stage3ProgramStepPlan] = &[ + Stage3ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage3.spartan_shift.gamma" }, + Stage3ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage3.instruction_input.gamma" }, + Stage3ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage3.registers.gamma" }, + Stage3ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage3.sumcheck" }, +]; + +pub const STAGE3_TRANSCRIPT_SQUEEZES: &[Stage3TranscriptSqueezePlan] = &[ + Stage3TranscriptSqueezePlan { symbol: "stage3.spartan_shift.gamma", label: "spartan_shift_gamma", kind: "challenge_scalar", count: 1 }, + Stage3TranscriptSqueezePlan { symbol: "stage3.instruction_input.gamma", label: "instruction_input_gamma", kind: "challenge_scalar", count: 1 }, + Stage3TranscriptSqueezePlan { symbol: "stage3.registers.gamma", label: "registers_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE3_OPENING_INPUTS: &[Stage3OpeningInputPlan] = &[ + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.NextUnexpandedPC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.NextUnexpandedPC", oracle: "NextUnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.NextPC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.NextPC", oracle: "NextPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.NextIsVirtual", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.NextIsVirtual", oracle: "NextIsVirtual", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.NextIsFirstInSequence", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.NextIsFirstInSequence", oracle: "NextIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.product_virtual.NextIsNoop", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.NextIsNoop", oracle: "NextIsNoop", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.product_virtual.LeftInstructionInput", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.product_virtual.RightInstructionInput", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.instruction_lookup.LeftInstructionInput", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.instruction_lookup.RightInstructionInput", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.RdWriteValue", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RdWriteValue", oracle: "RdWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.Rs1Value", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.Rs2Value", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, +]; + +pub const STAGE3_FIELD_CONSTANTS: &[Stage3FieldConstantPlan] = &[ + Stage3FieldConstantPlan { symbol: "stage3.field.one", field: "bn254_fr", value: 1 }, +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_0: &[&str] = &["stage3.spartan_shift.gamma"]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_1: &[&str] = &[ + "stage3.spartan_shift.gamma2", + "stage3.spartan_shift.gamma", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_2: &[&str] = &[ + "stage3.spartan_shift.gamma2", + "stage3.spartan_shift.gamma2", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_3: &[&str] = &[ + "stage3.spartan_shift.gamma", + "stage3.input.stage1.NextPC", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_4: &[&str] = &[ + "stage3.spartan_shift.gamma2", + "stage3.input.stage1.NextIsVirtual", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_5: &[&str] = &[ + "stage3.spartan_shift.gamma3", + "stage3.input.stage1.NextIsFirstInSequence", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_6: &[&str] = &[ + "stage3.field.one", + "stage3.input.stage2.product_virtual.NextIsNoop", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_7: &[&str] = &[ + "stage3.spartan_shift.gamma4", + "stage3.spartan_shift.one_minus.NextIsNoop", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_8: &[&str] = &[ + "stage3.input.stage1.NextUnexpandedPC", + "stage3.spartan_shift.term.NextPC", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_9: &[&str] = &[ + "stage3.spartan_shift.partial.NextUnexpandedPCNextPC", + "stage3.spartan_shift.term.NextIsVirtual", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_10: &[&str] = &[ + "stage3.spartan_shift.partial.NextIsVirtual", + "stage3.spartan_shift.term.NextIsFirstInSequence", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_11: &[&str] = &[ + "stage3.spartan_shift.partial.NextIsFirstInSequence", + "stage3.spartan_shift.term.NextIsNoop", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_12: &[&str] = &[ + "stage3.instruction_input.gamma", + "stage3.input.stage2.product_virtual.LeftInstructionInput", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_13: &[&str] = &[ + "stage3.input.stage2.product_virtual.RightInstructionInput", + "stage3.instruction_input.term.LeftInstructionInput", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_14: &[&str] = &["stage3.registers.gamma"]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_15: &[&str] = &[ + "stage3.registers.gamma", + "stage3.input.stage1.Rs1Value", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_16: &[&str] = &[ + "stage3.registers.gamma2", + "stage3.input.stage1.Rs2Value", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_17: &[&str] = &[ + "stage3.input.stage1.RdWriteValue", + "stage3.registers.term.Rs1Value", +]; + +pub const STAGE3_FIELD_EXPR_OPERANDS_18: &[&str] = &[ + "stage3.registers.partial.RdWriteValueRs1Value", + "stage3.registers.term.Rs2Value", +]; + +pub const STAGE3_FIELD_EXPRS: &[Stage3FieldExprPlan] = &[ + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.gamma2", kind: "op", formula: "field.pow:2", operand_names: STAGE3_FIELD_EXPR_OPERANDS_0, operands: STAGE3_FIELD_EXPR_OPERANDS_0 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.gamma3", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_1, operands: STAGE3_FIELD_EXPR_OPERANDS_1 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.gamma4", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_2, operands: STAGE3_FIELD_EXPR_OPERANDS_2 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.term.NextPC", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_3, operands: STAGE3_FIELD_EXPR_OPERANDS_3 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.term.NextIsVirtual", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_4, operands: STAGE3_FIELD_EXPR_OPERANDS_4 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.term.NextIsFirstInSequence", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_5, operands: STAGE3_FIELD_EXPR_OPERANDS_5 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.one_minus.NextIsNoop", kind: "op", formula: "field.sub", operand_names: STAGE3_FIELD_EXPR_OPERANDS_6, operands: STAGE3_FIELD_EXPR_OPERANDS_6 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.term.NextIsNoop", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_7, operands: STAGE3_FIELD_EXPR_OPERANDS_7 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.partial.NextUnexpandedPCNextPC", kind: "op", formula: "field.add", operand_names: STAGE3_FIELD_EXPR_OPERANDS_8, operands: STAGE3_FIELD_EXPR_OPERANDS_8 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.partial.NextIsVirtual", kind: "op", formula: "field.add", operand_names: STAGE3_FIELD_EXPR_OPERANDS_9, operands: STAGE3_FIELD_EXPR_OPERANDS_9 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.partial.NextIsFirstInSequence", kind: "op", formula: "field.add", operand_names: STAGE3_FIELD_EXPR_OPERANDS_10, operands: STAGE3_FIELD_EXPR_OPERANDS_10 }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE3_FIELD_EXPR_OPERANDS_11, operands: STAGE3_FIELD_EXPR_OPERANDS_11 }, + Stage3FieldExprPlan { symbol: "stage3.instruction_input.term.LeftInstructionInput", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_12, operands: STAGE3_FIELD_EXPR_OPERANDS_12 }, + Stage3FieldExprPlan { symbol: "stage3.instruction_input.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE3_FIELD_EXPR_OPERANDS_13, operands: STAGE3_FIELD_EXPR_OPERANDS_13 }, + Stage3FieldExprPlan { symbol: "stage3.registers.gamma2", kind: "op", formula: "field.pow:2", operand_names: STAGE3_FIELD_EXPR_OPERANDS_14, operands: STAGE3_FIELD_EXPR_OPERANDS_14 }, + Stage3FieldExprPlan { symbol: "stage3.registers.term.Rs1Value", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_15, operands: STAGE3_FIELD_EXPR_OPERANDS_15 }, + Stage3FieldExprPlan { symbol: "stage3.registers.term.Rs2Value", kind: "op", formula: "field.mul", operand_names: STAGE3_FIELD_EXPR_OPERANDS_16, operands: STAGE3_FIELD_EXPR_OPERANDS_16 }, + Stage3FieldExprPlan { symbol: "stage3.registers.partial.RdWriteValueRs1Value", kind: "op", formula: "field.add", operand_names: STAGE3_FIELD_EXPR_OPERANDS_17, operands: STAGE3_FIELD_EXPR_OPERANDS_17 }, + Stage3FieldExprPlan { symbol: "stage3.registers.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE3_FIELD_EXPR_OPERANDS_18, operands: STAGE3_FIELD_EXPR_OPERANDS_18 }, +]; +pub const STAGE3_KERNELS: &[Stage3KernelPlan] = &[ + Stage3KernelPlan { symbol: "jolt.cpu.stage3.spartan_shift", relation: "jolt.stage3.spartan_shift", kind: "sumcheck", backend: "cpu", abi: "jolt_stage3_spartan_shift" }, + Stage3KernelPlan { symbol: "jolt.cpu.stage3.instruction_input", relation: "jolt.stage3.instruction_input", kind: "sumcheck", backend: "cpu", abi: "jolt_stage3_instruction_input" }, + Stage3KernelPlan { symbol: "jolt.cpu.stage3.registers_claim_reduction", relation: "jolt.stage3.registers_claim_reduction", kind: "sumcheck", backend: "cpu", abi: "jolt_stage3_registers_claim_reduction" }, + Stage3KernelPlan { symbol: "jolt.cpu.stage3.batched", relation: "jolt.stage3.batched", kind: "sumcheck", backend: "cpu", abi: "jolt_stage3_batched" }, +]; + +pub const STAGE3_SUMCHECK_CLAIM_0_INPUT_OPENINGS: &[&str] = &[ + "stage3.input.stage1.NextUnexpandedPC", + "stage3.input.stage1.NextPC", + "stage3.input.stage1.NextIsVirtual", + "stage3.input.stage1.NextIsFirstInSequence", + "stage3.input.stage2.product_virtual.NextIsNoop", +]; + +pub const STAGE3_SUMCHECK_CLAIM_1_INPUT_OPENINGS: &[&str] = &[ + "stage3.input.stage2.product_virtual.RightInstructionInput", + "stage3.input.stage2.product_virtual.LeftInstructionInput", +]; + +pub const STAGE3_SUMCHECK_CLAIM_2_INPUT_OPENINGS: &[&str] = &[ + "stage3.input.stage1.RdWriteValue", + "stage3.input.stage1.Rs1Value", + "stage3.input.stage1.Rs2Value", +]; + +pub const STAGE3_SUMCHECK_CLAIMS: &[Stage3SumcheckClaimPlan] = &[ + Stage3SumcheckClaimPlan { symbol: "stage3.spartan_shift.input", stage: "stage3", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage3.spartan_shift.weighted_next_values", kernel: Some("jolt.cpu.stage3.spartan_shift"), relation: None, claim_value: "stage3.spartan_shift.claim_expr", input_openings: STAGE3_SUMCHECK_CLAIM_0_INPUT_OPENINGS }, + Stage3SumcheckClaimPlan { symbol: "stage3.instruction_input.input", stage: "stage3", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage3.instruction_input.weighted_inputs", kernel: Some("jolt.cpu.stage3.instruction_input"), relation: None, claim_value: "stage3.instruction_input.claim_expr", input_openings: STAGE3_SUMCHECK_CLAIM_1_INPUT_OPENINGS }, + Stage3SumcheckClaimPlan { symbol: "stage3.registers_claim_reduction.input", stage: "stage3", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage3.registers.weighted_register_values", kernel: Some("jolt.cpu.stage3.registers_claim_reduction"), relation: None, claim_value: "stage3.registers.claim_expr", input_openings: STAGE3_SUMCHECK_CLAIM_2_INPUT_OPENINGS }, +]; +pub const STAGE3_SUMCHECK_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage3.spartan_shift.input", + "stage3.instruction_input.input", + "stage3.registers_claim_reduction.input", +]; + +pub const STAGE3_SUMCHECK_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage3.spartan_shift.input", + "stage3.instruction_input.input", + "stage3.registers_claim_reduction.input", +]; + +pub const STAGE3_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 18, +]; + +pub const STAGE3_SUMCHECK_BATCHES: &[Stage3SumcheckBatchPlan] = &[ + Stage3SumcheckBatchPlan { symbol: "stage3.batch", stage: "stage3", proof_slot: "stage3.sumcheck", policy: "jolt_core_stage3_aligned", count: 3, ordered_claims: STAGE3_SUMCHECK_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE3_SUMCHECK_BATCH_0_CLAIM_OPERANDS, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE3_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE3_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 18, +]; + +pub const STAGE3_SUMCHECK_DRIVERS: &[Stage3SumcheckDriverPlan] = &[ + Stage3SumcheckDriverPlan { symbol: "stage3.sumcheck", stage: "stage3", proof_slot: "stage3.sumcheck", kernel: Some("jolt.cpu.stage3.batched"), relation: None, batch: "stage3.batch", policy: "jolt_core_stage3_aligned", round_schedule: STAGE3_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 18, degree: 3 }, +]; +pub const STAGE3_SUMCHECK_INSTANCE_RESULTS: &[Stage3SumcheckInstanceResultPlan] = &[ + Stage3SumcheckInstanceResultPlan { symbol: "stage3.spartan_shift.instance", source: "stage3.sumcheck", claim: "stage3.spartan_shift.input", relation: "jolt.stage3.spartan_shift", index: 0, point_arity: 18, num_rounds: 18, round_offset: 0, point_order: "reverse", degree: 2 }, + Stage3SumcheckInstanceResultPlan { symbol: "stage3.instruction_input.instance", source: "stage3.sumcheck", claim: "stage3.instruction_input.input", relation: "jolt.stage3.instruction_input", index: 1, point_arity: 18, num_rounds: 18, round_offset: 0, point_order: "reverse", degree: 3 }, + Stage3SumcheckInstanceResultPlan { symbol: "stage3.registers_claim_reduction.instance", source: "stage3.sumcheck", claim: "stage3.registers_claim_reduction.input", relation: "jolt.stage3.registers_claim_reduction", index: 2, point_arity: 18, num_rounds: 18, round_offset: 0, point_order: "reverse", degree: 2 }, +]; + +pub const STAGE3_SUMCHECK_EVALS: &[Stage3SumcheckEvalPlan] = &[ + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.UnexpandedPC", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.UnexpandedPC", index: 0, oracle: "UnexpandedPC" }, + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.PC", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.PC", index: 1, oracle: "PC" }, + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.OpFlagVirtualInstruction", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.OpFlagVirtualInstruction", index: 2, oracle: "OpFlagVirtualInstruction" }, + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.OpFlagIsFirstInSequence", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.OpFlagIsFirstInSequence", index: 3, oracle: "OpFlagIsFirstInSequence" }, + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.InstructionFlagIsNoop", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.InstructionFlagIsNoop", index: 4, oracle: "InstructionFlagIsNoop" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", index: 5, oracle: "InstructionFlagLeftOperandIsRs1Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.Rs1Value", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.Rs1Value", index: 6, oracle: "Rs1Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", index: 7, oracle: "InstructionFlagLeftOperandIsPC" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.UnexpandedPC", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.UnexpandedPC", index: 8, oracle: "UnexpandedPC" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", index: 9, oracle: "InstructionFlagRightOperandIsRs2Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.Rs2Value", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.Rs2Value", index: 10, oracle: "Rs2Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", index: 11, oracle: "InstructionFlagRightOperandIsImm" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.Imm", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.Imm", index: 12, oracle: "Imm" }, + Stage3SumcheckEvalPlan { symbol: "stage3.registers_claim_reduction.eval.RdWriteValue", source: "stage3.sumcheck", name: "stage3.registers_claim_reduction.eval.RdWriteValue", index: 13, oracle: "RdWriteValue" }, + Stage3SumcheckEvalPlan { symbol: "stage3.registers_claim_reduction.eval.Rs1Value", source: "stage3.sumcheck", name: "stage3.registers_claim_reduction.eval.Rs1Value", index: 14, oracle: "Rs1Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.registers_claim_reduction.eval.Rs2Value", source: "stage3.sumcheck", name: "stage3.registers_claim_reduction.eval.Rs2Value", index: 15, oracle: "Rs2Value" }, +]; + +pub const STAGE3_POINT_SLICES: &[Stage3PointSlicePlan] = &[ + +]; + +pub const STAGE3_POINT_CONCATS: &[Stage3PointConcatPlan] = &[ + +]; +pub const STAGE3_OPENING_CLAIMS: &[Stage3OpeningClaimPlan] = &[ + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.UnexpandedPC" }, + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.PC", oracle: "PC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.PC" }, + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.OpFlagVirtualInstruction" }, + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.OpFlagIsFirstInSequence", oracle: "OpFlagIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.OpFlagIsFirstInSequence" }, + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.InstructionFlagIsNoop", oracle: "InstructionFlagIsNoop", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.InstructionFlagIsNoop" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value", oracle: "InstructionFlagLeftOperandIsRs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.Rs1Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC", oracle: "InstructionFlagLeftOperandIsPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.UnexpandedPC" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value", oracle: "InstructionFlagRightOperandIsRs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.Rs2Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.InstructionFlagRightOperandIsImm", oracle: "InstructionFlagRightOperandIsImm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.Imm", oracle: "Imm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.Imm" }, + Stage3OpeningClaimPlan { symbol: "stage3.registers_claim_reduction.opening.RdWriteValue", oracle: "RdWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.registers_claim_reduction.instance", eval_source: "stage3.registers_claim_reduction.eval.RdWriteValue" }, + Stage3OpeningClaimPlan { symbol: "stage3.registers_claim_reduction.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.registers_claim_reduction.instance", eval_source: "stage3.registers_claim_reduction.eval.Rs1Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.registers_claim_reduction.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.registers_claim_reduction.instance", eval_source: "stage3.registers_claim_reduction.eval.Rs2Value" }, +]; + +pub const STAGE3_OPENING_EQUALITIES: &[Stage3OpeningClaimEqualityPlan] = &[ + Stage3OpeningClaimEqualityPlan { symbol: "stage3.instruction_input.left_claim_consistency", mode: "point_and_eval", lhs: "stage3.input.stage2.product_virtual.LeftInstructionInput", rhs: "stage3.input.stage2.instruction_lookup.LeftInstructionInput" }, + Stage3OpeningClaimEqualityPlan { symbol: "stage3.instruction_input.right_claim_consistency", mode: "point_and_eval", lhs: "stage3.input.stage2.product_virtual.RightInstructionInput", rhs: "stage3.input.stage2.instruction_lookup.RightInstructionInput" }, +]; + +pub const STAGE3_OPENING_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage3.spartan_shift.opening.UnexpandedPC", + "stage3.spartan_shift.opening.PC", + "stage3.spartan_shift.opening.OpFlagVirtualInstruction", + "stage3.spartan_shift.opening.OpFlagIsFirstInSequence", + "stage3.spartan_shift.opening.InstructionFlagIsNoop", + "stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value", + "stage3.instruction_input.opening.Rs1Value", + "stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC", + "stage3.instruction_input.opening.UnexpandedPC", + "stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value", + "stage3.instruction_input.opening.Rs2Value", + "stage3.instruction_input.opening.InstructionFlagRightOperandIsImm", + "stage3.instruction_input.opening.Imm", + "stage3.registers_claim_reduction.opening.RdWriteValue", + "stage3.registers_claim_reduction.opening.Rs1Value", + "stage3.registers_claim_reduction.opening.Rs2Value", +]; + +pub const STAGE3_OPENING_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage3.spartan_shift.opening.UnexpandedPC", + "stage3.spartan_shift.opening.PC", + "stage3.spartan_shift.opening.OpFlagVirtualInstruction", + "stage3.spartan_shift.opening.OpFlagIsFirstInSequence", + "stage3.spartan_shift.opening.InstructionFlagIsNoop", + "stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value", + "stage3.instruction_input.opening.Rs1Value", + "stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC", + "stage3.instruction_input.opening.UnexpandedPC", + "stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value", + "stage3.instruction_input.opening.Rs2Value", + "stage3.instruction_input.opening.InstructionFlagRightOperandIsImm", + "stage3.instruction_input.opening.Imm", + "stage3.registers_claim_reduction.opening.RdWriteValue", + "stage3.registers_claim_reduction.opening.Rs1Value", + "stage3.registers_claim_reduction.opening.Rs2Value", +]; + +pub const STAGE3_OPENING_BATCHES: &[Stage3OpeningBatchPlan] = &[ + Stage3OpeningBatchPlan { symbol: "stage3.openings", stage: "stage3", proof_slot: "stage3.openings", policy: "jolt_stage3_output_order", count: 16, ordered_claims: STAGE3_OPENING_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE3_OPENING_BATCH_0_CLAIM_OPERANDS }, +]; +pub const STAGE3_PROGRAM: Stage3CpuProgramPlan = Stage3CpuProgramPlan { + params: STAGE3_PARAMS, + steps: STAGE3_PROGRAM_STEPS, + transcript_squeezes: STAGE3_TRANSCRIPT_SQUEEZES, + opening_inputs: STAGE3_OPENING_INPUTS, + field_constants: STAGE3_FIELD_CONSTANTS, + field_exprs: STAGE3_FIELD_EXPRS, + kernels: STAGE3_KERNELS, + claims: STAGE3_SUMCHECK_CLAIMS, + batches: STAGE3_SUMCHECK_BATCHES, + drivers: STAGE3_SUMCHECK_DRIVERS, + instance_results: STAGE3_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE3_SUMCHECK_EVALS, + point_slices: STAGE3_POINT_SLICES, + point_concats: STAGE3_POINT_CONCATS, + opening_claims: STAGE3_OPENING_CLAIMS, + opening_equalities: STAGE3_OPENING_EQUALITIES, + opening_batches: STAGE3_OPENING_BATCHES, +}; + +pub fn execute_stage3_prover( + executor: &mut E, + transcript: &mut T, +) -> Result, Stage3KernelError> +where + E: Stage3KernelExecutor, + T: Transcript, +{ + execute_stage3_prover_with_program(&STAGE3_PROGRAM, executor, transcript) +} + +pub fn execute_stage3_prover_with_program( + program: &'static Stage3CpuProgramPlan, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage3KernelError> +where + E: Stage3KernelExecutor, + T: Transcript, +{ + execute_stage3_program(program, Stage3ExecutionMode::Prover, executor, transcript) +} diff --git a/crates/jolt-prover/src/stages/stage4.rs b/crates/jolt-prover/src/stages/stage4.rs new file mode 100644 index 0000000000..cc58a42256 --- /dev/null +++ b/crates/jolt-prover/src/stages/stage4.rs @@ -0,0 +1,255 @@ +#![allow(dead_code)] + +use jolt_field::Fr; +use jolt_kernels::stage4::{execute_stage4_program, Stage4CpuProgramPlan, Stage4ExecutionArtifacts, Stage4ExecutionMode, Stage4FieldConstantPlan, Stage4FieldExprPlan, Stage4KernelError, Stage4KernelExecutor, Stage4KernelPlan, Stage4OpeningBatchPlan, Stage4OpeningClaimEqualityPlan, Stage4OpeningClaimPlan, Stage4OpeningInputPlan, Stage4Params, Stage4PointConcatPlan, Stage4PointSlicePlan, Stage4ProgramStepPlan, Stage4SumcheckBatchPlan, Stage4SumcheckClaimPlan, Stage4SumcheckDriverPlan, Stage4SumcheckEvalPlan, Stage4SumcheckInstanceResultPlan, Stage4TranscriptAbsorbBytesPlan, Stage4TranscriptSqueezePlan}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage4Transcript = Blake2bTranscript; + +pub const STAGE4_PARAMS: Stage4Params = Stage4Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE4_PROGRAM_STEPS: &[Stage4ProgramStepPlan] = &[ + Stage4ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage4.registers_read_write.gamma" }, + Stage4ProgramStepPlan { kind: "transcript_absorb_bytes", symbol: "stage4.ram_val_check.domain_separator" }, + Stage4ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage4.ram_val_check.gamma" }, + Stage4ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage4.sumcheck" }, +]; + +pub const STAGE4_TRANSCRIPT_SQUEEZES: &[Stage4TranscriptSqueezePlan] = &[ + Stage4TranscriptSqueezePlan { symbol: "stage4.registers_read_write.gamma", label: "registers_read_write_gamma", kind: "challenge_scalar", count: 1 }, + Stage4TranscriptSqueezePlan { symbol: "stage4.ram_val_check.gamma", label: "ram_val_check_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE4_TRANSCRIPT_ABSORB_BYTES: &[Stage4TranscriptAbsorbBytesPlan] = &[ + Stage4TranscriptAbsorbBytesPlan { symbol: "stage4.ram_val_check.domain_separator", label: "ram_val_check_gamma", payload: "" }, +]; + +pub const STAGE4_OPENING_INPUTS: &[Stage4OpeningInputPlan] = &[ + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.registers.RdWriteValue", source_stage: "stage3", source_claim: "stage3.registers_claim_reduction.opening.RdWriteValue", oracle: "RdWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.registers.Rs1Value", source_stage: "stage3", source_claim: "stage3.registers_claim_reduction.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.registers.Rs2Value", source_stage: "stage3", source_claim: "stage3.registers_claim_reduction.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.instruction.Rs1Value", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.instruction.Rs2Value", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage2.RamVal", source_stage: "stage2", source_claim: "stage2.ram_read_write.opening.RamVal", oracle: "RamVal", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage2.RamValFinal", source_stage: "stage2", source_claim: "stage2.ram_output.opening.RamValFinal", oracle: "RamValFinal", domain: "jolt.ram_address_domain", point_arity: 14, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.initial_ram.RamValInit", source_stage: "stage4_precomputed", source_claim: "stage4.ram_val_check.initial_ram_eval", oracle: "RamValInit", domain: "jolt.ram_address_domain", point_arity: 14, claim_kind: "virtual" }, +]; + +pub const STAGE4_FIELD_CONSTANTS: &[Stage4FieldConstantPlan] = &[ + +]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_0: &[&str] = &["stage4.registers_read_write.gamma"]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_1: &[&str] = &[ + "stage4.registers_read_write.gamma", + "stage4.input.stage3.registers.Rs1Value", +]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_2: &[&str] = &[ + "stage4.registers_read_write.gamma2", + "stage4.input.stage3.registers.Rs2Value", +]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_3: &[&str] = &[ + "stage4.input.stage3.registers.RdWriteValue", + "stage4.registers_read_write.term.Rs1Value", +]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_4: &[&str] = &[ + "stage4.registers_read_write.partial.RdWriteValueRs1Value", + "stage4.registers_read_write.term.Rs2Value", +]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_5: &[&str] = &[ + "stage4.input.stage2.RamVal", + "stage4.input.initial_ram.RamValInit", +]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_6: &[&str] = &[ + "stage4.input.stage2.RamValFinal", + "stage4.input.initial_ram.RamValInit", +]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_7: &[&str] = &[ + "stage4.ram_val_check.gamma", + "stage4.ram_val_check.delta.RamValFinal", +]; + +pub const STAGE4_FIELD_EXPR_OPERANDS_8: &[&str] = &[ + "stage4.ram_val_check.delta.RamVal", + "stage4.ram_val_check.term.RamValFinal", +]; + +pub const STAGE4_FIELD_EXPRS: &[Stage4FieldExprPlan] = &[ + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.gamma2", kind: "op", formula: "field.pow:2", operand_names: STAGE4_FIELD_EXPR_OPERANDS_0, operands: STAGE4_FIELD_EXPR_OPERANDS_0 }, + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.term.Rs1Value", kind: "op", formula: "field.mul", operand_names: STAGE4_FIELD_EXPR_OPERANDS_1, operands: STAGE4_FIELD_EXPR_OPERANDS_1 }, + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.term.Rs2Value", kind: "op", formula: "field.mul", operand_names: STAGE4_FIELD_EXPR_OPERANDS_2, operands: STAGE4_FIELD_EXPR_OPERANDS_2 }, + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.partial.RdWriteValueRs1Value", kind: "op", formula: "field.add", operand_names: STAGE4_FIELD_EXPR_OPERANDS_3, operands: STAGE4_FIELD_EXPR_OPERANDS_3 }, + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE4_FIELD_EXPR_OPERANDS_4, operands: STAGE4_FIELD_EXPR_OPERANDS_4 }, + Stage4FieldExprPlan { symbol: "stage4.ram_val_check.delta.RamVal", kind: "op", formula: "field.sub", operand_names: STAGE4_FIELD_EXPR_OPERANDS_5, operands: STAGE4_FIELD_EXPR_OPERANDS_5 }, + Stage4FieldExprPlan { symbol: "stage4.ram_val_check.delta.RamValFinal", kind: "op", formula: "field.sub", operand_names: STAGE4_FIELD_EXPR_OPERANDS_6, operands: STAGE4_FIELD_EXPR_OPERANDS_6 }, + Stage4FieldExprPlan { symbol: "stage4.ram_val_check.term.RamValFinal", kind: "op", formula: "field.mul", operand_names: STAGE4_FIELD_EXPR_OPERANDS_7, operands: STAGE4_FIELD_EXPR_OPERANDS_7 }, + Stage4FieldExprPlan { symbol: "stage4.ram_val_check.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE4_FIELD_EXPR_OPERANDS_8, operands: STAGE4_FIELD_EXPR_OPERANDS_8 }, +]; +pub const STAGE4_KERNELS: &[Stage4KernelPlan] = &[ + Stage4KernelPlan { symbol: "jolt.cpu.stage4.registers_read_write", relation: "jolt.stage4.registers_read_write", kind: "sumcheck", backend: "cpu", abi: "jolt_stage4_registers_read_write" }, + Stage4KernelPlan { symbol: "jolt.cpu.stage4.ram_val_check", relation: "jolt.stage4.ram_val_check", kind: "sumcheck", backend: "cpu", abi: "jolt_stage4_ram_val_check" }, + Stage4KernelPlan { symbol: "jolt.cpu.stage4.batched", relation: "jolt.stage4.batched", kind: "sumcheck", backend: "cpu", abi: "jolt_stage4_batched" }, +]; + +pub const STAGE4_SUMCHECK_CLAIM_0_INPUT_OPENINGS: &[&str] = &[ + "stage4.input.stage3.registers.RdWriteValue", + "stage4.input.stage3.registers.Rs1Value", + "stage4.input.stage3.registers.Rs2Value", +]; + +pub const STAGE4_SUMCHECK_CLAIM_1_INPUT_OPENINGS: &[&str] = &[ + "stage4.input.stage2.RamVal", + "stage4.input.stage2.RamValFinal", + "stage4.input.initial_ram.RamValInit", +]; + +pub const STAGE4_SUMCHECK_CLAIMS: &[Stage4SumcheckClaimPlan] = &[ + Stage4SumcheckClaimPlan { symbol: "stage4.registers_read_write.input", stage: "stage4", domain: "jolt.stage4_registers_rw_domain", num_rounds: 25, degree: 3, claim: "stage4.registers_read_write.weighted_values", kernel: Some("jolt.cpu.stage4.registers_read_write"), relation: None, claim_value: "stage4.registers_read_write.claim_expr", input_openings: STAGE4_SUMCHECK_CLAIM_0_INPUT_OPENINGS }, + Stage4SumcheckClaimPlan { symbol: "stage4.ram_val_check.input", stage: "stage4", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage4.ram_val_check.weighted_values", kernel: Some("jolt.cpu.stage4.ram_val_check"), relation: None, claim_value: "stage4.ram_val_check.claim_expr", input_openings: STAGE4_SUMCHECK_CLAIM_1_INPUT_OPENINGS }, +]; +pub const STAGE4_SUMCHECK_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage4.registers_read_write.input", + "stage4.ram_val_check.input", +]; + +pub const STAGE4_SUMCHECK_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage4.registers_read_write.input", + "stage4.ram_val_check.input", +]; + +pub const STAGE4_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 18, + 7, +]; + +pub const STAGE4_SUMCHECK_BATCHES: &[Stage4SumcheckBatchPlan] = &[ + Stage4SumcheckBatchPlan { symbol: "stage4.batch", stage: "stage4", proof_slot: "stage4.sumcheck", policy: "jolt_core_stage4_aligned", count: 2, ordered_claims: STAGE4_SUMCHECK_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE4_SUMCHECK_BATCH_0_CLAIM_OPERANDS, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE4_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE4_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 18, + 7, +]; + +pub const STAGE4_SUMCHECK_DRIVERS: &[Stage4SumcheckDriverPlan] = &[ + Stage4SumcheckDriverPlan { symbol: "stage4.sumcheck", stage: "stage4", proof_slot: "stage4.sumcheck", kernel: Some("jolt.cpu.stage4.batched"), relation: None, batch: "stage4.batch", policy: "jolt_core_stage4_aligned", round_schedule: STAGE4_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 25, degree: 3 }, +]; +pub const STAGE4_SUMCHECK_INSTANCE_RESULTS: &[Stage4SumcheckInstanceResultPlan] = &[ + Stage4SumcheckInstanceResultPlan { symbol: "stage4.registers_read_write.instance", source: "stage4.sumcheck", claim: "stage4.registers_read_write.input", relation: "jolt.stage4.registers_read_write", index: 0, point_arity: 25, num_rounds: 25, round_offset: 0, point_order: "stage4_registers_rw", degree: 3 }, + Stage4SumcheckInstanceResultPlan { symbol: "stage4.ram_val_check.instance", source: "stage4.sumcheck", claim: "stage4.ram_val_check.input", relation: "jolt.stage4.ram_val_check", index: 1, point_arity: 18, num_rounds: 18, round_offset: 7, point_order: "reverse", degree: 3 }, +]; + +pub const STAGE4_SUMCHECK_EVALS: &[Stage4SumcheckEvalPlan] = &[ + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.RegistersVal", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.RegistersVal", index: 0, oracle: "RegistersVal" }, + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.Rs1Ra", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.Rs1Ra", index: 1, oracle: "Rs1Ra" }, + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.Rs2Ra", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.Rs2Ra", index: 2, oracle: "Rs2Ra" }, + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.RdWa", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.RdWa", index: 3, oracle: "RdWa" }, + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.RdInc", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.RdInc", index: 4, oracle: "RdInc" }, + Stage4SumcheckEvalPlan { symbol: "stage4.ram_val_check.eval.RamRa", source: "stage4.sumcheck", name: "stage4.ram_val_check.eval.RamRa", index: 0, oracle: "RamRa" }, + Stage4SumcheckEvalPlan { symbol: "stage4.ram_val_check.eval.RamInc", source: "stage4.sumcheck", name: "stage4.ram_val_check.eval.RamInc", index: 1, oracle: "RamInc" }, +]; + +pub const STAGE4_POINT_SLICES: &[Stage4PointSlicePlan] = &[ + Stage4PointSlicePlan { symbol: "stage4.registers_read_write.point.RdInc", source: "stage4.registers_read_write.instance", offset: 7, length: 18, input: "stage4.registers_read_write.instance" }, + Stage4PointSlicePlan { symbol: "stage4.ram_val_check.point.RamAddress", source: "stage4.input.stage2.RamVal", offset: 0, length: 14, input: "stage4.input.stage2.RamVal" }, +]; + +pub const STAGE4_POINT_CONCAT_0_INPUTS: &[&str] = &[ + "stage4.ram_val_check.point.RamAddress", + "stage4.ram_val_check.instance", +]; + +pub const STAGE4_POINT_CONCATS: &[Stage4PointConcatPlan] = &[ + Stage4PointConcatPlan { symbol: "stage4.ram_val_check.point.RamRa", layout: "address_then_cycle", arity: 32, inputs: STAGE4_POINT_CONCAT_0_INPUTS }, +]; +pub const STAGE4_OPENING_CLAIMS: &[Stage4OpeningClaimPlan] = &[ + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.RegistersVal", oracle: "RegistersVal", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage4.registers_read_write.instance", eval_source: "stage4.registers_read_write.eval.RegistersVal" }, + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.Rs1Ra", oracle: "Rs1Ra", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage4.registers_read_write.instance", eval_source: "stage4.registers_read_write.eval.Rs1Ra" }, + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.Rs2Ra", oracle: "Rs2Ra", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage4.registers_read_write.instance", eval_source: "stage4.registers_read_write.eval.Rs2Ra" }, + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.RdWa", oracle: "RdWa", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage4.registers_read_write.instance", eval_source: "stage4.registers_read_write.eval.RdWa" }, + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage4.registers_read_write.point.RdInc", eval_source: "stage4.registers_read_write.eval.RdInc" }, + Stage4OpeningClaimPlan { symbol: "stage4.ram_val_check.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage4.ram_val_check.point.RamRa", eval_source: "stage4.ram_val_check.eval.RamRa" }, + Stage4OpeningClaimPlan { symbol: "stage4.ram_val_check.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage4.ram_val_check.instance", eval_source: "stage4.ram_val_check.eval.RamInc" }, +]; + +pub const STAGE4_OPENING_EQUALITIES: &[Stage4OpeningClaimEqualityPlan] = &[ + Stage4OpeningClaimEqualityPlan { symbol: "stage4.registers.rs1_claim_consistency", mode: "point_and_eval", lhs: "stage4.input.stage3.registers.Rs1Value", rhs: "stage4.input.stage3.instruction.Rs1Value" }, + Stage4OpeningClaimEqualityPlan { symbol: "stage4.registers.rs2_claim_consistency", mode: "point_and_eval", lhs: "stage4.input.stage3.registers.Rs2Value", rhs: "stage4.input.stage3.instruction.Rs2Value" }, +]; + +pub const STAGE4_OPENING_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage4.registers_read_write.opening.RegistersVal", + "stage4.registers_read_write.opening.Rs1Ra", + "stage4.registers_read_write.opening.Rs2Ra", + "stage4.registers_read_write.opening.RdWa", + "stage4.registers_read_write.opening.RdInc", + "stage4.ram_val_check.opening.RamRa", + "stage4.ram_val_check.opening.RamInc", +]; + +pub const STAGE4_OPENING_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage4.registers_read_write.opening.RegistersVal", + "stage4.registers_read_write.opening.Rs1Ra", + "stage4.registers_read_write.opening.Rs2Ra", + "stage4.registers_read_write.opening.RdWa", + "stage4.registers_read_write.opening.RdInc", + "stage4.ram_val_check.opening.RamRa", + "stage4.ram_val_check.opening.RamInc", +]; + +pub const STAGE4_OPENING_BATCHES: &[Stage4OpeningBatchPlan] = &[ + Stage4OpeningBatchPlan { symbol: "stage4.openings", stage: "stage4", proof_slot: "stage4.openings", policy: "jolt_stage4_output_order", count: 7, ordered_claims: STAGE4_OPENING_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE4_OPENING_BATCH_0_CLAIM_OPERANDS }, +]; +pub const STAGE4_PROGRAM: Stage4CpuProgramPlan = Stage4CpuProgramPlan { + role: "prover", + params: STAGE4_PARAMS, + steps: STAGE4_PROGRAM_STEPS, + transcript_squeezes: STAGE4_TRANSCRIPT_SQUEEZES, + transcript_absorb_bytes: STAGE4_TRANSCRIPT_ABSORB_BYTES, + opening_inputs: STAGE4_OPENING_INPUTS, + field_constants: STAGE4_FIELD_CONSTANTS, + field_exprs: STAGE4_FIELD_EXPRS, + kernels: STAGE4_KERNELS, + claims: STAGE4_SUMCHECK_CLAIMS, + batches: STAGE4_SUMCHECK_BATCHES, + drivers: STAGE4_SUMCHECK_DRIVERS, + instance_results: STAGE4_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE4_SUMCHECK_EVALS, + point_slices: STAGE4_POINT_SLICES, + point_concats: STAGE4_POINT_CONCATS, + opening_claims: STAGE4_OPENING_CLAIMS, + opening_equalities: STAGE4_OPENING_EQUALITIES, + opening_batches: STAGE4_OPENING_BATCHES, +}; + +pub fn execute_stage4_prover( + executor: &mut E, + transcript: &mut T, +) -> Result, Stage4KernelError> +where + E: Stage4KernelExecutor, + T: Transcript, +{ + execute_stage4_prover_with_program(&STAGE4_PROGRAM, executor, transcript) +} + +pub fn execute_stage4_prover_with_program( + program: &'static Stage4CpuProgramPlan, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage4KernelError> +where + E: Stage4KernelExecutor, + T: Transcript, +{ + execute_stage4_program(program, Stage4ExecutionMode::Prover, executor, transcript) +} diff --git a/crates/jolt-prover/src/stages/stage5.rs b/crates/jolt-prover/src/stages/stage5.rs new file mode 100644 index 0000000000..9d6fe2b7c7 --- /dev/null +++ b/crates/jolt-prover/src/stages/stage5.rs @@ -0,0 +1,506 @@ +#![allow(dead_code)] + +use jolt_field::Fr; +use jolt_kernels::stage5::{execute_stage5_program, Stage5CpuProgramPlan, Stage5ExecutionArtifacts, Stage5ExecutionMode, Stage5FieldConstantPlan, Stage5FieldExprPlan, Stage5KernelError, Stage5KernelExecutor, Stage5KernelPlan, Stage5OpeningBatchPlan, Stage5OpeningClaimEqualityPlan, Stage5OpeningClaimPlan, Stage5OpeningInputPlan, Stage5Params, Stage5PointConcatPlan, Stage5PointSlicePlan, Stage5ProgramStepPlan, Stage5SumcheckBatchPlan, Stage5SumcheckClaimPlan, Stage5SumcheckDriverPlan, Stage5SumcheckEvalPlan, Stage5SumcheckInstanceResultPlan, Stage5TranscriptAbsorbBytesPlan, Stage5TranscriptSqueezePlan}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage5Transcript = Blake2bTranscript; + +pub const STAGE5_PARAMS: Stage5Params = Stage5Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE5_PROGRAM_STEPS: &[Stage5ProgramStepPlan] = &[ + Stage5ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage5.instruction_read_raf.gamma" }, + Stage5ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage5.ram_ra_claim_reduction.gamma" }, + Stage5ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage5.sumcheck" }, +]; + +pub const STAGE5_TRANSCRIPT_SQUEEZES: &[Stage5TranscriptSqueezePlan] = &[ + Stage5TranscriptSqueezePlan { symbol: "stage5.instruction_read_raf.gamma", label: "instruction_read_raf_gamma", kind: "challenge_scalar", count: 1 }, + Stage5TranscriptSqueezePlan { symbol: "stage5.ram_ra_claim_reduction.gamma", label: "ram_ra_claim_reduction_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE5_TRANSCRIPT_ABSORB_BYTES: &[Stage5TranscriptAbsorbBytesPlan] = &[ + +]; + +pub const STAGE5_OPENING_INPUTS: &[Stage5OpeningInputPlan] = &[ + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.instruction.LookupOutput", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.product_virtual.LookupOutput", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.instruction.LeftLookupOperand", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand", oracle: "LeftLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.instruction.RightLookupOperand", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand", oracle: "RightLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.ram_raf.RamRa", source_stage: "stage2", source_claim: "stage2.ram_raf.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.ram_read_write.RamRa", source_stage: "stage2", source_claim: "stage2.ram_read_write.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage4.ram_val_check.RamRa", source_stage: "stage4", source_claim: "stage4.ram_val_check.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage4.registers.RegistersVal", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.RegistersVal", oracle: "RegistersVal", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, +]; + +pub const STAGE5_FIELD_CONSTANTS: &[Stage5FieldConstantPlan] = &[ + +]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_0: &[&str] = &["stage5.instruction_read_raf.gamma"]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_1: &[&str] = &[ + "stage5.instruction_read_raf.gamma", + "stage5.input.stage2.instruction.LeftLookupOperand", +]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_2: &[&str] = &[ + "stage5.instruction_read_raf.gamma2", + "stage5.input.stage2.instruction.RightLookupOperand", +]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_3: &[&str] = &[ + "stage5.input.stage2.instruction.LookupOutput", + "stage5.instruction_read_raf.term.LeftLookupOperand", +]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_4: &[&str] = &[ + "stage5.instruction_read_raf.partial.LookupOutputLeftOperand", + "stage5.instruction_read_raf.term.RightLookupOperand", +]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_5: &[&str] = &["stage5.ram_ra_claim_reduction.gamma"]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_6: &[&str] = &[ + "stage5.ram_ra_claim_reduction.gamma", + "stage5.input.stage2.ram_read_write.RamRa", +]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_7: &[&str] = &[ + "stage5.ram_ra_claim_reduction.gamma2", + "stage5.input.stage4.ram_val_check.RamRa", +]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_8: &[&str] = &[ + "stage5.input.stage2.ram_raf.RamRa", + "stage5.ram_ra_claim_reduction.term.RamRaReadWrite", +]; + +pub const STAGE5_FIELD_EXPR_OPERANDS_9: &[&str] = &[ + "stage5.ram_ra_claim_reduction.partial.RafReadWrite", + "stage5.ram_ra_claim_reduction.term.RamRaValCheck", +]; + +pub const STAGE5_FIELD_EXPRS: &[Stage5FieldExprPlan] = &[ + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.gamma2", kind: "op", formula: "field.pow:2", operand_names: STAGE5_FIELD_EXPR_OPERANDS_0, operands: STAGE5_FIELD_EXPR_OPERANDS_0 }, + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.term.LeftLookupOperand", kind: "op", formula: "field.mul", operand_names: STAGE5_FIELD_EXPR_OPERANDS_1, operands: STAGE5_FIELD_EXPR_OPERANDS_1 }, + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.term.RightLookupOperand", kind: "op", formula: "field.mul", operand_names: STAGE5_FIELD_EXPR_OPERANDS_2, operands: STAGE5_FIELD_EXPR_OPERANDS_2 }, + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.partial.LookupOutputLeftOperand", kind: "op", formula: "field.add", operand_names: STAGE5_FIELD_EXPR_OPERANDS_3, operands: STAGE5_FIELD_EXPR_OPERANDS_3 }, + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE5_FIELD_EXPR_OPERANDS_4, operands: STAGE5_FIELD_EXPR_OPERANDS_4 }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.gamma2", kind: "op", formula: "field.pow:2", operand_names: STAGE5_FIELD_EXPR_OPERANDS_5, operands: STAGE5_FIELD_EXPR_OPERANDS_5 }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.term.RamRaReadWrite", kind: "op", formula: "field.mul", operand_names: STAGE5_FIELD_EXPR_OPERANDS_6, operands: STAGE5_FIELD_EXPR_OPERANDS_6 }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.term.RamRaValCheck", kind: "op", formula: "field.mul", operand_names: STAGE5_FIELD_EXPR_OPERANDS_7, operands: STAGE5_FIELD_EXPR_OPERANDS_7 }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.partial.RafReadWrite", kind: "op", formula: "field.add", operand_names: STAGE5_FIELD_EXPR_OPERANDS_8, operands: STAGE5_FIELD_EXPR_OPERANDS_8 }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.claim_expr", kind: "op", formula: "field.add", operand_names: STAGE5_FIELD_EXPR_OPERANDS_9, operands: STAGE5_FIELD_EXPR_OPERANDS_9 }, +]; +pub const STAGE5_KERNELS: &[Stage5KernelPlan] = &[ + Stage5KernelPlan { symbol: "jolt.cpu.stage5.instruction_read_raf", relation: "jolt.stage5.instruction_read_raf", kind: "sumcheck", backend: "cpu", abi: "jolt_stage5_instruction_read_raf" }, + Stage5KernelPlan { symbol: "jolt.cpu.stage5.ram_ra_claim_reduction", relation: "jolt.stage5.ram_ra_claim_reduction", kind: "sumcheck", backend: "cpu", abi: "jolt_stage5_ram_ra_claim_reduction" }, + Stage5KernelPlan { symbol: "jolt.cpu.stage5.registers_val_evaluation", relation: "jolt.stage5.registers_val_evaluation", kind: "sumcheck", backend: "cpu", abi: "jolt_stage5_registers_val_evaluation" }, + Stage5KernelPlan { symbol: "jolt.cpu.stage5.batched", relation: "jolt.stage5.batched", kind: "sumcheck", backend: "cpu", abi: "jolt_stage5_batched" }, +]; + +pub const STAGE5_SUMCHECK_CLAIM_0_INPUT_OPENINGS: &[&str] = &[ + "stage5.input.stage2.instruction.LookupOutput", + "stage5.input.stage2.instruction.LeftLookupOperand", + "stage5.input.stage2.instruction.RightLookupOperand", +]; + +pub const STAGE5_SUMCHECK_CLAIM_1_INPUT_OPENINGS: &[&str] = &[ + "stage5.input.stage2.ram_raf.RamRa", + "stage5.input.stage2.ram_read_write.RamRa", + "stage5.input.stage4.ram_val_check.RamRa", +]; + +pub const STAGE5_SUMCHECK_CLAIM_2_INPUT_OPENINGS: &[&str] = &["stage5.input.stage4.registers.RegistersVal"]; + +pub const STAGE5_SUMCHECK_CLAIMS: &[Stage5SumcheckClaimPlan] = &[ + Stage5SumcheckClaimPlan { symbol: "stage5.instruction_read_raf.input", stage: "stage5", domain: "jolt.stage5_instruction_read_raf_domain", num_rounds: 146, degree: 10, claim: "stage5.instruction_read_raf.weighted_lookup_values", kernel: Some("jolt.cpu.stage5.instruction_read_raf"), relation: None, claim_value: "stage5.instruction_read_raf.claim_expr", input_openings: STAGE5_SUMCHECK_CLAIM_0_INPUT_OPENINGS }, + Stage5SumcheckClaimPlan { symbol: "stage5.ram_ra_claim_reduction.input", stage: "stage5", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage5.ram_ra_claim_reduction.weighted_ram_ra", kernel: Some("jolt.cpu.stage5.ram_ra_claim_reduction"), relation: None, claim_value: "stage5.ram_ra_claim_reduction.claim_expr", input_openings: STAGE5_SUMCHECK_CLAIM_1_INPUT_OPENINGS }, + Stage5SumcheckClaimPlan { symbol: "stage5.registers_val_evaluation.input", stage: "stage5", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage5.registers_val_evaluation.registers_val", kernel: Some("jolt.cpu.stage5.registers_val_evaluation"), relation: None, claim_value: "stage5.input.stage4.registers.RegistersVal", input_openings: STAGE5_SUMCHECK_CLAIM_2_INPUT_OPENINGS }, +]; +pub const STAGE5_SUMCHECK_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage5.instruction_read_raf.input", + "stage5.ram_ra_claim_reduction.input", + "stage5.registers_val_evaluation.input", +]; + +pub const STAGE5_SUMCHECK_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage5.instruction_read_raf.input", + "stage5.ram_ra_claim_reduction.input", + "stage5.registers_val_evaluation.input", +]; + +pub const STAGE5_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 128, + 18, +]; + +pub const STAGE5_SUMCHECK_BATCHES: &[Stage5SumcheckBatchPlan] = &[ + Stage5SumcheckBatchPlan { symbol: "stage5.batch", stage: "stage5", proof_slot: "stage5.sumcheck", policy: "jolt_core_stage5_aligned", count: 3, ordered_claims: STAGE5_SUMCHECK_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE5_SUMCHECK_BATCH_0_CLAIM_OPERANDS, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE5_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE5_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 128, + 18, +]; + +pub const STAGE5_SUMCHECK_DRIVERS: &[Stage5SumcheckDriverPlan] = &[ + Stage5SumcheckDriverPlan { symbol: "stage5.sumcheck", stage: "stage5", proof_slot: "stage5.sumcheck", kernel: Some("jolt.cpu.stage5.batched"), relation: None, batch: "stage5.batch", policy: "jolt_core_stage5_aligned", round_schedule: STAGE5_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 146, degree: 10 }, +]; +pub const STAGE5_SUMCHECK_INSTANCE_RESULTS: &[Stage5SumcheckInstanceResultPlan] = &[ + Stage5SumcheckInstanceResultPlan { symbol: "stage5.instruction_read_raf.instance", source: "stage5.sumcheck", claim: "stage5.instruction_read_raf.input", relation: "jolt.stage5.instruction_read_raf", index: 0, point_arity: 146, num_rounds: 146, round_offset: 0, point_order: "instruction_read_raf", degree: 10 }, + Stage5SumcheckInstanceResultPlan { symbol: "stage5.ram_ra_claim_reduction.instance", source: "stage5.sumcheck", claim: "stage5.ram_ra_claim_reduction.input", relation: "jolt.stage5.ram_ra_claim_reduction", index: 1, point_arity: 18, num_rounds: 18, round_offset: 128, point_order: "reverse", degree: 2 }, + Stage5SumcheckInstanceResultPlan { symbol: "stage5.registers_val_evaluation.instance", source: "stage5.sumcheck", claim: "stage5.registers_val_evaluation.input", relation: "jolt.stage5.registers_val_evaluation", index: 2, point_arity: 18, num_rounds: 18, round_offset: 128, point_order: "reverse", degree: 3 }, +]; + +pub const STAGE5_SUMCHECK_EVALS: &[Stage5SumcheckEvalPlan] = &[ + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_0", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_0", index: 0, oracle: "LookupTableFlag_0" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_1", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_1", index: 1, oracle: "LookupTableFlag_1" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_2", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_2", index: 2, oracle: "LookupTableFlag_2" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_3", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_3", index: 3, oracle: "LookupTableFlag_3" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_4", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_4", index: 4, oracle: "LookupTableFlag_4" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_5", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_5", index: 5, oracle: "LookupTableFlag_5" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_6", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_6", index: 6, oracle: "LookupTableFlag_6" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_7", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_7", index: 7, oracle: "LookupTableFlag_7" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_8", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_8", index: 8, oracle: "LookupTableFlag_8" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_9", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_9", index: 9, oracle: "LookupTableFlag_9" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_10", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_10", index: 10, oracle: "LookupTableFlag_10" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_11", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_11", index: 11, oracle: "LookupTableFlag_11" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_12", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_12", index: 12, oracle: "LookupTableFlag_12" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_13", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_13", index: 13, oracle: "LookupTableFlag_13" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_14", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_14", index: 14, oracle: "LookupTableFlag_14" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_15", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_15", index: 15, oracle: "LookupTableFlag_15" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_16", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_16", index: 16, oracle: "LookupTableFlag_16" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_17", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_17", index: 17, oracle: "LookupTableFlag_17" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_18", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_18", index: 18, oracle: "LookupTableFlag_18" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_19", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_19", index: 19, oracle: "LookupTableFlag_19" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_20", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_20", index: 20, oracle: "LookupTableFlag_20" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_21", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_21", index: 21, oracle: "LookupTableFlag_21" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_22", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_22", index: 22, oracle: "LookupTableFlag_22" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_23", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_23", index: 23, oracle: "LookupTableFlag_23" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_24", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_24", index: 24, oracle: "LookupTableFlag_24" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_25", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_25", index: 25, oracle: "LookupTableFlag_25" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_26", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_26", index: 26, oracle: "LookupTableFlag_26" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_27", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_27", index: 27, oracle: "LookupTableFlag_27" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_28", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_28", index: 28, oracle: "LookupTableFlag_28" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_29", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_29", index: 29, oracle: "LookupTableFlag_29" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_30", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_30", index: 30, oracle: "LookupTableFlag_30" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_31", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_31", index: 31, oracle: "LookupTableFlag_31" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_32", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_32", index: 32, oracle: "LookupTableFlag_32" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_33", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_33", index: 33, oracle: "LookupTableFlag_33" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_34", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_34", index: 34, oracle: "LookupTableFlag_34" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_35", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_35", index: 35, oracle: "LookupTableFlag_35" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_36", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_36", index: 36, oracle: "LookupTableFlag_36" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_37", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_37", index: 37, oracle: "LookupTableFlag_37" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_38", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_38", index: 38, oracle: "LookupTableFlag_38" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_39", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_39", index: 39, oracle: "LookupTableFlag_39" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_0", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_0", index: 40, oracle: "InstructionRa_0" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_1", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_1", index: 41, oracle: "InstructionRa_1" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_2", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_2", index: 42, oracle: "InstructionRa_2" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_3", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_3", index: 43, oracle: "InstructionRa_3" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_4", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_4", index: 44, oracle: "InstructionRa_4" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_5", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_5", index: 45, oracle: "InstructionRa_5" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_6", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_6", index: 46, oracle: "InstructionRa_6" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_7", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_7", index: 47, oracle: "InstructionRa_7" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRafFlag", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRafFlag", index: 48, oracle: "InstructionRafFlag" }, + Stage5SumcheckEvalPlan { symbol: "stage5.ram_ra_claim_reduction.eval.RamRa", source: "stage5.sumcheck", name: "stage5.ram_ra_claim_reduction.eval.RamRa", index: 0, oracle: "RamRa" }, + Stage5SumcheckEvalPlan { symbol: "stage5.registers_val_evaluation.eval.RdInc", source: "stage5.sumcheck", name: "stage5.registers_val_evaluation.eval.RdInc", index: 0, oracle: "RdInc" }, + Stage5SumcheckEvalPlan { symbol: "stage5.registers_val_evaluation.eval.RdWa", source: "stage5.sumcheck", name: "stage5.registers_val_evaluation.eval.RdWa", index: 1, oracle: "RdWa" }, +]; + +pub const STAGE5_POINT_SLICES: &[Stage5PointSlicePlan] = &[ + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.Cycle", source: "stage5.instruction_read_raf.instance", offset: 128, length: 18, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_0.address", source: "stage5.instruction_read_raf.instance", offset: 0, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_1.address", source: "stage5.instruction_read_raf.instance", offset: 16, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_2.address", source: "stage5.instruction_read_raf.instance", offset: 32, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_3.address", source: "stage5.instruction_read_raf.instance", offset: 48, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_4.address", source: "stage5.instruction_read_raf.instance", offset: 64, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_5.address", source: "stage5.instruction_read_raf.instance", offset: 80, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_6.address", source: "stage5.instruction_read_raf.instance", offset: 96, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_7.address", source: "stage5.instruction_read_raf.instance", offset: 112, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.ram_ra_claim_reduction.point.RamAddress", source: "stage5.input.stage2.ram_raf.RamRa", offset: 0, length: 14, input: "stage5.input.stage2.ram_raf.RamRa" }, + Stage5PointSlicePlan { symbol: "stage5.registers_val_evaluation.point.RegisterAddress", source: "stage5.input.stage4.registers.RegistersVal", offset: 0, length: 7, input: "stage5.input.stage4.registers.RegistersVal" }, +]; + +pub const STAGE5_POINT_CONCAT_0_INPUTS: &[&str] = &[ + "stage5.instruction_read_raf.point.InstructionRa_0.address", + "stage5.instruction_read_raf.point.Cycle", +]; + +pub const STAGE5_POINT_CONCAT_1_INPUTS: &[&str] = &[ + "stage5.instruction_read_raf.point.InstructionRa_1.address", + "stage5.instruction_read_raf.point.Cycle", +]; + +pub const STAGE5_POINT_CONCAT_2_INPUTS: &[&str] = &[ + "stage5.instruction_read_raf.point.InstructionRa_2.address", + "stage5.instruction_read_raf.point.Cycle", +]; + +pub const STAGE5_POINT_CONCAT_3_INPUTS: &[&str] = &[ + "stage5.instruction_read_raf.point.InstructionRa_3.address", + "stage5.instruction_read_raf.point.Cycle", +]; + +pub const STAGE5_POINT_CONCAT_4_INPUTS: &[&str] = &[ + "stage5.instruction_read_raf.point.InstructionRa_4.address", + "stage5.instruction_read_raf.point.Cycle", +]; + +pub const STAGE5_POINT_CONCAT_5_INPUTS: &[&str] = &[ + "stage5.instruction_read_raf.point.InstructionRa_5.address", + "stage5.instruction_read_raf.point.Cycle", +]; + +pub const STAGE5_POINT_CONCAT_6_INPUTS: &[&str] = &[ + "stage5.instruction_read_raf.point.InstructionRa_6.address", + "stage5.instruction_read_raf.point.Cycle", +]; + +pub const STAGE5_POINT_CONCAT_7_INPUTS: &[&str] = &[ + "stage5.instruction_read_raf.point.InstructionRa_7.address", + "stage5.instruction_read_raf.point.Cycle", +]; + +pub const STAGE5_POINT_CONCAT_8_INPUTS: &[&str] = &[ + "stage5.ram_ra_claim_reduction.point.RamAddress", + "stage5.ram_ra_claim_reduction.instance", +]; + +pub const STAGE5_POINT_CONCAT_9_INPUTS: &[&str] = &[ + "stage5.registers_val_evaluation.point.RegisterAddress", + "stage5.registers_val_evaluation.instance", +]; + +pub const STAGE5_POINT_CONCATS: &[Stage5PointConcatPlan] = &[ + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_0", layout: "address_chunk_then_cycle", arity: 34, inputs: STAGE5_POINT_CONCAT_0_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_1", layout: "address_chunk_then_cycle", arity: 34, inputs: STAGE5_POINT_CONCAT_1_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_2", layout: "address_chunk_then_cycle", arity: 34, inputs: STAGE5_POINT_CONCAT_2_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_3", layout: "address_chunk_then_cycle", arity: 34, inputs: STAGE5_POINT_CONCAT_3_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_4", layout: "address_chunk_then_cycle", arity: 34, inputs: STAGE5_POINT_CONCAT_4_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_5", layout: "address_chunk_then_cycle", arity: 34, inputs: STAGE5_POINT_CONCAT_5_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_6", layout: "address_chunk_then_cycle", arity: 34, inputs: STAGE5_POINT_CONCAT_6_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_7", layout: "address_chunk_then_cycle", arity: 34, inputs: STAGE5_POINT_CONCAT_7_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.ram_ra_claim_reduction.point.RamRa", layout: "address_then_cycle", arity: 32, inputs: STAGE5_POINT_CONCAT_8_INPUTS }, + Stage5PointConcatPlan { symbol: "stage5.registers_val_evaluation.point.RdWa", layout: "register_address_then_cycle", arity: 25, inputs: STAGE5_POINT_CONCAT_9_INPUTS }, +]; +pub const STAGE5_OPENING_CLAIMS: &[Stage5OpeningClaimPlan] = &[ + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_0", oracle: "LookupTableFlag_0", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_0" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_1", oracle: "LookupTableFlag_1", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_1" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_2", oracle: "LookupTableFlag_2", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_2" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_3", oracle: "LookupTableFlag_3", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_3" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_4", oracle: "LookupTableFlag_4", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_4" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_5", oracle: "LookupTableFlag_5", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_5" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_6", oracle: "LookupTableFlag_6", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_6" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_7", oracle: "LookupTableFlag_7", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_7" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_8", oracle: "LookupTableFlag_8", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_8" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_9", oracle: "LookupTableFlag_9", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_9" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_10", oracle: "LookupTableFlag_10", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_10" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_11", oracle: "LookupTableFlag_11", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_11" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_12", oracle: "LookupTableFlag_12", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_12" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_13", oracle: "LookupTableFlag_13", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_13" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_14", oracle: "LookupTableFlag_14", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_14" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_15", oracle: "LookupTableFlag_15", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_15" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_16", oracle: "LookupTableFlag_16", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_16" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_17", oracle: "LookupTableFlag_17", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_17" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_18", oracle: "LookupTableFlag_18", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_18" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_19", oracle: "LookupTableFlag_19", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_19" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_20", oracle: "LookupTableFlag_20", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_20" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_21", oracle: "LookupTableFlag_21", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_21" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_22", oracle: "LookupTableFlag_22", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_22" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_23", oracle: "LookupTableFlag_23", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_23" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_24", oracle: "LookupTableFlag_24", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_24" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_25", oracle: "LookupTableFlag_25", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_25" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_26", oracle: "LookupTableFlag_26", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_26" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_27", oracle: "LookupTableFlag_27", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_27" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_28", oracle: "LookupTableFlag_28", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_28" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_29", oracle: "LookupTableFlag_29", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_29" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_30", oracle: "LookupTableFlag_30", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_30" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_31", oracle: "LookupTableFlag_31", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_31" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_32", oracle: "LookupTableFlag_32", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_32" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_33", oracle: "LookupTableFlag_33", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_33" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_34", oracle: "LookupTableFlag_34", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_34" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_35", oracle: "LookupTableFlag_35", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_35" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_36", oracle: "LookupTableFlag_36", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_36" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_37", oracle: "LookupTableFlag_37", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_37" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_38", oracle: "LookupTableFlag_38", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_38" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_39", oracle: "LookupTableFlag_39", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_39" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_0", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_0" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_1", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_1" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_2", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_2" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_3", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_3" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_4", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_4" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_5", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_5" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_6", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_6" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_7", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_7" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRafFlag", oracle: "InstructionRafFlag", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.InstructionRafFlag" }, + Stage5OpeningClaimPlan { symbol: "stage5.ram_ra_claim_reduction.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage5.ram_ra_claim_reduction.point.RamRa", eval_source: "stage5.ram_ra_claim_reduction.eval.RamRa" }, + Stage5OpeningClaimPlan { symbol: "stage5.registers_val_evaluation.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage5.registers_val_evaluation.instance", eval_source: "stage5.registers_val_evaluation.eval.RdInc" }, + Stage5OpeningClaimPlan { symbol: "stage5.registers_val_evaluation.opening.RdWa", oracle: "RdWa", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage5.registers_val_evaluation.point.RdWa", eval_source: "stage5.registers_val_evaluation.eval.RdWa" }, +]; + +pub const STAGE5_OPENING_EQUALITIES: &[Stage5OpeningClaimEqualityPlan] = &[ + Stage5OpeningClaimEqualityPlan { symbol: "stage5.instruction.lookup_output_claim_consistency", mode: "point_and_eval", lhs: "stage5.input.stage2.instruction.LookupOutput", rhs: "stage5.input.stage2.product_virtual.LookupOutput" }, +]; + +pub const STAGE5_OPENING_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage5.instruction_read_raf.opening.LookupTableFlag_0", + "stage5.instruction_read_raf.opening.LookupTableFlag_1", + "stage5.instruction_read_raf.opening.LookupTableFlag_2", + "stage5.instruction_read_raf.opening.LookupTableFlag_3", + "stage5.instruction_read_raf.opening.LookupTableFlag_4", + "stage5.instruction_read_raf.opening.LookupTableFlag_5", + "stage5.instruction_read_raf.opening.LookupTableFlag_6", + "stage5.instruction_read_raf.opening.LookupTableFlag_7", + "stage5.instruction_read_raf.opening.LookupTableFlag_8", + "stage5.instruction_read_raf.opening.LookupTableFlag_9", + "stage5.instruction_read_raf.opening.LookupTableFlag_10", + "stage5.instruction_read_raf.opening.LookupTableFlag_11", + "stage5.instruction_read_raf.opening.LookupTableFlag_12", + "stage5.instruction_read_raf.opening.LookupTableFlag_13", + "stage5.instruction_read_raf.opening.LookupTableFlag_14", + "stage5.instruction_read_raf.opening.LookupTableFlag_15", + "stage5.instruction_read_raf.opening.LookupTableFlag_16", + "stage5.instruction_read_raf.opening.LookupTableFlag_17", + "stage5.instruction_read_raf.opening.LookupTableFlag_18", + "stage5.instruction_read_raf.opening.LookupTableFlag_19", + "stage5.instruction_read_raf.opening.LookupTableFlag_20", + "stage5.instruction_read_raf.opening.LookupTableFlag_21", + "stage5.instruction_read_raf.opening.LookupTableFlag_22", + "stage5.instruction_read_raf.opening.LookupTableFlag_23", + "stage5.instruction_read_raf.opening.LookupTableFlag_24", + "stage5.instruction_read_raf.opening.LookupTableFlag_25", + "stage5.instruction_read_raf.opening.LookupTableFlag_26", + "stage5.instruction_read_raf.opening.LookupTableFlag_27", + "stage5.instruction_read_raf.opening.LookupTableFlag_28", + "stage5.instruction_read_raf.opening.LookupTableFlag_29", + "stage5.instruction_read_raf.opening.LookupTableFlag_30", + "stage5.instruction_read_raf.opening.LookupTableFlag_31", + "stage5.instruction_read_raf.opening.LookupTableFlag_32", + "stage5.instruction_read_raf.opening.LookupTableFlag_33", + "stage5.instruction_read_raf.opening.LookupTableFlag_34", + "stage5.instruction_read_raf.opening.LookupTableFlag_35", + "stage5.instruction_read_raf.opening.LookupTableFlag_36", + "stage5.instruction_read_raf.opening.LookupTableFlag_37", + "stage5.instruction_read_raf.opening.LookupTableFlag_38", + "stage5.instruction_read_raf.opening.LookupTableFlag_39", + "stage5.instruction_read_raf.opening.InstructionRa_0", + "stage5.instruction_read_raf.opening.InstructionRa_1", + "stage5.instruction_read_raf.opening.InstructionRa_2", + "stage5.instruction_read_raf.opening.InstructionRa_3", + "stage5.instruction_read_raf.opening.InstructionRa_4", + "stage5.instruction_read_raf.opening.InstructionRa_5", + "stage5.instruction_read_raf.opening.InstructionRa_6", + "stage5.instruction_read_raf.opening.InstructionRa_7", + "stage5.instruction_read_raf.opening.InstructionRafFlag", + "stage5.ram_ra_claim_reduction.opening.RamRa", + "stage5.registers_val_evaluation.opening.RdInc", + "stage5.registers_val_evaluation.opening.RdWa", +]; + +pub const STAGE5_OPENING_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage5.instruction_read_raf.opening.LookupTableFlag_0", + "stage5.instruction_read_raf.opening.LookupTableFlag_1", + "stage5.instruction_read_raf.opening.LookupTableFlag_2", + "stage5.instruction_read_raf.opening.LookupTableFlag_3", + "stage5.instruction_read_raf.opening.LookupTableFlag_4", + "stage5.instruction_read_raf.opening.LookupTableFlag_5", + "stage5.instruction_read_raf.opening.LookupTableFlag_6", + "stage5.instruction_read_raf.opening.LookupTableFlag_7", + "stage5.instruction_read_raf.opening.LookupTableFlag_8", + "stage5.instruction_read_raf.opening.LookupTableFlag_9", + "stage5.instruction_read_raf.opening.LookupTableFlag_10", + "stage5.instruction_read_raf.opening.LookupTableFlag_11", + "stage5.instruction_read_raf.opening.LookupTableFlag_12", + "stage5.instruction_read_raf.opening.LookupTableFlag_13", + "stage5.instruction_read_raf.opening.LookupTableFlag_14", + "stage5.instruction_read_raf.opening.LookupTableFlag_15", + "stage5.instruction_read_raf.opening.LookupTableFlag_16", + "stage5.instruction_read_raf.opening.LookupTableFlag_17", + "stage5.instruction_read_raf.opening.LookupTableFlag_18", + "stage5.instruction_read_raf.opening.LookupTableFlag_19", + "stage5.instruction_read_raf.opening.LookupTableFlag_20", + "stage5.instruction_read_raf.opening.LookupTableFlag_21", + "stage5.instruction_read_raf.opening.LookupTableFlag_22", + "stage5.instruction_read_raf.opening.LookupTableFlag_23", + "stage5.instruction_read_raf.opening.LookupTableFlag_24", + "stage5.instruction_read_raf.opening.LookupTableFlag_25", + "stage5.instruction_read_raf.opening.LookupTableFlag_26", + "stage5.instruction_read_raf.opening.LookupTableFlag_27", + "stage5.instruction_read_raf.opening.LookupTableFlag_28", + "stage5.instruction_read_raf.opening.LookupTableFlag_29", + "stage5.instruction_read_raf.opening.LookupTableFlag_30", + "stage5.instruction_read_raf.opening.LookupTableFlag_31", + "stage5.instruction_read_raf.opening.LookupTableFlag_32", + "stage5.instruction_read_raf.opening.LookupTableFlag_33", + "stage5.instruction_read_raf.opening.LookupTableFlag_34", + "stage5.instruction_read_raf.opening.LookupTableFlag_35", + "stage5.instruction_read_raf.opening.LookupTableFlag_36", + "stage5.instruction_read_raf.opening.LookupTableFlag_37", + "stage5.instruction_read_raf.opening.LookupTableFlag_38", + "stage5.instruction_read_raf.opening.LookupTableFlag_39", + "stage5.instruction_read_raf.opening.InstructionRa_0", + "stage5.instruction_read_raf.opening.InstructionRa_1", + "stage5.instruction_read_raf.opening.InstructionRa_2", + "stage5.instruction_read_raf.opening.InstructionRa_3", + "stage5.instruction_read_raf.opening.InstructionRa_4", + "stage5.instruction_read_raf.opening.InstructionRa_5", + "stage5.instruction_read_raf.opening.InstructionRa_6", + "stage5.instruction_read_raf.opening.InstructionRa_7", + "stage5.instruction_read_raf.opening.InstructionRafFlag", + "stage5.ram_ra_claim_reduction.opening.RamRa", + "stage5.registers_val_evaluation.opening.RdInc", + "stage5.registers_val_evaluation.opening.RdWa", +]; + +pub const STAGE5_OPENING_BATCHES: &[Stage5OpeningBatchPlan] = &[ + Stage5OpeningBatchPlan { symbol: "stage5.openings", stage: "stage5", proof_slot: "stage5.openings", policy: "jolt_stage5_output_order", count: 52, ordered_claims: STAGE5_OPENING_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE5_OPENING_BATCH_0_CLAIM_OPERANDS }, +]; +pub const STAGE5_PROGRAM: Stage5CpuProgramPlan = Stage5CpuProgramPlan { + role: "prover", + params: STAGE5_PARAMS, + steps: STAGE5_PROGRAM_STEPS, + transcript_squeezes: STAGE5_TRANSCRIPT_SQUEEZES, + transcript_absorb_bytes: STAGE5_TRANSCRIPT_ABSORB_BYTES, + opening_inputs: STAGE5_OPENING_INPUTS, + field_constants: STAGE5_FIELD_CONSTANTS, + field_exprs: STAGE5_FIELD_EXPRS, + kernels: STAGE5_KERNELS, + claims: STAGE5_SUMCHECK_CLAIMS, + batches: STAGE5_SUMCHECK_BATCHES, + drivers: STAGE5_SUMCHECK_DRIVERS, + instance_results: STAGE5_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE5_SUMCHECK_EVALS, + point_slices: STAGE5_POINT_SLICES, + point_concats: STAGE5_POINT_CONCATS, + opening_claims: STAGE5_OPENING_CLAIMS, + opening_equalities: STAGE5_OPENING_EQUALITIES, + opening_batches: STAGE5_OPENING_BATCHES, +}; + +pub fn execute_stage5_prover( + executor: &mut E, + transcript: &mut T, +) -> Result, Stage5KernelError> +where + E: Stage5KernelExecutor, + T: Transcript, +{ + execute_stage5_prover_with_program(&STAGE5_PROGRAM, executor, transcript) +} + +pub fn execute_stage5_prover_with_program( + program: &'static Stage5CpuProgramPlan, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage5KernelError> +where + E: Stage5KernelExecutor, + T: Transcript, +{ + execute_stage5_program(program, Stage5ExecutionMode::Prover, executor, transcript) +} diff --git a/crates/jolt-prover/src/stages/stage6.rs b/crates/jolt-prover/src/stages/stage6.rs new file mode 100644 index 0000000000..2a5c837b2c --- /dev/null +++ b/crates/jolt-prover/src/stages/stage6.rs @@ -0,0 +1,2537 @@ +#![allow(dead_code)] + +use jolt_field::Fr; +use jolt_kernels::stage6::{execute_stage6_program, Stage6CpuProgramPlan, Stage6ExecutionArtifacts, Stage6ExecutionMode, Stage6FieldConstantPlan, Stage6FieldExprPlan, Stage6KernelError, Stage6KernelExecutor, Stage6KernelPlan, Stage6OpeningBatchPlan, Stage6OpeningClaimEqualityPlan, Stage6OpeningClaimPlan, Stage6OpeningInputPlan, Stage6Params, Stage6PointConcatPlan, Stage6PointSlicePlan, Stage6PointZeroPlan, Stage6ProgramStepPlan, Stage6SumcheckBatchPlan, Stage6SumcheckClaimPlan, Stage6SumcheckDriverPlan, Stage6SumcheckEvalPlan, Stage6SumcheckInstanceResultPlan, Stage6TranscriptAbsorbBytesPlan, Stage6TranscriptSqueezePlan}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage6Transcript = Blake2bTranscript; + +pub const STAGE6_PARAMS: Stage6Params = Stage6Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE6_PROGRAM_STEPS: &[Stage6ProgramStepPlan] = &[ + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage1_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage2_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage3_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage4_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage5_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.booleanity.gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.instruction_ra_virtual.gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.inc_claim_reduction.gamma" }, + Stage6ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage6.sumcheck" }, +]; + +pub const STAGE6_TRANSCRIPT_SQUEEZES: &[Stage6TranscriptSqueezePlan] = &[ + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.gamma", label: "bc_raf_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage1_gamma", label: "bc_raf_stage1_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage2_gamma", label: "bc_raf_stage2_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage3_gamma", label: "bc_raf_stage3_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage4_gamma", label: "bc_raf_stage4_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage5_gamma", label: "bc_raf_stage5_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.booleanity.gamma", label: "booleanity_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.instruction_ra_virtual.gamma", label: "inst_ra_virtual_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.inc_claim_reduction.gamma", label: "inc_reduction_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE6_TRANSCRIPT_ABSORB_BYTES: &[Stage6TranscriptAbsorbBytesPlan] = &[ + +]; + +pub const STAGE6_OPENING_INPUTS: &[Stage6OpeningInputPlan] = &[ + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.UnexpandedPC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.Imm", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.Imm", oracle: "Imm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagAddOperands", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagAddOperands", oracle: "OpFlagAddOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagSubtractOperands", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagSubtractOperands", oracle: "OpFlagSubtractOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagMultiplyOperands", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagMultiplyOperands", oracle: "OpFlagMultiplyOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagLoad", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagLoad", oracle: "OpFlagLoad", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagStore", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagStore", oracle: "OpFlagStore", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagJump", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagJump", oracle: "OpFlagJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagWriteLookupOutputToRD", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagWriteLookupOutputToRD", oracle: "OpFlagWriteLookupOutputToRD", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagVirtualInstruction", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagAssert", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagAssert", oracle: "OpFlagAssert", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagDoNotUpdateUnexpandedPC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagDoNotUpdateUnexpandedPC", oracle: "OpFlagDoNotUpdateUnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagAdvice", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagAdvice", oracle: "OpFlagAdvice", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagIsCompressed", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagIsCompressed", oracle: "OpFlagIsCompressed", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagIsFirstInSequence", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagIsFirstInSequence", oracle: "OpFlagIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagIsLastInSequence", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagIsLastInSequence", oracle: "OpFlagIsLastInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.OpFlagJump", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.OpFlagJump", oracle: "OpFlagJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.InstructionFlagBranch", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.InstructionFlagBranch", oracle: "InstructionFlagBranch", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.OpFlagWriteLookupOutputToRD", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.OpFlagWriteLookupOutputToRD", oracle: "OpFlagWriteLookupOutputToRD", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.OpFlagVirtualInstruction", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.Imm", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.Imm", oracle: "Imm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.UnexpandedPC", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsRs1Value", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value", oracle: "InstructionFlagLeftOperandIsRs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsPC", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC", oracle: "InstructionFlagLeftOperandIsPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsRs2Value", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value", oracle: "InstructionFlagRightOperandIsRs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsImm", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.InstructionFlagRightOperandIsImm", oracle: "InstructionFlagRightOperandIsImm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.InstructionFlagIsNoop", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.InstructionFlagIsNoop", oracle: "InstructionFlagIsNoop", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.OpFlagVirtualInstruction", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.OpFlagIsFirstInSequence", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.OpFlagIsFirstInSequence", oracle: "OpFlagIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.RdWa", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.RdWa", oracle: "RdWa", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.Rs1Ra", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.Rs1Ra", oracle: "Rs1Ra", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.Rs2Ra", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.Rs2Ra", oracle: "Rs2Ra", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.registers_val_evaluation.RdWa", source_stage: "stage5", source_claim: "stage5.registers_val_evaluation.opening.RdWa", oracle: "RdWa", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.InstructionRafFlag", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRafFlag", oracle: "InstructionRafFlag", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_0", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_0", oracle: "LookupTableFlag_0", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_1", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_1", oracle: "LookupTableFlag_1", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_2", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_2", oracle: "LookupTableFlag_2", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_3", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_3", oracle: "LookupTableFlag_3", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_4", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_4", oracle: "LookupTableFlag_4", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_5", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_5", oracle: "LookupTableFlag_5", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_6", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_6", oracle: "LookupTableFlag_6", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_7", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_7", oracle: "LookupTableFlag_7", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_8", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_8", oracle: "LookupTableFlag_8", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_9", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_9", oracle: "LookupTableFlag_9", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_10", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_10", oracle: "LookupTableFlag_10", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_11", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_11", oracle: "LookupTableFlag_11", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_12", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_12", oracle: "LookupTableFlag_12", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_13", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_13", oracle: "LookupTableFlag_13", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_14", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_14", oracle: "LookupTableFlag_14", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_15", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_15", oracle: "LookupTableFlag_15", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_16", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_16", oracle: "LookupTableFlag_16", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_17", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_17", oracle: "LookupTableFlag_17", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_18", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_18", oracle: "LookupTableFlag_18", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_19", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_19", oracle: "LookupTableFlag_19", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_20", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_20", oracle: "LookupTableFlag_20", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_21", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_21", oracle: "LookupTableFlag_21", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_22", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_22", oracle: "LookupTableFlag_22", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_23", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_23", oracle: "LookupTableFlag_23", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_24", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_24", oracle: "LookupTableFlag_24", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_25", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_25", oracle: "LookupTableFlag_25", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_26", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_26", oracle: "LookupTableFlag_26", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_27", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_27", oracle: "LookupTableFlag_27", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_28", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_28", oracle: "LookupTableFlag_28", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_29", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_29", oracle: "LookupTableFlag_29", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_30", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_30", oracle: "LookupTableFlag_30", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_31", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_31", oracle: "LookupTableFlag_31", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_32", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_32", oracle: "LookupTableFlag_32", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_33", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_33", oracle: "LookupTableFlag_33", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_34", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_34", oracle: "LookupTableFlag_34", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_35", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_35", oracle: "LookupTableFlag_35", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_36", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_36", oracle: "LookupTableFlag_36", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_37", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_37", oracle: "LookupTableFlag_37", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_38", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_38", oracle: "LookupTableFlag_38", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_39", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_39", oracle: "LookupTableFlag_39", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.PC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.PC", oracle: "PC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.PC", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.PC", oracle: "PC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", source_stage: "stage5", source_claim: "stage5.ram_ra_claim_reduction.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.LookupOutput", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.ram_read_write.RamInc", source_stage: "stage2", source_claim: "stage2.ram_read_write.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.ram_val_check.RamInc", source_stage: "stage4", source_claim: "stage4.ram_val_check.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.registers_read_write.RdInc", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.registers_val_evaluation.RdInc", source_stage: "stage5", source_claim: "stage5.registers_val_evaluation.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed" }, +]; + +pub const STAGE6_FIELD_CONSTANTS: &[Stage6FieldConstantPlan] = &[ + Stage6FieldConstantPlan { symbol: "stage6.zero", field: "bn254_fr", value: 0 }, +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_0: &[&str] = &["stage6.booleanity.gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_1: &[&str] = &["stage6.bytecode_read_raf.stage1_gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_2: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term1.stage_gamma_pow", + "stage6.input.stage1.Imm", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_3: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term2.stage_gamma_pow", + "stage6.input.stage1.OpFlagAddOperands", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_4: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term3.stage_gamma_pow", + "stage6.input.stage1.OpFlagSubtractOperands", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_5: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term4.stage_gamma_pow", + "stage6.input.stage1.OpFlagMultiplyOperands", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_6: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term5.stage_gamma_pow", + "stage6.input.stage1.OpFlagLoad", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_7: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term6.stage_gamma_pow", + "stage6.input.stage1.OpFlagStore", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_8: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term7.stage_gamma_pow", + "stage6.input.stage1.OpFlagJump", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_9: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term8.stage_gamma_pow", + "stage6.input.stage1.OpFlagWriteLookupOutputToRD", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_10: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term9.stage_gamma_pow", + "stage6.input.stage1.OpFlagVirtualInstruction", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_11: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term10.stage_gamma_pow", + "stage6.input.stage1.OpFlagAssert", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_12: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term11.stage_gamma_pow", + "stage6.input.stage1.OpFlagDoNotUpdateUnexpandedPC", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_13: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term12.stage_gamma_pow", + "stage6.input.stage1.OpFlagAdvice", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_14: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term13.stage_gamma_pow", + "stage6.input.stage1.OpFlagIsCompressed", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_15: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term14.stage_gamma_pow", + "stage6.input.stage1.OpFlagIsFirstInSequence", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_16: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term15.stage_gamma_pow", + "stage6.input.stage1.OpFlagIsLastInSequence", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_17: &[&str] = &["stage6.bytecode_read_raf.gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_18: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term16.gamma_pow", + "stage6.input.stage2.OpFlagJump", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_19: &[&str] = &["stage6.bytecode_read_raf.stage2_gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_20: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term17.stage_gamma_pow", + "stage6.input.stage2.InstructionFlagBranch", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_21: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term17.gamma_pow", + "stage6.bytecode_read_raf.claim.term17.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_22: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term18.stage_gamma_pow", + "stage6.input.stage2.OpFlagWriteLookupOutputToRD", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_23: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term18.gamma_pow", + "stage6.bytecode_read_raf.claim.term18.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_24: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term19.stage_gamma_pow", + "stage6.input.stage2.OpFlagVirtualInstruction", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_25: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term19.gamma_pow", + "stage6.bytecode_read_raf.claim.term19.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_26: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term20.gamma_pow", + "stage6.input.stage3.instruction_input.Imm", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_27: &[&str] = &["stage6.bytecode_read_raf.stage3_gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_28: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term21.stage_gamma_pow", + "stage6.input.stage3.spartan_shift.UnexpandedPC", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_29: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term21.gamma_pow", + "stage6.bytecode_read_raf.claim.term21.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_30: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term22.stage_gamma_pow", + "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsRs1Value", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_31: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term22.gamma_pow", + "stage6.bytecode_read_raf.claim.term22.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_32: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term23.stage_gamma_pow", + "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsPC", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_33: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term23.gamma_pow", + "stage6.bytecode_read_raf.claim.term23.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_34: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term24.stage_gamma_pow", + "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsRs2Value", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_35: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term24.gamma_pow", + "stage6.bytecode_read_raf.claim.term24.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_36: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term25.stage_gamma_pow", + "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsImm", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_37: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term25.gamma_pow", + "stage6.bytecode_read_raf.claim.term25.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_38: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term26.stage_gamma_pow", + "stage6.input.stage3.spartan_shift.InstructionFlagIsNoop", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_39: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term26.gamma_pow", + "stage6.bytecode_read_raf.claim.term26.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_40: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term27.stage_gamma_pow", + "stage6.input.stage3.spartan_shift.OpFlagVirtualInstruction", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_41: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term27.gamma_pow", + "stage6.bytecode_read_raf.claim.term27.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_42: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term28.stage_gamma_pow", + "stage6.input.stage3.spartan_shift.OpFlagIsFirstInSequence", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_43: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term28.gamma_pow", + "stage6.bytecode_read_raf.claim.term28.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_44: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term29.gamma_pow", + "stage6.input.stage4.RdWa", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_45: &[&str] = &["stage6.bytecode_read_raf.stage4_gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_46: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term30.stage_gamma_pow", + "stage6.input.stage4.Rs1Ra", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_47: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term30.gamma_pow", + "stage6.bytecode_read_raf.claim.term30.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_48: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term31.stage_gamma_pow", + "stage6.input.stage4.Rs2Ra", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_49: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term31.gamma_pow", + "stage6.bytecode_read_raf.claim.term31.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_50: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term32.gamma_pow", + "stage6.input.stage5.registers_val_evaluation.RdWa", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_51: &[&str] = &["stage6.bytecode_read_raf.stage5_gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_52: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term33.stage_gamma_pow", + "stage6.input.stage5.InstructionRafFlag", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_53: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term33.gamma_pow", + "stage6.bytecode_read_raf.claim.term33.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_54: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term34.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_0", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_55: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term34.gamma_pow", + "stage6.bytecode_read_raf.claim.term34.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_56: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term35.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_1", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_57: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term35.gamma_pow", + "stage6.bytecode_read_raf.claim.term35.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_58: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term36.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_2", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_59: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term36.gamma_pow", + "stage6.bytecode_read_raf.claim.term36.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_60: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term37.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_3", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_61: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term37.gamma_pow", + "stage6.bytecode_read_raf.claim.term37.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_62: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term38.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_4", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_63: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term38.gamma_pow", + "stage6.bytecode_read_raf.claim.term38.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_64: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term39.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_5", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_65: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term39.gamma_pow", + "stage6.bytecode_read_raf.claim.term39.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_66: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term40.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_6", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_67: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term40.gamma_pow", + "stage6.bytecode_read_raf.claim.term40.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_68: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term41.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_7", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_69: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term41.gamma_pow", + "stage6.bytecode_read_raf.claim.term41.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_70: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term42.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_8", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_71: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term42.gamma_pow", + "stage6.bytecode_read_raf.claim.term42.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_72: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term43.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_9", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_73: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term43.gamma_pow", + "stage6.bytecode_read_raf.claim.term43.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_74: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term44.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_10", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_75: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term44.gamma_pow", + "stage6.bytecode_read_raf.claim.term44.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_76: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term45.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_11", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_77: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term45.gamma_pow", + "stage6.bytecode_read_raf.claim.term45.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_78: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term46.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_12", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_79: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term46.gamma_pow", + "stage6.bytecode_read_raf.claim.term46.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_80: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term47.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_13", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_81: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term47.gamma_pow", + "stage6.bytecode_read_raf.claim.term47.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_82: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term48.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_14", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_83: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term48.gamma_pow", + "stage6.bytecode_read_raf.claim.term48.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_84: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term49.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_15", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_85: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term49.gamma_pow", + "stage6.bytecode_read_raf.claim.term49.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_86: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term50.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_16", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_87: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term50.gamma_pow", + "stage6.bytecode_read_raf.claim.term50.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_88: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term51.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_17", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_89: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term51.gamma_pow", + "stage6.bytecode_read_raf.claim.term51.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_90: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term52.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_18", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_91: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term52.gamma_pow", + "stage6.bytecode_read_raf.claim.term52.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_92: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term53.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_19", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_93: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term53.gamma_pow", + "stage6.bytecode_read_raf.claim.term53.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_94: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term54.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_20", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_95: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term54.gamma_pow", + "stage6.bytecode_read_raf.claim.term54.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_96: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term55.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_21", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_97: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term55.gamma_pow", + "stage6.bytecode_read_raf.claim.term55.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_98: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term56.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_22", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_99: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term56.gamma_pow", + "stage6.bytecode_read_raf.claim.term56.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_100: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term57.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_23", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_101: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term57.gamma_pow", + "stage6.bytecode_read_raf.claim.term57.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_102: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term58.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_24", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_103: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term58.gamma_pow", + "stage6.bytecode_read_raf.claim.term58.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_104: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term59.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_25", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_105: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term59.gamma_pow", + "stage6.bytecode_read_raf.claim.term59.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_106: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term60.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_26", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_107: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term60.gamma_pow", + "stage6.bytecode_read_raf.claim.term60.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_108: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term61.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_27", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_109: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term61.gamma_pow", + "stage6.bytecode_read_raf.claim.term61.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_110: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term62.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_28", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_111: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term62.gamma_pow", + "stage6.bytecode_read_raf.claim.term62.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_112: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term63.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_29", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_113: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term63.gamma_pow", + "stage6.bytecode_read_raf.claim.term63.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_114: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term64.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_30", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_115: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term64.gamma_pow", + "stage6.bytecode_read_raf.claim.term64.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_116: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term65.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_31", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_117: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term65.gamma_pow", + "stage6.bytecode_read_raf.claim.term65.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_118: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term66.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_32", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_119: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term66.gamma_pow", + "stage6.bytecode_read_raf.claim.term66.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_120: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term67.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_33", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_121: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term67.gamma_pow", + "stage6.bytecode_read_raf.claim.term67.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_122: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term68.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_34", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_123: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term68.gamma_pow", + "stage6.bytecode_read_raf.claim.term68.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_124: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term69.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_35", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_125: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term69.gamma_pow", + "stage6.bytecode_read_raf.claim.term69.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_126: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term70.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_36", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_127: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term70.gamma_pow", + "stage6.bytecode_read_raf.claim.term70.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_128: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term71.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_37", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_129: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term71.gamma_pow", + "stage6.bytecode_read_raf.claim.term71.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_130: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term72.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_38", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_131: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term72.gamma_pow", + "stage6.bytecode_read_raf.claim.term72.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_132: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term73.stage_gamma_pow", + "stage6.input.stage5.LookupTableFlag_39", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_133: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term73.gamma_pow", + "stage6.bytecode_read_raf.claim.term73.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_134: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term74.gamma_pow", + "stage6.input.stage1.PC", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_135: &[&str] = &[ + "stage6.bytecode_read_raf.claim.term75.gamma_pow", + "stage6.input.stage3.spartan_shift.PC", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_136: &[&str] = &[ + "stage6.input.stage1.UnexpandedPC", + "stage6.bytecode_read_raf.claim.term1.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_137: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial0", + "stage6.bytecode_read_raf.claim.term2.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_138: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial1", + "stage6.bytecode_read_raf.claim.term3.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_139: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial2", + "stage6.bytecode_read_raf.claim.term4.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_140: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial3", + "stage6.bytecode_read_raf.claim.term5.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_141: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial4", + "stage6.bytecode_read_raf.claim.term6.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_142: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial5", + "stage6.bytecode_read_raf.claim.term7.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_143: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial6", + "stage6.bytecode_read_raf.claim.term8.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_144: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial7", + "stage6.bytecode_read_raf.claim.term9.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_145: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial8", + "stage6.bytecode_read_raf.claim.term10.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_146: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial9", + "stage6.bytecode_read_raf.claim.term11.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_147: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial10", + "stage6.bytecode_read_raf.claim.term12.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_148: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial11", + "stage6.bytecode_read_raf.claim.term13.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_149: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial12", + "stage6.bytecode_read_raf.claim.term14.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_150: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial13", + "stage6.bytecode_read_raf.claim.term15.stage_gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_151: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial14", + "stage6.bytecode_read_raf.claim.term16.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_152: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial15", + "stage6.bytecode_read_raf.claim.term17.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_153: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial16", + "stage6.bytecode_read_raf.claim.term18.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_154: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial17", + "stage6.bytecode_read_raf.claim.term19.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_155: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial18", + "stage6.bytecode_read_raf.claim.term20.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_156: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial19", + "stage6.bytecode_read_raf.claim.term21.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_157: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial20", + "stage6.bytecode_read_raf.claim.term22.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_158: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial21", + "stage6.bytecode_read_raf.claim.term23.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_159: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial22", + "stage6.bytecode_read_raf.claim.term24.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_160: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial23", + "stage6.bytecode_read_raf.claim.term25.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_161: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial24", + "stage6.bytecode_read_raf.claim.term26.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_162: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial25", + "stage6.bytecode_read_raf.claim.term27.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_163: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial26", + "stage6.bytecode_read_raf.claim.term28.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_164: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial27", + "stage6.bytecode_read_raf.claim.term29.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_165: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial28", + "stage6.bytecode_read_raf.claim.term30.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_166: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial29", + "stage6.bytecode_read_raf.claim.term31.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_167: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial30", + "stage6.bytecode_read_raf.claim.term32.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_168: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial31", + "stage6.bytecode_read_raf.claim.term33.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_169: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial32", + "stage6.bytecode_read_raf.claim.term34.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_170: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial33", + "stage6.bytecode_read_raf.claim.term35.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_171: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial34", + "stage6.bytecode_read_raf.claim.term36.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_172: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial35", + "stage6.bytecode_read_raf.claim.term37.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_173: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial36", + "stage6.bytecode_read_raf.claim.term38.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_174: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial37", + "stage6.bytecode_read_raf.claim.term39.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_175: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial38", + "stage6.bytecode_read_raf.claim.term40.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_176: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial39", + "stage6.bytecode_read_raf.claim.term41.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_177: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial40", + "stage6.bytecode_read_raf.claim.term42.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_178: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial41", + "stage6.bytecode_read_raf.claim.term43.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_179: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial42", + "stage6.bytecode_read_raf.claim.term44.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_180: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial43", + "stage6.bytecode_read_raf.claim.term45.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_181: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial44", + "stage6.bytecode_read_raf.claim.term46.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_182: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial45", + "stage6.bytecode_read_raf.claim.term47.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_183: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial46", + "stage6.bytecode_read_raf.claim.term48.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_184: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial47", + "stage6.bytecode_read_raf.claim.term49.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_185: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial48", + "stage6.bytecode_read_raf.claim.term50.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_186: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial49", + "stage6.bytecode_read_raf.claim.term51.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_187: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial50", + "stage6.bytecode_read_raf.claim.term52.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_188: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial51", + "stage6.bytecode_read_raf.claim.term53.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_189: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial52", + "stage6.bytecode_read_raf.claim.term54.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_190: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial53", + "stage6.bytecode_read_raf.claim.term55.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_191: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial54", + "stage6.bytecode_read_raf.claim.term56.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_192: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial55", + "stage6.bytecode_read_raf.claim.term57.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_193: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial56", + "stage6.bytecode_read_raf.claim.term58.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_194: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial57", + "stage6.bytecode_read_raf.claim.term59.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_195: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial58", + "stage6.bytecode_read_raf.claim.term60.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_196: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial59", + "stage6.bytecode_read_raf.claim.term61.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_197: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial60", + "stage6.bytecode_read_raf.claim.term62.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_198: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial61", + "stage6.bytecode_read_raf.claim.term63.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_199: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial62", + "stage6.bytecode_read_raf.claim.term64.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_200: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial63", + "stage6.bytecode_read_raf.claim.term65.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_201: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial64", + "stage6.bytecode_read_raf.claim.term66.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_202: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial65", + "stage6.bytecode_read_raf.claim.term67.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_203: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial66", + "stage6.bytecode_read_raf.claim.term68.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_204: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial67", + "stage6.bytecode_read_raf.claim.term69.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_205: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial68", + "stage6.bytecode_read_raf.claim.term70.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_206: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial69", + "stage6.bytecode_read_raf.claim.term71.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_207: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial70", + "stage6.bytecode_read_raf.claim.term72.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_208: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial71", + "stage6.bytecode_read_raf.claim.term73.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_209: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial72", + "stage6.bytecode_read_raf.claim.term74.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_210: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial73", + "stage6.bytecode_read_raf.claim.term75.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_211: &[&str] = &[ + "stage6.bytecode_read_raf.claim_expr.partial74", + "stage6.bytecode_read_raf.claim.entry_constant", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_212: &[&str] = &["stage6.instruction_ra_virtual.gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_213: &[&str] = &[ + "stage6.instruction_ra_virtual.claim.term1.gamma_pow", + "stage6.input.stage5.instruction_read_raf.InstructionRa_1", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_214: &[&str] = &[ + "stage6.instruction_ra_virtual.claim.term2.gamma_pow", + "stage6.input.stage5.instruction_read_raf.InstructionRa_2", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_215: &[&str] = &[ + "stage6.instruction_ra_virtual.claim.term3.gamma_pow", + "stage6.input.stage5.instruction_read_raf.InstructionRa_3", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_216: &[&str] = &[ + "stage6.instruction_ra_virtual.claim.term4.gamma_pow", + "stage6.input.stage5.instruction_read_raf.InstructionRa_4", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_217: &[&str] = &[ + "stage6.instruction_ra_virtual.claim.term5.gamma_pow", + "stage6.input.stage5.instruction_read_raf.InstructionRa_5", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_218: &[&str] = &[ + "stage6.instruction_ra_virtual.claim.term6.gamma_pow", + "stage6.input.stage5.instruction_read_raf.InstructionRa_6", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_219: &[&str] = &[ + "stage6.instruction_ra_virtual.claim.term7.gamma_pow", + "stage6.input.stage5.instruction_read_raf.InstructionRa_7", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_220: &[&str] = &[ + "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + "stage6.instruction_ra_virtual.claim.term1.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_221: &[&str] = &[ + "stage6.instruction_ra_virtual.claim_expr.partial0", + "stage6.instruction_ra_virtual.claim.term2.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_222: &[&str] = &[ + "stage6.instruction_ra_virtual.claim_expr.partial1", + "stage6.instruction_ra_virtual.claim.term3.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_223: &[&str] = &[ + "stage6.instruction_ra_virtual.claim_expr.partial2", + "stage6.instruction_ra_virtual.claim.term4.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_224: &[&str] = &[ + "stage6.instruction_ra_virtual.claim_expr.partial3", + "stage6.instruction_ra_virtual.claim.term5.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_225: &[&str] = &[ + "stage6.instruction_ra_virtual.claim_expr.partial4", + "stage6.instruction_ra_virtual.claim.term6.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_226: &[&str] = &[ + "stage6.instruction_ra_virtual.claim_expr.partial5", + "stage6.instruction_ra_virtual.claim.term7.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_227: &[&str] = &["stage6.inc_claim_reduction.gamma"]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_228: &[&str] = &[ + "stage6.inc_claim_reduction.claim.ram_inc_stage4.gamma_pow", + "stage6.input.stage4.ram_val_check.RamInc", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_229: &[&str] = &[ + "stage6.inc_claim_reduction.claim.rd_inc_stage4.gamma_pow", + "stage6.input.stage4.registers_read_write.RdInc", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_230: &[&str] = &[ + "stage6.inc_claim_reduction.claim.rd_inc_stage5.gamma_pow", + "stage6.input.stage5.registers_val_evaluation.RdInc", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_231: &[&str] = &[ + "stage6.input.stage2.ram_read_write.RamInc", + "stage6.inc_claim_reduction.claim.ram_inc_stage4.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_232: &[&str] = &[ + "stage6.inc_claim_reduction.claim_expr.partial0", + "stage6.inc_claim_reduction.claim.rd_inc_stage4.gamma_term", +]; + +pub const STAGE6_FIELD_EXPR_OPERANDS_233: &[&str] = &[ + "stage6.inc_claim_reduction.claim_expr.partial1", + "stage6.inc_claim_reduction.claim.rd_inc_stage5.gamma_term", +]; + +pub const STAGE6_FIELD_EXPRS: &[Stage6FieldExprPlan] = &[ + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_0", kind: "op", formula: "field.pow:0", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_1", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_2", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_3", kind: "op", formula: "field.pow:6", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_4", kind: "op", formula: "field.pow:8", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_5", kind: "op", formula: "field.pow:10", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_6", kind: "op", formula: "field.pow:12", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_7", kind: "op", formula: "field.pow:14", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_8", kind: "op", formula: "field.pow:16", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_9", kind: "op", formula: "field.pow:18", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_10", kind: "op", formula: "field.pow:20", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_11", kind: "op", formula: "field.pow:22", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_12", kind: "op", formula: "field.pow:24", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_13", kind: "op", formula: "field.pow:26", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_14", kind: "op", formula: "field.pow:28", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_15", kind: "op", formula: "field.pow:30", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_16", kind: "op", formula: "field.pow:32", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_17", kind: "op", formula: "field.pow:34", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_18", kind: "op", formula: "field.pow:36", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_19", kind: "op", formula: "field.pow:38", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_20", kind: "op", formula: "field.pow:40", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_21", kind: "op", formula: "field.pow:42", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_22", kind: "op", formula: "field.pow:44", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_23", kind: "op", formula: "field.pow:46", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_24", kind: "op", formula: "field.pow:48", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_25", kind: "op", formula: "field.pow:50", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_26", kind: "op", formula: "field.pow:52", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_27", kind: "op", formula: "field.pow:54", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_28", kind: "op", formula: "field.pow:56", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_29", kind: "op", formula: "field.pow:58", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_30", kind: "op", formula: "field.pow:60", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_31", kind: "op", formula: "field.pow:62", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_32", kind: "op", formula: "field.pow:64", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_33", kind: "op", formula: "field.pow:66", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_34", kind: "op", formula: "field.pow:68", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_35", kind: "op", formula: "field.pow:70", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_36", kind: "op", formula: "field.pow:72", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_37", kind: "op", formula: "field.pow:74", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_38", kind: "op", formula: "field.pow:76", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_sq_39", kind: "op", formula: "field.pow:78", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_0", kind: "op", formula: "field.pow:0", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_1", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_2", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_3", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_4", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_5", kind: "op", formula: "field.pow:5", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_6", kind: "op", formula: "field.pow:6", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_7", kind: "op", formula: "field.pow:7", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_8", kind: "op", formula: "field.pow:8", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_9", kind: "op", formula: "field.pow:9", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_10", kind: "op", formula: "field.pow:10", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_11", kind: "op", formula: "field.pow:11", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_12", kind: "op", formula: "field.pow:12", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_13", kind: "op", formula: "field.pow:13", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_14", kind: "op", formula: "field.pow:14", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_15", kind: "op", formula: "field.pow:15", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_16", kind: "op", formula: "field.pow:16", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_17", kind: "op", formula: "field.pow:17", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_18", kind: "op", formula: "field.pow:18", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_19", kind: "op", formula: "field.pow:19", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_20", kind: "op", formula: "field.pow:20", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_21", kind: "op", formula: "field.pow:21", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_22", kind: "op", formula: "field.pow:22", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_23", kind: "op", formula: "field.pow:23", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_24", kind: "op", formula: "field.pow:24", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_25", kind: "op", formula: "field.pow:25", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_26", kind: "op", formula: "field.pow:26", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_27", kind: "op", formula: "field.pow:27", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_28", kind: "op", formula: "field.pow:28", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_29", kind: "op", formula: "field.pow:29", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_30", kind: "op", formula: "field.pow:30", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_31", kind: "op", formula: "field.pow:31", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_32", kind: "op", formula: "field.pow:32", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_33", kind: "op", formula: "field.pow:33", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_34", kind: "op", formula: "field.pow:34", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_35", kind: "op", formula: "field.pow:35", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_36", kind: "op", formula: "field.pow:36", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_37", kind: "op", formula: "field.pow:37", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_38", kind: "op", formula: "field.pow:38", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.booleanity.gamma_pow_39", kind: "op", formula: "field.pow:39", operand_names: STAGE6_FIELD_EXPR_OPERANDS_0, operands: STAGE6_FIELD_EXPR_OPERANDS_0 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term1.stage_gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term1.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_2, operands: STAGE6_FIELD_EXPR_OPERANDS_2 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term2.stage_gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term2.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_3, operands: STAGE6_FIELD_EXPR_OPERANDS_3 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term3.stage_gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term3.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_4, operands: STAGE6_FIELD_EXPR_OPERANDS_4 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term4.stage_gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term4.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_5, operands: STAGE6_FIELD_EXPR_OPERANDS_5 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term5.stage_gamma_pow", kind: "op", formula: "field.pow:5", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term5.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_6, operands: STAGE6_FIELD_EXPR_OPERANDS_6 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term6.stage_gamma_pow", kind: "op", formula: "field.pow:6", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term6.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_7, operands: STAGE6_FIELD_EXPR_OPERANDS_7 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term7.stage_gamma_pow", kind: "op", formula: "field.pow:7", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term7.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_8, operands: STAGE6_FIELD_EXPR_OPERANDS_8 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term8.stage_gamma_pow", kind: "op", formula: "field.pow:8", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term8.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_9, operands: STAGE6_FIELD_EXPR_OPERANDS_9 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term9.stage_gamma_pow", kind: "op", formula: "field.pow:9", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term9.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_10, operands: STAGE6_FIELD_EXPR_OPERANDS_10 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term10.stage_gamma_pow", kind: "op", formula: "field.pow:10", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term10.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_11, operands: STAGE6_FIELD_EXPR_OPERANDS_11 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term11.stage_gamma_pow", kind: "op", formula: "field.pow:11", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term11.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_12, operands: STAGE6_FIELD_EXPR_OPERANDS_12 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term12.stage_gamma_pow", kind: "op", formula: "field.pow:12", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term12.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_13, operands: STAGE6_FIELD_EXPR_OPERANDS_13 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term13.stage_gamma_pow", kind: "op", formula: "field.pow:13", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term13.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_14, operands: STAGE6_FIELD_EXPR_OPERANDS_14 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term14.stage_gamma_pow", kind: "op", formula: "field.pow:14", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term14.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_15, operands: STAGE6_FIELD_EXPR_OPERANDS_15 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term15.stage_gamma_pow", kind: "op", formula: "field.pow:15", operand_names: STAGE6_FIELD_EXPR_OPERANDS_1, operands: STAGE6_FIELD_EXPR_OPERANDS_1 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term15.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_16, operands: STAGE6_FIELD_EXPR_OPERANDS_16 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term16.gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term16.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_18, operands: STAGE6_FIELD_EXPR_OPERANDS_18 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term17.stage_gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_19, operands: STAGE6_FIELD_EXPR_OPERANDS_19 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term17.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_20, operands: STAGE6_FIELD_EXPR_OPERANDS_20 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term17.gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term17.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_21, operands: STAGE6_FIELD_EXPR_OPERANDS_21 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term18.stage_gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_19, operands: STAGE6_FIELD_EXPR_OPERANDS_19 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term18.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_22, operands: STAGE6_FIELD_EXPR_OPERANDS_22 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term18.gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term18.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_23, operands: STAGE6_FIELD_EXPR_OPERANDS_23 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term19.stage_gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_19, operands: STAGE6_FIELD_EXPR_OPERANDS_19 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term19.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_24, operands: STAGE6_FIELD_EXPR_OPERANDS_24 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term19.gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term19.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_25, operands: STAGE6_FIELD_EXPR_OPERANDS_25 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term20.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term20.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_26, operands: STAGE6_FIELD_EXPR_OPERANDS_26 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term21.stage_gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_27, operands: STAGE6_FIELD_EXPR_OPERANDS_27 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term21.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_28, operands: STAGE6_FIELD_EXPR_OPERANDS_28 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term21.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term21.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_29, operands: STAGE6_FIELD_EXPR_OPERANDS_29 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term22.stage_gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_27, operands: STAGE6_FIELD_EXPR_OPERANDS_27 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term22.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_30, operands: STAGE6_FIELD_EXPR_OPERANDS_30 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term22.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term22.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_31, operands: STAGE6_FIELD_EXPR_OPERANDS_31 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term23.stage_gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_27, operands: STAGE6_FIELD_EXPR_OPERANDS_27 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term23.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_32, operands: STAGE6_FIELD_EXPR_OPERANDS_32 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term23.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term23.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_33, operands: STAGE6_FIELD_EXPR_OPERANDS_33 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term24.stage_gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_27, operands: STAGE6_FIELD_EXPR_OPERANDS_27 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term24.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_34, operands: STAGE6_FIELD_EXPR_OPERANDS_34 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term24.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term24.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_35, operands: STAGE6_FIELD_EXPR_OPERANDS_35 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term25.stage_gamma_pow", kind: "op", formula: "field.pow:5", operand_names: STAGE6_FIELD_EXPR_OPERANDS_27, operands: STAGE6_FIELD_EXPR_OPERANDS_27 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term25.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_36, operands: STAGE6_FIELD_EXPR_OPERANDS_36 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term25.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term25.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_37, operands: STAGE6_FIELD_EXPR_OPERANDS_37 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term26.stage_gamma_pow", kind: "op", formula: "field.pow:6", operand_names: STAGE6_FIELD_EXPR_OPERANDS_27, operands: STAGE6_FIELD_EXPR_OPERANDS_27 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term26.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_38, operands: STAGE6_FIELD_EXPR_OPERANDS_38 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term26.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term26.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_39, operands: STAGE6_FIELD_EXPR_OPERANDS_39 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term27.stage_gamma_pow", kind: "op", formula: "field.pow:7", operand_names: STAGE6_FIELD_EXPR_OPERANDS_27, operands: STAGE6_FIELD_EXPR_OPERANDS_27 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term27.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_40, operands: STAGE6_FIELD_EXPR_OPERANDS_40 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term27.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term27.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_41, operands: STAGE6_FIELD_EXPR_OPERANDS_41 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term28.stage_gamma_pow", kind: "op", formula: "field.pow:8", operand_names: STAGE6_FIELD_EXPR_OPERANDS_27, operands: STAGE6_FIELD_EXPR_OPERANDS_27 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term28.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_42, operands: STAGE6_FIELD_EXPR_OPERANDS_42 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term28.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term28.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_43, operands: STAGE6_FIELD_EXPR_OPERANDS_43 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term29.gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term29.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_44, operands: STAGE6_FIELD_EXPR_OPERANDS_44 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term30.stage_gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_45, operands: STAGE6_FIELD_EXPR_OPERANDS_45 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term30.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_46, operands: STAGE6_FIELD_EXPR_OPERANDS_46 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term30.gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term30.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_47, operands: STAGE6_FIELD_EXPR_OPERANDS_47 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term31.stage_gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_45, operands: STAGE6_FIELD_EXPR_OPERANDS_45 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term31.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_48, operands: STAGE6_FIELD_EXPR_OPERANDS_48 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term31.gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term31.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_49, operands: STAGE6_FIELD_EXPR_OPERANDS_49 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term32.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term32.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_50, operands: STAGE6_FIELD_EXPR_OPERANDS_50 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term33.stage_gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term33.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_52, operands: STAGE6_FIELD_EXPR_OPERANDS_52 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term33.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term33.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_53, operands: STAGE6_FIELD_EXPR_OPERANDS_53 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term34.stage_gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term34.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_54, operands: STAGE6_FIELD_EXPR_OPERANDS_54 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term34.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term34.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_55, operands: STAGE6_FIELD_EXPR_OPERANDS_55 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term35.stage_gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term35.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_56, operands: STAGE6_FIELD_EXPR_OPERANDS_56 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term35.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term35.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_57, operands: STAGE6_FIELD_EXPR_OPERANDS_57 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term36.stage_gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term36.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_58, operands: STAGE6_FIELD_EXPR_OPERANDS_58 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term36.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term36.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_59, operands: STAGE6_FIELD_EXPR_OPERANDS_59 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term37.stage_gamma_pow", kind: "op", formula: "field.pow:5", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term37.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_60, operands: STAGE6_FIELD_EXPR_OPERANDS_60 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term37.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term37.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_61, operands: STAGE6_FIELD_EXPR_OPERANDS_61 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term38.stage_gamma_pow", kind: "op", formula: "field.pow:6", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term38.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_62, operands: STAGE6_FIELD_EXPR_OPERANDS_62 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term38.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term38.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_63, operands: STAGE6_FIELD_EXPR_OPERANDS_63 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term39.stage_gamma_pow", kind: "op", formula: "field.pow:7", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term39.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_64, operands: STAGE6_FIELD_EXPR_OPERANDS_64 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term39.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term39.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_65, operands: STAGE6_FIELD_EXPR_OPERANDS_65 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term40.stage_gamma_pow", kind: "op", formula: "field.pow:8", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term40.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_66, operands: STAGE6_FIELD_EXPR_OPERANDS_66 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term40.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term40.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_67, operands: STAGE6_FIELD_EXPR_OPERANDS_67 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term41.stage_gamma_pow", kind: "op", formula: "field.pow:9", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term41.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_68, operands: STAGE6_FIELD_EXPR_OPERANDS_68 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term41.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term41.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_69, operands: STAGE6_FIELD_EXPR_OPERANDS_69 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term42.stage_gamma_pow", kind: "op", formula: "field.pow:10", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term42.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_70, operands: STAGE6_FIELD_EXPR_OPERANDS_70 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term42.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term42.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_71, operands: STAGE6_FIELD_EXPR_OPERANDS_71 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term43.stage_gamma_pow", kind: "op", formula: "field.pow:11", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term43.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_72, operands: STAGE6_FIELD_EXPR_OPERANDS_72 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term43.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term43.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_73, operands: STAGE6_FIELD_EXPR_OPERANDS_73 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term44.stage_gamma_pow", kind: "op", formula: "field.pow:12", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term44.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_74, operands: STAGE6_FIELD_EXPR_OPERANDS_74 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term44.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term44.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_75, operands: STAGE6_FIELD_EXPR_OPERANDS_75 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term45.stage_gamma_pow", kind: "op", formula: "field.pow:13", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term45.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_76, operands: STAGE6_FIELD_EXPR_OPERANDS_76 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term45.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term45.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_77, operands: STAGE6_FIELD_EXPR_OPERANDS_77 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term46.stage_gamma_pow", kind: "op", formula: "field.pow:14", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term46.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_78, operands: STAGE6_FIELD_EXPR_OPERANDS_78 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term46.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term46.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_79, operands: STAGE6_FIELD_EXPR_OPERANDS_79 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term47.stage_gamma_pow", kind: "op", formula: "field.pow:15", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term47.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_80, operands: STAGE6_FIELD_EXPR_OPERANDS_80 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term47.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term47.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_81, operands: STAGE6_FIELD_EXPR_OPERANDS_81 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term48.stage_gamma_pow", kind: "op", formula: "field.pow:16", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term48.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_82, operands: STAGE6_FIELD_EXPR_OPERANDS_82 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term48.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term48.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_83, operands: STAGE6_FIELD_EXPR_OPERANDS_83 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term49.stage_gamma_pow", kind: "op", formula: "field.pow:17", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term49.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_84, operands: STAGE6_FIELD_EXPR_OPERANDS_84 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term49.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term49.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_85, operands: STAGE6_FIELD_EXPR_OPERANDS_85 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term50.stage_gamma_pow", kind: "op", formula: "field.pow:18", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term50.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_86, operands: STAGE6_FIELD_EXPR_OPERANDS_86 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term50.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term50.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_87, operands: STAGE6_FIELD_EXPR_OPERANDS_87 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term51.stage_gamma_pow", kind: "op", formula: "field.pow:19", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term51.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_88, operands: STAGE6_FIELD_EXPR_OPERANDS_88 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term51.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term51.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_89, operands: STAGE6_FIELD_EXPR_OPERANDS_89 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term52.stage_gamma_pow", kind: "op", formula: "field.pow:20", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term52.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_90, operands: STAGE6_FIELD_EXPR_OPERANDS_90 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term52.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term52.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_91, operands: STAGE6_FIELD_EXPR_OPERANDS_91 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term53.stage_gamma_pow", kind: "op", formula: "field.pow:21", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term53.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_92, operands: STAGE6_FIELD_EXPR_OPERANDS_92 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term53.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term53.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_93, operands: STAGE6_FIELD_EXPR_OPERANDS_93 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term54.stage_gamma_pow", kind: "op", formula: "field.pow:22", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term54.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_94, operands: STAGE6_FIELD_EXPR_OPERANDS_94 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term54.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term54.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_95, operands: STAGE6_FIELD_EXPR_OPERANDS_95 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term55.stage_gamma_pow", kind: "op", formula: "field.pow:23", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term55.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_96, operands: STAGE6_FIELD_EXPR_OPERANDS_96 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term55.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term55.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_97, operands: STAGE6_FIELD_EXPR_OPERANDS_97 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term56.stage_gamma_pow", kind: "op", formula: "field.pow:24", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term56.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_98, operands: STAGE6_FIELD_EXPR_OPERANDS_98 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term56.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term56.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_99, operands: STAGE6_FIELD_EXPR_OPERANDS_99 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term57.stage_gamma_pow", kind: "op", formula: "field.pow:25", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term57.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_100, operands: STAGE6_FIELD_EXPR_OPERANDS_100 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term57.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term57.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_101, operands: STAGE6_FIELD_EXPR_OPERANDS_101 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term58.stage_gamma_pow", kind: "op", formula: "field.pow:26", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term58.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_102, operands: STAGE6_FIELD_EXPR_OPERANDS_102 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term58.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term58.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_103, operands: STAGE6_FIELD_EXPR_OPERANDS_103 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term59.stage_gamma_pow", kind: "op", formula: "field.pow:27", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term59.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_104, operands: STAGE6_FIELD_EXPR_OPERANDS_104 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term59.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term59.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_105, operands: STAGE6_FIELD_EXPR_OPERANDS_105 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term60.stage_gamma_pow", kind: "op", formula: "field.pow:28", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term60.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_106, operands: STAGE6_FIELD_EXPR_OPERANDS_106 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term60.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term60.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_107, operands: STAGE6_FIELD_EXPR_OPERANDS_107 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term61.stage_gamma_pow", kind: "op", formula: "field.pow:29", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term61.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_108, operands: STAGE6_FIELD_EXPR_OPERANDS_108 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term61.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term61.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_109, operands: STAGE6_FIELD_EXPR_OPERANDS_109 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term62.stage_gamma_pow", kind: "op", formula: "field.pow:30", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term62.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_110, operands: STAGE6_FIELD_EXPR_OPERANDS_110 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term62.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term62.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_111, operands: STAGE6_FIELD_EXPR_OPERANDS_111 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term63.stage_gamma_pow", kind: "op", formula: "field.pow:31", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term63.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_112, operands: STAGE6_FIELD_EXPR_OPERANDS_112 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term63.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term63.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_113, operands: STAGE6_FIELD_EXPR_OPERANDS_113 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term64.stage_gamma_pow", kind: "op", formula: "field.pow:32", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term64.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_114, operands: STAGE6_FIELD_EXPR_OPERANDS_114 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term64.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term64.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_115, operands: STAGE6_FIELD_EXPR_OPERANDS_115 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term65.stage_gamma_pow", kind: "op", formula: "field.pow:33", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term65.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_116, operands: STAGE6_FIELD_EXPR_OPERANDS_116 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term65.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term65.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_117, operands: STAGE6_FIELD_EXPR_OPERANDS_117 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term66.stage_gamma_pow", kind: "op", formula: "field.pow:34", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term66.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_118, operands: STAGE6_FIELD_EXPR_OPERANDS_118 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term66.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term66.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_119, operands: STAGE6_FIELD_EXPR_OPERANDS_119 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term67.stage_gamma_pow", kind: "op", formula: "field.pow:35", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term67.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_120, operands: STAGE6_FIELD_EXPR_OPERANDS_120 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term67.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term67.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_121, operands: STAGE6_FIELD_EXPR_OPERANDS_121 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term68.stage_gamma_pow", kind: "op", formula: "field.pow:36", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term68.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_122, operands: STAGE6_FIELD_EXPR_OPERANDS_122 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term68.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term68.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_123, operands: STAGE6_FIELD_EXPR_OPERANDS_123 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term69.stage_gamma_pow", kind: "op", formula: "field.pow:37", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term69.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_124, operands: STAGE6_FIELD_EXPR_OPERANDS_124 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term69.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term69.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_125, operands: STAGE6_FIELD_EXPR_OPERANDS_125 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term70.stage_gamma_pow", kind: "op", formula: "field.pow:38", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term70.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_126, operands: STAGE6_FIELD_EXPR_OPERANDS_126 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term70.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term70.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_127, operands: STAGE6_FIELD_EXPR_OPERANDS_127 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term71.stage_gamma_pow", kind: "op", formula: "field.pow:39", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term71.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_128, operands: STAGE6_FIELD_EXPR_OPERANDS_128 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term71.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term71.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_129, operands: STAGE6_FIELD_EXPR_OPERANDS_129 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term72.stage_gamma_pow", kind: "op", formula: "field.pow:40", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term72.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_130, operands: STAGE6_FIELD_EXPR_OPERANDS_130 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term72.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term72.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_131, operands: STAGE6_FIELD_EXPR_OPERANDS_131 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term73.stage_gamma_pow", kind: "op", formula: "field.pow:41", operand_names: STAGE6_FIELD_EXPR_OPERANDS_51, operands: STAGE6_FIELD_EXPR_OPERANDS_51 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term73.stage_gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_132, operands: STAGE6_FIELD_EXPR_OPERANDS_132 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term73.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term73.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_133, operands: STAGE6_FIELD_EXPR_OPERANDS_133 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term74.gamma_pow", kind: "op", formula: "field.pow:5", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term74.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_134, operands: STAGE6_FIELD_EXPR_OPERANDS_134 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term75.gamma_pow", kind: "op", formula: "field.pow:6", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.term75.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_135, operands: STAGE6_FIELD_EXPR_OPERANDS_135 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim.entry_constant", kind: "op", formula: "field.pow:7", operand_names: STAGE6_FIELD_EXPR_OPERANDS_17, operands: STAGE6_FIELD_EXPR_OPERANDS_17 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial0", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_136, operands: STAGE6_FIELD_EXPR_OPERANDS_136 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial1", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_137, operands: STAGE6_FIELD_EXPR_OPERANDS_137 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial2", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_138, operands: STAGE6_FIELD_EXPR_OPERANDS_138 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial3", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_139, operands: STAGE6_FIELD_EXPR_OPERANDS_139 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial4", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_140, operands: STAGE6_FIELD_EXPR_OPERANDS_140 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial5", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_141, operands: STAGE6_FIELD_EXPR_OPERANDS_141 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial6", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_142, operands: STAGE6_FIELD_EXPR_OPERANDS_142 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial7", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_143, operands: STAGE6_FIELD_EXPR_OPERANDS_143 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial8", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_144, operands: STAGE6_FIELD_EXPR_OPERANDS_144 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial9", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_145, operands: STAGE6_FIELD_EXPR_OPERANDS_145 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial10", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_146, operands: STAGE6_FIELD_EXPR_OPERANDS_146 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial11", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_147, operands: STAGE6_FIELD_EXPR_OPERANDS_147 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial12", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_148, operands: STAGE6_FIELD_EXPR_OPERANDS_148 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial13", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_149, operands: STAGE6_FIELD_EXPR_OPERANDS_149 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial14", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_150, operands: STAGE6_FIELD_EXPR_OPERANDS_150 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial15", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_151, operands: STAGE6_FIELD_EXPR_OPERANDS_151 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial16", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_152, operands: STAGE6_FIELD_EXPR_OPERANDS_152 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial17", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_153, operands: STAGE6_FIELD_EXPR_OPERANDS_153 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial18", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_154, operands: STAGE6_FIELD_EXPR_OPERANDS_154 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial19", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_155, operands: STAGE6_FIELD_EXPR_OPERANDS_155 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial20", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_156, operands: STAGE6_FIELD_EXPR_OPERANDS_156 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial21", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_157, operands: STAGE6_FIELD_EXPR_OPERANDS_157 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial22", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_158, operands: STAGE6_FIELD_EXPR_OPERANDS_158 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial23", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_159, operands: STAGE6_FIELD_EXPR_OPERANDS_159 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial24", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_160, operands: STAGE6_FIELD_EXPR_OPERANDS_160 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial25", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_161, operands: STAGE6_FIELD_EXPR_OPERANDS_161 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial26", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_162, operands: STAGE6_FIELD_EXPR_OPERANDS_162 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial27", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_163, operands: STAGE6_FIELD_EXPR_OPERANDS_163 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial28", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_164, operands: STAGE6_FIELD_EXPR_OPERANDS_164 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial29", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_165, operands: STAGE6_FIELD_EXPR_OPERANDS_165 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial30", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_166, operands: STAGE6_FIELD_EXPR_OPERANDS_166 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial31", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_167, operands: STAGE6_FIELD_EXPR_OPERANDS_167 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial32", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_168, operands: STAGE6_FIELD_EXPR_OPERANDS_168 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial33", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_169, operands: STAGE6_FIELD_EXPR_OPERANDS_169 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial34", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_170, operands: STAGE6_FIELD_EXPR_OPERANDS_170 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial35", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_171, operands: STAGE6_FIELD_EXPR_OPERANDS_171 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial36", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_172, operands: STAGE6_FIELD_EXPR_OPERANDS_172 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial37", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_173, operands: STAGE6_FIELD_EXPR_OPERANDS_173 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial38", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_174, operands: STAGE6_FIELD_EXPR_OPERANDS_174 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial39", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_175, operands: STAGE6_FIELD_EXPR_OPERANDS_175 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial40", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_176, operands: STAGE6_FIELD_EXPR_OPERANDS_176 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial41", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_177, operands: STAGE6_FIELD_EXPR_OPERANDS_177 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial42", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_178, operands: STAGE6_FIELD_EXPR_OPERANDS_178 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial43", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_179, operands: STAGE6_FIELD_EXPR_OPERANDS_179 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial44", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_180, operands: STAGE6_FIELD_EXPR_OPERANDS_180 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial45", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_181, operands: STAGE6_FIELD_EXPR_OPERANDS_181 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial46", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_182, operands: STAGE6_FIELD_EXPR_OPERANDS_182 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial47", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_183, operands: STAGE6_FIELD_EXPR_OPERANDS_183 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial48", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_184, operands: STAGE6_FIELD_EXPR_OPERANDS_184 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial49", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_185, operands: STAGE6_FIELD_EXPR_OPERANDS_185 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial50", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_186, operands: STAGE6_FIELD_EXPR_OPERANDS_186 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial51", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_187, operands: STAGE6_FIELD_EXPR_OPERANDS_187 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial52", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_188, operands: STAGE6_FIELD_EXPR_OPERANDS_188 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial53", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_189, operands: STAGE6_FIELD_EXPR_OPERANDS_189 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial54", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_190, operands: STAGE6_FIELD_EXPR_OPERANDS_190 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial55", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_191, operands: STAGE6_FIELD_EXPR_OPERANDS_191 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial56", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_192, operands: STAGE6_FIELD_EXPR_OPERANDS_192 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial57", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_193, operands: STAGE6_FIELD_EXPR_OPERANDS_193 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial58", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_194, operands: STAGE6_FIELD_EXPR_OPERANDS_194 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial59", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_195, operands: STAGE6_FIELD_EXPR_OPERANDS_195 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial60", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_196, operands: STAGE6_FIELD_EXPR_OPERANDS_196 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial61", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_197, operands: STAGE6_FIELD_EXPR_OPERANDS_197 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial62", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_198, operands: STAGE6_FIELD_EXPR_OPERANDS_198 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial63", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_199, operands: STAGE6_FIELD_EXPR_OPERANDS_199 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial64", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_200, operands: STAGE6_FIELD_EXPR_OPERANDS_200 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial65", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_201, operands: STAGE6_FIELD_EXPR_OPERANDS_201 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial66", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_202, operands: STAGE6_FIELD_EXPR_OPERANDS_202 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial67", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_203, operands: STAGE6_FIELD_EXPR_OPERANDS_203 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial68", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_204, operands: STAGE6_FIELD_EXPR_OPERANDS_204 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial69", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_205, operands: STAGE6_FIELD_EXPR_OPERANDS_205 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial70", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_206, operands: STAGE6_FIELD_EXPR_OPERANDS_206 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial71", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_207, operands: STAGE6_FIELD_EXPR_OPERANDS_207 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial72", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_208, operands: STAGE6_FIELD_EXPR_OPERANDS_208 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial73", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_209, operands: STAGE6_FIELD_EXPR_OPERANDS_209 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial74", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_210, operands: STAGE6_FIELD_EXPR_OPERANDS_210 }, + Stage6FieldExprPlan { symbol: "stage6.bytecode_read_raf.claim_expr.partial75", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_211, operands: STAGE6_FIELD_EXPR_OPERANDS_211 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term1.gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_212, operands: STAGE6_FIELD_EXPR_OPERANDS_212 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term1.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_213, operands: STAGE6_FIELD_EXPR_OPERANDS_213 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term2.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_212, operands: STAGE6_FIELD_EXPR_OPERANDS_212 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term2.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_214, operands: STAGE6_FIELD_EXPR_OPERANDS_214 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term3.gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_212, operands: STAGE6_FIELD_EXPR_OPERANDS_212 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term3.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_215, operands: STAGE6_FIELD_EXPR_OPERANDS_215 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term4.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE6_FIELD_EXPR_OPERANDS_212, operands: STAGE6_FIELD_EXPR_OPERANDS_212 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term4.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_216, operands: STAGE6_FIELD_EXPR_OPERANDS_216 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term5.gamma_pow", kind: "op", formula: "field.pow:5", operand_names: STAGE6_FIELD_EXPR_OPERANDS_212, operands: STAGE6_FIELD_EXPR_OPERANDS_212 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term5.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_217, operands: STAGE6_FIELD_EXPR_OPERANDS_217 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term6.gamma_pow", kind: "op", formula: "field.pow:6", operand_names: STAGE6_FIELD_EXPR_OPERANDS_212, operands: STAGE6_FIELD_EXPR_OPERANDS_212 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term6.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_218, operands: STAGE6_FIELD_EXPR_OPERANDS_218 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term7.gamma_pow", kind: "op", formula: "field.pow:7", operand_names: STAGE6_FIELD_EXPR_OPERANDS_212, operands: STAGE6_FIELD_EXPR_OPERANDS_212 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim.term7.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_219, operands: STAGE6_FIELD_EXPR_OPERANDS_219 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim_expr.partial0", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_220, operands: STAGE6_FIELD_EXPR_OPERANDS_220 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim_expr.partial1", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_221, operands: STAGE6_FIELD_EXPR_OPERANDS_221 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim_expr.partial2", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_222, operands: STAGE6_FIELD_EXPR_OPERANDS_222 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim_expr.partial3", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_223, operands: STAGE6_FIELD_EXPR_OPERANDS_223 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim_expr.partial4", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_224, operands: STAGE6_FIELD_EXPR_OPERANDS_224 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim_expr.partial5", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_225, operands: STAGE6_FIELD_EXPR_OPERANDS_225 }, + Stage6FieldExprPlan { symbol: "stage6.instruction_ra_virtual.claim_expr.partial6", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_226, operands: STAGE6_FIELD_EXPR_OPERANDS_226 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim.ram_inc_stage4.gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE6_FIELD_EXPR_OPERANDS_227, operands: STAGE6_FIELD_EXPR_OPERANDS_227 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim.ram_inc_stage4.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_228, operands: STAGE6_FIELD_EXPR_OPERANDS_228 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim.rd_inc_stage4.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE6_FIELD_EXPR_OPERANDS_227, operands: STAGE6_FIELD_EXPR_OPERANDS_227 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim.rd_inc_stage4.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_229, operands: STAGE6_FIELD_EXPR_OPERANDS_229 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim.rd_inc_stage5.gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE6_FIELD_EXPR_OPERANDS_227, operands: STAGE6_FIELD_EXPR_OPERANDS_227 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim.rd_inc_stage5.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE6_FIELD_EXPR_OPERANDS_230, operands: STAGE6_FIELD_EXPR_OPERANDS_230 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim_expr.partial0", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_231, operands: STAGE6_FIELD_EXPR_OPERANDS_231 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim_expr.partial1", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_232, operands: STAGE6_FIELD_EXPR_OPERANDS_232 }, + Stage6FieldExprPlan { symbol: "stage6.inc_claim_reduction.claim_expr.partial2", kind: "op", formula: "field.add", operand_names: STAGE6_FIELD_EXPR_OPERANDS_233, operands: STAGE6_FIELD_EXPR_OPERANDS_233 }, +]; +pub const STAGE6_KERNELS: &[Stage6KernelPlan] = &[ + Stage6KernelPlan { symbol: "jolt.cpu.stage6.bytecode_read_raf", relation: "jolt.stage6.bytecode_read_raf", kind: "sumcheck", backend: "cpu", abi: "jolt_stage6_bytecode_read_raf" }, + Stage6KernelPlan { symbol: "jolt.cpu.stage6.booleanity", relation: "jolt.stage6.booleanity", kind: "sumcheck", backend: "cpu", abi: "jolt_stage6_booleanity" }, + Stage6KernelPlan { symbol: "jolt.cpu.stage6.hamming_booleanity", relation: "jolt.stage6.hamming_booleanity", kind: "sumcheck", backend: "cpu", abi: "jolt_stage6_hamming_booleanity" }, + Stage6KernelPlan { symbol: "jolt.cpu.stage6.ram_ra_virtual", relation: "jolt.stage6.ram_ra_virtual", kind: "sumcheck", backend: "cpu", abi: "jolt_stage6_ram_ra_virtual" }, + Stage6KernelPlan { symbol: "jolt.cpu.stage6.instruction_ra_virtual", relation: "jolt.stage6.instruction_ra_virtual", kind: "sumcheck", backend: "cpu", abi: "jolt_stage6_instruction_ra_virtual" }, + Stage6KernelPlan { symbol: "jolt.cpu.stage6.inc_claim_reduction", relation: "jolt.stage6.inc_claim_reduction", kind: "sumcheck", backend: "cpu", abi: "jolt_stage6_inc_claim_reduction" }, + Stage6KernelPlan { symbol: "jolt.cpu.stage6.batched", relation: "jolt.stage6.batched", kind: "sumcheck", backend: "cpu", abi: "jolt_stage6_batched" }, +]; + +pub const STAGE6_SUMCHECK_CLAIM_0_INPUT_OPENINGS: &[&str] = &[ + "stage6.input.stage1.UnexpandedPC", + "stage6.input.stage1.Imm", + "stage6.input.stage1.OpFlagAddOperands", + "stage6.input.stage1.OpFlagSubtractOperands", + "stage6.input.stage1.OpFlagMultiplyOperands", + "stage6.input.stage1.OpFlagLoad", + "stage6.input.stage1.OpFlagStore", + "stage6.input.stage1.OpFlagJump", + "stage6.input.stage1.OpFlagWriteLookupOutputToRD", + "stage6.input.stage1.OpFlagVirtualInstruction", + "stage6.input.stage1.OpFlagAssert", + "stage6.input.stage1.OpFlagDoNotUpdateUnexpandedPC", + "stage6.input.stage1.OpFlagAdvice", + "stage6.input.stage1.OpFlagIsCompressed", + "stage6.input.stage1.OpFlagIsFirstInSequence", + "stage6.input.stage1.OpFlagIsLastInSequence", + "stage6.input.stage2.OpFlagJump", + "stage6.input.stage2.InstructionFlagBranch", + "stage6.input.stage2.OpFlagWriteLookupOutputToRD", + "stage6.input.stage2.OpFlagVirtualInstruction", + "stage6.input.stage3.instruction_input.Imm", + "stage6.input.stage3.spartan_shift.UnexpandedPC", + "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsRs1Value", + "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsPC", + "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsRs2Value", + "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsImm", + "stage6.input.stage3.spartan_shift.InstructionFlagIsNoop", + "stage6.input.stage3.spartan_shift.OpFlagVirtualInstruction", + "stage6.input.stage3.spartan_shift.OpFlagIsFirstInSequence", + "stage6.input.stage4.RdWa", + "stage6.input.stage4.Rs1Ra", + "stage6.input.stage4.Rs2Ra", + "stage6.input.stage5.registers_val_evaluation.RdWa", + "stage6.input.stage5.InstructionRafFlag", + "stage6.input.stage5.LookupTableFlag_0", + "stage6.input.stage5.LookupTableFlag_1", + "stage6.input.stage5.LookupTableFlag_2", + "stage6.input.stage5.LookupTableFlag_3", + "stage6.input.stage5.LookupTableFlag_4", + "stage6.input.stage5.LookupTableFlag_5", + "stage6.input.stage5.LookupTableFlag_6", + "stage6.input.stage5.LookupTableFlag_7", + "stage6.input.stage5.LookupTableFlag_8", + "stage6.input.stage5.LookupTableFlag_9", + "stage6.input.stage5.LookupTableFlag_10", + "stage6.input.stage5.LookupTableFlag_11", + "stage6.input.stage5.LookupTableFlag_12", + "stage6.input.stage5.LookupTableFlag_13", + "stage6.input.stage5.LookupTableFlag_14", + "stage6.input.stage5.LookupTableFlag_15", + "stage6.input.stage5.LookupTableFlag_16", + "stage6.input.stage5.LookupTableFlag_17", + "stage6.input.stage5.LookupTableFlag_18", + "stage6.input.stage5.LookupTableFlag_19", + "stage6.input.stage5.LookupTableFlag_20", + "stage6.input.stage5.LookupTableFlag_21", + "stage6.input.stage5.LookupTableFlag_22", + "stage6.input.stage5.LookupTableFlag_23", + "stage6.input.stage5.LookupTableFlag_24", + "stage6.input.stage5.LookupTableFlag_25", + "stage6.input.stage5.LookupTableFlag_26", + "stage6.input.stage5.LookupTableFlag_27", + "stage6.input.stage5.LookupTableFlag_28", + "stage6.input.stage5.LookupTableFlag_29", + "stage6.input.stage5.LookupTableFlag_30", + "stage6.input.stage5.LookupTableFlag_31", + "stage6.input.stage5.LookupTableFlag_32", + "stage6.input.stage5.LookupTableFlag_33", + "stage6.input.stage5.LookupTableFlag_34", + "stage6.input.stage5.LookupTableFlag_35", + "stage6.input.stage5.LookupTableFlag_36", + "stage6.input.stage5.LookupTableFlag_37", + "stage6.input.stage5.LookupTableFlag_38", + "stage6.input.stage5.LookupTableFlag_39", + "stage6.input.stage1.PC", + "stage6.input.stage3.spartan_shift.PC", +]; + +pub const STAGE6_SUMCHECK_CLAIM_1_INPUT_OPENINGS: &[&str] = &[]; + +pub const STAGE6_SUMCHECK_CLAIM_2_INPUT_OPENINGS: &[&str] = &["stage6.input.stage1.LookupOutput"]; + +pub const STAGE6_SUMCHECK_CLAIM_3_INPUT_OPENINGS: &[&str] = &["stage6.input.stage5.ram_ra_claim_reduction.RamRa"]; + +pub const STAGE6_SUMCHECK_CLAIM_4_INPUT_OPENINGS: &[&str] = &[ + "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + "stage6.input.stage5.instruction_read_raf.InstructionRa_1", + "stage6.input.stage5.instruction_read_raf.InstructionRa_2", + "stage6.input.stage5.instruction_read_raf.InstructionRa_3", + "stage6.input.stage5.instruction_read_raf.InstructionRa_4", + "stage6.input.stage5.instruction_read_raf.InstructionRa_5", + "stage6.input.stage5.instruction_read_raf.InstructionRa_6", + "stage6.input.stage5.instruction_read_raf.InstructionRa_7", +]; + +pub const STAGE6_SUMCHECK_CLAIM_5_INPUT_OPENINGS: &[&str] = &[ + "stage6.input.stage2.ram_read_write.RamInc", + "stage6.input.stage4.ram_val_check.RamInc", + "stage6.input.stage4.registers_read_write.RdInc", + "stage6.input.stage5.registers_val_evaluation.RdInc", +]; + +pub const STAGE6_SUMCHECK_CLAIMS: &[Stage6SumcheckClaimPlan] = &[ + Stage6SumcheckClaimPlan { symbol: "stage6.bytecode_read_raf.input", stage: "stage6", domain: "jolt.stage6_bytecode_read_raf_domain", num_rounds: 32, degree: 5, claim: "stage6.bytecode_read_raf.weighted_prior_stage_values", kernel: Some("jolt.cpu.stage6.bytecode_read_raf"), relation: None, claim_value: "stage6.bytecode_read_raf.claim_expr.partial75", input_openings: STAGE6_SUMCHECK_CLAIM_0_INPUT_OPENINGS }, + Stage6SumcheckClaimPlan { symbol: "stage6.booleanity.input", stage: "stage6", domain: "jolt.stage6_booleanity_domain", num_rounds: 22, degree: 3, claim: "stage6.booleanity.zero", kernel: Some("jolt.cpu.stage6.booleanity"), relation: None, claim_value: "stage6.zero", input_openings: STAGE6_SUMCHECK_CLAIM_1_INPUT_OPENINGS }, + Stage6SumcheckClaimPlan { symbol: "stage6.hamming_booleanity.input", stage: "stage6", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage6.hamming_booleanity.zero", kernel: Some("jolt.cpu.stage6.hamming_booleanity"), relation: None, claim_value: "stage6.zero", input_openings: STAGE6_SUMCHECK_CLAIM_2_INPUT_OPENINGS }, + Stage6SumcheckClaimPlan { symbol: "stage6.ram_ra_virtual.input", stage: "stage6", domain: "jolt.trace_domain", num_rounds: 18, degree: 5, claim: "stage6.ram_ra_virtual.weighted_ram_ra", kernel: Some("jolt.cpu.stage6.ram_ra_virtual"), relation: None, claim_value: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", input_openings: STAGE6_SUMCHECK_CLAIM_3_INPUT_OPENINGS }, + Stage6SumcheckClaimPlan { symbol: "stage6.instruction_ra_virtual.input", stage: "stage6", domain: "jolt.trace_domain", num_rounds: 18, degree: 5, claim: "stage6.instruction_ra_virtual.weighted_instruction_ra", kernel: Some("jolt.cpu.stage6.instruction_ra_virtual"), relation: None, claim_value: "stage6.instruction_ra_virtual.claim_expr.partial6", input_openings: STAGE6_SUMCHECK_CLAIM_4_INPUT_OPENINGS }, + Stage6SumcheckClaimPlan { symbol: "stage6.inc_claim_reduction.input", stage: "stage6", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage6.inc_claim_reduction.weighted_increments", kernel: Some("jolt.cpu.stage6.inc_claim_reduction"), relation: None, claim_value: "stage6.inc_claim_reduction.claim_expr.partial2", input_openings: STAGE6_SUMCHECK_CLAIM_5_INPUT_OPENINGS }, +]; +pub const STAGE6_SUMCHECK_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage6.bytecode_read_raf.input", + "stage6.booleanity.input", + "stage6.hamming_booleanity.input", + "stage6.ram_ra_virtual.input", + "stage6.instruction_ra_virtual.input", + "stage6.inc_claim_reduction.input", +]; + +pub const STAGE6_SUMCHECK_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage6.bytecode_read_raf.input", + "stage6.booleanity.input", + "stage6.hamming_booleanity.input", + "stage6.ram_ra_virtual.input", + "stage6.instruction_ra_virtual.input", + "stage6.inc_claim_reduction.input", +]; + +pub const STAGE6_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 14, + 18, +]; + +pub const STAGE6_SUMCHECK_BATCHES: &[Stage6SumcheckBatchPlan] = &[ + Stage6SumcheckBatchPlan { symbol: "stage6.batch", stage: "stage6", proof_slot: "stage6.sumcheck", policy: "jolt_core_stage6_aligned", count: 6, ordered_claims: STAGE6_SUMCHECK_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE6_SUMCHECK_BATCH_0_CLAIM_OPERANDS, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE6_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE6_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 14, + 18, +]; + +pub const STAGE6_SUMCHECK_DRIVERS: &[Stage6SumcheckDriverPlan] = &[ + Stage6SumcheckDriverPlan { symbol: "stage6.sumcheck", stage: "stage6", proof_slot: "stage6.sumcheck", kernel: Some("jolt.cpu.stage6.batched"), relation: None, batch: "stage6.batch", policy: "jolt_core_stage6_aligned", round_schedule: STAGE6_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 32, degree: 5 }, +]; +pub const STAGE6_SUMCHECK_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = &[ + Stage6SumcheckInstanceResultPlan { symbol: "stage6.bytecode_read_raf.instance", source: "stage6.sumcheck", claim: "stage6.bytecode_read_raf.input", relation: "jolt.stage6.bytecode_read_raf", index: 0, point_arity: 32, num_rounds: 32, round_offset: 0, point_order: "bytecode_read_raf", degree: 5 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.booleanity.instance", source: "stage6.sumcheck", claim: "stage6.booleanity.input", relation: "jolt.stage6.booleanity", index: 1, point_arity: 22, num_rounds: 22, round_offset: 10, point_order: "stage6_booleanity", degree: 3 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.hamming_booleanity.instance", source: "stage6.sumcheck", claim: "stage6.hamming_booleanity.input", relation: "jolt.stage6.hamming_booleanity", index: 2, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 3 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.ram_ra_virtual.instance", source: "stage6.sumcheck", claim: "stage6.ram_ra_virtual.input", relation: "jolt.stage6.ram_ra_virtual", index: 3, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 5 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.instruction_ra_virtual.instance", source: "stage6.sumcheck", claim: "stage6.instruction_ra_virtual.input", relation: "jolt.stage6.instruction_ra_virtual", index: 4, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 5 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.inc_claim_reduction.instance", source: "stage6.sumcheck", claim: "stage6.inc_claim_reduction.input", relation: "jolt.stage6.inc_claim_reduction", index: 5, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 2 }, +]; + +macro_rules! stage6_sumcheck_eval { + ($symbol:literal, $source:literal, $name:literal, $index:literal, $oracle:literal) => { + Stage6SumcheckEvalPlan { symbol: $symbol, source: $source, name: $name, index: $index, oracle: $oracle } + }; +} + +#[rustfmt::skip] +pub const STAGE6_SUMCHECK_EVALS: &[Stage6SumcheckEvalPlan] = &[ + stage6_sumcheck_eval!("stage6.bytecode_read_raf.eval.BytecodeRa_0", "stage6.sumcheck", "stage6.bytecode_read_raf.eval.BytecodeRa_0", 0, "BytecodeRa_0"), stage6_sumcheck_eval!("stage6.bytecode_read_raf.eval.BytecodeRa_1", "stage6.sumcheck", "stage6.bytecode_read_raf.eval.BytecodeRa_1", 1, "BytecodeRa_1"), stage6_sumcheck_eval!("stage6.bytecode_read_raf.eval.BytecodeRa_2", "stage6.sumcheck", "stage6.bytecode_read_raf.eval.BytecodeRa_2", 2, "BytecodeRa_2"), stage6_sumcheck_eval!("stage6.bytecode_read_raf.eval.BytecodeRa_3", "stage6.sumcheck", "stage6.bytecode_read_raf.eval.BytecodeRa_3", 3, "BytecodeRa_3"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_0", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_0", 0, "InstructionRa_0"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_1", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_1", 1, "InstructionRa_1"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_2", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_2", 2, "InstructionRa_2"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_3", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_3", 3, "InstructionRa_3"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_4", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_4", 4, "InstructionRa_4"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_5", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_5", 5, "InstructionRa_5"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_6", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_6", 6, "InstructionRa_6"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_7", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_7", 7, "InstructionRa_7"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_8", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_8", 8, "InstructionRa_8"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_9", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_9", 9, "InstructionRa_9"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_10", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_10", 10, "InstructionRa_10"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_11", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_11", 11, "InstructionRa_11"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_12", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_12", 12, "InstructionRa_12"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_13", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_13", 13, "InstructionRa_13"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_14", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_14", 14, "InstructionRa_14"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_15", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_15", 15, "InstructionRa_15"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_16", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_16", 16, "InstructionRa_16"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_17", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_17", 17, "InstructionRa_17"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_18", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_18", 18, "InstructionRa_18"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_19", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_19", 19, "InstructionRa_19"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_20", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_20", 20, "InstructionRa_20"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_21", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_21", 21, "InstructionRa_21"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_22", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_22", 22, "InstructionRa_22"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_23", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_23", 23, "InstructionRa_23"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_24", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_24", 24, "InstructionRa_24"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_25", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_25", 25, "InstructionRa_25"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_26", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_26", 26, "InstructionRa_26"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_27", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_27", 27, "InstructionRa_27"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_28", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_28", 28, "InstructionRa_28"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_29", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_29", 29, "InstructionRa_29"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_30", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_30", 30, "InstructionRa_30"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_31", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_31", 31, "InstructionRa_31"), + stage6_sumcheck_eval!("stage6.booleanity.eval.BytecodeRa_0", "stage6.sumcheck", "stage6.booleanity.eval.BytecodeRa_0", 32, "BytecodeRa_0"), stage6_sumcheck_eval!("stage6.booleanity.eval.BytecodeRa_1", "stage6.sumcheck", "stage6.booleanity.eval.BytecodeRa_1", 33, "BytecodeRa_1"), stage6_sumcheck_eval!("stage6.booleanity.eval.BytecodeRa_2", "stage6.sumcheck", "stage6.booleanity.eval.BytecodeRa_2", 34, "BytecodeRa_2"), stage6_sumcheck_eval!("stage6.booleanity.eval.BytecodeRa_3", "stage6.sumcheck", "stage6.booleanity.eval.BytecodeRa_3", 35, "BytecodeRa_3"), + stage6_sumcheck_eval!("stage6.booleanity.eval.RamRa_0", "stage6.sumcheck", "stage6.booleanity.eval.RamRa_0", 36, "RamRa_0"), stage6_sumcheck_eval!("stage6.booleanity.eval.RamRa_1", "stage6.sumcheck", "stage6.booleanity.eval.RamRa_1", 37, "RamRa_1"), stage6_sumcheck_eval!("stage6.booleanity.eval.RamRa_2", "stage6.sumcheck", "stage6.booleanity.eval.RamRa_2", 38, "RamRa_2"), stage6_sumcheck_eval!("stage6.booleanity.eval.RamRa_3", "stage6.sumcheck", "stage6.booleanity.eval.RamRa_3", 39, "RamRa_3"), + stage6_sumcheck_eval!("stage6.hamming_booleanity.eval.HammingWeight", "stage6.sumcheck", "stage6.hamming_booleanity.eval.HammingWeight", 0, "HammingWeight"), stage6_sumcheck_eval!("stage6.ram_ra_virtual.eval.RamRa_0", "stage6.sumcheck", "stage6.ram_ra_virtual.eval.RamRa_0", 0, "RamRa_0"), stage6_sumcheck_eval!("stage6.ram_ra_virtual.eval.RamRa_1", "stage6.sumcheck", "stage6.ram_ra_virtual.eval.RamRa_1", 1, "RamRa_1"), stage6_sumcheck_eval!("stage6.ram_ra_virtual.eval.RamRa_2", "stage6.sumcheck", "stage6.ram_ra_virtual.eval.RamRa_2", 2, "RamRa_2"), + stage6_sumcheck_eval!("stage6.ram_ra_virtual.eval.RamRa_3", "stage6.sumcheck", "stage6.ram_ra_virtual.eval.RamRa_3", 3, "RamRa_3"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_0", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_0", 0, "InstructionRa_0"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_1", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_1", 1, "InstructionRa_1"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_2", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_2", 2, "InstructionRa_2"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_3", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_3", 3, "InstructionRa_3"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_4", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_4", 4, "InstructionRa_4"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_5", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_5", 5, "InstructionRa_5"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_6", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_6", 6, "InstructionRa_6"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_7", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_7", 7, "InstructionRa_7"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_8", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_8", 8, "InstructionRa_8"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_9", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_9", 9, "InstructionRa_9"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_10", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_10", 10, "InstructionRa_10"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_11", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_11", 11, "InstructionRa_11"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_12", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_12", 12, "InstructionRa_12"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_13", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_13", 13, "InstructionRa_13"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_14", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_14", 14, "InstructionRa_14"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_15", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_15", 15, "InstructionRa_15"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_16", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_16", 16, "InstructionRa_16"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_17", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_17", 17, "InstructionRa_17"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_18", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_18", 18, "InstructionRa_18"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_19", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_19", 19, "InstructionRa_19"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_20", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_20", 20, "InstructionRa_20"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_21", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_21", 21, "InstructionRa_21"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_22", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_22", 22, "InstructionRa_22"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_23", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_23", 23, "InstructionRa_23"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_24", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_24", 24, "InstructionRa_24"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_25", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_25", 25, "InstructionRa_25"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_26", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_26", 26, "InstructionRa_26"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_27", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_27", 27, "InstructionRa_27"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_28", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_28", 28, "InstructionRa_28"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_29", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_29", 29, "InstructionRa_29"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_30", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_30", 30, "InstructionRa_30"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_31", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_31", 31, "InstructionRa_31"), stage6_sumcheck_eval!("stage6.inc_claim_reduction.eval.RamInc", "stage6.sumcheck", "stage6.inc_claim_reduction.eval.RamInc", 0, "RamInc"), stage6_sumcheck_eval!("stage6.inc_claim_reduction.eval.RdInc", "stage6.sumcheck", "stage6.inc_claim_reduction.eval.RdInc", 1, "RdInc"), +]; + +pub const STAGE6_POINT_ZEROS: &[Stage6PointZeroPlan] = &[ + Stage6PointZeroPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_0.address.zero_pad", field: "bn254_fr", arity: 2 }, + Stage6PointZeroPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_0.address.zero_pad", field: "bn254_fr", arity: 2 }, +]; + +pub const STAGE6_POINT_SLICES: &[Stage6PointSlicePlan] = &[ + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.Cycle", source: "stage6.bytecode_read_raf.instance", offset: 14, length: 18, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_0.address.source", source: "stage6.bytecode_read_raf.instance", offset: 0, length: 2, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_1.address", source: "stage6.bytecode_read_raf.instance", offset: 2, length: 4, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_2.address", source: "stage6.bytecode_read_raf.instance", offset: 6, length: 4, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_3.address", source: "stage6.bytecode_read_raf.instance", offset: 10, length: 4, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.ram_ra_virtual.point.RamRa_0.address.source", source: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", offset: 0, length: 2, input: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6PointSlicePlan { symbol: "stage6.ram_ra_virtual.point.RamRa_1.address", source: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", offset: 2, length: 4, input: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6PointSlicePlan { symbol: "stage6.ram_ra_virtual.point.RamRa_2.address", source: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", offset: 6, length: 4, input: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6PointSlicePlan { symbol: "stage6.ram_ra_virtual.point.RamRa_3.address", source: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", offset: 10, length: 4, input: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_0.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_1.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_2.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_3.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_4.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_1" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_5.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_1" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_6.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_1" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_7.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_1" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_8.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_2" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_9.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_2" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_10.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_2" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_11.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_2" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_12.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_3" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_13.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_3" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_14.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_3" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_15.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_3" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_16.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_4" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_17.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_4" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_18.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_4" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_19.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_4" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_20.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_5" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_21.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_5" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_22.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_5" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_23.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_5" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_24.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_6" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_25.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_6" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_26.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_6" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_27.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_6" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_28.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_29.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_30.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_31.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, +]; + +pub const STAGE6_POINT_CONCAT_0_INPUTS: &[&str] = &[ + "stage6.bytecode_read_raf.point.BytecodeRa_0.address.zero_pad", + "stage6.bytecode_read_raf.point.BytecodeRa_0.address.source", +]; + +pub const STAGE6_POINT_CONCAT_1_INPUTS: &[&str] = &[ + "stage6.bytecode_read_raf.point.BytecodeRa_0.address", + "stage6.bytecode_read_raf.point.Cycle", +]; + +pub const STAGE6_POINT_CONCAT_2_INPUTS: &[&str] = &[ + "stage6.bytecode_read_raf.point.BytecodeRa_1.address", + "stage6.bytecode_read_raf.point.Cycle", +]; + +pub const STAGE6_POINT_CONCAT_3_INPUTS: &[&str] = &[ + "stage6.bytecode_read_raf.point.BytecodeRa_2.address", + "stage6.bytecode_read_raf.point.Cycle", +]; + +pub const STAGE6_POINT_CONCAT_4_INPUTS: &[&str] = &[ + "stage6.bytecode_read_raf.point.BytecodeRa_3.address", + "stage6.bytecode_read_raf.point.Cycle", +]; + +pub const STAGE6_POINT_CONCAT_5_INPUTS: &[&str] = &[ + "stage6.ram_ra_virtual.point.RamRa_0.address.zero_pad", + "stage6.ram_ra_virtual.point.RamRa_0.address.source", +]; + +pub const STAGE6_POINT_CONCAT_6_INPUTS: &[&str] = &[ + "stage6.ram_ra_virtual.point.RamRa_0.address", + "stage6.ram_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_7_INPUTS: &[&str] = &[ + "stage6.ram_ra_virtual.point.RamRa_1.address", + "stage6.ram_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_8_INPUTS: &[&str] = &[ + "stage6.ram_ra_virtual.point.RamRa_2.address", + "stage6.ram_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_9_INPUTS: &[&str] = &[ + "stage6.ram_ra_virtual.point.RamRa_3.address", + "stage6.ram_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_10_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_0.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_11_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_1.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_12_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_2.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_13_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_3.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_14_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_4.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_15_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_5.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_16_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_6.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_17_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_7.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_18_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_8.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_19_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_9.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_20_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_10.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_21_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_11.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_22_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_12.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_23_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_13.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_24_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_14.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_25_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_15.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_26_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_16.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_27_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_17.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_28_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_18.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_29_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_19.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_30_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_20.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_31_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_21.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_32_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_22.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_33_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_23.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_34_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_24.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_35_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_25.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_36_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_26.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_37_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_27.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_38_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_28.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_39_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_29.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_40_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_30.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCAT_41_INPUTS: &[&str] = &[ + "stage6.instruction_ra_virtual.point.InstructionRa_31.address", + "stage6.instruction_ra_virtual.instance", +]; + +pub const STAGE6_POINT_CONCATS: &[Stage6PointConcatPlan] = &[ + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_0.address", layout: "left_zero_padded_address_chunk", arity: 4, inputs: STAGE6_POINT_CONCAT_0_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_0", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_1_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_1", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_2_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_2", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_3_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_3", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_4_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_0.address", layout: "left_zero_padded_address_chunk", arity: 4, inputs: STAGE6_POINT_CONCAT_5_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_0", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_6_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_1", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_7_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_2", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_8_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_3", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_9_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_0", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_10_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_1", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_11_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_2", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_12_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_3", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_13_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_4", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_14_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_5", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_15_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_6", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_16_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_7", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_17_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_8", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_18_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_9", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_19_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_10", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_20_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_11", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_21_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_12", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_22_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_13", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_23_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_14", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_24_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_15", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_25_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_16", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_26_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_17", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_27_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_18", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_28_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_19", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_29_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_20", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_30_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_21", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_31_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_22", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_32_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_23", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_33_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_24", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_34_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_25", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_35_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_26", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_36_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_27", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_37_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_28", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_38_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_29", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_39_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_30", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_40_INPUTS }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_31", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE6_POINT_CONCAT_41_INPUTS }, +]; +pub const STAGE6_OPENING_CLAIMS: &[Stage6OpeningClaimPlan] = &[ + Stage6OpeningClaimPlan { symbol: "stage6.bytecode_read_raf.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.bytecode_read_raf.point.BytecodeRa_0", eval_source: "stage6.bytecode_read_raf.eval.BytecodeRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.bytecode_read_raf.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.bytecode_read_raf.point.BytecodeRa_1", eval_source: "stage6.bytecode_read_raf.eval.BytecodeRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.bytecode_read_raf.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.bytecode_read_raf.point.BytecodeRa_2", eval_source: "stage6.bytecode_read_raf.eval.BytecodeRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.bytecode_read_raf.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.bytecode_read_raf.point.BytecodeRa_3", eval_source: "stage6.bytecode_read_raf.eval.BytecodeRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_4" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_5" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_6" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_7" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_8" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_9" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_10" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_11" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_12" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_13" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_14" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_15" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_16" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_17" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_18" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_19" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_20" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_21" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_22" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_23" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_24" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_25" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_26" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_27" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_28" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_29" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_30" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_31" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.BytecodeRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.BytecodeRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.BytecodeRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.BytecodeRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.RamRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.RamRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.RamRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.RamRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.hamming_booleanity.opening.HammingWeight", oracle: "HammingWeight", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage6.hamming_booleanity.instance", eval_source: "stage6.hamming_booleanity.eval.HammingWeight" }, + Stage6OpeningClaimPlan { symbol: "stage6.ram_ra_virtual.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.ram_ra_virtual.point.RamRa_0", eval_source: "stage6.ram_ra_virtual.eval.RamRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.ram_ra_virtual.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.ram_ra_virtual.point.RamRa_1", eval_source: "stage6.ram_ra_virtual.eval.RamRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.ram_ra_virtual.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.ram_ra_virtual.point.RamRa_2", eval_source: "stage6.ram_ra_virtual.eval.RamRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.ram_ra_virtual.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.ram_ra_virtual.point.RamRa_3", eval_source: "stage6.ram_ra_virtual.eval.RamRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_0", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_1", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_2", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_3", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_4", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_4" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_5", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_5" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_6", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_6" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_7", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_7" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_8", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_8" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_9", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_9" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_10", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_10" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_11", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_11" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_12", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_12" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_13", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_13" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_14", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_14" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_15", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_15" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_16", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_16" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_17", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_17" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_18", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_18" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_19", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_19" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_20", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_20" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_21", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_21" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_22", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_22" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_23", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_23" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_24", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_24" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_25", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_25" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_26", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_26" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_27", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_27" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_28", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_28" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_29", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_29" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_30", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_30" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_31", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_31" }, + Stage6OpeningClaimPlan { symbol: "stage6.inc_claim_reduction.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage6.inc_claim_reduction.instance", eval_source: "stage6.inc_claim_reduction.eval.RamInc" }, + Stage6OpeningClaimPlan { symbol: "stage6.inc_claim_reduction.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage6.inc_claim_reduction.instance", eval_source: "stage6.inc_claim_reduction.eval.RdInc" }, +]; + +pub const STAGE6_OPENING_EQUALITIES: &[Stage6OpeningClaimEqualityPlan] = &[ + +]; + +pub const STAGE6_OPENING_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage6.bytecode_read_raf.opening.BytecodeRa_0", + "stage6.bytecode_read_raf.opening.BytecodeRa_1", + "stage6.bytecode_read_raf.opening.BytecodeRa_2", + "stage6.bytecode_read_raf.opening.BytecodeRa_3", + "stage6.booleanity.opening.InstructionRa_0", + "stage6.booleanity.opening.InstructionRa_1", + "stage6.booleanity.opening.InstructionRa_2", + "stage6.booleanity.opening.InstructionRa_3", + "stage6.booleanity.opening.InstructionRa_4", + "stage6.booleanity.opening.InstructionRa_5", + "stage6.booleanity.opening.InstructionRa_6", + "stage6.booleanity.opening.InstructionRa_7", + "stage6.booleanity.opening.InstructionRa_8", + "stage6.booleanity.opening.InstructionRa_9", + "stage6.booleanity.opening.InstructionRa_10", + "stage6.booleanity.opening.InstructionRa_11", + "stage6.booleanity.opening.InstructionRa_12", + "stage6.booleanity.opening.InstructionRa_13", + "stage6.booleanity.opening.InstructionRa_14", + "stage6.booleanity.opening.InstructionRa_15", + "stage6.booleanity.opening.InstructionRa_16", + "stage6.booleanity.opening.InstructionRa_17", + "stage6.booleanity.opening.InstructionRa_18", + "stage6.booleanity.opening.InstructionRa_19", + "stage6.booleanity.opening.InstructionRa_20", + "stage6.booleanity.opening.InstructionRa_21", + "stage6.booleanity.opening.InstructionRa_22", + "stage6.booleanity.opening.InstructionRa_23", + "stage6.booleanity.opening.InstructionRa_24", + "stage6.booleanity.opening.InstructionRa_25", + "stage6.booleanity.opening.InstructionRa_26", + "stage6.booleanity.opening.InstructionRa_27", + "stage6.booleanity.opening.InstructionRa_28", + "stage6.booleanity.opening.InstructionRa_29", + "stage6.booleanity.opening.InstructionRa_30", + "stage6.booleanity.opening.InstructionRa_31", + "stage6.booleanity.opening.BytecodeRa_0", + "stage6.booleanity.opening.BytecodeRa_1", + "stage6.booleanity.opening.BytecodeRa_2", + "stage6.booleanity.opening.BytecodeRa_3", + "stage6.booleanity.opening.RamRa_0", + "stage6.booleanity.opening.RamRa_1", + "stage6.booleanity.opening.RamRa_2", + "stage6.booleanity.opening.RamRa_3", + "stage6.hamming_booleanity.opening.HammingWeight", + "stage6.ram_ra_virtual.opening.RamRa_0", + "stage6.ram_ra_virtual.opening.RamRa_1", + "stage6.ram_ra_virtual.opening.RamRa_2", + "stage6.ram_ra_virtual.opening.RamRa_3", + "stage6.instruction_ra_virtual.opening.InstructionRa_0", + "stage6.instruction_ra_virtual.opening.InstructionRa_1", + "stage6.instruction_ra_virtual.opening.InstructionRa_2", + "stage6.instruction_ra_virtual.opening.InstructionRa_3", + "stage6.instruction_ra_virtual.opening.InstructionRa_4", + "stage6.instruction_ra_virtual.opening.InstructionRa_5", + "stage6.instruction_ra_virtual.opening.InstructionRa_6", + "stage6.instruction_ra_virtual.opening.InstructionRa_7", + "stage6.instruction_ra_virtual.opening.InstructionRa_8", + "stage6.instruction_ra_virtual.opening.InstructionRa_9", + "stage6.instruction_ra_virtual.opening.InstructionRa_10", + "stage6.instruction_ra_virtual.opening.InstructionRa_11", + "stage6.instruction_ra_virtual.opening.InstructionRa_12", + "stage6.instruction_ra_virtual.opening.InstructionRa_13", + "stage6.instruction_ra_virtual.opening.InstructionRa_14", + "stage6.instruction_ra_virtual.opening.InstructionRa_15", + "stage6.instruction_ra_virtual.opening.InstructionRa_16", + "stage6.instruction_ra_virtual.opening.InstructionRa_17", + "stage6.instruction_ra_virtual.opening.InstructionRa_18", + "stage6.instruction_ra_virtual.opening.InstructionRa_19", + "stage6.instruction_ra_virtual.opening.InstructionRa_20", + "stage6.instruction_ra_virtual.opening.InstructionRa_21", + "stage6.instruction_ra_virtual.opening.InstructionRa_22", + "stage6.instruction_ra_virtual.opening.InstructionRa_23", + "stage6.instruction_ra_virtual.opening.InstructionRa_24", + "stage6.instruction_ra_virtual.opening.InstructionRa_25", + "stage6.instruction_ra_virtual.opening.InstructionRa_26", + "stage6.instruction_ra_virtual.opening.InstructionRa_27", + "stage6.instruction_ra_virtual.opening.InstructionRa_28", + "stage6.instruction_ra_virtual.opening.InstructionRa_29", + "stage6.instruction_ra_virtual.opening.InstructionRa_30", + "stage6.instruction_ra_virtual.opening.InstructionRa_31", + "stage6.inc_claim_reduction.opening.RamInc", + "stage6.inc_claim_reduction.opening.RdInc", +]; + +pub const STAGE6_OPENING_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage6.bytecode_read_raf.opening.BytecodeRa_0", + "stage6.bytecode_read_raf.opening.BytecodeRa_1", + "stage6.bytecode_read_raf.opening.BytecodeRa_2", + "stage6.bytecode_read_raf.opening.BytecodeRa_3", + "stage6.booleanity.opening.InstructionRa_0", + "stage6.booleanity.opening.InstructionRa_1", + "stage6.booleanity.opening.InstructionRa_2", + "stage6.booleanity.opening.InstructionRa_3", + "stage6.booleanity.opening.InstructionRa_4", + "stage6.booleanity.opening.InstructionRa_5", + "stage6.booleanity.opening.InstructionRa_6", + "stage6.booleanity.opening.InstructionRa_7", + "stage6.booleanity.opening.InstructionRa_8", + "stage6.booleanity.opening.InstructionRa_9", + "stage6.booleanity.opening.InstructionRa_10", + "stage6.booleanity.opening.InstructionRa_11", + "stage6.booleanity.opening.InstructionRa_12", + "stage6.booleanity.opening.InstructionRa_13", + "stage6.booleanity.opening.InstructionRa_14", + "stage6.booleanity.opening.InstructionRa_15", + "stage6.booleanity.opening.InstructionRa_16", + "stage6.booleanity.opening.InstructionRa_17", + "stage6.booleanity.opening.InstructionRa_18", + "stage6.booleanity.opening.InstructionRa_19", + "stage6.booleanity.opening.InstructionRa_20", + "stage6.booleanity.opening.InstructionRa_21", + "stage6.booleanity.opening.InstructionRa_22", + "stage6.booleanity.opening.InstructionRa_23", + "stage6.booleanity.opening.InstructionRa_24", + "stage6.booleanity.opening.InstructionRa_25", + "stage6.booleanity.opening.InstructionRa_26", + "stage6.booleanity.opening.InstructionRa_27", + "stage6.booleanity.opening.InstructionRa_28", + "stage6.booleanity.opening.InstructionRa_29", + "stage6.booleanity.opening.InstructionRa_30", + "stage6.booleanity.opening.InstructionRa_31", + "stage6.booleanity.opening.BytecodeRa_0", + "stage6.booleanity.opening.BytecodeRa_1", + "stage6.booleanity.opening.BytecodeRa_2", + "stage6.booleanity.opening.BytecodeRa_3", + "stage6.booleanity.opening.RamRa_0", + "stage6.booleanity.opening.RamRa_1", + "stage6.booleanity.opening.RamRa_2", + "stage6.booleanity.opening.RamRa_3", + "stage6.hamming_booleanity.opening.HammingWeight", + "stage6.ram_ra_virtual.opening.RamRa_0", + "stage6.ram_ra_virtual.opening.RamRa_1", + "stage6.ram_ra_virtual.opening.RamRa_2", + "stage6.ram_ra_virtual.opening.RamRa_3", + "stage6.instruction_ra_virtual.opening.InstructionRa_0", + "stage6.instruction_ra_virtual.opening.InstructionRa_1", + "stage6.instruction_ra_virtual.opening.InstructionRa_2", + "stage6.instruction_ra_virtual.opening.InstructionRa_3", + "stage6.instruction_ra_virtual.opening.InstructionRa_4", + "stage6.instruction_ra_virtual.opening.InstructionRa_5", + "stage6.instruction_ra_virtual.opening.InstructionRa_6", + "stage6.instruction_ra_virtual.opening.InstructionRa_7", + "stage6.instruction_ra_virtual.opening.InstructionRa_8", + "stage6.instruction_ra_virtual.opening.InstructionRa_9", + "stage6.instruction_ra_virtual.opening.InstructionRa_10", + "stage6.instruction_ra_virtual.opening.InstructionRa_11", + "stage6.instruction_ra_virtual.opening.InstructionRa_12", + "stage6.instruction_ra_virtual.opening.InstructionRa_13", + "stage6.instruction_ra_virtual.opening.InstructionRa_14", + "stage6.instruction_ra_virtual.opening.InstructionRa_15", + "stage6.instruction_ra_virtual.opening.InstructionRa_16", + "stage6.instruction_ra_virtual.opening.InstructionRa_17", + "stage6.instruction_ra_virtual.opening.InstructionRa_18", + "stage6.instruction_ra_virtual.opening.InstructionRa_19", + "stage6.instruction_ra_virtual.opening.InstructionRa_20", + "stage6.instruction_ra_virtual.opening.InstructionRa_21", + "stage6.instruction_ra_virtual.opening.InstructionRa_22", + "stage6.instruction_ra_virtual.opening.InstructionRa_23", + "stage6.instruction_ra_virtual.opening.InstructionRa_24", + "stage6.instruction_ra_virtual.opening.InstructionRa_25", + "stage6.instruction_ra_virtual.opening.InstructionRa_26", + "stage6.instruction_ra_virtual.opening.InstructionRa_27", + "stage6.instruction_ra_virtual.opening.InstructionRa_28", + "stage6.instruction_ra_virtual.opening.InstructionRa_29", + "stage6.instruction_ra_virtual.opening.InstructionRa_30", + "stage6.instruction_ra_virtual.opening.InstructionRa_31", + "stage6.inc_claim_reduction.opening.RamInc", + "stage6.inc_claim_reduction.opening.RdInc", +]; + +pub const STAGE6_OPENING_BATCHES: &[Stage6OpeningBatchPlan] = &[ + Stage6OpeningBatchPlan { symbol: "stage6.openings", stage: "stage6", proof_slot: "stage6.openings", policy: "jolt_stage6_output_order", count: 83, ordered_claims: STAGE6_OPENING_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE6_OPENING_BATCH_0_CLAIM_OPERANDS }, +]; +pub const STAGE6_PROGRAM: Stage6CpuProgramPlan = Stage6CpuProgramPlan { + role: "prover", + params: STAGE6_PARAMS, + steps: STAGE6_PROGRAM_STEPS, + transcript_squeezes: STAGE6_TRANSCRIPT_SQUEEZES, + transcript_absorb_bytes: STAGE6_TRANSCRIPT_ABSORB_BYTES, + opening_inputs: STAGE6_OPENING_INPUTS, + field_constants: STAGE6_FIELD_CONSTANTS, + field_exprs: STAGE6_FIELD_EXPRS, + kernels: STAGE6_KERNELS, + claims: STAGE6_SUMCHECK_CLAIMS, + batches: STAGE6_SUMCHECK_BATCHES, + drivers: STAGE6_SUMCHECK_DRIVERS, + instance_results: STAGE6_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE6_SUMCHECK_EVALS, + point_zeros: STAGE6_POINT_ZEROS, + point_slices: STAGE6_POINT_SLICES, + point_concats: STAGE6_POINT_CONCATS, + opening_claims: STAGE6_OPENING_CLAIMS, + opening_equalities: STAGE6_OPENING_EQUALITIES, + opening_batches: STAGE6_OPENING_BATCHES, +}; + +pub fn execute_stage6_prover( + executor: &mut E, + transcript: &mut T, +) -> Result, Stage6KernelError> +where + E: Stage6KernelExecutor, + T: Transcript, +{ + execute_stage6_prover_with_program(&STAGE6_PROGRAM, executor, transcript) +} + +pub fn execute_stage6_prover_with_program( + program: &'static Stage6CpuProgramPlan, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage6KernelError> +where + E: Stage6KernelExecutor, + T: Transcript, +{ + execute_stage6_program(program, Stage6ExecutionMode::Prover, executor, transcript) +} diff --git a/crates/jolt-prover/src/stages/stage7.rs b/crates/jolt-prover/src/stages/stage7.rs new file mode 100644 index 0000000000..4dfa6eec1a --- /dev/null +++ b/crates/jolt-prover/src/stages/stage7.rs @@ -0,0 +1,1996 @@ +#![allow(dead_code)] + +use jolt_field::Fr; +use jolt_kernels::stage7::{execute_stage7_program, Stage7CpuProgramPlan, Stage7ExecutionArtifacts, Stage7ExecutionMode, Stage7FieldConstantPlan, Stage7FieldExprPlan, Stage7KernelError, Stage7KernelExecutor, Stage7KernelPlan, Stage7OpeningBatchPlan, Stage7OpeningClaimEqualityPlan, Stage7OpeningClaimPlan, Stage7OpeningInputPlan, Stage7Params, Stage7PointConcatPlan, Stage7PointSlicePlan, Stage7PointZeroPlan, Stage7ProgramStepPlan, Stage7SumcheckBatchPlan, Stage7SumcheckClaimPlan, Stage7SumcheckDriverPlan, Stage7SumcheckEvalPlan, Stage7SumcheckInstanceResultPlan, Stage7TranscriptAbsorbBytesPlan, Stage7TranscriptSqueezePlan}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage7Transcript = Blake2bTranscript; + +pub const STAGE7_PARAMS: Stage7Params = Stage7Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE7_PROGRAM_STEPS: &[Stage7ProgramStepPlan] = &[ + Stage7ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage7.hamming_weight_claim_reduction.gamma" }, + Stage7ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage7.sumcheck" }, +]; + +pub const STAGE7_TRANSCRIPT_SQUEEZES: &[Stage7TranscriptSqueezePlan] = &[ + Stage7TranscriptSqueezePlan { symbol: "stage7.hamming_weight_claim_reduction.gamma", label: "hamming_weight_claim_reduction_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE7_TRANSCRIPT_ABSORB_BYTES: &[Stage7TranscriptAbsorbBytesPlan] = &[ + +]; + +pub const STAGE7_OPENING_INPUTS: &[Stage7OpeningInputPlan] = &[ + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.hamming_booleanity.HammingWeight", source_stage: "stage6", source_claim: "stage6.hamming_booleanity.opening.HammingWeight", oracle: "HammingWeight", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_0", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_0", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_1", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_1", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_2", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_2", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_3", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_3", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_4", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_4", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_5", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_5", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_6", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_6", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_7", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_7", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_8", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_8", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_9", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_9", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_10", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_10", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_11", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_11", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_12", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_12", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_13", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_13", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_14", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_14", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_15", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_15", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_16", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_16", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_17", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_17", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_18", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_18", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_19", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_19", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_20", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_20", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_21", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_21", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_22", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_22", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_23", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_23", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_24", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_24", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_25", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_25", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_26", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_26", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_27", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_27", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_28", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_28", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_29", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_29", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_30", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_30", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_31", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_31", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.BytecodeRa_0", source_stage: "stage6", source_claim: "stage6.booleanity.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.bytecode_read_raf.BytecodeRa_0", source_stage: "stage6", source_claim: "stage6.bytecode_read_raf.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.BytecodeRa_1", source_stage: "stage6", source_claim: "stage6.booleanity.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.bytecode_read_raf.BytecodeRa_1", source_stage: "stage6", source_claim: "stage6.bytecode_read_raf.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.BytecodeRa_2", source_stage: "stage6", source_claim: "stage6.booleanity.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.bytecode_read_raf.BytecodeRa_2", source_stage: "stage6", source_claim: "stage6.bytecode_read_raf.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.BytecodeRa_3", source_stage: "stage6", source_claim: "stage6.booleanity.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.bytecode_read_raf.BytecodeRa_3", source_stage: "stage6", source_claim: "stage6.bytecode_read_raf.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.RamRa_0", source_stage: "stage6", source_claim: "stage6.booleanity.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_0", source_stage: "stage6", source_claim: "stage6.ram_ra_virtual.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.RamRa_1", source_stage: "stage6", source_claim: "stage6.booleanity.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_1", source_stage: "stage6", source_claim: "stage6.ram_ra_virtual.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.RamRa_2", source_stage: "stage6", source_claim: "stage6.booleanity.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_2", source_stage: "stage6", source_claim: "stage6.ram_ra_virtual.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.RamRa_3", source_stage: "stage6", source_claim: "stage6.booleanity.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_3", source_stage: "stage6", source_claim: "stage6.ram_ra_virtual.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, +]; + +pub const STAGE7_FIELD_CONSTANTS: &[Stage7FieldConstantPlan] = &[ + Stage7FieldConstantPlan { symbol: "stage7.field.one", field: "bn254_fr", value: 1 }, +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_0: &[&str] = &["stage7.hamming_weight_claim_reduction.gamma"]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_1: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.0.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_0", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_2: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.0.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_0", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_3: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.1.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_4: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.1.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_1", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_5: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.1.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_1", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_6: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.2.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_7: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.2.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_2", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_8: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.2.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_2", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_9: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.3.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_10: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.3.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_3", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_11: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.3.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_3", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_12: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.4.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_13: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.4.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_4", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_14: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.4.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_4", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_15: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.5.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_16: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.5.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_5", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_17: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.5.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_5", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_18: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.6.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_19: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.6.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_6", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_20: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.6.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_6", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_21: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.7.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_22: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.7.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_7", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_23: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.7.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_7", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_24: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.8.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_25: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.8.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_8", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_26: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.8.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_8", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_27: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.9.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_28: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.9.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_9", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_29: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.9.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_9", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_30: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.10.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_31: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.10.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_10", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_32: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.10.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_10", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_33: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.11.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_34: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.11.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_11", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_35: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.11.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_11", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_36: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.12.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_37: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.12.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_12", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_38: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.12.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_12", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_39: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.13.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_40: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.13.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_13", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_41: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.13.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_13", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_42: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.14.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_43: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.14.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_14", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_44: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.14.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_14", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_45: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.15.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_46: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.15.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_15", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_47: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.15.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_15", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_48: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.16.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_49: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.16.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_16", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_50: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.16.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_16", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_51: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.17.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_52: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.17.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_17", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_53: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.17.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_17", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_54: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.18.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_55: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.18.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_18", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_56: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.18.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_18", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_57: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.19.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_58: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.19.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_19", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_59: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.19.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_19", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_60: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.20.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_61: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.20.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_20", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_62: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.20.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_20", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_63: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.21.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_64: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.21.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_21", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_65: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.21.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_21", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_66: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.22.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_67: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.22.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_22", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_68: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.22.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_22", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_69: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.23.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_70: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.23.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_23", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_71: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.23.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_23", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_72: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.24.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_73: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.24.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_24", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_74: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.24.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_24", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_75: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.25.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_76: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.25.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_25", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_77: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.25.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_25", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_78: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.26.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_79: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.26.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_26", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_80: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.26.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_26", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_81: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.27.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_82: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.27.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_27", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_83: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.27.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_27", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_84: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.28.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_85: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.28.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_28", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_86: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.28.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_28", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_87: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.29.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_88: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.29.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_29", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_89: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.29.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_29", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_90: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.30.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_91: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.30.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_30", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_92: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.30.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_30", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_93: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.31.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_94: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.31.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.InstructionRa_31", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_95: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.31.virtualization.gamma_pow", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_31", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_96: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.32.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_97: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.32.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.BytecodeRa_0", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_98: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.32.virtualization.gamma_pow", + "stage7.input.stage6.bytecode_read_raf.BytecodeRa_0", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_99: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.33.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_100: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.33.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.BytecodeRa_1", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_101: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.33.virtualization.gamma_pow", + "stage7.input.stage6.bytecode_read_raf.BytecodeRa_1", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_102: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.34.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_103: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.34.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.BytecodeRa_2", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_104: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.34.virtualization.gamma_pow", + "stage7.input.stage6.bytecode_read_raf.BytecodeRa_2", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_105: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.35.hw.gamma_pow", + "stage7.field.one", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_106: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.35.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.BytecodeRa_3", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_107: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.35.virtualization.gamma_pow", + "stage7.input.stage6.bytecode_read_raf.BytecodeRa_3", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_108: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.36.hw.gamma_pow", + "stage7.input.stage6.hamming_booleanity.HammingWeight", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_109: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.36.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.RamRa_0", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_110: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.36.virtualization.gamma_pow", + "stage7.input.stage6.ram_ra_virtual.RamRa_0", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_111: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.37.hw.gamma_pow", + "stage7.input.stage6.hamming_booleanity.HammingWeight", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_112: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.37.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.RamRa_1", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_113: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.37.virtualization.gamma_pow", + "stage7.input.stage6.ram_ra_virtual.RamRa_1", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_114: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.38.hw.gamma_pow", + "stage7.input.stage6.hamming_booleanity.HammingWeight", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_115: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.38.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.RamRa_2", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_116: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.38.virtualization.gamma_pow", + "stage7.input.stage6.ram_ra_virtual.RamRa_2", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_117: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.39.hw.gamma_pow", + "stage7.input.stage6.hamming_booleanity.HammingWeight", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_118: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.39.booleanity.gamma_pow", + "stage7.input.stage6.booleanity.RamRa_3", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_119: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim.39.virtualization.gamma_pow", + "stage7.input.stage6.ram_ra_virtual.RamRa_3", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_120: &[&str] = &[ + "stage7.field.one", + "stage7.hamming_weight_claim_reduction.claim.0.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_121: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial0", + "stage7.hamming_weight_claim_reduction.claim.0.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_122: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial1", + "stage7.hamming_weight_claim_reduction.claim.1.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_123: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial2", + "stage7.hamming_weight_claim_reduction.claim.1.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_124: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial3", + "stage7.hamming_weight_claim_reduction.claim.1.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_125: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial4", + "stage7.hamming_weight_claim_reduction.claim.2.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_126: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial5", + "stage7.hamming_weight_claim_reduction.claim.2.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_127: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial6", + "stage7.hamming_weight_claim_reduction.claim.2.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_128: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial7", + "stage7.hamming_weight_claim_reduction.claim.3.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_129: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial8", + "stage7.hamming_weight_claim_reduction.claim.3.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_130: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial9", + "stage7.hamming_weight_claim_reduction.claim.3.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_131: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial10", + "stage7.hamming_weight_claim_reduction.claim.4.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_132: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial11", + "stage7.hamming_weight_claim_reduction.claim.4.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_133: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial12", + "stage7.hamming_weight_claim_reduction.claim.4.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_134: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial13", + "stage7.hamming_weight_claim_reduction.claim.5.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_135: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial14", + "stage7.hamming_weight_claim_reduction.claim.5.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_136: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial15", + "stage7.hamming_weight_claim_reduction.claim.5.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_137: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial16", + "stage7.hamming_weight_claim_reduction.claim.6.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_138: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial17", + "stage7.hamming_weight_claim_reduction.claim.6.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_139: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial18", + "stage7.hamming_weight_claim_reduction.claim.6.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_140: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial19", + "stage7.hamming_weight_claim_reduction.claim.7.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_141: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial20", + "stage7.hamming_weight_claim_reduction.claim.7.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_142: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial21", + "stage7.hamming_weight_claim_reduction.claim.7.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_143: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial22", + "stage7.hamming_weight_claim_reduction.claim.8.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_144: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial23", + "stage7.hamming_weight_claim_reduction.claim.8.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_145: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial24", + "stage7.hamming_weight_claim_reduction.claim.8.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_146: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial25", + "stage7.hamming_weight_claim_reduction.claim.9.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_147: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial26", + "stage7.hamming_weight_claim_reduction.claim.9.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_148: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial27", + "stage7.hamming_weight_claim_reduction.claim.9.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_149: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial28", + "stage7.hamming_weight_claim_reduction.claim.10.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_150: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial29", + "stage7.hamming_weight_claim_reduction.claim.10.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_151: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial30", + "stage7.hamming_weight_claim_reduction.claim.10.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_152: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial31", + "stage7.hamming_weight_claim_reduction.claim.11.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_153: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial32", + "stage7.hamming_weight_claim_reduction.claim.11.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_154: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial33", + "stage7.hamming_weight_claim_reduction.claim.11.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_155: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial34", + "stage7.hamming_weight_claim_reduction.claim.12.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_156: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial35", + "stage7.hamming_weight_claim_reduction.claim.12.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_157: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial36", + "stage7.hamming_weight_claim_reduction.claim.12.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_158: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial37", + "stage7.hamming_weight_claim_reduction.claim.13.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_159: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial38", + "stage7.hamming_weight_claim_reduction.claim.13.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_160: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial39", + "stage7.hamming_weight_claim_reduction.claim.13.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_161: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial40", + "stage7.hamming_weight_claim_reduction.claim.14.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_162: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial41", + "stage7.hamming_weight_claim_reduction.claim.14.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_163: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial42", + "stage7.hamming_weight_claim_reduction.claim.14.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_164: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial43", + "stage7.hamming_weight_claim_reduction.claim.15.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_165: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial44", + "stage7.hamming_weight_claim_reduction.claim.15.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_166: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial45", + "stage7.hamming_weight_claim_reduction.claim.15.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_167: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial46", + "stage7.hamming_weight_claim_reduction.claim.16.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_168: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial47", + "stage7.hamming_weight_claim_reduction.claim.16.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_169: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial48", + "stage7.hamming_weight_claim_reduction.claim.16.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_170: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial49", + "stage7.hamming_weight_claim_reduction.claim.17.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_171: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial50", + "stage7.hamming_weight_claim_reduction.claim.17.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_172: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial51", + "stage7.hamming_weight_claim_reduction.claim.17.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_173: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial52", + "stage7.hamming_weight_claim_reduction.claim.18.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_174: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial53", + "stage7.hamming_weight_claim_reduction.claim.18.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_175: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial54", + "stage7.hamming_weight_claim_reduction.claim.18.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_176: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial55", + "stage7.hamming_weight_claim_reduction.claim.19.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_177: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial56", + "stage7.hamming_weight_claim_reduction.claim.19.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_178: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial57", + "stage7.hamming_weight_claim_reduction.claim.19.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_179: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial58", + "stage7.hamming_weight_claim_reduction.claim.20.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_180: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial59", + "stage7.hamming_weight_claim_reduction.claim.20.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_181: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial60", + "stage7.hamming_weight_claim_reduction.claim.20.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_182: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial61", + "stage7.hamming_weight_claim_reduction.claim.21.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_183: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial62", + "stage7.hamming_weight_claim_reduction.claim.21.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_184: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial63", + "stage7.hamming_weight_claim_reduction.claim.21.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_185: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial64", + "stage7.hamming_weight_claim_reduction.claim.22.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_186: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial65", + "stage7.hamming_weight_claim_reduction.claim.22.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_187: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial66", + "stage7.hamming_weight_claim_reduction.claim.22.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_188: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial67", + "stage7.hamming_weight_claim_reduction.claim.23.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_189: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial68", + "stage7.hamming_weight_claim_reduction.claim.23.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_190: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial69", + "stage7.hamming_weight_claim_reduction.claim.23.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_191: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial70", + "stage7.hamming_weight_claim_reduction.claim.24.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_192: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial71", + "stage7.hamming_weight_claim_reduction.claim.24.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_193: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial72", + "stage7.hamming_weight_claim_reduction.claim.24.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_194: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial73", + "stage7.hamming_weight_claim_reduction.claim.25.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_195: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial74", + "stage7.hamming_weight_claim_reduction.claim.25.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_196: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial75", + "stage7.hamming_weight_claim_reduction.claim.25.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_197: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial76", + "stage7.hamming_weight_claim_reduction.claim.26.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_198: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial77", + "stage7.hamming_weight_claim_reduction.claim.26.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_199: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial78", + "stage7.hamming_weight_claim_reduction.claim.26.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_200: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial79", + "stage7.hamming_weight_claim_reduction.claim.27.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_201: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial80", + "stage7.hamming_weight_claim_reduction.claim.27.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_202: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial81", + "stage7.hamming_weight_claim_reduction.claim.27.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_203: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial82", + "stage7.hamming_weight_claim_reduction.claim.28.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_204: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial83", + "stage7.hamming_weight_claim_reduction.claim.28.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_205: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial84", + "stage7.hamming_weight_claim_reduction.claim.28.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_206: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial85", + "stage7.hamming_weight_claim_reduction.claim.29.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_207: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial86", + "stage7.hamming_weight_claim_reduction.claim.29.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_208: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial87", + "stage7.hamming_weight_claim_reduction.claim.29.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_209: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial88", + "stage7.hamming_weight_claim_reduction.claim.30.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_210: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial89", + "stage7.hamming_weight_claim_reduction.claim.30.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_211: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial90", + "stage7.hamming_weight_claim_reduction.claim.30.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_212: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial91", + "stage7.hamming_weight_claim_reduction.claim.31.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_213: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial92", + "stage7.hamming_weight_claim_reduction.claim.31.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_214: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial93", + "stage7.hamming_weight_claim_reduction.claim.31.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_215: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial94", + "stage7.hamming_weight_claim_reduction.claim.32.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_216: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial95", + "stage7.hamming_weight_claim_reduction.claim.32.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_217: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial96", + "stage7.hamming_weight_claim_reduction.claim.32.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_218: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial97", + "stage7.hamming_weight_claim_reduction.claim.33.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_219: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial98", + "stage7.hamming_weight_claim_reduction.claim.33.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_220: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial99", + "stage7.hamming_weight_claim_reduction.claim.33.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_221: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial100", + "stage7.hamming_weight_claim_reduction.claim.34.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_222: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial101", + "stage7.hamming_weight_claim_reduction.claim.34.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_223: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial102", + "stage7.hamming_weight_claim_reduction.claim.34.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_224: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial103", + "stage7.hamming_weight_claim_reduction.claim.35.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_225: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial104", + "stage7.hamming_weight_claim_reduction.claim.35.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_226: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial105", + "stage7.hamming_weight_claim_reduction.claim.35.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_227: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial106", + "stage7.hamming_weight_claim_reduction.claim.36.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_228: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial107", + "stage7.hamming_weight_claim_reduction.claim.36.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_229: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial108", + "stage7.hamming_weight_claim_reduction.claim.36.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_230: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial109", + "stage7.hamming_weight_claim_reduction.claim.37.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_231: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial110", + "stage7.hamming_weight_claim_reduction.claim.37.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_232: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial111", + "stage7.hamming_weight_claim_reduction.claim.37.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_233: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial112", + "stage7.hamming_weight_claim_reduction.claim.38.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_234: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial113", + "stage7.hamming_weight_claim_reduction.claim.38.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_235: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial114", + "stage7.hamming_weight_claim_reduction.claim.38.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_236: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial115", + "stage7.hamming_weight_claim_reduction.claim.39.hw.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_237: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial116", + "stage7.hamming_weight_claim_reduction.claim.39.booleanity.gamma_term", +]; + +pub const STAGE7_FIELD_EXPR_OPERANDS_238: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.claim_expr.partial117", + "stage7.hamming_weight_claim_reduction.claim.39.virtualization.gamma_term", +]; + +pub const STAGE7_FIELD_EXPRS: &[Stage7FieldExprPlan] = &[ + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.0.booleanity.gamma_pow", kind: "op", formula: "field.pow:1", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.0.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_1, operands: STAGE7_FIELD_EXPR_OPERANDS_1 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.0.virtualization.gamma_pow", kind: "op", formula: "field.pow:2", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.0.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_2, operands: STAGE7_FIELD_EXPR_OPERANDS_2 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.1.hw.gamma_pow", kind: "op", formula: "field.pow:3", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.1.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_3, operands: STAGE7_FIELD_EXPR_OPERANDS_3 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.1.booleanity.gamma_pow", kind: "op", formula: "field.pow:4", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.1.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_4, operands: STAGE7_FIELD_EXPR_OPERANDS_4 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.1.virtualization.gamma_pow", kind: "op", formula: "field.pow:5", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.1.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_5, operands: STAGE7_FIELD_EXPR_OPERANDS_5 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.2.hw.gamma_pow", kind: "op", formula: "field.pow:6", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.2.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_6, operands: STAGE7_FIELD_EXPR_OPERANDS_6 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.2.booleanity.gamma_pow", kind: "op", formula: "field.pow:7", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.2.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_7, operands: STAGE7_FIELD_EXPR_OPERANDS_7 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.2.virtualization.gamma_pow", kind: "op", formula: "field.pow:8", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.2.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_8, operands: STAGE7_FIELD_EXPR_OPERANDS_8 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.3.hw.gamma_pow", kind: "op", formula: "field.pow:9", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.3.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_9, operands: STAGE7_FIELD_EXPR_OPERANDS_9 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.3.booleanity.gamma_pow", kind: "op", formula: "field.pow:10", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.3.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_10, operands: STAGE7_FIELD_EXPR_OPERANDS_10 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.3.virtualization.gamma_pow", kind: "op", formula: "field.pow:11", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.3.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_11, operands: STAGE7_FIELD_EXPR_OPERANDS_11 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.4.hw.gamma_pow", kind: "op", formula: "field.pow:12", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.4.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_12, operands: STAGE7_FIELD_EXPR_OPERANDS_12 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.4.booleanity.gamma_pow", kind: "op", formula: "field.pow:13", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.4.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_13, operands: STAGE7_FIELD_EXPR_OPERANDS_13 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.4.virtualization.gamma_pow", kind: "op", formula: "field.pow:14", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.4.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_14, operands: STAGE7_FIELD_EXPR_OPERANDS_14 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.5.hw.gamma_pow", kind: "op", formula: "field.pow:15", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.5.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_15, operands: STAGE7_FIELD_EXPR_OPERANDS_15 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.5.booleanity.gamma_pow", kind: "op", formula: "field.pow:16", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.5.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_16, operands: STAGE7_FIELD_EXPR_OPERANDS_16 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.5.virtualization.gamma_pow", kind: "op", formula: "field.pow:17", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.5.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_17, operands: STAGE7_FIELD_EXPR_OPERANDS_17 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.6.hw.gamma_pow", kind: "op", formula: "field.pow:18", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.6.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_18, operands: STAGE7_FIELD_EXPR_OPERANDS_18 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.6.booleanity.gamma_pow", kind: "op", formula: "field.pow:19", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.6.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_19, operands: STAGE7_FIELD_EXPR_OPERANDS_19 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.6.virtualization.gamma_pow", kind: "op", formula: "field.pow:20", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.6.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_20, operands: STAGE7_FIELD_EXPR_OPERANDS_20 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.7.hw.gamma_pow", kind: "op", formula: "field.pow:21", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.7.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_21, operands: STAGE7_FIELD_EXPR_OPERANDS_21 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.7.booleanity.gamma_pow", kind: "op", formula: "field.pow:22", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.7.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_22, operands: STAGE7_FIELD_EXPR_OPERANDS_22 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.7.virtualization.gamma_pow", kind: "op", formula: "field.pow:23", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.7.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_23, operands: STAGE7_FIELD_EXPR_OPERANDS_23 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.8.hw.gamma_pow", kind: "op", formula: "field.pow:24", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.8.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_24, operands: STAGE7_FIELD_EXPR_OPERANDS_24 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.8.booleanity.gamma_pow", kind: "op", formula: "field.pow:25", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.8.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_25, operands: STAGE7_FIELD_EXPR_OPERANDS_25 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.8.virtualization.gamma_pow", kind: "op", formula: "field.pow:26", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.8.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_26, operands: STAGE7_FIELD_EXPR_OPERANDS_26 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.9.hw.gamma_pow", kind: "op", formula: "field.pow:27", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.9.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_27, operands: STAGE7_FIELD_EXPR_OPERANDS_27 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.9.booleanity.gamma_pow", kind: "op", formula: "field.pow:28", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.9.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_28, operands: STAGE7_FIELD_EXPR_OPERANDS_28 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.9.virtualization.gamma_pow", kind: "op", formula: "field.pow:29", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.9.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_29, operands: STAGE7_FIELD_EXPR_OPERANDS_29 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.10.hw.gamma_pow", kind: "op", formula: "field.pow:30", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.10.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_30, operands: STAGE7_FIELD_EXPR_OPERANDS_30 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.10.booleanity.gamma_pow", kind: "op", formula: "field.pow:31", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.10.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_31, operands: STAGE7_FIELD_EXPR_OPERANDS_31 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.10.virtualization.gamma_pow", kind: "op", formula: "field.pow:32", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.10.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_32, operands: STAGE7_FIELD_EXPR_OPERANDS_32 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.11.hw.gamma_pow", kind: "op", formula: "field.pow:33", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.11.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_33, operands: STAGE7_FIELD_EXPR_OPERANDS_33 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.11.booleanity.gamma_pow", kind: "op", formula: "field.pow:34", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.11.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_34, operands: STAGE7_FIELD_EXPR_OPERANDS_34 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.11.virtualization.gamma_pow", kind: "op", formula: "field.pow:35", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.11.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_35, operands: STAGE7_FIELD_EXPR_OPERANDS_35 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.12.hw.gamma_pow", kind: "op", formula: "field.pow:36", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.12.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_36, operands: STAGE7_FIELD_EXPR_OPERANDS_36 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.12.booleanity.gamma_pow", kind: "op", formula: "field.pow:37", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.12.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_37, operands: STAGE7_FIELD_EXPR_OPERANDS_37 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.12.virtualization.gamma_pow", kind: "op", formula: "field.pow:38", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.12.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_38, operands: STAGE7_FIELD_EXPR_OPERANDS_38 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.13.hw.gamma_pow", kind: "op", formula: "field.pow:39", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.13.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_39, operands: STAGE7_FIELD_EXPR_OPERANDS_39 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.13.booleanity.gamma_pow", kind: "op", formula: "field.pow:40", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.13.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_40, operands: STAGE7_FIELD_EXPR_OPERANDS_40 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.13.virtualization.gamma_pow", kind: "op", formula: "field.pow:41", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.13.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_41, operands: STAGE7_FIELD_EXPR_OPERANDS_41 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.14.hw.gamma_pow", kind: "op", formula: "field.pow:42", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.14.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_42, operands: STAGE7_FIELD_EXPR_OPERANDS_42 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.14.booleanity.gamma_pow", kind: "op", formula: "field.pow:43", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.14.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_43, operands: STAGE7_FIELD_EXPR_OPERANDS_43 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.14.virtualization.gamma_pow", kind: "op", formula: "field.pow:44", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.14.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_44, operands: STAGE7_FIELD_EXPR_OPERANDS_44 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.15.hw.gamma_pow", kind: "op", formula: "field.pow:45", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.15.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_45, operands: STAGE7_FIELD_EXPR_OPERANDS_45 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.15.booleanity.gamma_pow", kind: "op", formula: "field.pow:46", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.15.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_46, operands: STAGE7_FIELD_EXPR_OPERANDS_46 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.15.virtualization.gamma_pow", kind: "op", formula: "field.pow:47", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.15.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_47, operands: STAGE7_FIELD_EXPR_OPERANDS_47 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.16.hw.gamma_pow", kind: "op", formula: "field.pow:48", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.16.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_48, operands: STAGE7_FIELD_EXPR_OPERANDS_48 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.16.booleanity.gamma_pow", kind: "op", formula: "field.pow:49", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.16.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_49, operands: STAGE7_FIELD_EXPR_OPERANDS_49 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.16.virtualization.gamma_pow", kind: "op", formula: "field.pow:50", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.16.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_50, operands: STAGE7_FIELD_EXPR_OPERANDS_50 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.17.hw.gamma_pow", kind: "op", formula: "field.pow:51", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.17.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_51, operands: STAGE7_FIELD_EXPR_OPERANDS_51 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.17.booleanity.gamma_pow", kind: "op", formula: "field.pow:52", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.17.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_52, operands: STAGE7_FIELD_EXPR_OPERANDS_52 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.17.virtualization.gamma_pow", kind: "op", formula: "field.pow:53", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.17.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_53, operands: STAGE7_FIELD_EXPR_OPERANDS_53 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.18.hw.gamma_pow", kind: "op", formula: "field.pow:54", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.18.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_54, operands: STAGE7_FIELD_EXPR_OPERANDS_54 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.18.booleanity.gamma_pow", kind: "op", formula: "field.pow:55", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.18.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_55, operands: STAGE7_FIELD_EXPR_OPERANDS_55 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.18.virtualization.gamma_pow", kind: "op", formula: "field.pow:56", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.18.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_56, operands: STAGE7_FIELD_EXPR_OPERANDS_56 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.19.hw.gamma_pow", kind: "op", formula: "field.pow:57", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.19.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_57, operands: STAGE7_FIELD_EXPR_OPERANDS_57 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.19.booleanity.gamma_pow", kind: "op", formula: "field.pow:58", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.19.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_58, operands: STAGE7_FIELD_EXPR_OPERANDS_58 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.19.virtualization.gamma_pow", kind: "op", formula: "field.pow:59", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.19.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_59, operands: STAGE7_FIELD_EXPR_OPERANDS_59 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.20.hw.gamma_pow", kind: "op", formula: "field.pow:60", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.20.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_60, operands: STAGE7_FIELD_EXPR_OPERANDS_60 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.20.booleanity.gamma_pow", kind: "op", formula: "field.pow:61", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.20.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_61, operands: STAGE7_FIELD_EXPR_OPERANDS_61 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.20.virtualization.gamma_pow", kind: "op", formula: "field.pow:62", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.20.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_62, operands: STAGE7_FIELD_EXPR_OPERANDS_62 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.21.hw.gamma_pow", kind: "op", formula: "field.pow:63", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.21.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_63, operands: STAGE7_FIELD_EXPR_OPERANDS_63 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.21.booleanity.gamma_pow", kind: "op", formula: "field.pow:64", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.21.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_64, operands: STAGE7_FIELD_EXPR_OPERANDS_64 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.21.virtualization.gamma_pow", kind: "op", formula: "field.pow:65", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.21.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_65, operands: STAGE7_FIELD_EXPR_OPERANDS_65 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.22.hw.gamma_pow", kind: "op", formula: "field.pow:66", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.22.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_66, operands: STAGE7_FIELD_EXPR_OPERANDS_66 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.22.booleanity.gamma_pow", kind: "op", formula: "field.pow:67", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.22.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_67, operands: STAGE7_FIELD_EXPR_OPERANDS_67 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.22.virtualization.gamma_pow", kind: "op", formula: "field.pow:68", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.22.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_68, operands: STAGE7_FIELD_EXPR_OPERANDS_68 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.23.hw.gamma_pow", kind: "op", formula: "field.pow:69", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.23.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_69, operands: STAGE7_FIELD_EXPR_OPERANDS_69 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.23.booleanity.gamma_pow", kind: "op", formula: "field.pow:70", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.23.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_70, operands: STAGE7_FIELD_EXPR_OPERANDS_70 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.23.virtualization.gamma_pow", kind: "op", formula: "field.pow:71", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.23.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_71, operands: STAGE7_FIELD_EXPR_OPERANDS_71 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.24.hw.gamma_pow", kind: "op", formula: "field.pow:72", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.24.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_72, operands: STAGE7_FIELD_EXPR_OPERANDS_72 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.24.booleanity.gamma_pow", kind: "op", formula: "field.pow:73", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.24.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_73, operands: STAGE7_FIELD_EXPR_OPERANDS_73 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.24.virtualization.gamma_pow", kind: "op", formula: "field.pow:74", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.24.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_74, operands: STAGE7_FIELD_EXPR_OPERANDS_74 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.25.hw.gamma_pow", kind: "op", formula: "field.pow:75", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.25.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_75, operands: STAGE7_FIELD_EXPR_OPERANDS_75 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.25.booleanity.gamma_pow", kind: "op", formula: "field.pow:76", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.25.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_76, operands: STAGE7_FIELD_EXPR_OPERANDS_76 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.25.virtualization.gamma_pow", kind: "op", formula: "field.pow:77", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.25.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_77, operands: STAGE7_FIELD_EXPR_OPERANDS_77 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.26.hw.gamma_pow", kind: "op", formula: "field.pow:78", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.26.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_78, operands: STAGE7_FIELD_EXPR_OPERANDS_78 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.26.booleanity.gamma_pow", kind: "op", formula: "field.pow:79", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.26.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_79, operands: STAGE7_FIELD_EXPR_OPERANDS_79 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.26.virtualization.gamma_pow", kind: "op", formula: "field.pow:80", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.26.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_80, operands: STAGE7_FIELD_EXPR_OPERANDS_80 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.27.hw.gamma_pow", kind: "op", formula: "field.pow:81", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.27.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_81, operands: STAGE7_FIELD_EXPR_OPERANDS_81 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.27.booleanity.gamma_pow", kind: "op", formula: "field.pow:82", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.27.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_82, operands: STAGE7_FIELD_EXPR_OPERANDS_82 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.27.virtualization.gamma_pow", kind: "op", formula: "field.pow:83", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.27.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_83, operands: STAGE7_FIELD_EXPR_OPERANDS_83 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.28.hw.gamma_pow", kind: "op", formula: "field.pow:84", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.28.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_84, operands: STAGE7_FIELD_EXPR_OPERANDS_84 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.28.booleanity.gamma_pow", kind: "op", formula: "field.pow:85", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.28.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_85, operands: STAGE7_FIELD_EXPR_OPERANDS_85 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.28.virtualization.gamma_pow", kind: "op", formula: "field.pow:86", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.28.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_86, operands: STAGE7_FIELD_EXPR_OPERANDS_86 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.29.hw.gamma_pow", kind: "op", formula: "field.pow:87", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.29.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_87, operands: STAGE7_FIELD_EXPR_OPERANDS_87 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.29.booleanity.gamma_pow", kind: "op", formula: "field.pow:88", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.29.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_88, operands: STAGE7_FIELD_EXPR_OPERANDS_88 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.29.virtualization.gamma_pow", kind: "op", formula: "field.pow:89", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.29.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_89, operands: STAGE7_FIELD_EXPR_OPERANDS_89 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.30.hw.gamma_pow", kind: "op", formula: "field.pow:90", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.30.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_90, operands: STAGE7_FIELD_EXPR_OPERANDS_90 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.30.booleanity.gamma_pow", kind: "op", formula: "field.pow:91", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.30.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_91, operands: STAGE7_FIELD_EXPR_OPERANDS_91 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.30.virtualization.gamma_pow", kind: "op", formula: "field.pow:92", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.30.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_92, operands: STAGE7_FIELD_EXPR_OPERANDS_92 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.31.hw.gamma_pow", kind: "op", formula: "field.pow:93", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.31.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_93, operands: STAGE7_FIELD_EXPR_OPERANDS_93 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.31.booleanity.gamma_pow", kind: "op", formula: "field.pow:94", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.31.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_94, operands: STAGE7_FIELD_EXPR_OPERANDS_94 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.31.virtualization.gamma_pow", kind: "op", formula: "field.pow:95", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.31.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_95, operands: STAGE7_FIELD_EXPR_OPERANDS_95 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.32.hw.gamma_pow", kind: "op", formula: "field.pow:96", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.32.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_96, operands: STAGE7_FIELD_EXPR_OPERANDS_96 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.32.booleanity.gamma_pow", kind: "op", formula: "field.pow:97", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.32.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_97, operands: STAGE7_FIELD_EXPR_OPERANDS_97 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.32.virtualization.gamma_pow", kind: "op", formula: "field.pow:98", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.32.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_98, operands: STAGE7_FIELD_EXPR_OPERANDS_98 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.33.hw.gamma_pow", kind: "op", formula: "field.pow:99", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.33.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_99, operands: STAGE7_FIELD_EXPR_OPERANDS_99 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.33.booleanity.gamma_pow", kind: "op", formula: "field.pow:100", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.33.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_100, operands: STAGE7_FIELD_EXPR_OPERANDS_100 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.33.virtualization.gamma_pow", kind: "op", formula: "field.pow:101", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.33.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_101, operands: STAGE7_FIELD_EXPR_OPERANDS_101 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.34.hw.gamma_pow", kind: "op", formula: "field.pow:102", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.34.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_102, operands: STAGE7_FIELD_EXPR_OPERANDS_102 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.34.booleanity.gamma_pow", kind: "op", formula: "field.pow:103", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.34.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_103, operands: STAGE7_FIELD_EXPR_OPERANDS_103 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.34.virtualization.gamma_pow", kind: "op", formula: "field.pow:104", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.34.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_104, operands: STAGE7_FIELD_EXPR_OPERANDS_104 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.35.hw.gamma_pow", kind: "op", formula: "field.pow:105", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.35.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_105, operands: STAGE7_FIELD_EXPR_OPERANDS_105 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.35.booleanity.gamma_pow", kind: "op", formula: "field.pow:106", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.35.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_106, operands: STAGE7_FIELD_EXPR_OPERANDS_106 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.35.virtualization.gamma_pow", kind: "op", formula: "field.pow:107", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.35.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_107, operands: STAGE7_FIELD_EXPR_OPERANDS_107 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.36.hw.gamma_pow", kind: "op", formula: "field.pow:108", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.36.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_108, operands: STAGE7_FIELD_EXPR_OPERANDS_108 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.36.booleanity.gamma_pow", kind: "op", formula: "field.pow:109", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.36.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_109, operands: STAGE7_FIELD_EXPR_OPERANDS_109 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.36.virtualization.gamma_pow", kind: "op", formula: "field.pow:110", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.36.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_110, operands: STAGE7_FIELD_EXPR_OPERANDS_110 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.37.hw.gamma_pow", kind: "op", formula: "field.pow:111", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.37.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_111, operands: STAGE7_FIELD_EXPR_OPERANDS_111 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.37.booleanity.gamma_pow", kind: "op", formula: "field.pow:112", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.37.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_112, operands: STAGE7_FIELD_EXPR_OPERANDS_112 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.37.virtualization.gamma_pow", kind: "op", formula: "field.pow:113", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.37.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_113, operands: STAGE7_FIELD_EXPR_OPERANDS_113 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.38.hw.gamma_pow", kind: "op", formula: "field.pow:114", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.38.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_114, operands: STAGE7_FIELD_EXPR_OPERANDS_114 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.38.booleanity.gamma_pow", kind: "op", formula: "field.pow:115", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.38.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_115, operands: STAGE7_FIELD_EXPR_OPERANDS_115 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.38.virtualization.gamma_pow", kind: "op", formula: "field.pow:116", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.38.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_116, operands: STAGE7_FIELD_EXPR_OPERANDS_116 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.39.hw.gamma_pow", kind: "op", formula: "field.pow:117", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.39.hw.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_117, operands: STAGE7_FIELD_EXPR_OPERANDS_117 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.39.booleanity.gamma_pow", kind: "op", formula: "field.pow:118", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.39.booleanity.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_118, operands: STAGE7_FIELD_EXPR_OPERANDS_118 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.39.virtualization.gamma_pow", kind: "op", formula: "field.pow:119", operand_names: STAGE7_FIELD_EXPR_OPERANDS_0, operands: STAGE7_FIELD_EXPR_OPERANDS_0 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim.39.virtualization.gamma_term", kind: "op", formula: "field.mul", operand_names: STAGE7_FIELD_EXPR_OPERANDS_119, operands: STAGE7_FIELD_EXPR_OPERANDS_119 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial0", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_120, operands: STAGE7_FIELD_EXPR_OPERANDS_120 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial1", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_121, operands: STAGE7_FIELD_EXPR_OPERANDS_121 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial2", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_122, operands: STAGE7_FIELD_EXPR_OPERANDS_122 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial3", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_123, operands: STAGE7_FIELD_EXPR_OPERANDS_123 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial4", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_124, operands: STAGE7_FIELD_EXPR_OPERANDS_124 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial5", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_125, operands: STAGE7_FIELD_EXPR_OPERANDS_125 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial6", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_126, operands: STAGE7_FIELD_EXPR_OPERANDS_126 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial7", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_127, operands: STAGE7_FIELD_EXPR_OPERANDS_127 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial8", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_128, operands: STAGE7_FIELD_EXPR_OPERANDS_128 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial9", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_129, operands: STAGE7_FIELD_EXPR_OPERANDS_129 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial10", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_130, operands: STAGE7_FIELD_EXPR_OPERANDS_130 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial11", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_131, operands: STAGE7_FIELD_EXPR_OPERANDS_131 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial12", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_132, operands: STAGE7_FIELD_EXPR_OPERANDS_132 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial13", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_133, operands: STAGE7_FIELD_EXPR_OPERANDS_133 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial14", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_134, operands: STAGE7_FIELD_EXPR_OPERANDS_134 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial15", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_135, operands: STAGE7_FIELD_EXPR_OPERANDS_135 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial16", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_136, operands: STAGE7_FIELD_EXPR_OPERANDS_136 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial17", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_137, operands: STAGE7_FIELD_EXPR_OPERANDS_137 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial18", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_138, operands: STAGE7_FIELD_EXPR_OPERANDS_138 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial19", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_139, operands: STAGE7_FIELD_EXPR_OPERANDS_139 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial20", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_140, operands: STAGE7_FIELD_EXPR_OPERANDS_140 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial21", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_141, operands: STAGE7_FIELD_EXPR_OPERANDS_141 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial22", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_142, operands: STAGE7_FIELD_EXPR_OPERANDS_142 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial23", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_143, operands: STAGE7_FIELD_EXPR_OPERANDS_143 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial24", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_144, operands: STAGE7_FIELD_EXPR_OPERANDS_144 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial25", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_145, operands: STAGE7_FIELD_EXPR_OPERANDS_145 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial26", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_146, operands: STAGE7_FIELD_EXPR_OPERANDS_146 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial27", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_147, operands: STAGE7_FIELD_EXPR_OPERANDS_147 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial28", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_148, operands: STAGE7_FIELD_EXPR_OPERANDS_148 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial29", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_149, operands: STAGE7_FIELD_EXPR_OPERANDS_149 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial30", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_150, operands: STAGE7_FIELD_EXPR_OPERANDS_150 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial31", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_151, operands: STAGE7_FIELD_EXPR_OPERANDS_151 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial32", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_152, operands: STAGE7_FIELD_EXPR_OPERANDS_152 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial33", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_153, operands: STAGE7_FIELD_EXPR_OPERANDS_153 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial34", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_154, operands: STAGE7_FIELD_EXPR_OPERANDS_154 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial35", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_155, operands: STAGE7_FIELD_EXPR_OPERANDS_155 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial36", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_156, operands: STAGE7_FIELD_EXPR_OPERANDS_156 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial37", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_157, operands: STAGE7_FIELD_EXPR_OPERANDS_157 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial38", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_158, operands: STAGE7_FIELD_EXPR_OPERANDS_158 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial39", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_159, operands: STAGE7_FIELD_EXPR_OPERANDS_159 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial40", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_160, operands: STAGE7_FIELD_EXPR_OPERANDS_160 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial41", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_161, operands: STAGE7_FIELD_EXPR_OPERANDS_161 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial42", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_162, operands: STAGE7_FIELD_EXPR_OPERANDS_162 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial43", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_163, operands: STAGE7_FIELD_EXPR_OPERANDS_163 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial44", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_164, operands: STAGE7_FIELD_EXPR_OPERANDS_164 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial45", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_165, operands: STAGE7_FIELD_EXPR_OPERANDS_165 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial46", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_166, operands: STAGE7_FIELD_EXPR_OPERANDS_166 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial47", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_167, operands: STAGE7_FIELD_EXPR_OPERANDS_167 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial48", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_168, operands: STAGE7_FIELD_EXPR_OPERANDS_168 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial49", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_169, operands: STAGE7_FIELD_EXPR_OPERANDS_169 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial50", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_170, operands: STAGE7_FIELD_EXPR_OPERANDS_170 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial51", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_171, operands: STAGE7_FIELD_EXPR_OPERANDS_171 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial52", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_172, operands: STAGE7_FIELD_EXPR_OPERANDS_172 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial53", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_173, operands: STAGE7_FIELD_EXPR_OPERANDS_173 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial54", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_174, operands: STAGE7_FIELD_EXPR_OPERANDS_174 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial55", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_175, operands: STAGE7_FIELD_EXPR_OPERANDS_175 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial56", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_176, operands: STAGE7_FIELD_EXPR_OPERANDS_176 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial57", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_177, operands: STAGE7_FIELD_EXPR_OPERANDS_177 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial58", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_178, operands: STAGE7_FIELD_EXPR_OPERANDS_178 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial59", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_179, operands: STAGE7_FIELD_EXPR_OPERANDS_179 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial60", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_180, operands: STAGE7_FIELD_EXPR_OPERANDS_180 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial61", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_181, operands: STAGE7_FIELD_EXPR_OPERANDS_181 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial62", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_182, operands: STAGE7_FIELD_EXPR_OPERANDS_182 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial63", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_183, operands: STAGE7_FIELD_EXPR_OPERANDS_183 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial64", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_184, operands: STAGE7_FIELD_EXPR_OPERANDS_184 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial65", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_185, operands: STAGE7_FIELD_EXPR_OPERANDS_185 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial66", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_186, operands: STAGE7_FIELD_EXPR_OPERANDS_186 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial67", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_187, operands: STAGE7_FIELD_EXPR_OPERANDS_187 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial68", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_188, operands: STAGE7_FIELD_EXPR_OPERANDS_188 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial69", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_189, operands: STAGE7_FIELD_EXPR_OPERANDS_189 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial70", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_190, operands: STAGE7_FIELD_EXPR_OPERANDS_190 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial71", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_191, operands: STAGE7_FIELD_EXPR_OPERANDS_191 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial72", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_192, operands: STAGE7_FIELD_EXPR_OPERANDS_192 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial73", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_193, operands: STAGE7_FIELD_EXPR_OPERANDS_193 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial74", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_194, operands: STAGE7_FIELD_EXPR_OPERANDS_194 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial75", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_195, operands: STAGE7_FIELD_EXPR_OPERANDS_195 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial76", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_196, operands: STAGE7_FIELD_EXPR_OPERANDS_196 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial77", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_197, operands: STAGE7_FIELD_EXPR_OPERANDS_197 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial78", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_198, operands: STAGE7_FIELD_EXPR_OPERANDS_198 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial79", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_199, operands: STAGE7_FIELD_EXPR_OPERANDS_199 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial80", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_200, operands: STAGE7_FIELD_EXPR_OPERANDS_200 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial81", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_201, operands: STAGE7_FIELD_EXPR_OPERANDS_201 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial82", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_202, operands: STAGE7_FIELD_EXPR_OPERANDS_202 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial83", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_203, operands: STAGE7_FIELD_EXPR_OPERANDS_203 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial84", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_204, operands: STAGE7_FIELD_EXPR_OPERANDS_204 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial85", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_205, operands: STAGE7_FIELD_EXPR_OPERANDS_205 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial86", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_206, operands: STAGE7_FIELD_EXPR_OPERANDS_206 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial87", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_207, operands: STAGE7_FIELD_EXPR_OPERANDS_207 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial88", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_208, operands: STAGE7_FIELD_EXPR_OPERANDS_208 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial89", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_209, operands: STAGE7_FIELD_EXPR_OPERANDS_209 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial90", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_210, operands: STAGE7_FIELD_EXPR_OPERANDS_210 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial91", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_211, operands: STAGE7_FIELD_EXPR_OPERANDS_211 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial92", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_212, operands: STAGE7_FIELD_EXPR_OPERANDS_212 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial93", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_213, operands: STAGE7_FIELD_EXPR_OPERANDS_213 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial94", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_214, operands: STAGE7_FIELD_EXPR_OPERANDS_214 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial95", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_215, operands: STAGE7_FIELD_EXPR_OPERANDS_215 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial96", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_216, operands: STAGE7_FIELD_EXPR_OPERANDS_216 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial97", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_217, operands: STAGE7_FIELD_EXPR_OPERANDS_217 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial98", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_218, operands: STAGE7_FIELD_EXPR_OPERANDS_218 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial99", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_219, operands: STAGE7_FIELD_EXPR_OPERANDS_219 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial100", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_220, operands: STAGE7_FIELD_EXPR_OPERANDS_220 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial101", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_221, operands: STAGE7_FIELD_EXPR_OPERANDS_221 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial102", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_222, operands: STAGE7_FIELD_EXPR_OPERANDS_222 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial103", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_223, operands: STAGE7_FIELD_EXPR_OPERANDS_223 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial104", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_224, operands: STAGE7_FIELD_EXPR_OPERANDS_224 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial105", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_225, operands: STAGE7_FIELD_EXPR_OPERANDS_225 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial106", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_226, operands: STAGE7_FIELD_EXPR_OPERANDS_226 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial107", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_227, operands: STAGE7_FIELD_EXPR_OPERANDS_227 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial108", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_228, operands: STAGE7_FIELD_EXPR_OPERANDS_228 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial109", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_229, operands: STAGE7_FIELD_EXPR_OPERANDS_229 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial110", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_230, operands: STAGE7_FIELD_EXPR_OPERANDS_230 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial111", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_231, operands: STAGE7_FIELD_EXPR_OPERANDS_231 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial112", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_232, operands: STAGE7_FIELD_EXPR_OPERANDS_232 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial113", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_233, operands: STAGE7_FIELD_EXPR_OPERANDS_233 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial114", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_234, operands: STAGE7_FIELD_EXPR_OPERANDS_234 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial115", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_235, operands: STAGE7_FIELD_EXPR_OPERANDS_235 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial116", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_236, operands: STAGE7_FIELD_EXPR_OPERANDS_236 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial117", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_237, operands: STAGE7_FIELD_EXPR_OPERANDS_237 }, + Stage7FieldExprPlan { symbol: "stage7.hamming_weight_claim_reduction.claim_expr.partial118", kind: "op", formula: "field.add", operand_names: STAGE7_FIELD_EXPR_OPERANDS_238, operands: STAGE7_FIELD_EXPR_OPERANDS_238 }, +]; +pub const STAGE7_KERNELS: &[Stage7KernelPlan] = &[ + Stage7KernelPlan { symbol: "jolt.cpu.stage7.hamming_weight_claim_reduction", relation: "jolt.stage7.hamming_weight_claim_reduction", kind: "sumcheck", backend: "cpu", abi: "jolt_stage7_hamming_weight_claim_reduction" }, + Stage7KernelPlan { symbol: "jolt.cpu.stage7.batched", relation: "jolt.stage7.batched", kind: "sumcheck", backend: "cpu", abi: "jolt_stage7_batched" }, +]; + +pub const STAGE7_SUMCHECK_CLAIM_0_INPUT_OPENINGS: &[&str] = &[ + "stage7.input.stage6.hamming_booleanity.HammingWeight", + "stage7.input.stage6.booleanity.InstructionRa_0", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_0", + "stage7.input.stage6.booleanity.InstructionRa_1", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_1", + "stage7.input.stage6.booleanity.InstructionRa_2", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_2", + "stage7.input.stage6.booleanity.InstructionRa_3", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_3", + "stage7.input.stage6.booleanity.InstructionRa_4", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_4", + "stage7.input.stage6.booleanity.InstructionRa_5", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_5", + "stage7.input.stage6.booleanity.InstructionRa_6", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_6", + "stage7.input.stage6.booleanity.InstructionRa_7", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_7", + "stage7.input.stage6.booleanity.InstructionRa_8", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_8", + "stage7.input.stage6.booleanity.InstructionRa_9", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_9", + "stage7.input.stage6.booleanity.InstructionRa_10", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_10", + "stage7.input.stage6.booleanity.InstructionRa_11", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_11", + "stage7.input.stage6.booleanity.InstructionRa_12", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_12", + "stage7.input.stage6.booleanity.InstructionRa_13", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_13", + "stage7.input.stage6.booleanity.InstructionRa_14", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_14", + "stage7.input.stage6.booleanity.InstructionRa_15", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_15", + "stage7.input.stage6.booleanity.InstructionRa_16", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_16", + "stage7.input.stage6.booleanity.InstructionRa_17", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_17", + "stage7.input.stage6.booleanity.InstructionRa_18", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_18", + "stage7.input.stage6.booleanity.InstructionRa_19", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_19", + "stage7.input.stage6.booleanity.InstructionRa_20", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_20", + "stage7.input.stage6.booleanity.InstructionRa_21", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_21", + "stage7.input.stage6.booleanity.InstructionRa_22", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_22", + "stage7.input.stage6.booleanity.InstructionRa_23", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_23", + "stage7.input.stage6.booleanity.InstructionRa_24", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_24", + "stage7.input.stage6.booleanity.InstructionRa_25", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_25", + "stage7.input.stage6.booleanity.InstructionRa_26", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_26", + "stage7.input.stage6.booleanity.InstructionRa_27", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_27", + "stage7.input.stage6.booleanity.InstructionRa_28", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_28", + "stage7.input.stage6.booleanity.InstructionRa_29", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_29", + "stage7.input.stage6.booleanity.InstructionRa_30", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_30", + "stage7.input.stage6.booleanity.InstructionRa_31", + "stage7.input.stage6.instruction_ra_virtual.InstructionRa_31", + "stage7.input.stage6.booleanity.BytecodeRa_0", + "stage7.input.stage6.bytecode_read_raf.BytecodeRa_0", + "stage7.input.stage6.booleanity.BytecodeRa_1", + "stage7.input.stage6.bytecode_read_raf.BytecodeRa_1", + "stage7.input.stage6.booleanity.BytecodeRa_2", + "stage7.input.stage6.bytecode_read_raf.BytecodeRa_2", + "stage7.input.stage6.booleanity.BytecodeRa_3", + "stage7.input.stage6.bytecode_read_raf.BytecodeRa_3", + "stage7.input.stage6.booleanity.RamRa_0", + "stage7.input.stage6.ram_ra_virtual.RamRa_0", + "stage7.input.stage6.booleanity.RamRa_1", + "stage7.input.stage6.ram_ra_virtual.RamRa_1", + "stage7.input.stage6.booleanity.RamRa_2", + "stage7.input.stage6.ram_ra_virtual.RamRa_2", + "stage7.input.stage6.booleanity.RamRa_3", + "stage7.input.stage6.ram_ra_virtual.RamRa_3", +]; + +pub const STAGE7_SUMCHECK_CLAIMS: &[Stage7SumcheckClaimPlan] = &[ + Stage7SumcheckClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.input", stage: "stage7", domain: "jolt.stage7_hamming_weight_claim_reduction_domain", num_rounds: 4, degree: 2, claim: "stage7.hamming_weight_claim_reduction.weighted_stage6_claims", kernel: Some("jolt.cpu.stage7.hamming_weight_claim_reduction"), relation: None, claim_value: "stage7.hamming_weight_claim_reduction.claim_expr.partial118", input_openings: STAGE7_SUMCHECK_CLAIM_0_INPUT_OPENINGS }, +]; +pub const STAGE7_SUMCHECK_BATCH_0_ORDERED_CLAIMS: &[&str] = &["stage7.hamming_weight_claim_reduction.input"]; + +pub const STAGE7_SUMCHECK_BATCH_0_CLAIM_OPERANDS: &[&str] = &["stage7.hamming_weight_claim_reduction.input"]; + +pub const STAGE7_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 4, +]; + +pub const STAGE7_SUMCHECK_BATCHES: &[Stage7SumcheckBatchPlan] = &[ + Stage7SumcheckBatchPlan { symbol: "stage7.batch", stage: "stage7", proof_slot: "stage7.sumcheck", policy: "jolt_core_stage7_aligned", count: 1, ordered_claims: STAGE7_SUMCHECK_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE7_SUMCHECK_BATCH_0_CLAIM_OPERANDS, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE7_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE7_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 4, +]; + +pub const STAGE7_SUMCHECK_DRIVERS: &[Stage7SumcheckDriverPlan] = &[ + Stage7SumcheckDriverPlan { symbol: "stage7.sumcheck", stage: "stage7", proof_slot: "stage7.sumcheck", kernel: Some("jolt.cpu.stage7.batched"), relation: None, batch: "stage7.batch", policy: "jolt_core_stage7_aligned", round_schedule: STAGE7_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 4, degree: 2 }, +]; +pub const STAGE7_SUMCHECK_INSTANCE_RESULTS: &[Stage7SumcheckInstanceResultPlan] = &[ + Stage7SumcheckInstanceResultPlan { symbol: "stage7.hamming_weight_claim_reduction.instance", source: "stage7.sumcheck", claim: "stage7.hamming_weight_claim_reduction.input", relation: "jolt.stage7.hamming_weight_claim_reduction", index: 0, point_arity: 4, num_rounds: 4, round_offset: 0, point_order: "reverse", degree: 2 }, +]; + +macro_rules! stage7_sumcheck_eval { + ($symbol:literal, $source:literal, $name:literal, $index:literal, $oracle:literal) => { + Stage7SumcheckEvalPlan { symbol: $symbol, source: $source, name: $name, index: $index, oracle: $oracle } + }; +} + +#[rustfmt::skip] +pub const STAGE7_SUMCHECK_EVALS: &[Stage7SumcheckEvalPlan] = &[ + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", 0, "InstructionRa_0"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_1", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_1", 1, "InstructionRa_1"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_2", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_2", 2, "InstructionRa_2"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_3", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_3", 3, "InstructionRa_3"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_4", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_4", 4, "InstructionRa_4"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_5", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_5", 5, "InstructionRa_5"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_6", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_6", 6, "InstructionRa_6"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_7", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_7", 7, "InstructionRa_7"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_8", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_8", 8, "InstructionRa_8"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_9", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_9", 9, "InstructionRa_9"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_10", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_10", 10, "InstructionRa_10"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_11", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_11", 11, "InstructionRa_11"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_12", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_12", 12, "InstructionRa_12"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_13", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_13", 13, "InstructionRa_13"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_14", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_14", 14, "InstructionRa_14"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_15", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_15", 15, "InstructionRa_15"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_16", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_16", 16, "InstructionRa_16"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_17", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_17", 17, "InstructionRa_17"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_18", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_18", 18, "InstructionRa_18"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_19", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_19", 19, "InstructionRa_19"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_20", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_20", 20, "InstructionRa_20"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_21", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_21", 21, "InstructionRa_21"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_22", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_22", 22, "InstructionRa_22"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_23", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_23", 23, "InstructionRa_23"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_24", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_24", 24, "InstructionRa_24"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_25", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_25", 25, "InstructionRa_25"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_26", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_26", 26, "InstructionRa_26"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_27", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_27", 27, "InstructionRa_27"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_28", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_28", 28, "InstructionRa_28"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_29", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_29", 29, "InstructionRa_29"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_30", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_30", 30, "InstructionRa_30"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_31", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_31", 31, "InstructionRa_31"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0", 32, "BytecodeRa_0"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1", 33, "BytecodeRa_1"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2", 34, "BytecodeRa_2"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3", 35, "BytecodeRa_3"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.RamRa_0", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.RamRa_0", 36, "RamRa_0"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.RamRa_1", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.RamRa_1", 37, "RamRa_1"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.RamRa_2", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.RamRa_2", 38, "RamRa_2"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.RamRa_3", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.RamRa_3", 39, "RamRa_3"), +]; + +pub const STAGE7_POINT_ZEROS: &[Stage7PointZeroPlan] = &[ + +]; + +pub const STAGE7_POINT_SLICES: &[Stage7PointSlicePlan] = &[ + Stage7PointSlicePlan { symbol: "stage7.hamming_weight_claim_reduction.point.cycle", source: "stage7.input.stage6.booleanity.InstructionRa_0", offset: 4, length: 18, input: "stage7.input.stage6.booleanity.InstructionRa_0" }, +]; + +pub const STAGE7_POINT_CONCAT_0_INPUTS: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.instance", + "stage7.hamming_weight_claim_reduction.point.cycle", +]; + +pub const STAGE7_POINT_CONCATS: &[Stage7PointConcatPlan] = &[ + Stage7PointConcatPlan { symbol: "stage7.hamming_weight_claim_reduction.point", layout: "address_chunk_then_cycle", arity: 22, inputs: STAGE7_POINT_CONCAT_0_INPUTS }, +]; +pub const STAGE7_OPENING_CLAIMS: &[Stage7OpeningClaimPlan] = &[ + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_1" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_2" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_3" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_4" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_5" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_6" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_7" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_8" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_9" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_10" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_11" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_12" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_13" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_14" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_15" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_16" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_17" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_18" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_19" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_20" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_21" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_22" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_23" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_24" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_25" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_26" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_27" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_28" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_29" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_30" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_31" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_0" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_1" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_2" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_3" }, +]; + +pub const STAGE7_OPENING_EQUALITIES: &[Stage7OpeningClaimEqualityPlan] = &[ + +]; + +pub const STAGE7_OPENING_BATCH_0_ORDERED_CLAIMS: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_1", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_2", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_3", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_4", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_5", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_6", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_7", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_8", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_9", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_10", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_11", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_12", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_13", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_14", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_15", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_16", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_17", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_18", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_19", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_20", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_21", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_22", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_23", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_24", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_25", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_26", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_27", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_28", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_29", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_30", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_31", + "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_0", + "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_1", + "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_2", + "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_3", + "stage7.hamming_weight_claim_reduction.opening.RamRa_0", + "stage7.hamming_weight_claim_reduction.opening.RamRa_1", + "stage7.hamming_weight_claim_reduction.opening.RamRa_2", + "stage7.hamming_weight_claim_reduction.opening.RamRa_3", +]; + +pub const STAGE7_OPENING_BATCH_0_CLAIM_OPERANDS: &[&str] = &[ + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_1", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_2", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_3", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_4", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_5", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_6", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_7", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_8", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_9", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_10", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_11", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_12", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_13", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_14", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_15", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_16", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_17", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_18", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_19", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_20", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_21", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_22", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_23", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_24", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_25", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_26", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_27", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_28", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_29", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_30", + "stage7.hamming_weight_claim_reduction.opening.InstructionRa_31", + "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_0", + "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_1", + "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_2", + "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_3", + "stage7.hamming_weight_claim_reduction.opening.RamRa_0", + "stage7.hamming_weight_claim_reduction.opening.RamRa_1", + "stage7.hamming_weight_claim_reduction.opening.RamRa_2", + "stage7.hamming_weight_claim_reduction.opening.RamRa_3", +]; + +pub const STAGE7_OPENING_BATCHES: &[Stage7OpeningBatchPlan] = &[ + Stage7OpeningBatchPlan { symbol: "stage7.openings", stage: "stage7", proof_slot: "stage7.openings", policy: "jolt_stage7_output_order", count: 40, ordered_claims: STAGE7_OPENING_BATCH_0_ORDERED_CLAIMS, claim_operands: STAGE7_OPENING_BATCH_0_CLAIM_OPERANDS }, +]; +pub const STAGE7_PROGRAM: Stage7CpuProgramPlan = Stage7CpuProgramPlan { + role: "prover", + params: STAGE7_PARAMS, + steps: STAGE7_PROGRAM_STEPS, + transcript_squeezes: STAGE7_TRANSCRIPT_SQUEEZES, + transcript_absorb_bytes: STAGE7_TRANSCRIPT_ABSORB_BYTES, + opening_inputs: STAGE7_OPENING_INPUTS, + field_constants: STAGE7_FIELD_CONSTANTS, + field_exprs: STAGE7_FIELD_EXPRS, + kernels: STAGE7_KERNELS, + claims: STAGE7_SUMCHECK_CLAIMS, + batches: STAGE7_SUMCHECK_BATCHES, + drivers: STAGE7_SUMCHECK_DRIVERS, + instance_results: STAGE7_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE7_SUMCHECK_EVALS, + point_zeros: STAGE7_POINT_ZEROS, + point_slices: STAGE7_POINT_SLICES, + point_concats: STAGE7_POINT_CONCATS, + opening_claims: STAGE7_OPENING_CLAIMS, + opening_equalities: STAGE7_OPENING_EQUALITIES, + opening_batches: STAGE7_OPENING_BATCHES, +}; + +pub fn execute_stage7_prover( + executor: &mut E, + transcript: &mut T, +) -> Result, Stage7KernelError> +where + E: Stage7KernelExecutor, + T: Transcript, +{ + execute_stage7_prover_with_program(&STAGE7_PROGRAM, executor, transcript) +} + +pub fn execute_stage7_prover_with_program( + program: &'static Stage7CpuProgramPlan, + executor: &mut E, + transcript: &mut T, +) -> Result, Stage7KernelError> +where + E: Stage7KernelExecutor, + T: Transcript, +{ + execute_stage7_program(program, Stage7ExecutionMode::Prover, executor, transcript) +} diff --git a/crates/jolt-prover/src/stages/stage8.rs b/crates/jolt-prover/src/stages/stage8.rs new file mode 100644 index 0000000000..b2d7a77d6d --- /dev/null +++ b/crates/jolt-prover/src/stages/stage8.rs @@ -0,0 +1,175 @@ +#![allow(clippy::too_many_lines)] + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub family: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub point_source: &'static str, + pub eval_source: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8OpeningBatchPlan { + pub symbol: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8PcsProofPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub pcs: &'static str, + pub proof_slot: &'static str, + pub transcript_label: &'static str, + pub batch: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8EvaluationProgramPlan { + pub role: &'static str, + pub function: &'static str, + pub params: Stage8Params, + pub evaluation_point_source: Stage8OpeningInputPlan, + pub opening_inputs: &'static [Stage8OpeningInputPlan], + pub opening_claims: &'static [Stage8OpeningClaimPlan], + pub opening_batch: Stage8OpeningBatchPlan, + pub pcs_proof: Stage8PcsProofPlan, +} + +pub const STAGE8_PARAMS: Stage8Params = Stage8Params { field: "bn254_fr", pcs: "dory", transcript: "blake2b_transcript" }; + +pub const STAGE8_EVALUATION_POINT_SOURCE: Stage8OpeningInputPlan = Stage8OpeningInputPlan { symbol: "stage8.evaluation.point_source", source_stage: "stage7", source_claim: "stage7.input.stage6.booleanity.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }; + +pub const STAGE8_OPENING_INPUTS: &[Stage8OpeningInputPlan] = &[ + Stage8OpeningInputPlan { symbol: "stage8.evaluation.point_source", source_stage: "stage7", source_claim: "stage7.input.stage6.booleanity.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage6.RamInc", source_stage: "stage6", source_claim: "stage6.inc_claim_reduction.eval.RamInc", oracle: "RamInc", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage6.RdInc", source_stage: "stage6", source_claim: "stage6.inc_claim_reduction.eval.RdInc", oracle: "RdInc", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_4", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_5", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_6", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_7", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_8", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_9", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_10", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_11", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_12", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_13", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_14", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_15", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_16", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_17", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_18", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_19", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_20", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_21", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_22", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_23", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_24", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_25", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_26", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_27", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_28", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_29", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_30", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_31", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.BytecodeRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.BytecodeRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.BytecodeRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.BytecodeRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.RamRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.RamRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.RamRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.RamRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, +]; + +pub const STAGE8_OPENING_CLAIMS: &[Stage8OpeningClaimPlan] = &[ + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamInc", oracle: "RamInc", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage6.RamInc", eval_source: "stage8.input.stage6.RamInc", source_stage: "stage6", source_claim: "stage6.inc_claim_reduction.eval.RamInc" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RdInc", oracle: "RdInc", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage6.RdInc", eval_source: "stage8.input.stage6.RdInc", source_stage: "stage6", source_claim: "stage6.inc_claim_reduction.eval.RdInc" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_0", oracle: "InstructionRa_0", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_0", eval_source: "stage8.input.stage7.InstructionRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_1", oracle: "InstructionRa_1", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_1", eval_source: "stage8.input.stage7.InstructionRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_1" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_2", oracle: "InstructionRa_2", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_2", eval_source: "stage8.input.stage7.InstructionRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_2" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_3", oracle: "InstructionRa_3", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_3", eval_source: "stage8.input.stage7.InstructionRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_3" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_4", oracle: "InstructionRa_4", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_4", eval_source: "stage8.input.stage7.InstructionRa_4", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_4" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_5", oracle: "InstructionRa_5", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_5", eval_source: "stage8.input.stage7.InstructionRa_5", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_5" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_6", oracle: "InstructionRa_6", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_6", eval_source: "stage8.input.stage7.InstructionRa_6", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_6" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_7", oracle: "InstructionRa_7", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_7", eval_source: "stage8.input.stage7.InstructionRa_7", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_7" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_8", oracle: "InstructionRa_8", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_8", eval_source: "stage8.input.stage7.InstructionRa_8", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_8" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_9", oracle: "InstructionRa_9", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_9", eval_source: "stage8.input.stage7.InstructionRa_9", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_9" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_10", oracle: "InstructionRa_10", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_10", eval_source: "stage8.input.stage7.InstructionRa_10", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_10" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_11", oracle: "InstructionRa_11", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_11", eval_source: "stage8.input.stage7.InstructionRa_11", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_11" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_12", oracle: "InstructionRa_12", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_12", eval_source: "stage8.input.stage7.InstructionRa_12", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_12" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_13", oracle: "InstructionRa_13", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_13", eval_source: "stage8.input.stage7.InstructionRa_13", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_13" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_14", oracle: "InstructionRa_14", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_14", eval_source: "stage8.input.stage7.InstructionRa_14", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_14" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_15", oracle: "InstructionRa_15", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_15", eval_source: "stage8.input.stage7.InstructionRa_15", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_15" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_16", oracle: "InstructionRa_16", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_16", eval_source: "stage8.input.stage7.InstructionRa_16", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_16" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_17", oracle: "InstructionRa_17", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_17", eval_source: "stage8.input.stage7.InstructionRa_17", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_17" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_18", oracle: "InstructionRa_18", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_18", eval_source: "stage8.input.stage7.InstructionRa_18", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_18" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_19", oracle: "InstructionRa_19", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_19", eval_source: "stage8.input.stage7.InstructionRa_19", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_19" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_20", oracle: "InstructionRa_20", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_20", eval_source: "stage8.input.stage7.InstructionRa_20", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_20" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_21", oracle: "InstructionRa_21", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_21", eval_source: "stage8.input.stage7.InstructionRa_21", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_21" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_22", oracle: "InstructionRa_22", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_22", eval_source: "stage8.input.stage7.InstructionRa_22", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_22" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_23", oracle: "InstructionRa_23", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_23", eval_source: "stage8.input.stage7.InstructionRa_23", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_23" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_24", oracle: "InstructionRa_24", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_24", eval_source: "stage8.input.stage7.InstructionRa_24", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_24" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_25", oracle: "InstructionRa_25", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_25", eval_source: "stage8.input.stage7.InstructionRa_25", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_25" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_26", oracle: "InstructionRa_26", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_26", eval_source: "stage8.input.stage7.InstructionRa_26", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_26" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_27", oracle: "InstructionRa_27", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_27", eval_source: "stage8.input.stage7.InstructionRa_27", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_27" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_28", oracle: "InstructionRa_28", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_28", eval_source: "stage8.input.stage7.InstructionRa_28", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_28" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_29", oracle: "InstructionRa_29", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_29", eval_source: "stage8.input.stage7.InstructionRa_29", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_29" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_30", oracle: "InstructionRa_30", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_30", eval_source: "stage8.input.stage7.InstructionRa_30", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_30" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_31", oracle: "InstructionRa_31", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_31", eval_source: "stage8.input.stage7.InstructionRa_31", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_31" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.BytecodeRa_0", oracle: "BytecodeRa_0", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.BytecodeRa_0", eval_source: "stage8.input.stage7.BytecodeRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.BytecodeRa_1", oracle: "BytecodeRa_1", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.BytecodeRa_1", eval_source: "stage8.input.stage7.BytecodeRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.BytecodeRa_2", oracle: "BytecodeRa_2", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.BytecodeRa_2", eval_source: "stage8.input.stage7.BytecodeRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.BytecodeRa_3", oracle: "BytecodeRa_3", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.BytecodeRa_3", eval_source: "stage8.input.stage7.BytecodeRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamRa_0", oracle: "RamRa_0", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.RamRa_0", eval_source: "stage8.input.stage7.RamRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_0" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamRa_1", oracle: "RamRa_1", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.RamRa_1", eval_source: "stage8.input.stage7.RamRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_1" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamRa_2", oracle: "RamRa_2", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.RamRa_2", eval_source: "stage8.input.stage7.RamRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_2" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamRa_3", oracle: "RamRa_3", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.RamRa_3", eval_source: "stage8.input.stage7.RamRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_3" }, +]; + +pub const STAGE8_OPENING_BATCH_ORDERED_CLAIMS: &[&str] = &["stage8.evaluation.opening.RamInc", "stage8.evaluation.opening.RdInc", "stage8.evaluation.opening.InstructionRa_0", "stage8.evaluation.opening.InstructionRa_1", "stage8.evaluation.opening.InstructionRa_2", "stage8.evaluation.opening.InstructionRa_3", "stage8.evaluation.opening.InstructionRa_4", "stage8.evaluation.opening.InstructionRa_5", "stage8.evaluation.opening.InstructionRa_6", "stage8.evaluation.opening.InstructionRa_7", "stage8.evaluation.opening.InstructionRa_8", "stage8.evaluation.opening.InstructionRa_9", "stage8.evaluation.opening.InstructionRa_10", "stage8.evaluation.opening.InstructionRa_11", "stage8.evaluation.opening.InstructionRa_12", "stage8.evaluation.opening.InstructionRa_13", "stage8.evaluation.opening.InstructionRa_14", "stage8.evaluation.opening.InstructionRa_15", "stage8.evaluation.opening.InstructionRa_16", "stage8.evaluation.opening.InstructionRa_17", "stage8.evaluation.opening.InstructionRa_18", "stage8.evaluation.opening.InstructionRa_19", "stage8.evaluation.opening.InstructionRa_20", "stage8.evaluation.opening.InstructionRa_21", "stage8.evaluation.opening.InstructionRa_22", "stage8.evaluation.opening.InstructionRa_23", "stage8.evaluation.opening.InstructionRa_24", "stage8.evaluation.opening.InstructionRa_25", "stage8.evaluation.opening.InstructionRa_26", "stage8.evaluation.opening.InstructionRa_27", "stage8.evaluation.opening.InstructionRa_28", "stage8.evaluation.opening.InstructionRa_29", "stage8.evaluation.opening.InstructionRa_30", "stage8.evaluation.opening.InstructionRa_31", "stage8.evaluation.opening.BytecodeRa_0", "stage8.evaluation.opening.BytecodeRa_1", "stage8.evaluation.opening.BytecodeRa_2", "stage8.evaluation.opening.BytecodeRa_3", "stage8.evaluation.opening.RamRa_0", "stage8.evaluation.opening.RamRa_1", "stage8.evaluation.opening.RamRa_2", "stage8.evaluation.opening.RamRa_3"]; + +pub const STAGE8_OPENING_BATCH: Stage8OpeningBatchPlan = Stage8OpeningBatchPlan { symbol: "stage8.evaluation.openings", proof_slot: "stage8.evaluation", policy: "jolt_stage8_joint_rlc", count: 42, ordered_claims: STAGE8_OPENING_BATCH_ORDERED_CLAIMS }; + +pub const STAGE8_PCS_PROOF: Stage8PcsProofPlan = Stage8PcsProofPlan { symbol: "stage8.evaluation.proof", mode: "open", pcs: "dory", proof_slot: "stage8.evaluation", transcript_label: "rlc_claims", batch: "stage8.evaluation.openings" }; + +pub const STAGE8_PROGRAM: Stage8EvaluationProgramPlan = Stage8EvaluationProgramPlan { + role: "prover", + function: "jolt.stage8", + params: STAGE8_PARAMS, + evaluation_point_source: STAGE8_EVALUATION_POINT_SOURCE, + opening_inputs: STAGE8_OPENING_INPUTS, + opening_claims: STAGE8_OPENING_CLAIMS, + opening_batch: STAGE8_OPENING_BATCH, + pcs_proof: STAGE8_PCS_PROOF, +}; diff --git a/crates/jolt-r1cs/src/constraint.rs b/crates/jolt-r1cs/src/constraint.rs index 7222b4c352..296261f035 100644 --- a/crates/jolt-r1cs/src/constraint.rs +++ b/crates/jolt-r1cs/src/constraint.rs @@ -149,7 +149,7 @@ fn dot(row: &[(usize, F)], witness: &[F]) -> F { #[cfg(test)] mod tests { use super::*; - use jolt_field::{Fr, FromPrimitiveInt}; + use jolt_field::Fr; #[test] fn satisfied_constraint() { diff --git a/crates/jolt-r1cs/src/constraints/rv64.rs b/crates/jolt-r1cs/src/constraints/rv64.rs index 33d24178c1..0bd95331ce 100644 --- a/crates/jolt-r1cs/src/constraints/rv64.rs +++ b/crates/jolt-r1cs/src/constraints/rv64.rs @@ -388,7 +388,7 @@ pub fn rv64_constraints() -> crate::ConstraintMatrices { #[expect(clippy::expect_used, reason = "tests may unwind via panic")] mod tests { use super::*; - use jolt_field::{Fr, FromPrimitiveInt}; + use jolt_field::Fr; use num_traits::Zero; /// A no-op cycle: const=1, all else zero. All eq-conditional guards diff --git a/crates/jolt-r1cs/src/key.rs b/crates/jolt-r1cs/src/key.rs index 767199a9ef..b25625c2ba 100644 --- a/crates/jolt-r1cs/src/key.rs +++ b/crates/jolt-r1cs/src/key.rs @@ -280,7 +280,7 @@ impl R1csKey { mod tests { use super::*; use crate::constraint::ConstraintMatrices; - use jolt_field::{Fr, FromPrimitiveInt, RandomSampling}; + use jolt_field::{Field, Fr}; use num_traits::{One, Zero}; /// x * x = y, y * x = z — 2 constraints, 4 vars [1, x, y, z] diff --git a/crates/jolt-r1cs/src/lib.rs b/crates/jolt-r1cs/src/lib.rs index 62f0f4176d..64131de158 100644 --- a/crates/jolt-r1cs/src/lib.rs +++ b/crates/jolt-r1cs/src/lib.rs @@ -13,8 +13,10 @@ pub mod constraint; pub mod constraints; pub mod key; pub mod provider; +pub mod row_dots; pub use column::R1csColumn; pub use constraint::ConstraintMatrices; pub use key::R1csKey; pub use provider::{R1csSource, SpartanChallenges}; +pub use row_dots::{R1csRowDotSlice, R1csRowDotTable}; diff --git a/crates/jolt-r1cs/src/provider.rs b/crates/jolt-r1cs/src/provider.rs index 79f52c9509..1fa2ea7910 100644 --- a/crates/jolt-r1cs/src/provider.rs +++ b/crates/jolt-r1cs/src/provider.rs @@ -147,7 +147,7 @@ impl<'a, F: Field> R1csSource<'a, F> { mod tests { use super::*; use crate::constraint::ConstraintMatrices; - use jolt_field::{Fr, FromPrimitiveInt}; + use jolt_field::{Field, Fr}; use num_traits::{One, Zero}; #[test] diff --git a/crates/jolt-r1cs/src/row_dots.rs b/crates/jolt-r1cs/src/row_dots.rs new file mode 100644 index 0000000000..11a040a10b --- /dev/null +++ b/crates/jolt-r1cs/src/row_dots.rs @@ -0,0 +1,118 @@ +use jolt_field::Field; + +use crate::R1csKey; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +#[derive(Clone, Debug)] +pub struct R1csRowDotTable { + row_count: usize, + cycle_count: usize, + a: Vec, + b: Vec, +} + +#[derive(Clone, Copy, Debug)] +pub struct R1csRowDotSlice<'a, F: Field> { + pub a: &'a [F], + pub b: &'a [F], +} + +impl R1csRowDotTable { + #[tracing::instrument(skip_all, name = "R1csRowDotTable::compute_ab_prefix")] + pub fn compute_ab_prefix(key: &R1csKey, witness: &[F], row_count: usize) -> Self { + assert!( + row_count <= key.matrices.num_constraints, + "row_count exceeds R1CS constraint count" + ); + let expected = key.num_cycles * key.num_vars_padded; + assert_eq!( + witness.len(), + expected, + "R1CS witness length does not match key shape" + ); + + let total = key.num_cycles * row_count; + let mut a = vec![F::zero(); total]; + let mut b = vec![F::zero(); total]; + compute_row_dots(key, witness, row_count, &mut a, &mut b); + + Self { + row_count, + cycle_count: key.num_cycles, + a, + b, + } + } + + #[inline] + pub fn row_count(&self) -> usize { + self.row_count + } + + #[inline] + pub fn cycle_count(&self) -> usize { + self.cycle_count + } + + #[inline] + pub fn cycle(&self, cycle: usize) -> R1csRowDotSlice<'_, F> { + assert!(cycle < self.cycle_count, "cycle index out of bounds"); + let start = cycle * self.row_count; + let end = start + self.row_count; + R1csRowDotSlice { + a: &self.a[start..end], + b: &self.b[start..end], + } + } +} + +#[cfg(feature = "parallel")] +fn compute_row_dots( + key: &R1csKey, + witness: &[F], + row_count: usize, + a: &mut [F], + b: &mut [F], +) { + a.par_chunks_mut(row_count) + .zip(b.par_chunks_mut(row_count)) + .enumerate() + .for_each(|(cycle, (a_chunk, b_chunk))| { + let start = cycle * key.num_vars_padded; + let witness_row = &witness[start..start + key.matrices.num_vars]; + for row in 0..row_count { + a_chunk[row] = row_dot(&key.matrices.a[row], witness_row); + b_chunk[row] = row_dot(&key.matrices.b[row], witness_row); + } + }); +} + +#[cfg(not(feature = "parallel"))] +fn compute_row_dots( + key: &R1csKey, + witness: &[F], + row_count: usize, + a: &mut [F], + b: &mut [F], +) { + for cycle in 0..key.num_cycles { + let witness_start = cycle * key.num_vars_padded; + let row_start = cycle * row_count; + let witness_row = &witness[witness_start..witness_start + key.matrices.num_vars]; + for row in 0..row_count { + a[row_start + row] = row_dot(&key.matrices.a[row], witness_row); + b[row_start + row] = row_dot(&key.matrices.b[row], witness_row); + } + } +} + +#[inline] +fn row_dot(row: &[(usize, F)], witness: &[F]) -> F { + let mut acc = F::zero(); + for &(variable, coefficient) in row { + acc += coefficient * witness[variable]; + } + acc +} diff --git a/crates/jolt-riscv/src/lib.rs b/crates/jolt-riscv/src/lib.rs index 5ee9a433d0..988cf8f7aa 100644 --- a/crates/jolt-riscv/src/lib.rs +++ b/crates/jolt-riscv/src/lib.rs @@ -51,13 +51,12 @@ macro_rules! jolt_instruction { fn circuit_flags(&self) -> $crate::CircuitFlagSet { let mut flags = $crate::CircuitFlagSet::default() $(.set($crate::CircuitFlags::$circuit))*; - if let Some(virtual_sequence_remaining) = self.0.virtual_sequence_remaining() { + let virtual_sequence_remaining = self.0.virtual_sequence_remaining(); + if virtual_sequence_remaining.is_some() { flags = flags.set($crate::CircuitFlags::VirtualInstruction); - if virtual_sequence_remaining == 0 { - flags = flags.set($crate::CircuitFlags::IsLastInSequence); - } + $crate::jolt_instruction!(@set_is_last_in_sequence flags, $name, virtual_sequence_remaining); } - if self.0.virtual_sequence_remaining().unwrap_or(0) != 0 { + if virtual_sequence_remaining.unwrap_or(0) != 0 { flags = flags.set($crate::CircuitFlags::DoNotUpdateUnexpandedPC); } if self.0.is_compressed() { @@ -89,13 +88,12 @@ macro_rules! jolt_instruction { #[inline] fn circuit_flags(&self) -> $crate::CircuitFlagSet { let mut flags = $crate::CircuitFlagSet::default(); - if let Some(virtual_sequence_remaining) = self.0.virtual_sequence_remaining() { + let virtual_sequence_remaining = self.0.virtual_sequence_remaining(); + if virtual_sequence_remaining.is_some() { flags = flags.set($crate::CircuitFlags::VirtualInstruction); - if virtual_sequence_remaining == 0 { - flags = flags.set($crate::CircuitFlags::IsLastInSequence); - } + $crate::jolt_instruction!(@set_is_last_in_sequence flags, $name, virtual_sequence_remaining); } - if self.0.virtual_sequence_remaining().unwrap_or(0) != 0 { + if virtual_sequence_remaining.unwrap_or(0) != 0 { flags = flags.set($crate::CircuitFlags::DoNotUpdateUnexpandedPC); } if self.0.is_compressed() { @@ -133,6 +131,14 @@ macro_rules! jolt_instruction { pub struct $name(pub T); }; + (@set_is_last_in_sequence $flags:ident, Jalr, $virtual_sequence_remaining:expr) => { + if $virtual_sequence_remaining == Some(0) { + $flags = $flags.set($crate::CircuitFlags::IsLastInSequence); + } + }; + + (@set_is_last_in_sequence $flags:ident, $name:ident, $virtual_sequence_remaining:expr) => {}; + // Internal: emit `JoltInstruction` for `$name` whenever `T` is itself a // `RISCVInstruction`. Lets call sites treat the wrapper newtype as a Jolt // instruction directly without unwrapping `self.0`. diff --git a/crates/jolt-sumcheck/Cargo.toml b/crates/jolt-sumcheck/Cargo.toml index ff40a99d36..f790381acd 100644 --- a/crates/jolt-sumcheck/Cargo.toml +++ b/crates/jolt-sumcheck/Cargo.toml @@ -15,7 +15,3 @@ jolt-transcript.workspace = true serde = { workspace = true, features = ["derive"] } tracing.workspace = true thiserror.workspace = true - -[dev-dependencies] -num-traits = { workspace = true } -rand_core = { workspace = true } diff --git a/crates/jolt-sumcheck/src/batched.rs b/crates/jolt-sumcheck/src/batched.rs new file mode 100644 index 0000000000..f37b5d9397 --- /dev/null +++ b/crates/jolt-sumcheck/src/batched.rs @@ -0,0 +1,81 @@ +//! Batched sumcheck verification: reduces multiple claims into one via random +//! linear combination. +//! +//! Supports claims with **different** `num_vars` and `degree` bounds via +//! front-loaded batching: shorter instances are active only in the last +//! `num_vars` rounds and are padded with constant dummy polynomials in +//! earlier rounds. Each claim is scaled by $2^{N - n_i}$ where $N$ is the +//! maximum `num_vars` across all claims. + +use jolt_field::Field; +use jolt_transcript::{AppendToTranscript, Transcript}; + +use crate::claim::SumcheckClaim; +use crate::error::SumcheckError; +use crate::round::RoundVerifier; + +/// Batched sumcheck verifier. +/// +/// Recomputes the combined claim with the same scaling and batching +/// coefficients as the prover, then delegates to the single-instance +/// verifier. +pub struct BatchedSumcheckVerifier; + +impl BatchedSumcheckVerifier { + /// Verifies a batched sumcheck proof with a pluggable round verifier. + /// + /// Returns `(v, r)` on success, where `v` is the combined final + /// evaluation and `r` is the full challenge vector of length + /// `max(num_vars)`. + /// + /// # Errors + /// + /// Returns [`SumcheckError`] if verification fails. + #[tracing::instrument(skip_all, name = "BatchedSumcheckVerifier::verify")] + pub fn verify( + claims: &[SumcheckClaim], + round_proofs: &[V::RoundProof], + transcript: &mut T, + verifier: &V, + ) -> Result<(F, Vec), SumcheckError> + where + F: Field, + T: Transcript, + V: RoundVerifier, + { + if claims.is_empty() { + return Err(SumcheckError::EmptyClaims); + } + + let max_num_vars = claims.iter().map(|c| c.num_vars).max().unwrap(); + let max_degree = claims.iter().map(|c| c.degree).max().unwrap(); + + // Fiat-Shamir: absorb claimed sums (must match prover). + for claim in claims { + claim.claimed_sum.append_to_transcript(transcript); + } + + let alpha: F = transcript.challenge(); + + let combined_sum: F = claims + .iter() + .enumerate() + .fold(F::zero(), |acc, (j, claim)| { + let scaled = claim.claimed_sum.mul_pow_2(max_num_vars - claim.num_vars); + acc + alpha.pow(j) * scaled + }); + + let combined_claim = SumcheckClaim { + num_vars: max_num_vars, + degree: max_degree, + claimed_sum: combined_sum, + }; + + crate::verifier::SumcheckVerifier::verify( + &combined_claim, + round_proofs, + transcript, + verifier, + ) + } +} diff --git a/crates/jolt-sumcheck/src/batched_verifier.rs b/crates/jolt-sumcheck/src/batched_verifier.rs index 984c9afdb2..9f6f0b043b 100644 --- a/crates/jolt-sumcheck/src/batched_verifier.rs +++ b/crates/jolt-sumcheck/src/batched_verifier.rs @@ -7,12 +7,12 @@ //! earlier rounds. Each claim is scaled by $2^{N - n_i}$ where $N$ is the //! maximum `num_vars` across all claims. +use jolt_field::Field; use jolt_transcript::{AppendToTranscript, Transcript}; use crate::claim::{EvaluationClaim, SumcheckClaim}; use crate::error::SumcheckError; use crate::round_proof::RoundProof; -use crate::scalar::SumcheckScalar; /// Batched sumcheck verifier. /// @@ -38,7 +38,7 @@ impl BatchedSumcheckVerifier { transcript: &mut T, ) -> Result, SumcheckError> where - F: SumcheckScalar, + F: Field, T: Transcript, P: RoundProof, { diff --git a/crates/jolt-sumcheck/src/claim.rs b/crates/jolt-sumcheck/src/claim.rs index 64d910f70a..83ea885de3 100644 --- a/crates/jolt-sumcheck/src/claim.rs +++ b/crates/jolt-sumcheck/src/claim.rs @@ -1,7 +1,6 @@ //! Sumcheck claim: the public statement that the protocol proves. -use jolt_field::FieldCore; - +use jolt_field::Field; /// A sumcheck claim asserting that /// $\sum_{x \in \{0,1\}^n} g(x) = C$ /// where $g$ is a polynomial of individual degree at most `degree` in each variable. @@ -13,7 +12,7 @@ use jolt_field::FieldCore; /// For a product of $k$ multilinear polynomials, `degree = k`. /// * `claimed_sum` -- the value $C$ that the prover claims the sum equals. #[derive(Clone, Debug)] -pub struct SumcheckClaim { +pub struct SumcheckClaim { /// Number of Boolean variables in the summation. pub num_vars: usize, /// Maximum degree of each round polynomial. @@ -22,7 +21,7 @@ pub struct SumcheckClaim { pub claimed_sum: F, } -impl SumcheckClaim { +impl SumcheckClaim { /// Construct a sumcheck claim. /// /// # Panics @@ -50,7 +49,7 @@ impl SumcheckClaim { /// BlindFold, etc.) to retain soundness — sumcheck alone does not /// verify `v` against any commitment. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct EvaluationClaim { +pub struct EvaluationClaim { /// Challenge point `r = (r_1, ..., r_n)`. pub point: Vec, /// Claimed evaluation `g(r) = v`. diff --git a/crates/jolt-sumcheck/src/clear.rs b/crates/jolt-sumcheck/src/clear.rs new file mode 100644 index 0000000000..6bc19e9b30 --- /dev/null +++ b/crates/jolt-sumcheck/src/clear.rs @@ -0,0 +1,118 @@ +//! Helpers for cleartext sumcheck verifier calls. + +use jolt_field::Field; +use jolt_transcript::Transcript; + +use crate::{ClearRoundVerifier, SumcheckClaim, SumcheckError, SumcheckProof, SumcheckVerifier}; + +/// Wire encoding used for cleartext round polynomial transcript absorption. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ClearRoundEncoding { + /// Absorb every round-polynomial coefficient. + Full, + /// Absorb every coefficient except the recoverable linear term. + Compressed, +} + +/// Static verifier plan for one cleartext sumcheck instance. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ClearSumcheckPlan { + /// Number of Boolean variables in the sumcheck claim. + pub num_vars: usize, + /// Maximum allowed degree for each round polynomial. + pub degree: usize, + /// Domain-separation label absorbed before each round polynomial. + pub round_label: &'static [u8], + /// Transcript wire encoding for each round polynomial. + pub round_encoding: ClearRoundEncoding, +} + +/// Successful cleartext sumcheck verification output. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ClearSumcheckOutput { + /// Final reduced evaluation claim. + pub final_eval: F, + /// Fiat-Shamir challenge point produced by verification. + pub point: Vec, +} + +impl ClearSumcheckPlan { + /// Verifies a cleartext sumcheck proof and returns its reduced evaluation claim. + pub fn verify( + &self, + claimed_sum: F, + proof: &SumcheckProof, + transcript: &mut T, + ) -> Result, SumcheckError> + where + F: Field, + T: Transcript, + { + let claim = SumcheckClaim { + num_vars: self.num_vars, + degree: self.degree, + claimed_sum, + }; + let verifier = match self.round_encoding { + ClearRoundEncoding::Full => ClearRoundVerifier::with_label(self.round_label), + ClearRoundEncoding::Compressed => { + ClearRoundVerifier::with_label_compressed(self.round_label) + } + }; + let (final_eval, point) = + SumcheckVerifier::verify(&claim, &proof.round_polynomials, transcript, &verifier)?; + Ok(ClearSumcheckOutput { final_eval, point }) + } +} + +#[cfg(test)] +mod tests { + use jolt_field::{Field, Fr}; + use jolt_poly::UnivariatePoly; + use jolt_transcript::Blake2bTranscript; + + use super::*; + + #[test] + fn verifies_labeled_full_rounds() { + let proof = SumcheckProof { + round_polynomials: vec![UnivariatePoly::new(vec![Fr::from_u64(1), Fr::from_u64(1)])], + }; + let plan = ClearSumcheckPlan { + num_vars: 1, + degree: 1, + round_label: b"round", + round_encoding: ClearRoundEncoding::Full, + }; + let mut transcript = Blake2bTranscript::::new(b"test"); + + let output = plan + .verify(Fr::from_u64(3), &proof, &mut transcript) + .expect("valid proof"); + + assert_eq!(output.point.len(), 1); + assert_eq!( + output.final_eval, + proof.round_polynomials[0].evaluate(output.point[0]) + ); + } + + #[test] + fn verifies_labeled_compressed_rounds() { + let proof = SumcheckProof { + round_polynomials: vec![UnivariatePoly::new(vec![Fr::from_u64(1), Fr::from_u64(1)])], + }; + let plan = ClearSumcheckPlan { + num_vars: 1, + degree: 1, + round_label: b"round", + round_encoding: ClearRoundEncoding::Compressed, + }; + let mut transcript = Blake2bTranscript::::new(b"test"); + + let output = plan + .verify(Fr::from_u64(3), &proof, &mut transcript) + .expect("valid compressed proof"); + assert_eq!(output.point.len(), 1); + } +} diff --git a/crates/jolt-sumcheck/src/error.rs b/crates/jolt-sumcheck/src/error.rs index 6a17f358bf..2cad6982c0 100644 --- a/crates/jolt-sumcheck/src/error.rs +++ b/crates/jolt-sumcheck/src/error.rs @@ -1,6 +1,6 @@ //! Error types for sumcheck protocol verification failures. -use jolt_field::FieldCore; +use jolt_field::Field; /// Errors that can occur during sumcheck verification. /// @@ -9,7 +9,7 @@ use jolt_field::FieldCore; /// diverged. #[derive(Debug, thiserror::Error)] #[non_exhaustive] -pub enum SumcheckError { +pub enum SumcheckError { /// Round check failed: the sum $s_i(0) + s_i(1)$ did not match the /// expected value carried forward from the previous round. #[error("round {round}: expected sum {expected}, got {actual}")] diff --git a/crates/jolt-sumcheck/src/lib.rs b/crates/jolt-sumcheck/src/lib.rs index a37124ee2f..b71fd2d471 100644 --- a/crates/jolt-sumcheck/src/lib.rs +++ b/crates/jolt-sumcheck/src/lib.rs @@ -1,9 +1,9 @@ //! Sumcheck protocol: claims, proofs, and verification. //! //! Verifier-side types and logic for the sumcheck protocol, used by the Jolt -//! zkVM. This crate is **verifier-only** and **backend-agnostic**: any field and -//! transcript can be plugged in. Proving is handled by `jolt-zkvm`'s runtime, -//! which drives sumcheck rounds via `ComputeBackend` primitives. +//! zkVM. This crate is **verifier-side** and **backend-agnostic**: any field and +//! transcript can be plugged in. Prover-side code drives sumcheck rounds and +//! emits proof data that these verifier primitives check. //! //! # Protocol overview //! @@ -65,7 +65,6 @@ pub mod claim; pub mod error; pub mod proof; pub mod round_proof; -pub mod scalar; pub mod verifier; #[cfg(test)] @@ -76,5 +75,4 @@ pub use claim::{EvaluationClaim, SumcheckClaim}; pub use error::SumcheckError; pub use proof::SumcheckProof; pub use round_proof::{CompressedLabeledRoundPoly, LabeledRoundPoly, RoundProof}; -pub use scalar::SumcheckScalar; pub use verifier::SumcheckVerifier; diff --git a/crates/jolt-sumcheck/src/proof.rs b/crates/jolt-sumcheck/src/proof.rs index bff701c267..ff0b91724d 100644 --- a/crates/jolt-sumcheck/src/proof.rs +++ b/crates/jolt-sumcheck/src/proof.rs @@ -1,5 +1,6 @@ //! Proof structures for single and batched sumcheck protocols. +use jolt_field::Field; use jolt_poly::UnivariatePoly; use serde::{Deserialize, Serialize}; @@ -15,7 +16,7 @@ use serde::{Deserialize, Serialize}; /// $(r_1, \ldots, r_n)$. #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(bound = "")] -pub struct SumcheckProof { +pub struct SumcheckProof { /// Round polynomials $s_1, \ldots, s_n$ in the order they were generated. pub round_polynomials: Vec>, } diff --git a/crates/jolt-sumcheck/src/round.rs b/crates/jolt-sumcheck/src/round.rs new file mode 100644 index 0000000000..666c05bd9b --- /dev/null +++ b/crates/jolt-sumcheck/src/round.rs @@ -0,0 +1,143 @@ +//! Strategy trait for sumcheck round polynomial verification. +//! +//! [`RoundVerifier`] defines how round polynomials are verified against the +//! Fiat-Shamir transcript: +//! +//! - **Clear mode** ([`ClearRoundVerifier`]): polynomial coefficients are +//! checked directly — `poly(0) + poly(1) == running_sum`. +//! - **Committed mode** (future, in `jolt-blindfold`): polynomial commitments +//! are absorbed into the transcript; consistency is verified via BlindFold. + +use jolt_field::Field; +use jolt_poly::{UnivariatePoly, UnivariatePolynomial}; +use jolt_transcript::{AppendToTranscript, LabelWithCount, Transcript}; + +use crate::error::SumcheckError; + +/// Strategy for how the verifier processes per-round proof data. +/// +/// Verification proceeds in two phases per round: +/// 1. [`absorb_and_check`](Self::absorb_and_check) — absorb round data into +/// the transcript, optionally verify consistency (clear mode checks +/// `poly(0) + poly(1) == running_sum`; committed mode skips this). +/// 2. [`next_running_sum`](Self::next_running_sum) — compute the next running +/// sum using the derived challenge (clear mode evaluates the polynomial; +/// committed mode returns zero since BlindFold verifies later). +pub trait RoundVerifier { + /// Per-round proof data (`UnivariatePoly` for clear, commitment for ZK). + type RoundProof; + + /// Absorb round data into the transcript and verify consistency. + /// + /// Called BEFORE challenge derivation. The implementation must append + /// the same bytes to the transcript as the prover appended, to maintain + /// Fiat-Shamir synchronization. + fn absorb_and_check( + &self, + proof: &Self::RoundProof, + running_sum: F, + degree_bound: usize, + round: usize, + transcript: &mut impl Transcript, + ) -> Result<(), SumcheckError>; + + /// Compute the next running sum given the Fiat-Shamir challenge. + /// + /// Called AFTER challenge derivation. + fn next_running_sum(&self, proof: &Self::RoundProof, challenge: F) -> F; +} + +/// Cleartext verifier: checks polynomial consistency and evaluates. +/// +/// When `label` is `Some`, a [`LabelWithCount`] word is absorbed before +/// each round's coefficients. +/// +/// When `compressed` is `true`, the linear term `c1` is omitted from +/// transcript absorption (matching the prover's compressed wire format: +/// `c1` is recoverable from `running_sum - c0`). The label count then +/// uses `coeffs.len() - 1`. +#[derive(Default)] +pub struct ClearRoundVerifier { + label: Option<&'static [u8]>, + compressed: bool, +} + +impl ClearRoundVerifier { + /// Verifier with no domain-separation labels (raw coefficient absorption). + pub fn new() -> Self { + Self::default() + } + + /// Verifier that prepends a `LabelWithCount` word before each round's coefficients. + pub fn with_label(label: &'static [u8]) -> Self { + Self { + label: Some(label), + compressed: false, + } + } + + /// Verifier that absorbs the compressed form (omits linear term `c1`). + /// Matches the prover's `RoundPolyEncoding::Compressed` wire format. + pub fn with_label_compressed(label: &'static [u8]) -> Self { + Self { + label: Some(label), + compressed: true, + } + } +} + +impl RoundVerifier for ClearRoundVerifier { + type RoundProof = UnivariatePoly; + + fn absorb_and_check( + &self, + proof: &UnivariatePoly, + running_sum: F, + degree_bound: usize, + round: usize, + transcript: &mut impl Transcript, + ) -> Result<(), SumcheckError> { + if proof.degree() > degree_bound { + return Err(SumcheckError::DegreeBoundExceeded { + got: proof.degree(), + max: degree_bound, + }); + } + + let sum = proof.evaluate(F::zero()) + proof.evaluate(F::one()); + if sum != running_sum { + return Err(SumcheckError::RoundCheckFailed { + round, + expected: format!("{running_sum}"), + actual: format!("{sum}"), + }); + } + + let coeffs = proof.coefficients(); + if self.compressed { + let compressed_len = coeffs.len().saturating_sub(1); + if let Some(label) = self.label { + transcript.append(&LabelWithCount(label, compressed_len as u64)); + } + if let Some(c0) = coeffs.first() { + c0.append_to_transcript(transcript); + } + for c in coeffs.iter().skip(2) { + c.append_to_transcript(transcript); + } + } else { + if let Some(label) = self.label { + transcript.append(&LabelWithCount(label, coeffs.len() as u64)); + } + for coeff in coeffs { + coeff.append_to_transcript(transcript); + } + } + + Ok(()) + } + + fn next_running_sum(&self, proof: &UnivariatePoly, challenge: F) -> F { + proof.evaluate(challenge) + } +} diff --git a/crates/jolt-sumcheck/src/round_proof.rs b/crates/jolt-sumcheck/src/round_proof.rs index 54c592119a..013839a57f 100644 --- a/crates/jolt-sumcheck/src/round_proof.rs +++ b/crates/jolt-sumcheck/src/round_proof.rs @@ -11,7 +11,6 @@ use jolt_poly::{UnivariatePoly, UnivariatePolynomial}; use jolt_transcript::{AppendToTranscript, LabelWithCount, Transcript}; use crate::error::SumcheckError; -use crate::scalar::SumcheckScalar; /// Per-round proof operations used by the sumcheck verifier. /// @@ -20,7 +19,7 @@ use crate::scalar::SumcheckScalar; /// transcript labelling and compression choices. The single verifier loop /// in [`crate::SumcheckVerifier::verify`] drives any impl uniformly; future /// ZK support is a new impl, not a new strategy trait. -pub trait RoundProof { +pub trait RoundProof { /// Degree of this round polynomial (for the degree-bound check). fn degree(&self) -> usize; diff --git a/crates/jolt-sumcheck/src/scalar.rs b/crates/jolt-sumcheck/src/scalar.rs deleted file mode 100644 index 761a833ffe..0000000000 --- a/crates/jolt-sumcheck/src/scalar.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::{ - fmt::{Debug, Display}, - hash::Hash, -}; - -use jolt_field::{ - CanonicalBytes, FieldCore, FixedByteSize, FromPrimitiveInt, MulPow2, TranscriptChallenge, -}; - -/// Scalar capabilities used by the verifier-side sumcheck crate. -pub trait SumcheckScalar: - FieldCore - + FromPrimitiveInt - + MulPow2 - + CanonicalBytes - + FixedByteSize - + TranscriptChallenge - + Copy - + Default - + Eq - + Debug - + Display - + Hash - + Send - + Sync - + 'static -{ -} - -impl SumcheckScalar for F where - F: FieldCore - + FromPrimitiveInt - + MulPow2 - + CanonicalBytes - + FixedByteSize - + TranscriptChallenge - + Copy - + Default - + Eq - + Debug - + Display - + Hash - + Send - + Sync - + 'static -{ -} diff --git a/crates/jolt-sumcheck/src/tests.rs b/crates/jolt-sumcheck/src/tests.rs index b3cce06f73..1666917ab2 100644 --- a/crates/jolt-sumcheck/src/tests.rs +++ b/crates/jolt-sumcheck/src/tests.rs @@ -6,7 +6,7 @@ reason = "tests may panic on assertion failures" )] -use jolt_field::{Fr, FromPrimitiveInt}; +use jolt_field::{Field, Fr}; use jolt_poly::UnivariatePoly; use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript}; diff --git a/crates/jolt-sumcheck/src/verifier.rs b/crates/jolt-sumcheck/src/verifier.rs index ae5a1bb3d5..3ab12e01be 100644 --- a/crates/jolt-sumcheck/src/verifier.rs +++ b/crates/jolt-sumcheck/src/verifier.rs @@ -1,11 +1,11 @@ //! Sumcheck verifier: checks round polynomials against the claimed sum. +use jolt_field::Field; use jolt_transcript::Transcript; use crate::claim::{EvaluationClaim, SumcheckClaim}; use crate::error::SumcheckError; use crate::round_proof::RoundProof; -use crate::scalar::SumcheckScalar; /// Stateless sumcheck verifier engine. /// @@ -50,7 +50,7 @@ impl SumcheckVerifier { transcript: &mut T, ) -> Result, SumcheckError> where - F: SumcheckScalar, + F: Field, T: Transcript, P: RoundProof, { diff --git a/crates/jolt-sumcheck/tests/mersenne61_compat.rs b/crates/jolt-sumcheck/tests/mersenne61_compat.rs deleted file mode 100644 index 4a3142f934..0000000000 --- a/crates/jolt-sumcheck/tests/mersenne61_compat.rs +++ /dev/null @@ -1,377 +0,0 @@ -#![expect(clippy::unwrap_used, reason = "tests may panic on assertion failures")] - -//! Compatibility test for non-BN254 sumcheck verifier plumbing. -//! -//! `Mersenne61` is intentionally small and exists here only to prove that the -//! transcript and verifier APIs no longer depend on BN254-specific helper -//! surface. It is not a production proving field. Real sumcheck soundness with -//! this base field would require an adequately large extension field. - -use std::{ - fmt::{Debug, Display}, - hash::Hash, - iter::{Product, Sum}, - ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, -}; - -use jolt_field::{ - AdditiveGroup, CanonicalBitLength, CanonicalBytes, CanonicalU64, FieldCore, FixedByteSize, - FixedBytes, FromPrimitiveInt, Invertible, MulPow2, MulPrimitiveInt, NaiveAccumulator, - RandomSampling, ReducingBytes, RingCore, TranscriptChallenge, WithAccumulator, -}; -use jolt_sumcheck::{EvaluationClaim, RoundProof, SumcheckClaim, SumcheckError, SumcheckVerifier}; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, KeccakTranscript, Transcript}; -use num_traits::{One, Zero}; - -const MODULUS: u64 = (1u64 << 61) - 1; - -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)] -struct Mersenne61(u64); - -impl Mersenne61 { - fn reduce_u128(x: u128) -> Self { - let p = MODULUS as u128; - let mut y = (x & p) + (x >> 61); - y = (y & p) + (y >> 61); - if y >= p { - y -= p; - } - Self(y as u64) - } - - fn pow(self, mut exp: u64) -> Self { - let mut base = self; - let mut acc = Self::one(); - while exp > 0 { - if exp & 1 == 1 { - acc *= base; - } - base *= base; - exp >>= 1; - } - acc - } -} - -impl Debug for Mersenne61 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.0, f) - } -} - -impl Display for Mersenne61 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl Zero for Mersenne61 { - fn zero() -> Self { - Self(0) - } - - fn is_zero(&self) -> bool { - self.0 == 0 - } -} - -impl One for Mersenne61 { - fn one() -> Self { - Self(1) - } - - fn is_one(&self) -> bool { - self.0 == 1 - } -} - -impl Add for Mersenne61 { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - let mut sum = self.0 + rhs.0; - if sum >= MODULUS { - sum -= MODULUS; - } - Self(sum) - } -} - -impl Add<&Self> for Mersenne61 { - type Output = Self; - - fn add(self, rhs: &Self) -> Self::Output { - self + *rhs - } -} - -impl AddAssign for Mersenne61 { - fn add_assign(&mut self, rhs: Self) { - *self = *self + rhs; - } -} - -impl Sub for Mersenne61 { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - if self.0 >= rhs.0 { - Self(self.0 - rhs.0) - } else { - Self(MODULUS - (rhs.0 - self.0)) - } - } -} - -impl Sub<&Self> for Mersenne61 { - type Output = Self; - - fn sub(self, rhs: &Self) -> Self::Output { - self - *rhs - } -} - -impl SubAssign for Mersenne61 { - fn sub_assign(&mut self, rhs: Self) { - *self = *self - rhs; - } -} - -impl Neg for Mersenne61 { - type Output = Self; - - fn neg(self) -> Self::Output { - if self.is_zero() { - self - } else { - Self(MODULUS - self.0) - } - } -} - -impl Mul for Mersenne61 { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - Self::reduce_u128(self.0 as u128 * rhs.0 as u128) - } -} - -impl Mul<&Self> for Mersenne61 { - type Output = Self; - - fn mul(self, rhs: &Self) -> Self::Output { - self * *rhs - } -} - -impl MulAssign for Mersenne61 { - fn mul_assign(&mut self, rhs: Self) { - *self = *self * rhs; - } -} - -impl Sum for Mersenne61 { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |acc, x| acc + x) - } -} - -impl<'a> Sum<&'a Mersenne61> for Mersenne61 { - fn sum>(iter: I) -> Self { - iter.copied().sum() - } -} - -impl Product for Mersenne61 { - fn product>(iter: I) -> Self { - iter.fold(Self::one(), |acc, x| acc * x) - } -} - -impl<'a> Product<&'a Mersenne61> for Mersenne61 { - fn product>(iter: I) -> Self { - iter.copied().product() - } -} - -impl AdditiveGroup for Mersenne61 {} -impl RingCore for Mersenne61 {} - -impl Invertible for Mersenne61 { - fn inverse(&self) -> Option { - if self.is_zero() { - None - } else { - Some(self.pow(MODULUS - 2)) - } - } -} - -impl FieldCore for Mersenne61 {} - -impl FromPrimitiveInt for Mersenne61 { - fn from_u64(v: u64) -> Self { - Self::reduce_u128(v as u128) - } - - fn from_i64(v: i64) -> Self { - if v >= 0 { - Self::from_u64(v as u64) - } else { - -Self::from_u64(v.unsigned_abs()) - } - } - - fn from_u128(v: u128) -> Self { - Self::reduce_u128(v) - } - - fn from_i128(v: i128) -> Self { - if v >= 0 { - Self::from_u128(v as u128) - } else { - -Self::from_u128(v.unsigned_abs()) - } - } -} - -impl RandomSampling for Mersenne61 { - fn random(rng: &mut R) -> Self { - Self::from_u64(rng.next_u64()) - } -} - -impl CanonicalBytes for Mersenne61 { - fn to_bytes_le(&self, out: &mut [u8]) { - assert_eq!(out.len(), 8); - out.copy_from_slice(&self.0.to_le_bytes()); - } -} - -impl ReducingBytes for Mersenne61 { - fn from_le_bytes_mod_order(bytes: &[u8]) -> Self { - let mut buf = [0u8; 16]; - let len = bytes.len().min(16); - buf[..len].copy_from_slice(&bytes[..len]); - Self::from_u128(u128::from_le_bytes(buf)) - } -} - -impl TranscriptChallenge for Mersenne61 { - fn from_challenge_bytes(bytes: &[u8]) -> Self { - Self::from_le_bytes_mod_order(bytes) - } -} - -impl FixedByteSize for Mersenne61 { - const NUM_BYTES: usize = 8; -} - -impl FixedBytes<8> for Mersenne61 {} - -impl CanonicalBitLength for Mersenne61 { - fn num_bits(&self) -> u32 { - u64::BITS - self.0.leading_zeros() - } -} - -impl CanonicalU64 for Mersenne61 { - fn to_canonical_u64_checked(&self) -> Option { - Some(self.0) - } -} - -impl WithAccumulator for Mersenne61 { - type Accumulator = NaiveAccumulator; -} - -impl MulPow2 for Mersenne61 {} -impl MulPrimitiveInt for Mersenne61 {} - -#[derive(Clone, Debug)] -struct LinearRound { - coeffs: [Mersenne61; 2], -} - -impl RoundProof for LinearRound { - fn degree(&self) -> usize { - 1 - } - - fn check_sum( - &self, - running_sum: Mersenne61, - round: usize, - ) -> Result<(), SumcheckError> { - let actual = self.evaluate(Mersenne61::zero()) + self.evaluate(Mersenne61::one()); - if actual == running_sum { - Ok(()) - } else { - Err(SumcheckError::RoundCheckFailed { - round, - expected: running_sum, - actual, - }) - } - } - - fn evaluate(&self, challenge: Mersenne61) -> Mersenne61 { - self.coeffs[0] + self.coeffs[1] * challenge - } - - fn append_to_transcript(&self, transcript: &mut impl Transcript) { - self.coeffs[0].append_to_transcript(transcript); - self.coeffs[1].append_to_transcript(transcript); - } -} - -fn build_rounds() -> ( - SumcheckClaim, - Vec, - EvaluationClaim, -) { - let mut transcript = Blake2bTranscript::::new(b"mersenne61"); - let mut running_sum = Mersenne61::from_u64(10); - let mut point = Vec::new(); - let mut rounds = Vec::new(); - - for _ in 0..4 { - let c0 = running_sum * Mersenne61::from_u64(3); - let c1 = running_sum - c0 - c0; - let round = LinearRound { coeffs: [c0, c1] }; - round.append_to_transcript(&mut transcript); - let r = transcript.challenge(); - running_sum = round.evaluate(r); - point.push(r); - rounds.push(round); - } - - ( - SumcheckClaim::new(4, 1, Mersenne61::from_u64(10)), - rounds, - EvaluationClaim { - point, - value: running_sum, - }, - ) -} - -#[test] -fn hash_transcripts_accept_mersenne61_without_bn254_field_surface() { - let mut blake = Blake2bTranscript::::new(b"compat"); - let mut keccak = KeccakTranscript::::new(b"compat"); - Mersenne61::from_u64(42).append_to_transcript(&mut blake); - Mersenne61::from_u64(42).append_to_transcript(&mut keccak); - - let _: Mersenne61 = blake.challenge(); - let _: Mersenne61 = keccak.challenge(); -} - -#[test] -fn sumcheck_verifier_accepts_mersenne61_round_proof() { - let (claim, rounds, expected) = build_rounds(); - let mut verifier_transcript = Blake2bTranscript::::new(b"mersenne61"); - let actual = SumcheckVerifier::verify(&claim, &rounds, &mut verifier_transcript).unwrap(); - assert_eq!(actual, expected); -} diff --git a/crates/jolt-sumcheck/tests/roundtrip.rs b/crates/jolt-sumcheck/tests/roundtrip.rs index 06199c9b4b..7c6e301d70 100644 --- a/crates/jolt-sumcheck/tests/roundtrip.rs +++ b/crates/jolt-sumcheck/tests/roundtrip.rs @@ -6,7 +6,7 @@ #![expect(clippy::unwrap_used, reason = "tests may panic on assertion failures")] -use jolt_field::{Fr, FromPrimitiveInt, MulPow2}; +use jolt_field::{Field, Fr}; use jolt_poly::{Polynomial, UnivariatePoly}; use jolt_sumcheck::claim::{EvaluationClaim, SumcheckClaim}; use jolt_sumcheck::proof::SumcheckProof; diff --git a/crates/jolt-sumcheck/tests/soundness.rs b/crates/jolt-sumcheck/tests/soundness.rs index b4a78fb8ce..997c49495b 100644 --- a/crates/jolt-sumcheck/tests/soundness.rs +++ b/crates/jolt-sumcheck/tests/soundness.rs @@ -6,7 +6,7 @@ #![expect(clippy::unwrap_used, reason = "tests may panic on assertion failures")] -use jolt_field::{Fr, FromPrimitiveInt}; +use jolt_field::{Field, Fr}; use jolt_poly::{Polynomial, UnivariatePoly}; use jolt_sumcheck::claim::{EvaluationClaim, SumcheckClaim}; use jolt_sumcheck::error::SumcheckError; diff --git a/crates/jolt-trace/Cargo.toml b/crates/jolt-trace/Cargo.toml index 15689376a3..388fd655b7 100644 --- a/crates/jolt-trace/Cargo.toml +++ b/crates/jolt-trace/Cargo.toml @@ -17,7 +17,10 @@ test-utils = [] [dependencies] bincode.workspace = true common = { workspace = true } +jolt-field = { workspace = true } +jolt-r1cs = { workspace = true } jolt-riscv = { workspace = true } +jolt-witness = { workspace = true } rand = { workspace = true } serde.workspace = true tracer = { workspace = true, features = ["std", "test-utils"] } diff --git a/crates/jolt-trace/src/bytecode.rs b/crates/jolt-trace/src/bytecode.rs index 461f45d8d1..805b566f9d 100644 --- a/crates/jolt-trace/src/bytecode.rs +++ b/crates/jolt-trace/src/bytecode.rs @@ -8,7 +8,7 @@ //! - [`BytecodePCMapper::get_pc`] — resolves `(address, virtual_sequence_remaining)` //! to a dense bytecode table index, accounting for virtual instruction expansion. -use crate::JoltInstruction; +use crate::{CycleRow, JoltInstruction}; use common::constants::{ALIGNMENT_FACTOR_BYTECODE, RAM_START_ADDRESS}; use serde::{Deserialize, Serialize}; use tracer::instruction::Instruction; @@ -49,6 +49,29 @@ impl BytecodePreprocessing { } } + /// Like [`preprocess`] but pads the bytecode up to at least `min_code_size` + /// (rounded up to the next power of two). Lets a small guest reuse a + /// larger goldens-baked fixture shape. + #[tracing::instrument(skip_all, name = "BytecodePreprocessing::preprocess_padded")] + pub fn preprocess_padded( + mut bytecode: Vec, + entry_address: u64, + min_code_size: usize, + ) -> Self { + bytecode.insert(0, Instruction::NoOp); + let pc_map = BytecodePCMapper::new(&bytecode); + let natural = bytecode.len().next_power_of_two().max(2); + let code_size = natural.max(min_code_size.next_power_of_two()); + bytecode.resize(code_size, Instruction::NoOp); + + Self { + code_size, + bytecode, + pc_map, + entry_address, + } + } + /// Dense bytecode table index for the ELF entry point. pub fn entry_bytecode_index(&self) -> usize { self.pc_map.get_pc(self.entry_address as usize, 0) @@ -64,6 +87,16 @@ impl BytecodePreprocessing { cycle.virtual_sequence_remaining().unwrap_or(0), ) } + + pub fn get_cycle_pc(&self, cycle: &impl CycleRow) -> usize { + if cycle.is_noop() { + return 0; + } + self.pc_map.get_pc( + cycle.unexpanded_pc() as usize, + cycle.virtual_sequence_remaining().unwrap_or(0), + ) + } } /// Maps physical instruction addresses to dense bytecode table indices. diff --git a/crates/jolt-trace/src/cycle_row.rs b/crates/jolt-trace/src/cycle_row.rs new file mode 100644 index 0000000000..e7722c3845 --- /dev/null +++ b/crates/jolt-trace/src/cycle_row.rs @@ -0,0 +1,77 @@ +//! Abstract cycle interface for the proving pipeline. +//! +//! [`CycleRow`] is the boundary between the tracer (which produces concrete +//! `Cycle` values) and the proving system (which consumes per-cycle data to +//! build witnesses). All ISA-specific logic (instruction dispatch, flag +//! computation, operand routing) is pushed into the `CycleRow` implementation, +//! so the prover sees only scalars and static flag sets. + +use jolt_riscv::{CircuitFlagSet, InstructionFlagSet}; + +/// Abstract interface for one execution cycle of a RISC-V trace. +/// +/// Bolt's witness layer is generic over `CycleRow`. The concrete implementation +/// for `tracer::Cycle` lives in this crate. +pub trait CycleRow: Copy { + /// A no-op (padding) cycle. + fn noop() -> Self; + + /// True if this cycle is a no-op (padding). + fn is_noop(&self) -> bool; + + /// The unexpanded (pre-virtual-expansion) program counter. + fn unexpanded_pc(&self) -> u64; + + /// Remaining steps in a virtual instruction sequence, or `None` if + /// this is a real (non-virtual) instruction. + fn virtual_sequence_remaining(&self) -> Option; + + /// True if this is the first instruction in a virtual sequence. + fn is_first_in_sequence(&self) -> bool; + + /// True if this is a virtual (expanded) instruction. + fn is_virtual(&self) -> bool; + + /// RS1 register read: `(register_index, value)`, or `None` if unused. + fn rs1_read(&self) -> Option<(u8, u64)>; + + /// RS2 register read: `(register_index, value)`, or `None` if unused. + fn rs2_read(&self) -> Option<(u8, u64)>; + + /// RD register write: `(register_index, pre_value, post_value)`, or `None`. + fn rd_write(&self) -> Option<(u8, u64, u64)>; + + /// The static `rd` operand from the instruction encoding. + fn rd_operand(&self) -> Option; + + /// RAM access address, or `None` if no RAM access this cycle. + fn ram_access_address(&self) -> Option; + + /// RAM read value (pre-access value). `None` if no RAM access. + fn ram_read_value(&self) -> Option; + + /// RAM write value (post-access value). `None` if no RAM access. + fn ram_write_value(&self) -> Option; + + /// The immediate operand, sign-extended. + fn imm(&self) -> i128; + + /// R1CS circuit flags. + fn circuit_flags(&self) -> CircuitFlagSet; + + /// Non-R1CS instruction flags. + fn instruction_flags(&self) -> InstructionFlagSet; + + /// Combined lookup index for RA polynomial construction (128-bit). + fn lookup_index(&self) -> u128; + + /// Lookup table evaluation result. + /// + /// For arithmetic: the computation result (e.g., rs1 + rs2 for ADD). + /// For branches: the comparison result (0 or 1). + /// For stores: zero. + /// For no-ops: zero. + /// + /// This is the value of V_LOOKUP_OUTPUT in the R1CS witness. + fn lookup_output(&self) -> u64; +} diff --git a/crates/jolt-trace/src/extract.rs b/crates/jolt-trace/src/extract.rs new file mode 100644 index 0000000000..852c447751 --- /dev/null +++ b/crates/jolt-trace/src/extract.rs @@ -0,0 +1,118 @@ +//! Single-pass trace extraction: witness inputs + R1CS + instruction flags. +//! +//! [`extract_trace`] iterates the trace once, producing all three artifacts +//! the prover needs. R1CS witness construction delegates to +//! [`r1cs_cycle_witness`](crate::r1cs_witness::r1cs_cycle_witness). + +use common::jolt_device::MemoryLayout; +use jolt_field::Field; +use jolt_r1cs::constraints::rv64::*; +use jolt_riscv::{InstructionFlagSet, InstructionFlags}; +use jolt_witness::CycleInput; + +use crate::bytecode::BytecodePreprocessing; +use crate::r1cs_witness::r1cs_cycle_witness; +use crate::CycleRow; + +/// Per-cycle instruction flag polynomials for sumcheck instances. +pub struct InstructionFlagData { + pub is_noop: Vec, + pub left_is_rs1: Vec, + pub left_is_pc: Vec, + pub right_is_rs2: Vec, + pub right_is_imm: Vec, +} + +/// Extract witness inputs, R1CS witness, and instruction flags in one pass. +/// +/// Produces `size`-length outputs, padding beyond `trace.len()` with defaults. +pub fn extract_trace( + trace: &[C], + size: usize, + bytecode: &BytecodePreprocessing, + memory_layout: &MemoryLayout, + num_vars_padded: usize, +) -> (Vec, Vec, InstructionFlagData) { + let mut inputs = Vec::with_capacity(size); + let mut r1cs = vec![F::from_u64(0); size * num_vars_padded]; + let mut flags = InstructionFlagData::new(size); + + for t in 0..size { + let offset = t * num_vars_padded; + + if t < trace.len() { + let cycle = &trace[t]; + + if cycle.is_noop() { + inputs.push(CycleInput::PADDING); + } else { + inputs.push(cycle_input(cycle, bytecode, memory_layout)); + } + + let row = r1cs_cycle_witness::(trace, t, bytecode); + r1cs[offset..offset + NUM_VARS_PER_CYCLE].copy_from_slice(&row); + + flags.set(t, cycle.instruction_flags()); + } else { + inputs.push(CycleInput::PADDING); + flags.is_noop[t] = F::from_u64(1); + r1cs[offset + V_CONST] = F::from_u64(1); + r1cs[offset + V_FLAG_DO_NOT_UPDATE_UNEXPANDED_PC] = F::from_u64(1); + r1cs[offset + V_NEXT_IS_NOOP] = F::from_u64(1); + } + } + + (inputs, r1cs, flags) +} + +fn cycle_input( + cycle: &impl CycleRow, + bytecode: &BytecodePreprocessing, + memory_layout: &MemoryLayout, +) -> CycleInput { + let rd_inc = match cycle.rd_write() { + Some((_, pre, post)) => post as i128 - pre as i128, + None => 0, + }; + let ram_inc = match (cycle.ram_read_value(), cycle.ram_write_value()) { + (Some(pre), Some(post)) => post as i128 - pre as i128, + _ => 0, + }; + let lowest = memory_layout.get_lowest_address(); + let ram_address = cycle.ram_access_address().map(|addr| { + debug_assert!( + addr >= lowest, + "RAM address {addr:#x} below lowest {lowest:#x}" + ); + ((addr - lowest) / 8) as u128 + }); + + CycleInput { + dense: [rd_inc, ram_inc], + one_hot: [ + Some(cycle.lookup_index()), + Some(bytecode.get_cycle_pc(cycle) as u128), + ram_address, + ], + } +} + +impl InstructionFlagData { + fn new(size: usize) -> Self { + Self { + is_noop: vec![F::from_u64(0); size], + left_is_rs1: vec![F::from_u64(0); size], + left_is_pc: vec![F::from_u64(0); size], + right_is_rs2: vec![F::from_u64(0); size], + right_is_imm: vec![F::from_u64(0); size], + } + } + + fn set(&mut self, t: usize, iflags: InstructionFlagSet) { + self.is_noop[t] = F::from_u64(iflags[InstructionFlags::IsNoop] as u64); + self.left_is_rs1[t] = F::from_u64(iflags[InstructionFlags::LeftOperandIsRs1Value] as u64); + self.left_is_pc[t] = F::from_u64(iflags[InstructionFlags::LeftOperandIsPC] as u64); + self.right_is_rs2[t] = F::from_u64(iflags[InstructionFlags::RightOperandIsRs2Value] as u64); + self.right_is_imm[t] = F::from_u64(iflags[InstructionFlags::RightOperandIsImm] as u64); + } +} diff --git a/crates/jolt-trace/src/lib.rs b/crates/jolt-trace/src/lib.rs index d48b3a5edb..101293688b 100644 --- a/crates/jolt-trace/src/lib.rs +++ b/crates/jolt-trace/src/lib.rs @@ -6,11 +6,17 @@ mod analyze; pub mod bytecode; +mod cycle_row; +mod extract; mod jolt_cycle; mod program; +pub mod r1cs_witness; pub mod ram; +mod tracer_cycle; pub use bytecode::BytecodePreprocessing; +pub use cycle_row::CycleRow; +pub use extract::{extract_trace, InstructionFlagData}; pub use jolt_cycle::JoltCycle; pub use jolt_riscv::instructions; pub use jolt_riscv::{ @@ -18,6 +24,8 @@ pub use jolt_riscv::{ InterleavedBitsMarker, JoltInstruction, JoltInstructions, NUM_CIRCUIT_FLAGS, NUM_INSTRUCTION_FLAGS, }; +pub use r1cs_witness::{build_r1cs_witness, r1cs_cycle_witness}; +pub use tracer_cycle::{instruction_circuit_flags, instruction_instruction_flags}; use std::path::{Path, PathBuf}; diff --git a/crates/jolt-trace/src/r1cs_witness.rs b/crates/jolt-trace/src/r1cs_witness.rs new file mode 100644 index 0000000000..c879be10fd --- /dev/null +++ b/crates/jolt-trace/src/r1cs_witness.rs @@ -0,0 +1,172 @@ +//! Per-cycle R1CS witness extraction from [`CycleRow`] data. +//! +//! Produces the witness vector for one cycle, matching the variable +//! layout in [`jolt_r1cs::constraints::rv64`]. + +use jolt_field::Field; +use jolt_r1cs::constraints::rv64::*; +use jolt_riscv::{CircuitFlagSet, CircuitFlags, InstructionFlags}; + +use crate::bytecode::BytecodePreprocessing; +use crate::CycleRow; + +/// Per-cycle R1CS witness matching `jolt_r1cs::constraints::rv64` layout. +pub fn r1cs_cycle_witness( + trace: &[C], + t: usize, + bytecode: &BytecodePreprocessing, +) -> [F; NUM_VARS_PER_CYCLE] { + let cycle = &trace[t]; + let next = trace.get(t + 1); + + let mut w = [F::from_u64(0); NUM_VARS_PER_CYCLE]; + w[V_CONST] = F::from_u64(1); + + if cycle.is_noop() { + w[V_FLAG_DO_NOT_UPDATE_UNEXPANDED_PC] = F::from_u64(1); + w[V_NEXT_IS_NOOP] = F::from_u64(next.is_none_or(|c| c.is_noop()) as u64); + fill_next_fields(&mut w, next, bytecode); + return w; + } + + let cflags = cycle.circuit_flags(); + let iflags = cycle.instruction_flags(); + + // Instruction inputs + let left_input = if iflags[InstructionFlags::LeftOperandIsPC] { + cycle.unexpanded_pc() + } else if iflags[InstructionFlags::LeftOperandIsRs1Value] { + cycle.rs1_read().map_or(0, |(_, v)| v) + } else { + 0 + }; + + let right_input: i128 = if iflags[InstructionFlags::RightOperandIsImm] { + cycle.imm() + } else if iflags[InstructionFlags::RightOperandIsRs2Value] { + cycle.rs2_read().map_or(0, |(_, v)| v as i128) + } else { + 0 + }; + + w[V_LEFT_INSTRUCTION_INPUT] = F::from_u64(left_input); + w[V_RIGHT_INSTRUCTION_INPUT] = F::from_i128(right_input); + w[V_PRODUCT] = w[V_LEFT_INSTRUCTION_INPUT] * w[V_RIGHT_INSTRUCTION_INPUT]; + + // Lookup output + let lookup_output = cycle.lookup_output(); + w[V_LOOKUP_OUTPUT] = F::from_u64(lookup_output); + + // Lookup operands — must match R1CS constraints + let (left_lookup, right_lookup) = + lookup_operands(left_input, right_input, w[V_PRODUCT], cflags, lookup_output); + w[V_LEFT_LOOKUP_OPERAND] = left_lookup; + w[V_RIGHT_LOOKUP_OPERAND] = right_lookup; + + // Registers + w[V_RS1_VALUE] = F::from_u64(cycle.rs1_read().map_or(0, |(_, v)| v)); + w[V_RS2_VALUE] = F::from_u64(cycle.rs2_read().map_or(0, |(_, v)| v)); + w[V_RD_WRITE_VALUE] = F::from_u64(cycle.rd_write().map_or(0, |(_, _, post)| post)); + + // RAM + w[V_RAM_ADDRESS] = F::from_u64(cycle.ram_access_address().unwrap_or(0)); + w[V_RAM_READ_VALUE] = F::from_u64(cycle.ram_read_value().unwrap_or(0)); + w[V_RAM_WRITE_VALUE] = F::from_u64(cycle.ram_write_value().unwrap_or(0)); + + // PCs + w[V_PC] = F::from_u64(bytecode.get_cycle_pc(cycle) as u64); + w[V_UNEXPANDED_PC] = F::from_u64(cycle.unexpanded_pc()); + w[V_IMM] = F::from_i128(cycle.imm()); + + // Circuit flags + w[V_FLAG_ADD_OPERANDS] = F::from_u64(cflags[CircuitFlags::AddOperands] as u64); + w[V_FLAG_SUBTRACT_OPERANDS] = F::from_u64(cflags[CircuitFlags::SubtractOperands] as u64); + w[V_FLAG_MULTIPLY_OPERANDS] = F::from_u64(cflags[CircuitFlags::MultiplyOperands] as u64); + w[V_FLAG_LOAD] = F::from_u64(cflags[CircuitFlags::Load] as u64); + w[V_FLAG_STORE] = F::from_u64(cflags[CircuitFlags::Store] as u64); + w[V_FLAG_JUMP] = F::from_u64(cflags[CircuitFlags::Jump] as u64); + w[V_FLAG_WRITE_LOOKUP_OUTPUT_TO_RD] = + F::from_u64(cflags[CircuitFlags::WriteLookupOutputToRD] as u64); + w[V_FLAG_VIRTUAL_INSTRUCTION] = F::from_u64(cflags[CircuitFlags::VirtualInstruction] as u64); + w[V_FLAG_ASSERT] = F::from_u64(cflags[CircuitFlags::Assert] as u64); + w[V_FLAG_DO_NOT_UPDATE_UNEXPANDED_PC] = + F::from_u64(cflags[CircuitFlags::DoNotUpdateUnexpandedPC] as u64); + w[V_FLAG_ADVICE] = F::from_u64(cflags[CircuitFlags::Advice] as u64); + w[V_FLAG_IS_COMPRESSED] = F::from_u64(cflags[CircuitFlags::IsCompressed] as u64); + w[V_FLAG_IS_FIRST_IN_SEQUENCE] = F::from_u64(cflags[CircuitFlags::IsFirstInSequence] as u64); + w[V_FLAG_IS_LAST_IN_SEQUENCE] = F::from_u64(cflags[CircuitFlags::IsLastInSequence] as u64); + + // Product factors + w[V_BRANCH] = F::from_u64(iflags[InstructionFlags::Branch] as u64); + w[V_SHOULD_BRANCH] = w[V_LOOKUP_OUTPUT] * w[V_BRANCH]; + + // Next-cycle fields + fill_next_fields(&mut w, next, bytecode); + let next_is_noop = next.is_none_or(|c| c.is_noop()); + w[V_NEXT_IS_NOOP] = F::from_u64(next_is_noop as u64); + w[V_SHOULD_JUMP] = w[V_FLAG_JUMP] * (F::from_u64(1) - w[V_NEXT_IS_NOOP]); + + w +} + +fn fill_next_fields( + w: &mut [F; NUM_VARS_PER_CYCLE], + next: Option<&C>, + bytecode: &BytecodePreprocessing, +) { + if let Some(nc) = next { + w[V_NEXT_PC] = F::from_u64(bytecode.get_cycle_pc(nc) as u64); + w[V_NEXT_UNEXPANDED_PC] = F::from_u64(nc.unexpanded_pc()); + let nc_flags = nc.circuit_flags(); + w[V_NEXT_IS_VIRTUAL] = F::from_u64(nc_flags[CircuitFlags::VirtualInstruction] as u64); + w[V_NEXT_IS_FIRST_IN_SEQUENCE] = + F::from_u64(nc_flags[CircuitFlags::IsFirstInSequence] as u64); + } +} + +/// Lookup operands matching the R1CS constraints: +/// - Add: `(0, left + right)` +/// - Sub: `(0, left - right + 2^64)` +/// - Mul: `(0, product)` +/// - Advice: `(0, lookup_output)` +/// - Default: `(left, right)` +fn lookup_operands( + left: u64, + right: i128, + product: F, + cflags: CircuitFlagSet, + lookup_output: u64, +) -> (F, F) { + if cflags[CircuitFlags::AddOperands] { + (F::from_u64(0), F::from_i128(left as i128 + right)) + } else if cflags[CircuitFlags::SubtractOperands] { + ( + F::from_u64(0), + F::from_i128(left as i128 - right + (1i128 << 64)), + ) + } else if cflags[CircuitFlags::MultiplyOperands] { + (F::from_u64(0), product) + } else if cflags[CircuitFlags::Advice] { + (F::from_u64(0), F::from_u64(lookup_output)) + } else { + (F::from_u64(left), F::from_i128(right)) + } +} + +/// Flat R1CS witness for the entire trace. +/// +/// Returns `Vec` of length `trace.len() * num_vars_padded`. +pub fn build_r1cs_witness( + trace: &[C], + bytecode: &BytecodePreprocessing, + num_vars_padded: usize, +) -> Vec { + let n = trace.len(); + let mut witness = vec![F::from_u64(0); n * num_vars_padded]; + for t in 0..n { + let row = r1cs_cycle_witness::(trace, t, bytecode); + witness[t * num_vars_padded..t * num_vars_padded + NUM_VARS_PER_CYCLE] + .copy_from_slice(&row); + } + witness +} diff --git a/crates/jolt-trace/src/ram.rs b/crates/jolt-trace/src/ram.rs index 982879b159..3c6c1ecf7d 100644 --- a/crates/jolt-trace/src/ram.rs +++ b/crates/jolt-trace/src/ram.rs @@ -5,7 +5,7 @@ //! - `Memory`: final tracer memory state //! - `JoltDevice`: I/O data (inputs, outputs, advice, panic/termination) //! -//! Mirrors jolt-core's `gen_ram_memory_states` but uses jolt-host types. +//! Mirrors jolt-core's `gen_ram_memory_states` but uses jolt-trace types. use common::constants::RAM_START_ADDRESS; use common::jolt_device::JoltDevice; diff --git a/crates/jolt-trace/src/tracer_cycle.rs b/crates/jolt-trace/src/tracer_cycle.rs new file mode 100644 index 0000000000..d64a1d7dd5 --- /dev/null +++ b/crates/jolt-trace/src/tracer_cycle.rs @@ -0,0 +1,548 @@ +//! `CycleRow` implementation for `tracer::Cycle`. +//! +//! Maps `Instruction` variants to ISA structs via [`with_isa_struct!`], then +//! derives circuit flags and instruction flags. + +use jolt_riscv::{CircuitFlagSet, CircuitFlags, Flags, InstructionFlagSet, InstructionFlags}; +use tracer::instruction::{Cycle, Instruction, RAMAccess}; + +use crate::CycleRow; + +/// Map an `Instruction` variant to its ISA struct, bind it to `$i`, evaluate `$body`. +/// The `noop =>` arm handles `Instruction::NoOp` separately. +#[macro_export] +macro_rules! with_isa_struct { + ($instr:expr, |$i:ident| $body:expr, noop => $noop:expr) => {{ + use jolt_riscv::instructions::*; + match $instr { + Instruction::ADD(value) => { + let $i = Add(*value); + $body + } + Instruction::ADDI(value) => { + let $i = Addi(*value); + $body + } + Instruction::SUB(value) => { + let $i = Sub(*value); + $body + } + Instruction::LUI(value) => { + let $i = Lui(*value); + $body + } + Instruction::AUIPC(value) => { + let $i = Auipc(*value); + $body + } + Instruction::MUL(value) => { + let $i = Mul(*value); + $body + } + Instruction::MULHU(value) => { + let $i = MulHU(*value); + $body + } + Instruction::AND(value) => { + let $i = And(*value); + $body + } + Instruction::ANDI(value) => { + let $i = AndI(*value); + $body + } + Instruction::ANDN(value) => { + let $i = Andn(*value); + $body + } + Instruction::OR(value) => { + let $i = Or(*value); + $body + } + Instruction::ORI(value) => { + let $i = OrI(*value); + $body + } + Instruction::XOR(value) => { + let $i = Xor(*value); + $body + } + Instruction::XORI(value) => { + let $i = XorI(*value); + $body + } + Instruction::SLT(value) => { + let $i = Slt(*value); + $body + } + Instruction::SLTI(value) => { + let $i = SltI(*value); + $body + } + Instruction::SLTIU(value) => { + let $i = SltIU(*value); + $body + } + Instruction::SLTU(value) => { + let $i = SltU(*value); + $body + } + Instruction::BEQ(value) => { + let $i = Beq(*value); + $body + } + Instruction::BGE(value) => { + let $i = Bge(*value); + $body + } + Instruction::BGEU(value) => { + let $i = BgeU(*value); + $body + } + Instruction::BLT(value) => { + let $i = Blt(*value); + $body + } + Instruction::BLTU(value) => { + let $i = BltU(*value); + $body + } + Instruction::BNE(value) => { + let $i = Bne(*value); + $body + } + Instruction::JAL(value) => { + let $i = Jal(*value); + $body + } + Instruction::JALR(value) => { + let $i = Jalr(*value); + $body + } + Instruction::LD(value) => { + let $i = Ld(*value); + $body + } + Instruction::SD(value) => { + let $i = Sd(*value); + $body + } + Instruction::EBREAK(value) => { + let $i = Ebreak(*value); + $body + } + Instruction::ECALL(value) => { + let $i = Ecall(*value); + $body + } + Instruction::FENCE(value) => { + let $i = Fence(*value); + $body + } + Instruction::VirtualAdvice(value) => { + let $i = VirtualAdvice(*value); + $body + } + Instruction::VirtualAdviceLen(value) => { + let $i = VirtualAdviceLen(*value); + $body + } + Instruction::VirtualAdviceLoad(value) => { + let $i = VirtualAdviceLoad(*value); + $body + } + Instruction::VirtualHostIO(value) => { + let $i = VirtualHostIO(*value); + $body + } + Instruction::VirtualMULI(value) => { + let $i = MulI(*value); + $body + } + Instruction::VirtualPow2(value) => { + let $i = Pow2(*value); + $body + } + Instruction::VirtualPow2I(value) => { + let $i = Pow2I(*value); + $body + } + Instruction::VirtualPow2W(value) => { + let $i = Pow2W(*value); + $body + } + Instruction::VirtualPow2IW(value) => { + let $i = Pow2IW(*value); + $body + } + Instruction::VirtualAssertEQ(value) => { + let $i = AssertEq(*value); + $body + } + Instruction::VirtualAssertLTE(value) => { + let $i = AssertLte(*value); + $body + } + Instruction::VirtualAssertValidDiv0(value) => { + let $i = AssertValidDiv0(*value); + $body + } + Instruction::VirtualAssertValidUnsignedRemainder(value) => { + let $i = AssertValidUnsignedRemainder(*value); + $body + } + Instruction::VirtualAssertMulUNoOverflow(value) => { + let $i = AssertMulUNoOverflow(*value); + $body + } + Instruction::VirtualAssertWordAlignment(value) => { + let $i = AssertWordAlignment(*value); + $body + } + Instruction::VirtualAssertHalfwordAlignment(value) => { + let $i = AssertHalfwordAlignment(*value); + $body + } + Instruction::VirtualMovsign(value) => { + let $i = MovSign(*value); + $body + } + Instruction::VirtualRev8W(value) => { + let $i = VirtualRev8W(*value); + $body + } + Instruction::VirtualChangeDivisor(value) => { + let $i = VirtualChangeDivisor(*value); + $body + } + Instruction::VirtualChangeDivisorW(value) => { + let $i = VirtualChangeDivisorW(*value); + $body + } + Instruction::VirtualZeroExtendWord(value) => { + let $i = VirtualZeroExtendWord(*value); + $body + } + Instruction::VirtualSignExtendWord(value) => { + let $i = VirtualSignExtendWord(*value); + $body + } + Instruction::VirtualSRL(value) => { + let $i = VirtualSrl(*value); + $body + } + Instruction::VirtualSRLI(value) => { + let $i = VirtualSrli(*value); + $body + } + Instruction::VirtualSRA(value) => { + let $i = VirtualSra(*value); + $body + } + Instruction::VirtualSRAI(value) => { + let $i = VirtualSrai(*value); + $body + } + Instruction::VirtualShiftRightBitmask(value) => { + let $i = VirtualShiftRightBitmask(*value); + $body + } + Instruction::VirtualShiftRightBitmaskI(value) => { + let $i = VirtualShiftRightBitmaski(*value); + $body + } + Instruction::VirtualROTRI(value) => { + let $i = VirtualRotri(*value); + $body + } + Instruction::VirtualROTRIW(value) => { + let $i = VirtualRotriw(*value); + $body + } + Instruction::VirtualXORROT32(value) => { + let $i = VirtualXorRot32(*value); + $body + } + Instruction::VirtualXORROT24(value) => { + let $i = VirtualXorRot24(*value); + $body + } + Instruction::VirtualXORROT16(value) => { + let $i = VirtualXorRot16(*value); + $body + } + Instruction::VirtualXORROT63(value) => { + let $i = VirtualXorRot63(*value); + $body + } + Instruction::VirtualXORROTW16(value) => { + let $i = VirtualXorRotW16(*value); + $body + } + Instruction::VirtualXORROTW12(value) => { + let $i = VirtualXorRotW12(*value); + $body + } + Instruction::VirtualXORROTW8(value) => { + let $i = VirtualXorRotW8(*value); + $body + } + Instruction::VirtualXORROTW7(value) => { + let $i = VirtualXorRotW7(*value); + $body + } + Instruction::NoOp => $noop, + Instruction::INLINE(x) => panic!( + "INLINE reached CycleRow: opcode={}, funct3={}, funct7={}", + x.opcode, x.funct3, x.funct7 + ), + _ => panic!("unsupported instruction: {:?}", $instr), + } + }}; +} + +impl CycleRow for Cycle { + fn noop() -> Self { + Cycle::NoOp + } + + fn is_noop(&self) -> bool { + matches!(self, Cycle::NoOp) + } + + fn unexpanded_pc(&self) -> u64 { + match self { + Cycle::NoOp => 0, + _ => self.instruction().normalize().address as u64, + } + } + + fn virtual_sequence_remaining(&self) -> Option { + match self { + Cycle::NoOp => None, + _ => self.instruction().normalize().virtual_sequence_remaining, + } + } + + fn is_first_in_sequence(&self) -> bool { + match self { + Cycle::NoOp => false, + _ => self.instruction().normalize().is_first_in_sequence, + } + } + + fn is_virtual(&self) -> bool { + self.virtual_sequence_remaining().is_some() + } + + fn rs1_read(&self) -> Option<(u8, u64)> { + self.rs1_read() + } + + fn rs2_read(&self) -> Option<(u8, u64)> { + self.rs2_read() + } + + fn rd_write(&self) -> Option<(u8, u64, u64)> { + self.rd_write() + } + + fn rd_operand(&self) -> Option { + match self { + Cycle::NoOp => None, + _ => self.instruction().normalize().operands.rd, + } + } + + fn ram_access_address(&self) -> Option { + match self.ram_access() { + RAMAccess::Read(r) => Some(r.address), + RAMAccess::Write(w) => Some(w.address), + RAMAccess::NoOp => None, + } + } + + fn ram_read_value(&self) -> Option { + match self.ram_access() { + RAMAccess::Read(r) => Some(r.value), + RAMAccess::Write(w) => Some(w.pre_value), + RAMAccess::NoOp => None, + } + } + + fn ram_write_value(&self) -> Option { + match self.ram_access() { + RAMAccess::Read(r) => Some(r.value), + RAMAccess::Write(w) => Some(w.post_value), + RAMAccess::NoOp => None, + } + } + + fn imm(&self) -> i128 { + match self { + Cycle::NoOp => 0, + _ => self.instruction().normalize().operands.imm, + } + } + + fn circuit_flags(&self) -> CircuitFlagSet { + static_circuit_flags(&self.instruction()) + } + + fn instruction_flags(&self) -> InstructionFlagSet { + static_instruction_flags(&self.instruction()) + } + + fn lookup_index(&self) -> u128 { + let cflags = self.circuit_flags(); + let iflags = self.instruction_flags(); + let (left, right) = instruction_inputs(self, iflags); + + if cflags[CircuitFlags::AddOperands] { + (left as u128).wrapping_add(right) + } else if cflags[CircuitFlags::SubtractOperands] { + (1u128 << 64).wrapping_sub(right).wrapping_add(left as u128) + } else if cflags[CircuitFlags::MultiplyOperands] { + (left as u128).wrapping_mul(right) + } else if cflags[CircuitFlags::Advice] { + self.rd_write().map_or(0, |(_, _, post)| post as u128) + } else if self.is_noop() { + 0 + } else { + interleave_bits(left, right as u64) + } + } + + fn lookup_output(&self) -> u64 { + if self.is_noop() { + return 0; + } + let cflags = self.circuit_flags(); + let iflags = self.instruction_flags(); + + if cflags[CircuitFlags::Jump] { + // JAL/JALR: lookup output = jump target, not return address. + let left = if iflags[InstructionFlags::LeftOperandIsPC] { + self.unexpanded_pc() + } else { + self.rs1_read().map_or(0, |(_, v)| v) + }; + let target = (left as i64).wrapping_add(self.imm() as i64) as u64; + if iflags[InstructionFlags::LeftOperandIsRs1Value] { + target & !1 // JALR aligns to 2-byte boundary + } else { + target + } + } else if cflags[CircuitFlags::Assert] { + 1 + } else if iflags[InstructionFlags::Branch] { + let rs1 = self.rs1_read().map_or(0, |(_, v)| v); + let rs2 = self.rs2_read().map_or(0, |(_, v)| v); + branch_result(self.instruction(), rs1, rs2) + } else if cflags[CircuitFlags::WriteLookupOutputToRD] { + self.rd_write().map_or(0, |(_, _, post)| post) + } else { + 0 + } + } +} + +fn instruction_inputs(cycle: &impl CycleRow, iflags: InstructionFlagSet) -> (u64, u128) { + let left = if iflags[InstructionFlags::LeftOperandIsPC] { + cycle.unexpanded_pc() + } else if iflags[InstructionFlags::LeftOperandIsRs1Value] { + cycle.rs1_read().map_or(0, |(_, v)| v) + } else { + 0 + }; + + let right: i128 = if iflags[InstructionFlags::RightOperandIsImm] { + cycle.imm() + } else if iflags[InstructionFlags::RightOperandIsRs2Value] { + cycle.rs2_read().map_or(0, |(_, v)| v as i128) + } else { + 0 + }; + + (left, right as u64 as u128) +} + +pub fn instruction_circuit_flags(instr: &Instruction) -> CircuitFlagSet { + with_isa_struct!(instr, |i| Flags::circuit_flags(&i), noop => { + CircuitFlagSet::default().set(CircuitFlags::DoNotUpdateUnexpandedPC) + }) +} + +pub fn instruction_instruction_flags(instr: &Instruction) -> InstructionFlagSet { + with_isa_struct!(instr, |i| Flags::instruction_flags(&i), noop => { + InstructionFlagSet::default().set(InstructionFlags::IsNoop) + }) +} + +fn static_circuit_flags(instr: &Instruction) -> CircuitFlagSet { + instruction_circuit_flags(instr) +} + +fn static_instruction_flags(instr: &Instruction) -> InstructionFlagSet { + instruction_instruction_flags(instr) +} + +#[inline] +fn interleave_bits(x: u64, y: u64) -> u128 { + let mut x_bits = x as u128; + x_bits = (x_bits | (x_bits << 32)) & 0x0000_0000_FFFF_FFFF_0000_0000_FFFF_FFFF; + x_bits = (x_bits | (x_bits << 16)) & 0x0000_FFFF_0000_FFFF_0000_FFFF_0000_FFFF; + x_bits = (x_bits | (x_bits << 8)) & 0x00FF_00FF_00FF_00FF_00FF_00FF_00FF_00FF; + x_bits = (x_bits | (x_bits << 4)) & 0x0F0F_0F0F_0F0F_0F0F_0F0F_0F0F_0F0F_0F0F; + x_bits = (x_bits | (x_bits << 2)) & 0x3333_3333_3333_3333_3333_3333_3333_3333; + x_bits = (x_bits | (x_bits << 1)) & 0x5555_5555_5555_5555_5555_5555_5555_5555; + + let mut y_bits = y as u128; + y_bits = (y_bits | (y_bits << 32)) & 0x0000_0000_FFFF_FFFF_0000_0000_FFFF_FFFF; + y_bits = (y_bits | (y_bits << 16)) & 0x0000_FFFF_0000_FFFF_0000_FFFF_0000_FFFF; + y_bits = (y_bits | (y_bits << 8)) & 0x00FF_00FF_00FF_00FF_00FF_00FF_00FF_00FF; + y_bits = (y_bits | (y_bits << 4)) & 0x0F0F_0F0F_0F0F_0F0F_0F0F_0F0F_0F0F_0F0F; + y_bits = (y_bits | (y_bits << 2)) & 0x3333_3333_3333_3333_3333_3333_3333_3333; + y_bits = (y_bits | (y_bits << 1)) & 0x5555_5555_5555_5555_5555_5555_5555_5555; + + (x_bits << 1) | y_bits +} + +fn branch_result(instr: Instruction, rs1: u64, rs2: u64) -> u64 { + let taken = match instr { + Instruction::BEQ(_) => rs1 == rs2, + Instruction::BNE(_) => rs1 != rs2, + Instruction::BLT(_) => (rs1 as i64) < (rs2 as i64), + Instruction::BGE(_) => (rs1 as i64) >= (rs2 as i64), + Instruction::BLTU(_) => rs1 < rs2, + Instruction::BGEU(_) => rs1 >= rs2, + _ => false, + }; + taken as u64 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn noop_trait_methods() { + let noop = Cycle::noop(); + assert!(noop.is_noop()); + assert_eq!(noop.unexpanded_pc(), 0); + assert!(noop.ram_access_address().is_none()); + assert!(noop.rs1_read().is_none()); + assert!(noop.rd_write().is_none()); + + let cflags = CycleRow::circuit_flags(&noop); + assert!(cflags[CircuitFlags::DoNotUpdateUnexpandedPC]); + + let iflags = CycleRow::instruction_flags(&noop); + assert!(iflags[InstructionFlags::IsNoop]); + } + + #[test] + fn noop_lookup_index_is_zero() { + assert_eq!(Cycle::noop().lookup_index(), 0); + } +} diff --git a/crates/jolt-transcript/benches/transcript_ops.rs b/crates/jolt-transcript/benches/transcript_ops.rs index 2cc82cbd65..0f2a682be1 100644 --- a/crates/jolt-transcript/benches/transcript_ops.rs +++ b/crates/jolt-transcript/benches/transcript_ops.rs @@ -4,7 +4,9 @@ use std::hint::black_box; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use jolt_field::Fr; -use jolt_transcript::{Blake2bTranscript, KeccakTranscript, PoseidonTranscript, Transcript}; +#[cfg(feature = "poseidon")] +use jolt_transcript::PoseidonTranscript; +use jolt_transcript::{Blake2bTranscript, KeccakTranscript, Transcript}; fn bench_append_bytes(c: &mut Criterion) { let mut group = c.benchmark_group("append_bytes"); @@ -32,6 +34,7 @@ fn bench_append_bytes(c: &mut Criterion) { criterion::BatchSize::SmallInput, ); }); + #[cfg(feature = "poseidon")] group.bench_with_input(BenchmarkId::new("Poseidon", label), data, |bench, data| { bench.iter_batched( || PoseidonTranscript::::new(b"bench"), @@ -73,6 +76,7 @@ fn bench_challenge(c: &mut Criterion) { ); }); + #[cfg(feature = "poseidon")] group.bench_function("Poseidon", |bench| { bench.iter_batched( || { diff --git a/crates/jolt-transcript/fuzz/Cargo.toml b/crates/jolt-transcript/fuzz/Cargo.toml index 4c5897b2f1..40c4bb8798 100644 --- a/crates/jolt-transcript/fuzz/Cargo.toml +++ b/crates/jolt-transcript/fuzz/Cargo.toml @@ -21,7 +21,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" -jolt-transcript = { path = ".." } +jolt-transcript = { path = "..", features = ["poseidon"] } [[bin]] name = "transcript_no_panic" diff --git a/crates/jolt-transcript/src/blake2b.rs b/crates/jolt-transcript/src/blake2b.rs index ac5abf8d25..4b47d4a28b 100644 --- a/crates/jolt-transcript/src/blake2b.rs +++ b/crates/jolt-transcript/src/blake2b.rs @@ -5,9 +5,4 @@ use blake2::{digest::consts::U32, Blake2b}; use crate::digest::DigestTranscript; /// Fiat-Shamir transcript backed by Blake2b-256. -#[cfg(feature = "poseidon")] pub type Blake2bTranscript = DigestTranscript, F>; - -/// Fiat-Shamir transcript backed by Blake2b-256. -#[cfg(not(feature = "poseidon"))] -pub type Blake2bTranscript = DigestTranscript, F>; diff --git a/crates/jolt-transcript/src/blanket.rs b/crates/jolt-transcript/src/blanket.rs index d080b2f5c4..0cc1342ac9 100644 --- a/crates/jolt-transcript/src/blanket.rs +++ b/crates/jolt-transcript/src/blanket.rs @@ -1,16 +1,19 @@ //! Blanket implementation of [`AppendToTranscript`] for field elements. -use jolt_field::CanonicalBytes; +use jolt_field::Field; use crate::transcript::{AppendToTranscript, Transcript}; /// Absorbs any field element as big-endian bytes (reversed from the canonical /// LE layout) for EVM compatibility. -impl AppendToTranscript for F { +impl AppendToTranscript for F { fn append_to_transcript(&self, transcript: &mut T) { - let mut buf = vec![0u8; F::NUM_BYTES]; - self.to_bytes_le(&mut buf); + let mut buf = self.to_bytes(); buf.reverse(); transcript.append_bytes(&buf); } + + fn serialized_len(&self) -> u64 { + 32 + } } diff --git a/crates/jolt-transcript/src/digest.rs b/crates/jolt-transcript/src/digest.rs index 67e9fef4c2..410919d2cc 100644 --- a/crates/jolt-transcript/src/digest.rs +++ b/crates/jolt-transcript/src/digest.rs @@ -18,8 +18,11 @@ struct TestState { /// Fiat-Shamir transcript backed by a 256-bit digest. /// /// Generic over the hash function `D` and field type `F`. Challenges are -/// produced as field elements through `F::from_challenge_bytes`. -pub struct DigestTranscript + 'static, F> { +/// produced as full-width field elements via `F::from_bytes()`. +pub struct DigestTranscript< + D: Digest + 'static, + F: jolt_field::Field = jolt_field::Fr, +> { state: [u8; 32], n_rounds: u32, #[cfg(test)] @@ -27,11 +30,7 @@ pub struct DigestTranscript + 'static, F> { _marker: std::marker::PhantomData<(fn() -> D, F)>, } -impl Clone for DigestTranscript -where - D: Digest, - F: jolt_field::TranscriptChallenge, -{ +impl, F: jolt_field::Field> Clone for DigestTranscript { fn clone(&self) -> Self { Self { state: self.state, @@ -43,21 +42,13 @@ where } } -impl Default for DigestTranscript -where - D: Digest, - F: jolt_field::TranscriptChallenge, -{ +impl, F: jolt_field::Field> Default for DigestTranscript { fn default() -> Self { Self::new(b"") } } -impl std::fmt::Debug for DigestTranscript -where - D: Digest, - F: jolt_field::TranscriptChallenge, -{ +impl, F: jolt_field::Field> std::fmt::Debug for DigestTranscript { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DigestTranscript") .field("state", &format_args!("{:02x?}", self.state)) @@ -66,11 +57,7 @@ where } } -impl DigestTranscript -where - D: Digest, - F: jolt_field::TranscriptChallenge, -{ +impl, F: jolt_field::Field> DigestTranscript { #[inline] fn hasher(&self) -> D { let mut round_bytes = [0u8; 32]; @@ -97,11 +84,7 @@ where #[inline] fn challenge_bytes32(&mut self, out: &mut [u8; 32]) { - let hash: [u8; 32] = self - .hasher() - .chain_update([0x01]) // squeeze domain tag - .finalize() - .into(); + let hash: [u8; 32] = self.hasher().finalize().into(); out.copy_from_slice(&hash); self.update_state(hash); } @@ -124,11 +107,7 @@ where } } -impl Transcript for DigestTranscript -where - D: Digest, - F: jolt_field::TranscriptChallenge, -{ +impl, F: jolt_field::Field> Transcript for DigestTranscript { type Challenge = F; fn new(label: &'static [u8]) -> Self { @@ -155,19 +134,14 @@ where } fn append_bytes(&mut self, bytes: &[u8]) { - let hash: [u8; 32] = self - .hasher() - .chain_update([0x00]) // absorb domain tag - .chain_update(bytes) - .finalize() - .into(); + let hash: [u8; 32] = self.hasher().chain_update(bytes).finalize().into(); self.update_state(hash); } fn challenge(&mut self) -> F { - let mut buf = [0u8; 16]; + let mut buf = vec![0u8; F::NUM_BYTES]; self.challenge_bytes(&mut buf); - F::from_challenge_bytes(&buf) + F::from_bytes(&buf) } #[inline] diff --git a/crates/jolt-transcript/src/domain.rs b/crates/jolt-transcript/src/domain.rs index 5be57e8800..00cab90ccc 100644 --- a/crates/jolt-transcript/src/domain.rs +++ b/crates/jolt-transcript/src/domain.rs @@ -31,6 +31,10 @@ impl AppendToTranscript for Label { padded[..self.0.len()].copy_from_slice(self.0); transcript.append_bytes(&padded); } + + fn serialized_len(&self) -> u64 { + 32 + } } /// Packed label (24 bytes) + count (8 bytes BE) in one 32-byte word. @@ -56,6 +60,10 @@ impl AppendToTranscript for LabelWithCount { packed[24..32].copy_from_slice(&self.1.to_be_bytes()); transcript.append_bytes(&packed); } + + fn serialized_len(&self) -> u64 { + 32 + } } /// EVM-compatible left-padded u64: 24 zero bytes + 8-byte BE value. @@ -69,6 +77,10 @@ impl AppendToTranscript for U64Word { packed[24..].copy_from_slice(&self.0.to_be_bytes()); transcript.append_bytes(&packed); } + + fn serialized_len(&self) -> u64 { + 32 + } } #[cfg(test)] diff --git a/crates/jolt-transcript/src/impl_transcript.rs b/crates/jolt-transcript/src/impl_transcript.rs new file mode 100644 index 0000000000..e8461e8279 --- /dev/null +++ b/crates/jolt-transcript/src/impl_transcript.rs @@ -0,0 +1,166 @@ +//! Macro for implementing the Transcript trait with different hash functions. + +/// Implements the `Transcript` trait for a hash-based transcript. +/// +/// The generated struct is generic over `F: Field`, producing full-width +/// field-element challenges directly via `F::from_bytes()`. +macro_rules! impl_transcript { + ($name:ident, $hasher:ty, $new_hasher:expr) => { + use $crate::transcript::Transcript; + + /// Internal state for test-time transcript comparison. + #[cfg(test)] + #[derive(Clone, Default)] + struct TestState { + state_history: Vec<[u8; 32]>, + expected_state_history: Option>, + } + + #[doc = concat!("Fiat-Shamir transcript backed by ", stringify!($hasher), ".")] + /// + /// Generic over the field type `F`. Challenges are produced as + /// full-width field elements directly via `F::from_bytes()`. + #[derive(Clone)] + pub struct $name { + /// 256-bit running state. + state: [u8; 32], + /// Round counter for domain separation. + n_rounds: u32, + /// Test-only state for transcript comparison. + #[cfg(test)] + test_state: TestState, + _field: std::marker::PhantomData, + } + + impl Default for $name { + fn default() -> Self { + Self { + state: [0u8; 32], + n_rounds: 0, + #[cfg(test)] + test_state: TestState::default(), + _field: std::marker::PhantomData, + } + } + } + + impl std::fmt::Debug for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(stringify!($name)) + .field("state", &format_args!("{:02x?}", self.state)) + .field("n_rounds", &self.n_rounds) + .finish() + } + } + + impl $name { + /// Returns a hasher seeded with `state || round_counter` for domain separation. + #[inline] + fn hasher(&self) -> $hasher { + let mut round_bytes = [0u8; 32]; + round_bytes[28..].copy_from_slice(&self.n_rounds.to_be_bytes()); + <$hasher as Digest>::new() + .chain_update(self.state) + .chain_update(round_bytes) + } + + /// Fills `out` with challenge bytes, using `ceil(len / 32)` hash invocations. + fn challenge_bytes(&mut self, out: &mut [u8]) { + let mut remaining = out.len(); + let mut offset = 0; + + while remaining > 32 { + let mut chunk = [0u8; 32]; + self.challenge_bytes32(&mut chunk); + out[offset..offset + 32].copy_from_slice(&chunk); + offset += 32; + remaining -= 32; + } + + let mut final_chunk = [0u8; 32]; + self.challenge_bytes32(&mut final_chunk); + out[offset..offset + remaining].copy_from_slice(&final_chunk[..remaining]); + } + + /// Squeezes exactly 32 bytes from the transcript state. + #[inline] + fn challenge_bytes32(&mut self, out: &mut [u8; 32]) { + let hash: [u8; 32] = self.hasher().finalize().into(); + out.copy_from_slice(&hash); + self.update_state(hash); + } + + fn update_state(&mut self, new_state: [u8; 32]) { + self.state = new_state; + self.n_rounds += 1; + + #[cfg(test)] + { + if let Some(ref expected) = self.test_state.expected_state_history { + assert_eq!( + new_state, expected[self.n_rounds as usize], + "Fiat-Shamir transcript mismatch at round {}", + self.n_rounds + ); + } + self.test_state.state_history.push(new_state); + } + } + } + + impl Transcript for $name { + type Challenge = F; + + fn new(label: &'static [u8]) -> Self { + assert!( + label.len() <= $crate::transcript::MAX_LABEL_LEN, + "label must be at most {} bytes", + $crate::transcript::MAX_LABEL_LEN, + ); + + let mut padded = [0u8; $crate::transcript::MAX_LABEL_LEN]; + padded[..label.len()].copy_from_slice(label); + + let hash: [u8; 32] = <$hasher as Digest>::new() + .chain_update(padded) + .finalize() + .into(); + + Self { + state: hash, + n_rounds: 0, + #[cfg(test)] + test_state: TestState { + state_history: vec![hash], + expected_state_history: None, + }, + _field: std::marker::PhantomData, + } + } + + fn append_bytes(&mut self, bytes: &[u8]) { + let hash: [u8; 32] = self.hasher().chain_update(bytes).finalize().into(); + self.update_state(hash); + } + + fn challenge(&mut self) -> F { + let mut buf = vec![0u8; F::NUM_BYTES]; + self.challenge_bytes(&mut buf); + F::from_bytes(&buf) + } + + #[inline] + fn state(&self) -> &[u8; 32] { + &self.state + } + + #[cfg(test)] + fn compare_to(&mut self, other: &Self) { + self.test_state.expected_state_history = + Some(other.test_state.state_history.clone()); + } + } + }; +} + +pub(crate) use impl_transcript; diff --git a/crates/jolt-transcript/src/keccak.rs b/crates/jolt-transcript/src/keccak.rs index bd4bae354b..9449baedc2 100644 --- a/crates/jolt-transcript/src/keccak.rs +++ b/crates/jolt-transcript/src/keccak.rs @@ -5,9 +5,4 @@ use sha3::Keccak256; use crate::digest::DigestTranscript; /// Fiat-Shamir transcript backed by Keccak-256. -#[cfg(feature = "poseidon")] pub type KeccakTranscript = DigestTranscript; - -/// Fiat-Shamir transcript backed by Keccak-256. -#[cfg(not(feature = "poseidon"))] -pub type KeccakTranscript = DigestTranscript; diff --git a/crates/jolt-transcript/src/lib.rs b/crates/jolt-transcript/src/lib.rs index 9fcc5f4fee..a529f1e505 100644 --- a/crates/jolt-transcript/src/lib.rs +++ b/crates/jolt-transcript/src/lib.rs @@ -13,23 +13,25 @@ //! //! # Implementations //! -//! Hash backends use a `state || round_counter` domain separation scheme and -//! delegate field-family challenge decoding to `TranscriptChallenge`. +//! Hash backends produce full-width field challenges and use a +//! `state || round_counter` domain separation scheme. //! //! - [`Blake2bTranscript`]: Uses Blake2b-256. Default choice for Jolt proofs. //! - [`KeccakTranscript`]: Uses Keccak-256. EVM-compatible for on-chain verification. -//! - [`PoseidonTranscript`]: Uses Poseidon over BN254 when the `poseidon` feature is enabled. +//! - `PoseidonTranscript`: Uses Poseidon over BN254 when the `poseidon` feature is enabled. //! //! # Dependency position //! -//! Depends on `jolt-field` for field challenge decoding and transcript -//! absorption. The `poseidon` feature enables BN254/Poseidon dependencies. +//! Depends on `jolt-field` (for the blanket [`AppendToTranscript`] impl on +//! [`Field`](jolt_field::Field) types). Used by `jolt-crypto`, `jolt-sumcheck`, +//! `jolt-openings`, `jolt-dory`, `jolt-blindfold`, and generated Jolt protocol +//! crates. //! //! # Example //! -//! ```ignore +//! ``` //! use jolt_transcript::{Transcript, Blake2bTranscript}; -//! use jolt_field::{FromPrimitiveInt, Fr}; +//! use jolt_field::{Field, Fr}; //! //! let mut transcript = Blake2bTranscript::::new(b"my_protocol"); //! @@ -51,6 +53,7 @@ mod blanket; mod digest; pub mod domain; mod keccak; +mod mock; #[cfg(feature = "poseidon")] mod poseidon; mod transcript; @@ -59,6 +62,7 @@ pub use blake2b::Blake2bTranscript; pub use digest::DigestTranscript; pub use domain::{Label, LabelWithCount, U64Word}; pub use keccak::KeccakTranscript; +pub use mock::MockTranscript; #[cfg(feature = "poseidon")] pub use poseidon::PoseidonTranscript; pub use transcript::{AppendToTranscript, Transcript}; diff --git a/crates/jolt-transcript/src/mock.rs b/crates/jolt-transcript/src/mock.rs new file mode 100644 index 0000000000..66df0d9521 --- /dev/null +++ b/crates/jolt-transcript/src/mock.rs @@ -0,0 +1,109 @@ +//! Deterministic mock transcript for testing. +//! +//! All absorb operations are no-ops. Challenges come from a seeded Blake2b +//! counter, producing the same sequence regardless of what is absorbed. +//! Use the same seed in both jolt-core's and jolt-transcript's mock +//! transcripts to get identical challenges for cross-system comparison. + +use crate::transcript::{AppendToTranscript, Transcript}; +use blake2::digest::consts::U32; +use blake2::{Blake2b, Digest}; +use jolt_field::Field; +use std::marker::PhantomData; + +type Blake2b256 = Blake2b; + +/// Mock transcript that ignores absorbs and produces deterministic challenges. +/// +/// Challenges are derived from `H(seed || counter)` where counter increments +/// on each squeeze. Two mock transcripts with the same seed always produce +/// identical challenge sequences. +#[derive(Clone)] +pub struct MockTranscript { + seed: [u8; 32], + counter: u64, + _field: PhantomData, +} + +impl Default for MockTranscript { + fn default() -> Self { + Self { + seed: [0u8; 32], + counter: 0, + _field: PhantomData, + } + } +} + +impl MockTranscript { + /// Creates a mock transcript with the given seed bytes. + #[must_use] + pub fn with_seed(seed: &[u8]) -> Self { + let hash: [u8; 32] = Blake2b256::new().chain_update(seed).finalize().into(); + Self { + seed: hash, + counter: 0, + _field: PhantomData, + } + } + + fn next_bytes32(&mut self) -> [u8; 32] { + let hash: [u8; 32] = Blake2b256::new() + .chain_update(self.seed) + .chain_update(self.counter.to_le_bytes()) + .finalize() + .into(); + self.counter += 1; + hash + } +} + +impl Transcript for MockTranscript { + type Challenge = F; + + fn new(_label: &'static [u8]) -> Self { + Self::with_seed(b"mock_transcript_default_seed") + } + + fn append_bytes(&mut self, _bytes: &[u8]) {} + + fn append(&mut self, _value: &A) {} + + fn challenge(&mut self) -> F { + F::from_bytes(&self.next_bytes32()) + } + + fn state(&self) -> &[u8; 32] { + &self.seed + } + + #[cfg(test)] + fn compare_to(&mut self, _other: &Self) {} +} + +#[cfg(test)] +mod tests { + use super::*; + use jolt_field::Fr; + + #[test] + fn same_seed_same_challenges() { + let mut t1 = MockTranscript::::with_seed(b"test"); + let mut t2 = MockTranscript::::with_seed(b"test"); + + // Absorb different things — shouldn't matter + t1.append_bytes(b"hello"); + t2.append_bytes(b"world"); + + for _ in 0..100 { + assert_eq!(t1.challenge(), t2.challenge()); + } + } + + #[test] + fn different_seed_different_challenges() { + let mut t1 = MockTranscript::::with_seed(b"seed_a"); + let mut t2 = MockTranscript::::with_seed(b"seed_b"); + assert_ne!(t1.challenge(), t2.challenge()); + } +} diff --git a/crates/jolt-transcript/src/poseidon.rs b/crates/jolt-transcript/src/poseidon.rs index 1a008839f0..4cd923d0f8 100644 --- a/crates/jolt-transcript/src/poseidon.rs +++ b/crates/jolt-transcript/src/poseidon.rs @@ -1,5 +1,4 @@ //! Poseidon-based Fiat-Shamir transcript for SNARK-friendly verification. -#![cfg(feature = "poseidon")] //! //! Uses 3-input Poseidon (width-4 permutation: 3 inputs + 1 capacity element) //! over BN254 Fr with circom-compatible parameters via [`light_poseidon`]. @@ -43,8 +42,8 @@ const BYTES_PER_CHUNK: usize = 32; /// Fiat-Shamir transcript using Poseidon hash over BN254. /// -/// Generic over the field type `F`. Challenges are produced as field -/// elements directly via `TranscriptChallenge`. +/// Generic over the field type `F`. Challenges are produced as full-width +/// field elements directly via `F::from_bytes()`. pub struct PoseidonTranscript { /// 256-bit running state (canonical LE serialization of Fr). state: [u8; 32], @@ -233,9 +232,9 @@ impl Transcript for PoseidonTranscript { } fn challenge(&mut self) -> F { - let mut buf = [0u8; 16]; + let mut buf = vec![0u8; F::NUM_BYTES]; self.challenge_bytes(&mut buf); - ::from_challenge_bytes(&buf) + F::from_bytes(&buf) } #[inline] diff --git a/crates/jolt-transcript/src/transcript.rs b/crates/jolt-transcript/src/transcript.rs index cc4d9580cd..9b0053efcf 100644 --- a/crates/jolt-transcript/src/transcript.rs +++ b/crates/jolt-transcript/src/transcript.rs @@ -78,4 +78,8 @@ pub const MAX_LABEL_LEN: usize = 32; pub trait AppendToTranscript { /// Absorbs this value into the transcript. fn append_to_transcript(&self, transcript: &mut T); + + /// Number of raw bytes that [`append_to_transcript`](Self::append_to_transcript) + /// feeds to [`Transcript::append_bytes`]. + fn serialized_len(&self) -> u64; } diff --git a/crates/jolt-transcript/tests/blake2b_tests.rs b/crates/jolt-transcript/tests/blake2b_tests.rs index f2af8920a0..3dba55874c 100644 --- a/crates/jolt-transcript/tests/blake2b_tests.rs +++ b/crates/jolt-transcript/tests/blake2b_tests.rs @@ -1,9 +1,8 @@ //! Tests for Blake2bTranscript implementation. -#![cfg(feature = "poseidon")] mod common; -use jolt_field::{Fr, FromPrimitiveInt}; +use jolt_field::Fr; use jolt_transcript::Blake2bTranscript; use num_traits::Zero; @@ -61,6 +60,7 @@ fn test_field_zero_one_distinct_states() { #[test] fn test_field_element_ordering_sensitivity() { + use jolt_field::{Field, Fr}; use jolt_transcript::{AppendToTranscript, Transcript}; let a = Fr::from_u64(42); diff --git a/crates/jolt-transcript/tests/keccak_tests.rs b/crates/jolt-transcript/tests/keccak_tests.rs index 8ba613d52b..9058f8da61 100644 --- a/crates/jolt-transcript/tests/keccak_tests.rs +++ b/crates/jolt-transcript/tests/keccak_tests.rs @@ -1,5 +1,4 @@ //! Tests for KeccakTranscript implementation. -#![cfg(feature = "poseidon")] mod common; diff --git a/crates/jolt-transcript/tests/poseidon_tests.rs b/crates/jolt-transcript/tests/poseidon_tests.rs index 64a9e7557e..0d1e47a125 100644 --- a/crates/jolt-transcript/tests/poseidon_tests.rs +++ b/crates/jolt-transcript/tests/poseidon_tests.rs @@ -1,4 +1,5 @@ //! Tests for PoseidonTranscript implementation. + #![cfg(feature = "poseidon")] mod common; diff --git a/crates/jolt-verifier/Cargo.toml b/crates/jolt-verifier/Cargo.toml new file mode 100644 index 0000000000..3b6ecdafd5 --- /dev/null +++ b/crates/jolt-verifier/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "jolt-verifier" +version = "0.0.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Bolt-generated Jolt verifier role crate" +repository = "https://github.com/a16z/jolt" + +[lints] +workspace = true + +[dependencies] +jolt-dory.workspace = true +jolt-field.workspace = true +jolt-lookup-tables.workspace = true +jolt-openings.workspace = true +jolt-poly.workspace = true +jolt-sumcheck.workspace = true +jolt-transcript.workspace = true +serde.workspace = true +tracing.workspace = true diff --git a/crates/jolt-verifier/src/lib.rs b/crates/jolt-verifier/src/lib.rs new file mode 100644 index 0000000000..97589fc2d6 --- /dev/null +++ b/crates/jolt-verifier/src/lib.rs @@ -0,0 +1,87 @@ +pub mod stages; +#[rustfmt::skip] +pub mod verifier; + +pub use stages::{ + stage1_outer::{verify_stage1_outer_with_program, Stage1VerifierProgramPlan}, + stage2::{verify_stage2_with_program, Stage2VerifierProgramPlan}, + stage3::{verify_stage3_with_program, Stage3VerifierProgramPlan}, + stage4::{verify_stage4_with_program, Stage4VerifierProgramPlan}, + stage5::{verify_stage5_with_program, Stage5VerifierProgramPlan}, + stage6::{verify_stage6_with_program, Stage6VerifierProgramPlan}, + stage7::{verify_stage7_with_program, Stage7VerifierProgramPlan}, +}; + +pub use verifier::{ + default_verifier_programs, verify_jolt, verify_jolt_evaluation_proof, verify_jolt_prefix, + verify_jolt_prefix_with_programs, verify_jolt_through_stage5, + verify_jolt_through_stage5_with_programs, verify_jolt_through_stage6, + verify_jolt_through_stage6_with_programs, verify_jolt_through_stage7, + verify_jolt_through_stage7_with_programs, verify_jolt_with_programs, JoltEvaluationProof, + JoltEvaluationProofError, JoltNamedEval, JoltProof, JoltStage2RamAccess, JoltStage2RamData, + JoltStage2RamOutputLayout, JoltStageChallengeVector, JoltStageExecutionArtifacts, + JoltStage6BytecodeEntry, JoltStage6BytecodeReadRafData, JoltStage6VerifierData, + JoltStageOpeningInputValue, JoltStageProof, JoltSumcheckOutput, JoltVerificationArtifacts, + JoltVerifierInputs, JoltVerifierPrograms, JoltVerifierTarget, JoltVerifyError, +}; + +pub const TRANSCRIPT_LABEL: &[u8] = b"Jolt"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct GeneratedStage { + pub name: &'static str, + pub module: &'static str, + pub ordinal: usize, +} + +pub const GENERATED_STAGES: &[GeneratedStage] = &[ + GeneratedStage { + name: "commitment", + module: "commitment", + ordinal: 0, + }, + GeneratedStage { + name: "stage1_outer", + module: "stage1_outer", + ordinal: 1, + }, + GeneratedStage { + name: "stage2", + module: "stage2", + ordinal: 2, + }, + GeneratedStage { + name: "stage3", + module: "stage3", + ordinal: 3, + }, + GeneratedStage { + name: "stage4", + module: "stage4", + ordinal: 4, + }, + GeneratedStage { + name: "stage5", + module: "stage5", + ordinal: 5, + }, + GeneratedStage { + name: "stage6", + module: "stage6", + ordinal: 6, + }, + GeneratedStage { + name: "stage7", + module: "stage7", + ordinal: 7, + }, + GeneratedStage { + name: "stage8", + module: "stage8", + ordinal: 8, + }, +]; + +pub fn generated_stage_names() -> impl Iterator { + GENERATED_STAGES.iter().map(|stage| stage.name) +} diff --git a/crates/jolt-verifier/src/stages/commitment.rs b/crates/jolt-verifier/src/stages/commitment.rs new file mode 100644 index 0000000000..6f66c25019 --- /dev/null +++ b/crates/jolt-verifier/src/stages/commitment.rs @@ -0,0 +1,345 @@ +#![allow(dead_code)] + +use jolt_dory::DoryCommitment; +use jolt_field::Fr; +use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript}; + +pub type DefaultCommitmentTranscript = Blake2bTranscript; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentParams { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OraclePlan { + pub oracle: &'static str, + pub domain: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentBatchPlan { + pub artifact: &'static str, + pub pcs: &'static str, + pub oracle_family: &'static str, + pub label: &'static str, + pub oracles: &'static [&'static str], + pub count: usize, + pub domain: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OptionalSkipPolicy { + MissingOrZero, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OptionalCommitmentPlan { + pub artifact: &'static str, + pub pcs: &'static str, + pub oracle: &'static str, + pub label: &'static str, + pub domain: &'static str, + pub num_vars: usize, + pub skip_policy: OptionalSkipPolicy, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TranscriptStep { + pub label: &'static str, + pub source: &'static str, + pub optional: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentVerifierProgramPlan { + pub params: CommitmentParams, + pub oracle_plans: &'static [OraclePlan], + pub batch_plans: &'static [CommitmentBatchPlan], + pub optional_plans: &'static [OptionalCommitmentPlan], + pub transcript_steps: &'static [TranscriptStep], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CommitmentRecord { + pub artifact: &'static str, + pub oracle: &'static str, + pub label: &'static str, + pub num_vars: usize, +} + +#[derive(Clone, Debug, Default)] +pub struct CommitmentArtifacts { + pub commitments: Vec>, + pub records: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CommitmentPhaseError { + MissingProofCommitment { oracle: &'static str }, + MissingProofCommitmentSlot { artifact: &'static str, oracle: &'static str }, + MissingTranscriptSource { source: &'static str }, + PlanCountMismatch { artifact: &'static str, expected: usize, actual: usize }, + ProofCommitmentCountMismatch { expected: usize, actual: usize }, +} +pub const COMMITMENT_PARAMS: CommitmentParams = CommitmentParams { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const ORACLE_PLANS: &[OraclePlan] = &[ + OraclePlan { oracle: "RdInc", domain: "jolt.trace_domain", num_vars: 18 }, + OraclePlan { oracle: "RamInc", domain: "jolt.trace_domain", num_vars: 18 }, + OraclePlan { oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", num_vars: 22 }, + OraclePlan { oracle: "UntrustedAdvice", domain: "jolt.trace_domain", num_vars: 18 }, + OraclePlan { oracle: "TrustedAdvice", domain: "jolt.trace_domain", num_vars: 18 }, +]; +pub const COMMITMENT_BATCH_0_ORACLES: &[&str] = &[ + "RdInc", + "RamInc", + "InstructionRa_0", + "InstructionRa_1", + "InstructionRa_2", + "InstructionRa_3", + "InstructionRa_4", + "InstructionRa_5", + "InstructionRa_6", + "InstructionRa_7", + "InstructionRa_8", + "InstructionRa_9", + "InstructionRa_10", + "InstructionRa_11", + "InstructionRa_12", + "InstructionRa_13", + "InstructionRa_14", + "InstructionRa_15", + "InstructionRa_16", + "InstructionRa_17", + "InstructionRa_18", + "InstructionRa_19", + "InstructionRa_20", + "InstructionRa_21", + "InstructionRa_22", + "InstructionRa_23", + "InstructionRa_24", + "InstructionRa_25", + "InstructionRa_26", + "InstructionRa_27", + "InstructionRa_28", + "InstructionRa_29", + "InstructionRa_30", + "InstructionRa_31", + "RamRa_0", + "RamRa_1", + "RamRa_2", + "RamRa_3", + "BytecodeRa_0", + "BytecodeRa_1", + "BytecodeRa_2", + "BytecodeRa_3", +]; +pub const COMMITMENT_BATCH_PLANS: &[CommitmentBatchPlan] = &[ + CommitmentBatchPlan { artifact: "jolt.main_witness_commitments", pcs: "dory", oracle_family: "jolt.main_witness_polys", label: "commitment", oracles: COMMITMENT_BATCH_0_ORACLES, count: 42, domain: "jolt.main_witness_commit_domain", num_vars: 22 }, +]; +pub const OPTIONAL_COMMITMENT_PLANS: &[OptionalCommitmentPlan] = &[ + OptionalCommitmentPlan { artifact: "jolt.untrusted_advice_commitment", pcs: "dory", oracle: "UntrustedAdvice", label: "untrusted_advice", domain: "jolt.trace_domain", num_vars: 18, skip_policy: OptionalSkipPolicy::MissingOrZero }, + OptionalCommitmentPlan { artifact: "jolt.trusted_advice_commitment", pcs: "dory", oracle: "TrustedAdvice", label: "trusted_advice", domain: "jolt.trace_domain", num_vars: 18, skip_policy: OptionalSkipPolicy::MissingOrZero }, +]; +pub const TRANSCRIPT_PLAN: &[TranscriptStep] = &[ + TranscriptStep { label: "commitment", source: "jolt.main_witness_commitments", optional: false }, + TranscriptStep { label: "untrusted_advice", source: "jolt.untrusted_advice_commitment", optional: true }, + TranscriptStep { label: "trusted_advice", source: "jolt.trusted_advice_commitment", optional: true }, +]; +pub const COMMITMENT_PROGRAM: CommitmentVerifierProgramPlan = CommitmentVerifierProgramPlan { + params: COMMITMENT_PARAMS, + oracle_plans: ORACLE_PLANS, + batch_plans: COMMITMENT_BATCH_PLANS, + optional_plans: OPTIONAL_COMMITMENT_PLANS, + transcript_steps: TRANSCRIPT_PLAN, +}; + +pub fn verify_commitment_phase( + proof_commitments: &[Option], + transcript: &mut T, +) -> Result +where + T: Transcript, +{ + verify_commitment_phase_with_program(&COMMITMENT_PROGRAM, proof_commitments, transcript) +} + +pub fn verify_commitment_phase_with_program( + program: &'static CommitmentVerifierProgramPlan, + proof_commitments: &[Option], + transcript: &mut T, +) -> Result +where + T: Transcript, +{ + let mut artifacts = CommitmentArtifacts::default(); + let mut cursor = 0usize; + for plan in program.batch_plans { + receive_batch(program, proof_commitments, &mut cursor, &mut artifacts, plan)?; + } + for plan in program.optional_plans { + receive_optional(program, proof_commitments, &mut cursor, &mut artifacts, plan)?; + } + if cursor != proof_commitments.len() { + return Err(CommitmentPhaseError::ProofCommitmentCountMismatch { + expected: cursor, + actual: proof_commitments.len(), + }); + } + absorb_transcript(program, &artifacts, transcript)?; + Ok(artifacts) +} + +fn receive_batch( + program: &'static CommitmentVerifierProgramPlan, + proof_commitments: &[Option], + cursor: &mut usize, + artifacts: &mut CommitmentArtifacts, + plan: &CommitmentBatchPlan, +) -> Result<(), CommitmentPhaseError> { + if plan.count != plan.oracles.len() { + return Err(CommitmentPhaseError::PlanCountMismatch { + artifact: plan.artifact, + expected: plan.count, + actual: plan.oracles.len(), + }); + } + for &oracle in plan.oracles { + let commitment = proof_commitments + .get(*cursor) + .ok_or(CommitmentPhaseError::MissingProofCommitmentSlot { + artifact: plan.artifact, + oracle, + })? + .as_ref() + .ok_or(CommitmentPhaseError::MissingProofCommitment { oracle })? + .clone(); + *cursor += 1; + let oracle_num_vars = oracle_num_vars(program, oracle, plan.num_vars); + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle, + label: plan.label, + num_vars: oracle_num_vars, + }); + artifacts.commitments.push(Some(commitment)); + } + Ok(()) +} + +fn receive_optional( + program: &'static CommitmentVerifierProgramPlan, + proof_commitments: &[Option], + cursor: &mut usize, + artifacts: &mut CommitmentArtifacts, + plan: &OptionalCommitmentPlan, +) -> Result<(), CommitmentPhaseError> { + let commitment = proof_commitments + .get(*cursor) + .ok_or(CommitmentPhaseError::MissingProofCommitmentSlot { + artifact: plan.artifact, + oracle: plan.oracle, + })? + .clone(); + *cursor += 1; + artifacts.records.push(CommitmentRecord { + artifact: plan.artifact, + oracle: plan.oracle, + label: plan.label, + num_vars: oracle_num_vars(program, plan.oracle, plan.num_vars), + }); + artifacts.commitments.push(commitment); + Ok(()) +} + +pub fn commitment_verifier_program() -> &'static CommitmentVerifierProgramPlan { + &COMMITMENT_PROGRAM +} + +fn oracle_num_vars( + program: &'static CommitmentVerifierProgramPlan, + oracle: &'static str, + fallback: usize, +) -> usize { + program + .oracle_plans + .iter() + .find(|plan| plan.oracle == oracle) + .map_or(fallback, |plan| plan.num_vars) +} + +fn absorb_transcript( + program: &'static CommitmentVerifierProgramPlan, + artifacts: &CommitmentArtifacts, + transcript: &mut T, +) -> Result<(), CommitmentPhaseError> +where + T: Transcript, +{ + for step in program.transcript_steps { + let mut appended = false; + for (record, commitment) in artifacts.records.iter().zip(&artifacts.commitments) { + if record.artifact != step.source { + continue; + } + if let Some(commitment) = commitment { + transcript.append(&LabelWithCount(step.label.as_bytes(), commitment.serialized_len())); + commitment.append_to_transcript(transcript); + appended = true; + } + } + if !step.optional && !appended { + return Err(CommitmentPhaseError::MissingTranscriptSource { + source: step.source, + }); + } + } + Ok(()) +} diff --git a/crates/jolt-verifier/src/stages/common.rs b/crates/jolt-verifier/src/stages/common.rs new file mode 100644 index 0000000000..5c31bfa6ef --- /dev/null +++ b/crates/jolt-verifier/src/stages/common.rs @@ -0,0 +1,1789 @@ +#![expect( + clippy::too_many_arguments, + reason = "generated verifier helpers mirror staged protocol ABIs" +)] + +use jolt_field::{Field, Fr}; +use jolt_poly::EqPolynomial; +use jolt_sumcheck::{ + CompressedLabeledRoundPoly, SumcheckClaim, SumcheckError, SumcheckProof, SumcheckVerifier, +}; +use jolt_transcript::{Label, Transcript}; +use serde::Serialize; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageParams { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct KernelPlan { + pub symbol: &'static str, + pub relation: &'static str, + pub kind: &'static str, + pub backend: &'static str, + pub abi: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TranscriptSqueezePlan { + pub symbol: &'static str, + pub label: &'static str, + pub kind: &'static str, + pub count: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TranscriptAbsorbBytesPlan { + pub symbol: &'static str, + pub label: &'static str, + pub payload: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ProgramStepPlan { + pub kind: &'static str, + pub symbol: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FieldConstantPlan { + pub symbol: &'static str, + pub field: &'static str, + pub value: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FieldExprPlan { + pub symbol: &'static str, + pub kind: &'static str, + pub formula: &'static str, + pub operands: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckClaimPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub domain: &'static str, + pub num_rounds: usize, + pub degree: usize, + pub claim: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub claim_value: &'static str, + pub input_openings: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static str, + pub claim_operands: &'static str, + pub claim_label: &'static str, + pub round_label: &'static str, + pub round_schedule: &'static [usize], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckDriverPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub kernel: Option<&'static str>, + pub relation: Option<&'static str>, + pub batch: &'static str, + pub policy: &'static str, + pub round_schedule: &'static [usize], + pub claim_label: &'static str, + pub round_label: &'static str, + pub num_rounds: usize, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckInstanceResultPlan { + pub symbol: &'static str, + pub source: &'static str, + pub claim: &'static str, + pub relation: &'static str, + pub index: usize, + pub point_arity: usize, + pub num_rounds: usize, + pub round_offset: usize, + pub point_order: &'static str, + pub degree: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SumcheckEvalPlan { + pub symbol: &'static str, + pub source: &'static str, + pub name: &'static str, + pub index: usize, + pub oracle: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PointZeroPlan { + pub symbol: &'static str, + pub field: &'static str, + pub arity: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PointSlicePlan { + pub symbol: &'static str, + pub source: &'static str, + pub offset: usize, + pub length: usize, + pub input: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PointConcatPlan { + pub symbol: &'static str, + pub layout: &'static str, + pub arity: usize, + pub inputs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, + pub point_source: &'static str, + pub eval_source: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpeningClaimEqualityPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub lhs: &'static str, + pub rhs: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpeningBatchPlan { + pub symbol: &'static str, + pub stage: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static str, + pub claim_operands: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageProgramPlan { + pub role: &'static str, + pub params: StageParams, + pub steps: &'static [ProgramStepPlan], + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [OpeningInputPlan], + pub field_constants: &'static [FieldConstantPlan], + pub field_exprs: &'static [FieldExprPlan], + pub kernels: &'static [KernelPlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub point_zeros: &'static [PointZeroPlan], + pub point_slices: &'static [PointSlicePlan], + pub point_concats: &'static [PointConcatPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_equalities: &'static [OpeningClaimEqualityPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageProgramPlanNoPointZeros { + pub role: &'static str, + pub params: StageParams, + pub steps: &'static [ProgramStepPlan], + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub transcript_absorb_bytes: &'static [TranscriptAbsorbBytesPlan], + pub opening_inputs: &'static [OpeningInputPlan], + pub field_constants: &'static [FieldConstantPlan], + pub field_exprs: &'static [FieldExprPlan], + pub kernels: &'static [KernelPlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub point_slices: &'static [PointSlicePlan], + pub point_concats: &'static [PointConcatPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_equalities: &'static [OpeningClaimEqualityPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageVerifierProgramPlan { + pub params: StageParams, + pub steps: &'static [ProgramStepPlan], + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub opening_inputs: &'static [OpeningInputPlan], + pub field_constants: &'static [FieldConstantPlan], + pub field_exprs: &'static [FieldExprPlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub point_slices: &'static [PointSlicePlan], + pub point_concats: &'static [PointConcatPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_equalities: &'static [OpeningClaimEqualityPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StageVerifierProgramPlanNoEqualities { + pub params: StageParams, + pub steps: &'static [ProgramStepPlan], + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub opening_inputs: &'static [OpeningInputPlan], + pub field_constants: &'static [FieldConstantPlan], + pub field_exprs: &'static [FieldExprPlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub point_slices: &'static [PointSlicePlan], + pub point_concats: &'static [PointConcatPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct VerifierProgramPlanMinimal { + pub params: StageParams, + pub transcript_squeezes: &'static [TranscriptSqueezePlan], + pub claims: &'static [SumcheckClaimPlan], + pub batches: &'static [SumcheckBatchPlan], + pub drivers: &'static [SumcheckDriverPlan], + pub instance_results: &'static [SumcheckInstanceResultPlan], + pub evals: &'static [SumcheckEvalPlan], + pub opening_claims: &'static [OpeningClaimPlan], + pub opening_batches: &'static [OpeningBatchPlan], +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct StageNamedEval { + pub name: &'static str, + pub oracle: &'static str, + pub value: F, +} + +#[derive(Clone, Debug, Serialize)] +pub struct StageSumcheckOutput { + pub driver: &'static str, + pub point: Vec, + pub evals: Vec>, + pub proof: SumcheckProof, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StageChallengeVector { + pub symbol: &'static str, + pub values: Vec, +} + +#[derive(Clone, Debug)] +pub struct StageExecutionArtifacts { + pub challenge_vectors: Vec>, + pub sumchecks: Vec>, + pub opening_batches: Vec<&'static OpeningBatchPlan>, +} + +impl Default for StageExecutionArtifacts { + fn default() -> Self { + Self { + challenge_vectors: Vec::new(), + sumchecks: Vec::new(), + opening_batches: Vec::new(), + } + } +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct StageProof { + pub sumchecks: Vec>, +} + +#[derive(Clone, Debug)] +pub struct StageOpeningInputValue { + pub symbol: &'static str, + pub point: Vec, + pub eval: F, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RuntimePlanError { + MissingBatch { + driver: &'static str, + batch: &'static str, + }, + MissingClaim { + batch: &'static str, + claim: &'static str, + }, + MissingValue { + symbol: &'static str, + }, + InvalidInputLength { + input: &'static str, + expected: usize, + actual: usize, + }, + InvalidProof { + driver: &'static str, + reason: &'static str, + }, + UnsupportedFieldExpr { + symbol: &'static str, + formula: &'static str, + }, +} + +macro_rules! impl_runtime_plan_error_conversion { + ($error:ident) => { + impl From for $error { + fn from(error: super::common::RuntimePlanError) -> Self { + match error { + super::common::RuntimePlanError::MissingBatch { driver, batch } => { + Self::MissingBatch { driver, batch } + } + super::common::RuntimePlanError::MissingClaim { batch, claim } => { + Self::MissingClaim { batch, claim } + } + super::common::RuntimePlanError::MissingValue { symbol } => { + Self::MissingValue { symbol } + } + super::common::RuntimePlanError::InvalidInputLength { + input, + expected, + actual, + } => Self::InvalidInputLength { + input, + expected, + actual, + }, + super::common::RuntimePlanError::InvalidProof { driver, reason } => { + Self::InvalidProof { driver, reason } + } + super::common::RuntimePlanError::UnsupportedFieldExpr { symbol, formula } => { + Self::UnsupportedFieldExpr { symbol, formula } + } + } + } + } + }; +} + +pub(crate) use impl_runtime_plan_error_conversion; + +pub trait SymbolPlan { + fn symbol(&self) -> &'static str; +} + +impl SymbolPlan for TranscriptSqueezePlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for TranscriptAbsorbBytesPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for SumcheckBatchPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for SumcheckClaimPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for SumcheckDriverPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +impl SymbolPlan for OpeningClaimPlan { + fn symbol(&self) -> &'static str { + self.symbol + } +} + +pub trait SumcheckClaimInfo: SymbolPlan { + fn num_rounds(&self) -> usize; + fn claim_value(&self) -> &'static str; +} + +impl SumcheckClaimInfo for SumcheckClaimPlan { + fn num_rounds(&self) -> usize { + self.num_rounds + } + + fn claim_value(&self) -> &'static str { + self.claim_value + } +} + +pub trait SumcheckDriverInfo: SymbolPlan { + fn batch(&self) -> &'static str; + fn num_rounds(&self) -> usize; + fn degree(&self) -> usize; + fn round_label(&self) -> &'static str; +} + +impl SumcheckDriverInfo for SumcheckDriverPlan { + fn batch(&self) -> &'static str { + self.batch + } + + fn num_rounds(&self) -> usize { + self.num_rounds + } + + fn degree(&self) -> usize { + self.degree + } + + fn round_label(&self) -> &'static str { + self.round_label + } +} + +#[derive(Clone, Debug, Default)] +pub struct ValueStore { + scalars: Vec<(&'static str, F)>, + points: Vec<(&'static str, Vec)>, +} + +impl ValueStore { + pub fn with_opening_inputs( + inputs: &[StageOpeningInputValue], + expected_inputs: &[OpeningInputPlan], + ) -> Result { + if inputs.len() != expected_inputs.len() { + return Err(RuntimePlanError::InvalidInputLength { + input: "opening_inputs", + expected: expected_inputs.len(), + actual: inputs.len(), + }); + } + for expected in expected_inputs { + let matching_count = inputs + .iter() + .filter(|input| input.symbol == expected.symbol) + .count(); + if matching_count != 1 { + return Err(RuntimePlanError::InvalidInputLength { + input: expected.symbol, + expected: 1, + actual: matching_count, + }); + } + if let Some(input) = inputs.iter().find(|input| input.symbol == expected.symbol) { + if input.point.len() != expected.point_arity { + return Err(RuntimePlanError::InvalidInputLength { + input: expected.symbol, + expected: expected.point_arity, + actual: input.point.len(), + }); + } + } + } + let mut store = Self::default(); + for input in inputs { + store.insert_scalar(input.symbol, input.eval); + store.insert_point(input.symbol, input.point.clone()); + } + Ok(store) + } + + pub fn seed_constants(&mut self, constants: &[FieldConstantPlan]) { + for constant in constants { + self.insert_scalar(constant.symbol, F::from_u64(constant.value as u64)); + } + } + + pub fn seed_point_zeros(&mut self, point_zeros: &[PointZeroPlan]) { + for zero in point_zeros { + self.insert_point(zero.symbol, vec![F::from_u64(0); zero.arity]); + } + } + + pub fn observe_challenge_vector( + &mut self, + plan: &TranscriptSqueezePlan, + values: &[F], + invalid_input_length: impl Fn(&'static str, usize, usize) -> E, + ) -> Result<(), E> { + self.insert_point(plan.symbol, values.to_vec()); + if matches!(plan.kind, "challenge_scalar" | "scalar") { + if values.len() != 1 { + return Err(invalid_input_length(plan.symbol, 1, values.len())); + } + self.insert_scalar(plan.symbol, values[0]); + } + Ok(()) + } + + pub fn observe_sumcheck_output( + &mut self, + instance_results: &[SumcheckInstanceResultPlan], + evals: &[SumcheckEvalPlan], + output: &StageSumcheckOutput, + normalize_point: impl Fn(&SumcheckInstanceResultPlan, Vec) -> Result, E>, + invalid_input_length: impl Fn(&'static str, usize, usize) -> E, + missing_value: impl Fn(&'static str) -> E, + ) -> Result<(), E> { + self.insert_point(output.driver, output.point.clone()); + for instance in instance_results + .iter() + .filter(|instance| instance.source == output.driver) + { + let end = instance.round_offset + instance.point_arity; + let point = output + .point + .get(instance.round_offset..end) + .ok_or_else(|| invalid_input_length(instance.symbol, end, output.point.len()))? + .to_vec(); + self.insert_point(instance.symbol, normalize_point(instance, point)?); + } + for eval in evals.iter().filter(|eval| eval.source == output.driver) { + let value = output + .evals + .iter() + .find(|value| value.name == eval.name) + .or_else(|| output.evals.get(eval.index)) + .ok_or_else(|| missing_value(eval.symbol))? + .value; + self.insert_scalar(eval.symbol, value); + self.insert_scalar(eval.name, value); + } + Ok(()) + } + + pub fn evaluate_available_points( + &mut self, + point_slices: &[PointSlicePlan], + point_concats: &[PointConcatPlan], + invalid_input_length: impl Fn(&'static str, usize, usize) -> E, + ) -> Result<(), E> { + loop { + let mut progress = 0usize; + for slice in point_slices { + if self.try_point(slice.symbol).is_some() { + continue; + } + let Some(input) = self.try_point(slice.input) else { + continue; + }; + let end = slice.offset + slice.length; + let point = input + .get(slice.offset..end) + .ok_or_else(|| invalid_input_length(slice.symbol, end, input.len()))? + .to_vec(); + self.insert_point(slice.symbol, point); + progress += 1; + } + for concat in point_concats { + if self.try_point(concat.symbol).is_some() { + continue; + } + let Some(point) = self.try_concat_point(concat) else { + continue; + }; + if point.len() != concat.arity { + return Err(invalid_input_length( + concat.symbol, + concat.arity, + point.len(), + )); + } + self.insert_point(concat.symbol, point); + progress += 1; + } + if progress == 0 { + return Ok(()); + } + } + } + + pub fn evaluate_available_field_exprs( + &mut self, + field_exprs: &[FieldExprPlan], + evaluate: impl Fn(&FieldExprPlan, &[F]) -> Result, + ) -> Result<(), E> { + loop { + let mut progress = 0usize; + for expr in field_exprs { + if self.try_scalar(expr.symbol).is_some() { + continue; + } + let Some(operands) = self.try_expr_operands(expr) else { + continue; + }; + self.insert_scalar(expr.symbol, evaluate(expr, &operands)?); + progress += 1; + } + if progress == 0 { + return Ok(()); + } + } + } + + pub fn verify_opening_equalities( + &self, + opening_equalities: &[OpeningClaimEqualityPlan], + invalid_proof: impl Fn(&'static str, &'static str) -> E, + missing_value: impl Fn(&'static str) -> E, + ) -> Result<(), E> { + for equality in opening_equalities { + match equality.mode { + "point_and_eval" => { + if self.point_or(equality.lhs, &missing_value)? + != self.point_or(equality.rhs, &missing_value)? + || self.scalar_or(equality.lhs, &missing_value)? + != self.scalar_or(equality.rhs, &missing_value)? + { + return Err(invalid_proof( + equality.symbol, + "opening claim equality failed", + )); + } + } + _ => { + return Err(invalid_proof( + equality.symbol, + "unsupported opening equality mode", + )); + } + } + } + Ok(()) + } + + pub fn insert_scalar(&mut self, symbol: &'static str, value: F) { + if let Some((_, existing)) = self.scalars.iter_mut().find(|(name, _)| *name == symbol) { + *existing = value; + } else { + self.scalars.push((symbol, value)); + } + } + + pub fn insert_point(&mut self, symbol: &'static str, point: Vec) { + if let Some((_, existing)) = self.points.iter_mut().find(|(name, _)| *name == symbol) { + *existing = point; + } else { + self.points.push((symbol, point)); + } + } + + pub fn scalar_or( + &self, + symbol: &'static str, + missing_value: impl FnOnce(&'static str) -> E, + ) -> Result { + self.try_scalar(symbol).ok_or_else(|| missing_value(symbol)) + } + + pub fn try_scalar(&self, symbol: &str) -> Option { + self.scalars + .iter() + .find(|(name, _)| *name == symbol) + .map(|(_, value)| *value) + } + + pub fn point_or( + &self, + symbol: &'static str, + missing_value: impl FnOnce(&'static str) -> E, + ) -> Result<&[F], E> { + self.try_point(symbol).ok_or_else(|| missing_value(symbol)) + } + + pub fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.points + .iter() + .find(|(name, _)| *name == symbol) + .map(|(_, point)| point.as_slice()) + } + + fn try_expr_operands(&self, expr: &FieldExprPlan) -> Option> { + if expr.operands.is_empty() { + return Some(Vec::new()); + } + expr.operands + .split('|') + .map(|operand| self.try_scalar(operand)) + .collect() + } + + fn try_concat_point(&self, concat: &PointConcatPlan) -> Option> { + let mut point = Vec::with_capacity(concat.arity); + for input in symbol_list(concat.inputs) { + point.extend_from_slice(self.try_point(input)?); + } + Some(point) + } +} + +pub fn symbol_list(symbols: &'static str) -> impl Iterator { + symbols.split('|').filter(|symbol| !symbol.is_empty()) +} + +pub fn find_plan<'a, T: SymbolPlan>(plans: &'a [T], symbol: &str) -> Option<&'a T> { + plans.iter().find(|plan| plan.symbol() == symbol) +} + +pub fn find_batch<'a>( + batches: &'a [SumcheckBatchPlan], + driver: &'static str, + batch: &'static str, +) -> Result<&'a SumcheckBatchPlan, RuntimePlanError> { + find_plan(batches, batch).ok_or(RuntimePlanError::MissingBatch { driver, batch }) +} + +pub fn batch_claims<'a, C: SymbolPlan>( + claims: &'a [C], + batch: &SumcheckBatchPlan, +) -> Result, RuntimePlanError> { + symbol_list(batch.claim_operands) + .map(|symbol| { + find_plan(claims, symbol).ok_or(RuntimePlanError::MissingClaim { + batch: batch.symbol, + claim: symbol, + }) + }) + .collect() +} + +pub fn batch_claim_values( + claims: &[&C], + field_exprs: &[FieldExprPlan], + store: &mut ValueStore, +) -> Result, RuntimePlanError> { + claims + .iter() + .map(|claim| { + store.evaluate_available_field_exprs(field_exprs, evaluate_field_expr)?; + store.scalar_or(claim.claim_value(), |symbol| { + RuntimePlanError::MissingValue { symbol } + }) + }) + .collect() +} + +pub fn verify_batched_sumcheck( + driver: &'static D, + proof: &StageSumcheckOutput, + claims: &'static [C], + batches: &'static [SumcheckBatchPlan], + field_exprs: &'static [FieldExprPlan], + opening_inputs: &'static [OpeningInputPlan], + opening_claims: &'static [OpeningClaimPlan], + opening_batches: &'static [OpeningBatchPlan], + store: &mut ValueStore, + transcript: &mut T, + expected_output: Expected, + observe_output: Observe, + map_sumcheck: MapSumcheck, +) -> Result, E> +where + T: Transcript, + E: From, + C: SumcheckClaimInfo, + D: SumcheckDriverInfo, + Expected: FnOnce(&ValueStore, &[StageNamedEval], &[Fr], &[Fr]) -> Result, + Observe: FnOnce(&mut ValueStore, &StageSumcheckOutput) -> Result<(), E>, + MapSumcheck: FnOnce(&'static str, SumcheckError) -> E, +{ + if proof.driver != driver.symbol() { + return Err(RuntimePlanError::InvalidProof { + driver: driver.symbol(), + reason: "driver symbol mismatch", + } + .into()); + } + let batch = find_batch(batches, driver.symbol(), driver.batch())?; + let claims = batch_claims(claims, batch)?; + let input_claims = batch_claim_values(&claims, field_exprs, store)?; + for claim in &input_claims { + append_labeled_scalar(transcript, batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let claimed_sum = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), coefficient)| { + claim.mul_pow_2(driver.num_rounds() - plan.num_rounds()) * *coefficient + }) + .sum::(); + let claim = SumcheckClaim::new(driver.num_rounds(), driver.degree(), claimed_sum); + let round_proofs = proof + .proof + .round_polynomials + .iter() + .map(|poly| CompressedLabeledRoundPoly::new(poly, driver.round_label().as_bytes())) + .collect::>(); + let output = SumcheckVerifier::verify(&claim, &round_proofs, transcript) + .map_err(|error| map_sumcheck(driver.symbol(), error))?; + if !proof.point.is_empty() && proof.point != output.point { + return Err(RuntimePlanError::InvalidProof { + driver: driver.symbol(), + reason: "batched point mismatch", + } + .into()); + } + let expected = expected_output(store, &proof.evals, &output.point, &batching_coeffs)?; + if output.value != expected { + return Err(RuntimePlanError::InvalidProof { + driver: driver.symbol(), + reason: "batched output claim mismatch", + } + .into()); + } + let verified = StageSumcheckOutput { + driver: driver.symbol(), + point: output.point, + evals: proof.evals.clone(), + proof: proof.proof.clone(), + }; + observe_output(store, &verified)?; + append_opening_claims( + opening_inputs, + opening_claims, + opening_batches, + store, + transcript, + &verified.evals, + |batch, claim| RuntimePlanError::MissingClaim { batch, claim }, + |symbol| RuntimePlanError::MissingValue { symbol }, + )?; + Ok(verified) +} + +pub fn eval_by_name( + evals: &[StageNamedEval], + name: &'static str, +) -> Result { + evals + .iter() + .find(|eval| eval.name == name) + .map(|eval| eval.value) + .ok_or(RuntimePlanError::MissingValue { symbol: name }) +} + +pub fn indexed_evals_by_prefix( + evals: &[StageNamedEval], + prefix: &'static str, + count: usize, +) -> Result, RuntimePlanError> { + let mut values = vec![None; count]; + for eval in evals { + let Some(suffix) = eval.name.strip_prefix(prefix) else { + continue; + }; + let index = suffix + .parse::() + .map_err(|_| RuntimePlanError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval suffix", + })?; + if index >= count || values[index].is_some() { + return Err(RuntimePlanError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval", + }); + } + values[index] = Some(eval.value); + } + values + .into_iter() + .map(|value| value.ok_or(RuntimePlanError::MissingValue { symbol: prefix })) + .collect() +} + +pub fn indexed_evals_by_prefix_any( + evals: &[StageNamedEval], + prefix: &'static str, +) -> Result, RuntimePlanError> { + let mut indexed_values = Vec::new(); + for eval in evals { + let Some(suffix) = eval.name.strip_prefix(prefix) else { + continue; + }; + let index = suffix + .parse::() + .map_err(|_| RuntimePlanError::InvalidProof { + driver: prefix, + reason: "invalid indexed eval suffix", + })?; + if indexed_values + .iter() + .any(|(existing_index, _)| *existing_index == index) + { + return Err(RuntimePlanError::InvalidProof { + driver: prefix, + reason: "duplicate indexed eval", + }); + } + indexed_values.push((index, eval.value)); + } + if indexed_values.is_empty() { + return Err(RuntimePlanError::MissingValue { symbol: prefix }); + } + indexed_values.sort_by_key(|(index, _)| *index); + for (expected, (actual, _)) in indexed_values.iter().enumerate() { + if *actual != expected { + return Err(RuntimePlanError::InvalidProof { + driver: prefix, + reason: "non-contiguous indexed eval", + }); + } + } + Ok(indexed_values.into_iter().map(|(_, value)| value).collect()) +} + +pub fn single_operand( + symbol: &'static str, + operands: &[F], +) -> Result { + require_operand_count(symbol, 1, operands.len())?; + Ok(operands[0]) +} + +pub fn require_operand_count( + input: &'static str, + expected: usize, + actual: usize, +) -> Result<(), RuntimePlanError> { + if expected == actual { + Ok(()) + } else { + Err(RuntimePlanError::InvalidInputLength { + input, + expected, + actual, + }) + } +} + +pub fn evaluate_field_expr( + expr: &FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => Ok(single_operand(expr.symbol, operands)?), + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + formula => { + if let Some(exponent) = formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + RuntimePlanError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + Err(RuntimePlanError::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }) + } + } +} + +pub fn bytecode_gamma_powers(gamma: Fr) -> [Fr; 8] { + let mut powers = [Fr::from_u64(1); 8]; + for index in 1..powers.len() { + powers[index] = powers[index - 1] * gamma; + } + powers +} + +pub fn indexed_boolean_eq(index: usize, point: &[Fr]) -> Fr { + point + .iter() + .enumerate() + .map(|(bit, value)| { + if (index >> (point.len() - 1 - bit)) & 1 == 1 { + *value + } else { + Fr::from_u64(1) - *value + } + }) + .product() +} + +pub fn field_powers(base: Fr, count: usize) -> Vec { + let mut powers = Vec::with_capacity(count); + let mut power = Fr::from_u64(1); + for _ in 0..count { + powers.push(power); + power *= base; + } + powers +} + +pub fn prefix_point<'a, F: Field>( + point: &'a [F], + length: usize, + input: &'static str, +) -> Result<&'a [F], RuntimePlanError> { + point + .get(..length) + .filter(|prefix| prefix.len() == length) + .ok_or(RuntimePlanError::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +pub fn suffix_point<'a, F: Field>( + point: &'a [F], + length: usize, + input: &'static str, +) -> Result<&'a [F], RuntimePlanError> { + point + .get(point.len().saturating_sub(length)..) + .filter(|suffix| suffix.len() == length) + .ok_or(RuntimePlanError::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +pub fn normalize_bytecode_read_raf_point( + point: &[F], + log_t: usize, + input: &'static str, +) -> Result, RuntimePlanError> { + let log_k = point + .len() + .checked_sub(log_t) + .ok_or(RuntimePlanError::InvalidInputLength { + input, + expected: log_t, + actual: point.len(), + })?; + let mut normalized = point.to_vec(); + normalized[..log_k].reverse(); + normalized[log_k..].reverse(); + Ok(normalized) +} + +pub fn normalize_instruction_read_raf_point( + point: &[F], + input: &'static str, +) -> Result, RuntimePlanError> { + const LOG_K: usize = 128; + if point.len() < LOG_K { + return Err(RuntimePlanError::InvalidInputLength { + input, + expected: LOG_K, + actual: point.len(), + }); + } + let mut normalized = point.to_vec(); + normalized[LOG_K..].reverse(); + Ok(normalized) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage67RelationSymbols { + pub hamming_booleanity_relation: &'static str, + pub hamming_booleanity_instance: &'static str, + pub booleanity_point: &'static str, + pub stage5_instruction_ra0: &'static str, + pub booleanity_combined_point: &'static str, + pub booleanity_gamma: &'static str, + pub booleanity_instruction_ra_prefix: &'static str, + pub booleanity_bytecode_ra_prefix: &'static str, + pub booleanity_ram_ra_prefix: &'static str, + pub hamming_weight_eval: &'static str, + pub hamming_lookup_output: &'static str, + pub ram_ra_virtual_cycle: &'static str, + pub ram_ra_virtual_eval_prefix: &'static str, + pub instruction_ra_virtual_cycle: &'static str, + pub instruction_ra_virtual_eval_prefix: &'static str, + pub instruction_ra_virtual_input_prefix: &'static str, + pub instruction_ra_virtual_gamma: &'static str, + pub inc_ram_stage2: &'static str, + pub inc_ram_stage4: &'static str, + pub inc_rd_stage4: &'static str, + pub inc_rd_stage5: &'static str, + pub inc_gamma: &'static str, + pub inc_ram_eval: &'static str, + pub inc_rd_eval: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage67BytecodeSymbols { + pub point: &'static str, + pub gamma: &'static str, + pub bytecode_ra_eval_prefix: &'static str, + pub entries: &'static str, + pub entry_bytecode_index: &'static str, + pub stage_gammas: [&'static str; 5], + pub stage_cycle_points: [&'static str; 5], + pub stage4_register_point: &'static str, + pub stage5_register_point: &'static str, + pub entry_rd: &'static str, + pub entry_rs1: &'static str, + pub entry_rs2: &'static str, + pub entry_lookup_table: &'static str, +} + +pub trait Stage67BytecodeEntry { + fn address(&self) -> Fr; + fn imm(&self) -> Fr; + fn circuit_flags(&self) -> &[bool; 14]; + fn rd(&self) -> Option; + fn rs1(&self) -> Option; + fn rs2(&self) -> Option; + fn lookup_table(&self) -> Option; + fn is_interleaved(&self) -> bool; + fn is_branch(&self) -> bool; + fn left_is_rs1(&self) -> bool; + fn left_is_pc(&self) -> bool; + fn right_is_rs2(&self) -> bool; + fn right_is_imm(&self) -> bool; + fn is_noop(&self) -> bool; +} + +pub fn store_scalar(store: &ValueStore, symbol: &'static str) -> Result { + store.scalar_or(symbol, |symbol| RuntimePlanError::MissingValue { symbol }) +} + +pub fn store_point<'a>( + store: &'a ValueStore, + symbol: &'static str, +) -> Result<&'a [Fr], RuntimePlanError> { + store.point_or(symbol, |symbol| RuntimePlanError::MissingValue { symbol }) +} + +pub fn stage67_trace_rounds( + instance_results: &[SumcheckInstanceResultPlan], + symbols: &Stage67RelationSymbols, +) -> Result { + instance_results + .iter() + .find(|instance| instance.relation == symbols.hamming_booleanity_relation) + .map(|instance| instance.num_rounds) + .ok_or(RuntimePlanError::MissingValue { + symbol: symbols.hamming_booleanity_instance, + }) +} + +pub fn expected_stage67_bytecode_read_raf( + entries: &[E], + entry_bytecode_index: usize, + num_lookup_tables: usize, + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + log_t: usize, + symbols: &Stage67BytecodeSymbols, +) -> Result { + let opening_point = normalize_bytecode_read_raf_point(local_point, log_t, symbols.point)?; + let log_k = opening_point.len() - log_t; + let (r_address_prime, r_cycle_prime) = opening_point.split_at(log_k); + + let gamma = store_scalar(store, symbols.gamma)?; + let gamma_powers = bytecode_gamma_powers(gamma); + let int_eval = identity_polynomial_eval(r_address_prime); + let stage_value_evals = stage67_bytecode_stage_value_evals( + entries, + entry_bytecode_index, + num_lookup_tables, + store, + r_address_prime, + r_cycle_prime.len(), + symbols, + )?; + let stage_cycle_points = + stage67_bytecode_stage_cycle_points(store, r_cycle_prime.len(), symbols)?; + let int_contrib = [ + gamma_powers[5] * int_eval, + Fr::from_u64(0), + gamma_powers[4] * int_eval, + Fr::from_u64(0), + Fr::from_u64(0), + ]; + + let mut val = Fr::from_u64(0); + for index in 0..stage_value_evals.len() { + val += (stage_value_evals[index] + int_contrib[index]) + * EqPolynomial::::mle(&stage_cycle_points[index], r_cycle_prime) + * gamma_powers[index]; + } + + let entry_bits = (0..log_k) + .map(|index| Fr::from_u64(((entry_bytecode_index >> (log_k - 1 - index)) & 1) as u64)) + .collect::>(); + let zero_cycle = vec![Fr::from_u64(0); r_cycle_prime.len()]; + let entry_contrib = gamma_powers[7] + * EqPolynomial::::mle(&entry_bits, r_address_prime) + * EqPolynomial::::mle(&zero_cycle, r_cycle_prime); + let bytecode_ra = indexed_evals_by_prefix_any(evals, symbols.bytecode_ra_eval_prefix)? + .into_iter() + .product::(); + Ok((val + entry_contrib) * bytecode_ra) +} + +pub fn expected_stage67_booleanity( + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + log_t: usize, + symbols: &Stage67RelationSymbols, +) -> Result { + let log_k_chunk = + local_point + .len() + .checked_sub(log_t) + .ok_or(RuntimePlanError::InvalidInputLength { + input: symbols.booleanity_point, + expected: log_t, + actual: local_point.len(), + })?; + let stage5_point = store_point(store, symbols.stage5_instruction_ra0)?; + let stage5_address_len = + stage5_point + .len() + .checked_sub(log_t) + .ok_or(RuntimePlanError::InvalidInputLength { + input: symbols.stage5_instruction_ra0, + expected: log_t, + actual: stage5_point.len(), + })?; + if stage5_address_len < log_k_chunk { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.stage5_instruction_ra0, + expected: log_k_chunk + log_t, + actual: stage5_point.len(), + }); + } + + let mut stage5_addr = stage5_point[..stage5_address_len].to_vec(); + stage5_addr.reverse(); + let mut combined_r = stage5_addr[stage5_address_len - log_k_chunk..].to_vec(); + combined_r.extend(stage5_point[stage5_address_len..].iter().rev().copied()); + if combined_r.len() != local_point.len() { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.booleanity_combined_point, + expected: local_point.len(), + actual: combined_r.len(), + }); + } + let mut verifier_point = combined_r[..log_k_chunk].to_vec(); + verifier_point.reverse(); + verifier_point.extend(combined_r[log_k_chunk..].iter().rev().copied()); + let eq_eval = EqPolynomial::::mle(local_point, &verifier_point); + + let gamma = store_scalar(store, symbols.booleanity_gamma)?; + let gamma_sq = gamma.square(); + let mut gamma_power = Fr::from_u64(1); + let mut booleanity = Fr::from_u64(0); + for ra in stage67_booleanity_evals(evals, symbols)? { + booleanity += gamma_power * (ra.square() - ra); + gamma_power *= gamma_sq; + } + Ok(eq_eval * booleanity) +} + +pub fn expected_stage67_hamming_booleanity( + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + symbols: &Stage67RelationSymbols, +) -> Result { + let hamming = eval_by_name(evals, symbols.hamming_weight_eval)?; + let lookup_output_point = reverse_slice(store_point(store, symbols.hamming_lookup_output)?); + if lookup_output_point.len() != local_point.len() { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.hamming_lookup_output, + expected: local_point.len(), + actual: lookup_output_point.len(), + }); + } + let eq_eval = EqPolynomial::::mle(local_point, &lookup_output_point); + Ok((hamming.square() - hamming) * eq_eval) +} + +pub fn expected_stage67_ram_ra_virtual( + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + symbols: &Stage67RelationSymbols, +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle = suffix_point( + store_point(store, symbols.ram_ra_virtual_cycle)?, + r_cycle_reduced.len(), + symbols.ram_ra_virtual_cycle, + )?; + let eq_eval = EqPolynomial::::mle(r_cycle, &r_cycle_reduced); + let ram_ra = indexed_evals_by_prefix_any(evals, symbols.ram_ra_virtual_eval_prefix)? + .into_iter() + .product::(); + Ok(eq_eval * ram_ra) +} + +pub fn expected_stage67_instruction_ra_virtual( + opening_inputs: &[OpeningInputPlan], + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + symbols: &Stage67RelationSymbols, +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle = suffix_point( + store_point(store, symbols.instruction_ra_virtual_cycle)?, + r_cycle_reduced.len(), + symbols.instruction_ra_virtual_cycle, + )?; + let eq_eval = EqPolynomial::::mle(r_cycle, &r_cycle_reduced); + let committed_ra = + indexed_evals_by_prefix_any(evals, symbols.instruction_ra_virtual_eval_prefix)?; + let virtual_count = opening_inputs + .iter() + .filter(|input| { + input + .symbol + .starts_with(symbols.instruction_ra_virtual_input_prefix) + }) + .count(); + if virtual_count == 0 || committed_ra.len() % virtual_count != 0 { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.instruction_ra_virtual_eval_prefix, + expected: virtual_count, + actual: committed_ra.len(), + }); + } + let committed_per_virtual = committed_ra.len() / virtual_count; + let gamma = store_scalar(store, symbols.instruction_ra_virtual_gamma)?; + let mut gamma_power = Fr::from_u64(1); + let mut value = Fr::from_u64(0); + for chunk in committed_ra.chunks(committed_per_virtual) { + value += gamma_power * chunk.iter().copied().product::(); + gamma_power *= gamma; + } + Ok(eq_eval * value) +} + +pub fn expected_stage67_inc_claim_reduction( + store: &ValueStore, + evals: &[StageNamedEval], + local_point: &[Fr], + symbols: &Stage67RelationSymbols, +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let ram_inc_stage2 = suffix_point( + store_point(store, symbols.inc_ram_stage2)?, + r_cycle_reduced.len(), + symbols.inc_ram_stage2, + )?; + let ram_inc_stage4 = suffix_point( + store_point(store, symbols.inc_ram_stage4)?, + r_cycle_reduced.len(), + symbols.inc_ram_stage4, + )?; + let rd_inc_stage4 = suffix_point( + store_point(store, symbols.inc_rd_stage4)?, + r_cycle_reduced.len(), + symbols.inc_rd_stage4, + )?; + let rd_inc_stage5 = suffix_point( + store_point(store, symbols.inc_rd_stage5)?, + r_cycle_reduced.len(), + symbols.inc_rd_stage5, + )?; + let gamma = store_scalar(store, symbols.inc_gamma)?; + let eq_ram_combined = EqPolynomial::::mle(ram_inc_stage2, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(ram_inc_stage4, &r_cycle_reduced); + let eq_rd_combined = EqPolynomial::::mle(rd_inc_stage4, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(rd_inc_stage5, &r_cycle_reduced); + let ram_inc = eval_by_name(evals, symbols.inc_ram_eval)?; + let rd_inc = eval_by_name(evals, symbols.inc_rd_eval)?; + Ok(ram_inc * eq_ram_combined + gamma.square() * rd_inc * eq_rd_combined) +} + +fn stage67_booleanity_evals( + evals: &[StageNamedEval], + symbols: &Stage67RelationSymbols, +) -> Result, RuntimePlanError> { + let mut values = indexed_evals_by_prefix_any(evals, symbols.booleanity_instruction_ra_prefix)?; + values.extend(indexed_evals_by_prefix_any( + evals, + symbols.booleanity_bytecode_ra_prefix, + )?); + values.extend(indexed_evals_by_prefix_any( + evals, + symbols.booleanity_ram_ra_prefix, + )?); + Ok(values) +} + +fn stage67_bytecode_stage_cycle_points( + store: &ValueStore, + log_t: usize, + symbols: &Stage67BytecodeSymbols, +) -> Result<[Vec; 5], RuntimePlanError> { + let point = |index| { + let symbol = symbols.stage_cycle_points[index]; + suffix_point(store_point(store, symbol)?, log_t, symbol).map(|point| point.to_vec()) + }; + Ok([point(0)?, point(1)?, point(2)?, point(3)?, point(4)?]) +} + +fn stage67_bytecode_stage_value_evals( + entries: &[E], + entry_bytecode_index: usize, + num_lookup_tables: usize, + store: &ValueStore, + r_address: &[Fr], + log_t: usize, + symbols: &Stage67BytecodeSymbols, +) -> Result<[Fr; 5], RuntimePlanError> { + let expected_len = + 1usize + .checked_shl(r_address.len() as u32) + .ok_or(RuntimePlanError::InvalidInputLength { + input: symbols.entries, + expected: usize::BITS as usize, + actual: r_address.len(), + })?; + if entries.len() != expected_len { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.entries, + expected: expected_len, + actual: entries.len(), + }); + } + if entry_bytecode_index >= expected_len { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.entry_bytecode_index, + expected: expected_len, + actual: entry_bytecode_index + 1, + }); + } + + let stage1_gamma_powers = field_powers(store_scalar(store, symbols.stage_gammas[0])?, 16); + let stage2_gamma_powers = field_powers(store_scalar(store, symbols.stage_gammas[1])?, 4); + let stage3_gamma_powers = field_powers(store_scalar(store, symbols.stage_gammas[2])?, 9); + let stage4_gamma_powers = field_powers(store_scalar(store, symbols.stage_gammas[3])?, 3); + let stage5_gamma_powers = field_powers( + store_scalar(store, symbols.stage_gammas[4])?, + num_lookup_tables + 2, + ); + + let stage4_register_point = + stage67_register_prefix_point(store, symbols.stage4_register_point, log_t)?; + let stage5_register_point = + stage67_register_prefix_point(store, symbols.stage5_register_point, log_t)?; + + let mut evals = [Fr::from_u64(0); 5]; + for (index, entry) in entries.iter().enumerate() { + let eq = indexed_boolean_eq(index, r_address); + let values = stage67_bytecode_entry_stage_values( + entry, + num_lookup_tables, + stage4_register_point, + stage5_register_point, + &stage1_gamma_powers, + &stage2_gamma_powers, + &stage3_gamma_powers, + &stage4_gamma_powers, + &stage5_gamma_powers, + symbols, + )?; + for stage in 0..evals.len() { + evals[stage] += eq * values[stage]; + } + } + Ok(evals) +} + +fn stage67_bytecode_entry_stage_values( + entry: &E, + num_lookup_tables: usize, + stage4_register_point: &[Fr], + stage5_register_point: &[Fr], + stage1_gamma_powers: &[Fr], + stage2_gamma_powers: &[Fr], + stage3_gamma_powers: &[Fr], + stage4_gamma_powers: &[Fr], + stage5_gamma_powers: &[Fr], + symbols: &Stage67BytecodeSymbols, +) -> Result<[Fr; 5], RuntimePlanError> { + let flags = entry.circuit_flags(); + let mut stage1 = entry.address() + entry.imm() * stage1_gamma_powers[1]; + for (flag, gamma) in flags.iter().zip(stage1_gamma_powers.iter().skip(2)) { + if *flag { + stage1 += *gamma; + } + } + + let mut stage2 = Fr::from_u64(0); + if flags[5] { + stage2 += stage2_gamma_powers[0]; + } + if entry.is_branch() { + stage2 += stage2_gamma_powers[1]; + } + if flags[6] { + stage2 += stage2_gamma_powers[2]; + } + if flags[7] { + stage2 += stage2_gamma_powers[3]; + } + + let mut stage3 = entry.imm() + entry.address() * stage3_gamma_powers[1]; + if entry.left_is_rs1() { + stage3 += stage3_gamma_powers[2]; + } + if entry.left_is_pc() { + stage3 += stage3_gamma_powers[3]; + } + if entry.right_is_rs2() { + stage3 += stage3_gamma_powers[4]; + } + if entry.right_is_imm() { + stage3 += stage3_gamma_powers[5]; + } + if entry.is_noop() { + stage3 += stage3_gamma_powers[6]; + } + if flags[7] { + stage3 += stage3_gamma_powers[7]; + } + if flags[12] { + stage3 += stage3_gamma_powers[8]; + } + + let stage4 = stage67_register_eq(entry.rd(), stage4_register_point, symbols.entry_rd)? + * stage4_gamma_powers[0] + + stage67_register_eq(entry.rs1(), stage4_register_point, symbols.entry_rs1)? + * stage4_gamma_powers[1] + + stage67_register_eq(entry.rs2(), stage4_register_point, symbols.entry_rs2)? + * stage4_gamma_powers[2]; + + let mut stage5 = stage67_register_eq(entry.rd(), stage5_register_point, symbols.entry_rd)? + * stage5_gamma_powers[0]; + if !entry.is_interleaved() { + stage5 += stage5_gamma_powers[1]; + } + if let Some(table) = entry.lookup_table() { + if table >= num_lookup_tables { + return Err(RuntimePlanError::InvalidInputLength { + input: symbols.entry_lookup_table, + expected: num_lookup_tables, + actual: table + 1, + }); + } + stage5 += stage5_gamma_powers[2 + table]; + } + + Ok([stage1, stage2, stage3, stage4, stage5]) +} + +fn stage67_register_eq( + index: Option, + point: &[Fr], + input: &'static str, +) -> Result { + let Some(index) = index else { + return Ok(Fr::from_u64(0)); + }; + let register_count = + 1usize + .checked_shl(point.len() as u32) + .ok_or(RuntimePlanError::InvalidInputLength { + input, + expected: usize::BITS as usize, + actual: point.len(), + })?; + if index >= register_count { + return Err(RuntimePlanError::InvalidInputLength { + input, + expected: register_count, + actual: index + 1, + }); + } + Ok(indexed_boolean_eq(index, point)) +} + +fn stage67_register_prefix_point<'a>( + store: &'a ValueStore, + symbol: &'static str, + log_t: usize, +) -> Result<&'a [Fr], RuntimePlanError> { + let point = store_point(store, symbol)?; + let register_len = + point + .len() + .checked_sub(log_t) + .ok_or(RuntimePlanError::InvalidInputLength { + input: symbol, + expected: log_t, + actual: point.len(), + })?; + prefix_point(point, register_len, symbol) +} + +pub fn operand_polynomial_eval(point: &[Fr], left: bool) -> Fr { + let stride_offset = usize::from(!left); + let operand_bits = point.len() / 2; + (0..operand_bits) + .map(|index| point[2 * index + stride_offset].mul_pow_2(operand_bits - 1 - index)) + .sum() +} + +pub fn identity_polynomial_eval(point: &[Fr]) -> Fr { + point + .iter() + .enumerate() + .map(|(index, value)| value.mul_pow_2(point.len() - 1 - index)) + .sum() +} + +pub fn append_labeled_scalar(transcript: &mut T, label: &'static str, scalar: &Fr) +where + T: Transcript, +{ + transcript.append(&Label(label.as_bytes())); + transcript.append(scalar); +} + +pub fn append_opening_claims( + opening_inputs: &[OpeningInputPlan], + opening_claims: &[OpeningClaimPlan], + opening_batches: &[OpeningBatchPlan], + store: &mut ValueStore, + transcript: &mut T, + evals: &[StageNamedEval], + missing_claim: impl Fn(&'static str, &'static str) -> E, + missing_value: impl Fn(&'static str) -> E, +) -> Result<(), E> +where + T: Transcript, +{ + if opening_batches.is_empty() { + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } + return Ok(()); + } + let mut seen = opening_inputs + .iter() + .filter_map(|input| { + store + .try_point(input.symbol) + .map(|point| (input.claim_kind, input.oracle, point.to_vec())) + }) + .collect::>(); + for batch in opening_batches { + for symbol in symbol_list(batch.claim_operands) { + let claim = opening_claims + .iter() + .find(|claim| claim.symbol == symbol) + .ok_or_else(|| missing_claim(batch.symbol, symbol))?; + let point = store.point_or(claim.point_source, &missing_value)?.to_vec(); + if seen.iter().any(|(kind, oracle, seen_point)| { + *kind == claim.claim_kind && *oracle == claim.oracle && seen_point == &point + }) { + continue; + } + let value = store.scalar_or(claim.eval_source, &missing_value)?; + append_labeled_scalar(transcript, "opening_claim", &value); + seen.push((claim.claim_kind, claim.oracle, point)); + } + } + Ok(()) +} + +pub fn lt_polynomial_eval(x: &[Fr], y: &[Fr]) -> Fr { + let mut lt_eval = Fr::from_u64(0); + let mut eq_term = Fr::from_u64(1); + for (x_i, y_i) in x.iter().zip(y.iter()) { + lt_eval += (Fr::from_u64(1) - *x_i) * *y_i * eq_term; + eq_term *= Fr::from_u64(1) - *x_i - *y_i + *x_i * *y_i + *x_i * *y_i; + } + lt_eval +} + +pub fn pow_field(base: F, mut exponent: usize) -> F { + let mut result = F::one(); + let mut power = base; + while exponent != 0 { + if exponent & 1 == 1 { + result *= power; + } + power = power.square(); + exponent >>= 1; + } + result +} + +pub fn reverse_slice(values: &[Fr]) -> Vec { + values.iter().rev().copied().collect() +} diff --git a/crates/jolt-verifier/src/stages/mod.rs b/crates/jolt-verifier/src/stages/mod.rs new file mode 100644 index 0000000000..14f741edaa --- /dev/null +++ b/crates/jolt-verifier/src/stages/mod.rs @@ -0,0 +1,19 @@ +pub mod common; +#[rustfmt::skip] +pub mod commitment; +#[rustfmt::skip] +pub mod stage1_outer; +#[rustfmt::skip] +pub mod stage2; +#[rustfmt::skip] +pub mod stage3; +#[rustfmt::skip] +pub mod stage4; +#[rustfmt::skip] +pub mod stage5; +#[rustfmt::skip] +pub mod stage6; +#[rustfmt::skip] +pub mod stage7; +#[rustfmt::skip] +pub mod stage8; diff --git a/crates/jolt-verifier/src/stages/stage1_outer.rs b/crates/jolt-verifier/src/stages/stage1_outer.rs new file mode 100644 index 0000000000..6b0ba58aa9 --- /dev/null +++ b/crates/jolt-verifier/src/stages/stage1_outer.rs @@ -0,0 +1,406 @@ +#![allow(dead_code)] + +use super::common::append_labeled_scalar; +use jolt_field::{Field, Fr}; +use jolt_sumcheck::{CompressedLabeledRoundPoly, LabeledRoundPoly, SumcheckClaim, SumcheckError, SumcheckVerifier}; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage1Transcript = Blake2bTranscript; + +pub type Stage1Params = super::common::StageParams; +pub type Stage1NamedEval = super::common::StageNamedEval; +pub type Stage1SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage1ChallengeVector = super::common::StageChallengeVector; +pub type Stage1ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage1Proof = super::common::StageProof; +pub type Stage1VerifierProgramPlan = super::common::VerifierProgramPlanMinimal; + +pub use super::common::{ + OpeningBatchPlan as Stage1OpeningBatchPlan, OpeningClaimPlan as Stage1OpeningClaimPlan, + SumcheckBatchPlan as Stage1SumcheckBatchPlan, SumcheckEvalPlan as Stage1SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage1SumcheckInstanceResultPlan, + TranscriptSqueezePlan as Stage1TranscriptSqueezePlan, + SumcheckClaimPlan as Stage1SumcheckClaimPlan, + SumcheckDriverPlan as Stage1SumcheckDriverPlan, +}; + +#[derive(Debug)] +pub enum VerifyStage1Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { driver: &'static str, claim: &'static str }, + MissingDependency { driver: &'static str, dependency: &'static str }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +pub const STAGE1_PARAMS: Stage1Params = Stage1Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE1_TRANSCRIPT_SQUEEZES: &[Stage1TranscriptSqueezePlan] = &[ + Stage1TranscriptSqueezePlan { symbol: "stage1.tau", label: "outer_tau", kind: "challenge_vector", count: 20 }, +]; + +pub const STAGE1_SUMCHECK_CLAIMS: &[Stage1SumcheckClaimPlan] = &[ + Stage1SumcheckClaimPlan { symbol: "stage1.uniskip.input", stage: "stage1", domain: "jolt.stage1_uniskip_domain", num_rounds: 1, degree: 27, claim: "stage1.zero", kernel: None, relation: Some("jolt.stage1.outer.uniskip"), claim_value: "stage1.zero", input_openings: "" }, + Stage1SumcheckClaimPlan { symbol: "stage1.outer_remaining.input", stage: "stage1", domain: "jolt.trace_domain", num_rounds: 19, degree: 3, claim: "stage1.uniskip.eval", kernel: None, relation: Some("jolt.stage1.outer.remaining"), claim_value: "stage1.uniskip.eval", input_openings: "stage1.uniskip.opening" }, +]; +pub const STAGE1_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 1, +]; + +pub const STAGE1_SUMCHECK_BATCH_1_ROUND_SCHEDULE: &[usize] = &[ + 19, +]; + +pub const STAGE1_SUMCHECK_BATCHES: &[Stage1SumcheckBatchPlan] = &[ + Stage1SumcheckBatchPlan { symbol: "stage1.uniskip.batch", stage: "stage1", proof_slot: "stage1.uni_skip_first_round", policy: "single_instance", count: 1, ordered_claims: "stage1.uniskip.input", claim_operands: "stage1.uniskip.input", claim_label: "uniskip_claim", round_label: "uniskip_poly", round_schedule: STAGE1_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, + Stage1SumcheckBatchPlan { symbol: "stage1.outer_remaining.batch", stage: "stage1", proof_slot: "stage1.sumcheck", policy: "jolt_core_front_loaded", count: 1, ordered_claims: "stage1.outer_remaining.input", claim_operands: "stage1.outer_remaining.input", claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE1_SUMCHECK_BATCH_1_ROUND_SCHEDULE }, +]; +pub const STAGE1_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 1, +]; + +pub const STAGE1_SUMCHECK_DRIVER_1_ROUND_SCHEDULE: &[usize] = &[ + 19, +]; + +pub const STAGE1_SUMCHECK_DRIVERS: &[Stage1SumcheckDriverPlan] = &[ + Stage1SumcheckDriverPlan { symbol: "stage1.uniskip.sumcheck", stage: "stage1", proof_slot: "stage1.uni_skip_first_round", kernel: None, relation: Some("jolt.stage1.outer.uniskip"), batch: "stage1.uniskip.batch", policy: "univariate_skip", round_schedule: STAGE1_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "uniskip_claim", round_label: "uniskip_poly", num_rounds: 1, degree: 27 }, + Stage1SumcheckDriverPlan { symbol: "stage1.outer_remaining.sumcheck", stage: "stage1", proof_slot: "stage1.sumcheck", kernel: None, relation: Some("jolt.stage1.outer.remaining"), batch: "stage1.outer_remaining.batch", policy: "jolt_core_front_loaded", round_schedule: STAGE1_SUMCHECK_DRIVER_1_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 19, degree: 3 }, +]; +pub const STAGE1_SUMCHECK_INSTANCE_RESULTS: &[Stage1SumcheckInstanceResultPlan] = &[ + Stage1SumcheckInstanceResultPlan { symbol: "stage1.uniskip.instance", source: "stage1.uniskip.sumcheck", claim: "stage1.uniskip.input", relation: "jolt.stage1.outer.uniskip", index: 0, point_arity: 1, num_rounds: 1, round_offset: 0, point_order: "as_is", degree: 27 }, + Stage1SumcheckInstanceResultPlan { symbol: "stage1.outer_remaining.instance", source: "stage1.outer_remaining.sumcheck", claim: "stage1.outer_remaining.input", relation: "jolt.stage1.outer.remaining", index: 0, point_arity: 18, num_rounds: 19, round_offset: 1, point_order: "reverse", degree: 3 }, +]; + +pub const STAGE1_SUMCHECK_EVALS: &[Stage1SumcheckEvalPlan] = &[ + Stage1SumcheckEvalPlan { symbol: "stage1.uniskip.eval", source: "stage1.uniskip.sumcheck", name: "stage1.uniskip.eval", index: 0, oracle: "UnivariateSkip" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.LeftInstructionInput", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.LeftInstructionInput", index: 0, oracle: "LeftInstructionInput" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RightInstructionInput", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RightInstructionInput", index: 1, oracle: "RightInstructionInput" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.Product", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.Product", index: 2, oracle: "Product" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.ShouldBranch", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.ShouldBranch", index: 3, oracle: "ShouldBranch" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.PC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.PC", index: 4, oracle: "PC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.UnexpandedPC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.UnexpandedPC", index: 5, oracle: "UnexpandedPC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.Imm", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.Imm", index: 6, oracle: "Imm" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RamAddress", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RamAddress", index: 7, oracle: "RamAddress" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.Rs1Value", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.Rs1Value", index: 8, oracle: "Rs1Value" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.Rs2Value", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.Rs2Value", index: 9, oracle: "Rs2Value" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RdWriteValue", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RdWriteValue", index: 10, oracle: "RdWriteValue" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RamReadValue", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RamReadValue", index: 11, oracle: "RamReadValue" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RamWriteValue", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RamWriteValue", index: 12, oracle: "RamWriteValue" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.LeftLookupOperand", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.LeftLookupOperand", index: 13, oracle: "LeftLookupOperand" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.RightLookupOperand", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.RightLookupOperand", index: 14, oracle: "RightLookupOperand" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.NextUnexpandedPC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.NextUnexpandedPC", index: 15, oracle: "NextUnexpandedPC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.NextPC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.NextPC", index: 16, oracle: "NextPC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.NextIsVirtual", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.NextIsVirtual", index: 17, oracle: "NextIsVirtual" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.NextIsFirstInSequence", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.NextIsFirstInSequence", index: 18, oracle: "NextIsFirstInSequence" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.LookupOutput", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.LookupOutput", index: 19, oracle: "LookupOutput" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.ShouldJump", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.ShouldJump", index: 20, oracle: "ShouldJump" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagAddOperands", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagAddOperands", index: 21, oracle: "OpFlagAddOperands" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagSubtractOperands", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagSubtractOperands", index: 22, oracle: "OpFlagSubtractOperands" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagMultiplyOperands", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagMultiplyOperands", index: 23, oracle: "OpFlagMultiplyOperands" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagLoad", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagLoad", index: 24, oracle: "OpFlagLoad" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagStore", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagStore", index: 25, oracle: "OpFlagStore" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagJump", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagJump", index: 26, oracle: "OpFlagJump" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagWriteLookupOutputToRD", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagWriteLookupOutputToRD", index: 27, oracle: "OpFlagWriteLookupOutputToRD" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagVirtualInstruction", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagVirtualInstruction", index: 28, oracle: "OpFlagVirtualInstruction" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagAssert", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagAssert", index: 29, oracle: "OpFlagAssert" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagDoNotUpdateUnexpandedPC", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagDoNotUpdateUnexpandedPC", index: 30, oracle: "OpFlagDoNotUpdateUnexpandedPC" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagAdvice", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagAdvice", index: 31, oracle: "OpFlagAdvice" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagIsCompressed", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagIsCompressed", index: 32, oracle: "OpFlagIsCompressed" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagIsFirstInSequence", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagIsFirstInSequence", index: 33, oracle: "OpFlagIsFirstInSequence" }, + Stage1SumcheckEvalPlan { symbol: "stage1.outer_remaining.eval.OpFlagIsLastInSequence", source: "stage1.outer_remaining.sumcheck", name: "stage1.outer_remaining.eval.OpFlagIsLastInSequence", index: 34, oracle: "OpFlagIsLastInSequence" }, +]; + +pub const STAGE1_OPENING_CLAIMS: &[Stage1OpeningClaimPlan] = &[ + Stage1OpeningClaimPlan { symbol: "stage1.uniskip.opening", oracle: "UnivariateSkip", domain: "jolt.stage1_uniskip_domain", point_arity: 1, claim_kind: "virtual", point_source: "stage1.uniskip.instance", eval_source: "stage1.uniskip.eval" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.LeftInstructionInput" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RightInstructionInput" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.Product", oracle: "Product", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.Product" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.ShouldBranch", oracle: "ShouldBranch", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.ShouldBranch" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.PC", oracle: "PC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.PC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.UnexpandedPC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.Imm", oracle: "Imm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.Imm" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RamAddress", oracle: "RamAddress", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RamAddress" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.Rs1Value" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.Rs2Value" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RdWriteValue", oracle: "RdWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RdWriteValue" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RamReadValue", oracle: "RamReadValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RamReadValue" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RamWriteValue", oracle: "RamWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RamWriteValue" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.LeftLookupOperand", oracle: "LeftLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.LeftLookupOperand" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.RightLookupOperand", oracle: "RightLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.RightLookupOperand" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.NextUnexpandedPC", oracle: "NextUnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.NextUnexpandedPC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.NextPC", oracle: "NextPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.NextPC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.NextIsVirtual", oracle: "NextIsVirtual", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.NextIsVirtual" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.NextIsFirstInSequence", oracle: "NextIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.NextIsFirstInSequence" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.LookupOutput" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.ShouldJump", oracle: "ShouldJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.ShouldJump" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagAddOperands", oracle: "OpFlagAddOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagAddOperands" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagSubtractOperands", oracle: "OpFlagSubtractOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagSubtractOperands" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagMultiplyOperands", oracle: "OpFlagMultiplyOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagMultiplyOperands" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagLoad", oracle: "OpFlagLoad", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagLoad" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagStore", oracle: "OpFlagStore", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagStore" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagJump", oracle: "OpFlagJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagJump" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagWriteLookupOutputToRD", oracle: "OpFlagWriteLookupOutputToRD", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagWriteLookupOutputToRD" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagVirtualInstruction" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagAssert", oracle: "OpFlagAssert", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagAssert" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagDoNotUpdateUnexpandedPC", oracle: "OpFlagDoNotUpdateUnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagDoNotUpdateUnexpandedPC" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagAdvice", oracle: "OpFlagAdvice", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagAdvice" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagIsCompressed", oracle: "OpFlagIsCompressed", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagIsCompressed" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagIsFirstInSequence", oracle: "OpFlagIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagIsFirstInSequence" }, + Stage1OpeningClaimPlan { symbol: "stage1.outer_remaining.opening.OpFlagIsLastInSequence", oracle: "OpFlagIsLastInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage1.outer_remaining.instance", eval_source: "stage1.outer_remaining.eval.OpFlagIsLastInSequence" }, +]; + +pub const STAGE1_OPENING_BATCHES: &[Stage1OpeningBatchPlan] = &[ + Stage1OpeningBatchPlan { symbol: "stage1.outer_remaining.openings", stage: "stage1", proof_slot: "stage1.virtual_openings", policy: "jolt_r1cs_input_order", count: 35, ordered_claims: "stage1.outer_remaining.opening.LeftInstructionInput|stage1.outer_remaining.opening.RightInstructionInput|stage1.outer_remaining.opening.Product|stage1.outer_remaining.opening.ShouldBranch|stage1.outer_remaining.opening.PC|stage1.outer_remaining.opening.UnexpandedPC|stage1.outer_remaining.opening.Imm|stage1.outer_remaining.opening.RamAddress|stage1.outer_remaining.opening.Rs1Value|stage1.outer_remaining.opening.Rs2Value|stage1.outer_remaining.opening.RdWriteValue|stage1.outer_remaining.opening.RamReadValue|stage1.outer_remaining.opening.RamWriteValue|stage1.outer_remaining.opening.LeftLookupOperand|stage1.outer_remaining.opening.RightLookupOperand|stage1.outer_remaining.opening.NextUnexpandedPC|stage1.outer_remaining.opening.NextPC|stage1.outer_remaining.opening.NextIsVirtual|stage1.outer_remaining.opening.NextIsFirstInSequence|stage1.outer_remaining.opening.LookupOutput|stage1.outer_remaining.opening.ShouldJump|stage1.outer_remaining.opening.OpFlagAddOperands|stage1.outer_remaining.opening.OpFlagSubtractOperands|stage1.outer_remaining.opening.OpFlagMultiplyOperands|stage1.outer_remaining.opening.OpFlagLoad|stage1.outer_remaining.opening.OpFlagStore|stage1.outer_remaining.opening.OpFlagJump|stage1.outer_remaining.opening.OpFlagWriteLookupOutputToRD|stage1.outer_remaining.opening.OpFlagVirtualInstruction|stage1.outer_remaining.opening.OpFlagAssert|stage1.outer_remaining.opening.OpFlagDoNotUpdateUnexpandedPC|stage1.outer_remaining.opening.OpFlagAdvice|stage1.outer_remaining.opening.OpFlagIsCompressed|stage1.outer_remaining.opening.OpFlagIsFirstInSequence|stage1.outer_remaining.opening.OpFlagIsLastInSequence", claim_operands: "stage1.outer_remaining.opening.LeftInstructionInput|stage1.outer_remaining.opening.RightInstructionInput|stage1.outer_remaining.opening.Product|stage1.outer_remaining.opening.ShouldBranch|stage1.outer_remaining.opening.PC|stage1.outer_remaining.opening.UnexpandedPC|stage1.outer_remaining.opening.Imm|stage1.outer_remaining.opening.RamAddress|stage1.outer_remaining.opening.Rs1Value|stage1.outer_remaining.opening.Rs2Value|stage1.outer_remaining.opening.RdWriteValue|stage1.outer_remaining.opening.RamReadValue|stage1.outer_remaining.opening.RamWriteValue|stage1.outer_remaining.opening.LeftLookupOperand|stage1.outer_remaining.opening.RightLookupOperand|stage1.outer_remaining.opening.NextUnexpandedPC|stage1.outer_remaining.opening.NextPC|stage1.outer_remaining.opening.NextIsVirtual|stage1.outer_remaining.opening.NextIsFirstInSequence|stage1.outer_remaining.opening.LookupOutput|stage1.outer_remaining.opening.ShouldJump|stage1.outer_remaining.opening.OpFlagAddOperands|stage1.outer_remaining.opening.OpFlagSubtractOperands|stage1.outer_remaining.opening.OpFlagMultiplyOperands|stage1.outer_remaining.opening.OpFlagLoad|stage1.outer_remaining.opening.OpFlagStore|stage1.outer_remaining.opening.OpFlagJump|stage1.outer_remaining.opening.OpFlagWriteLookupOutputToRD|stage1.outer_remaining.opening.OpFlagVirtualInstruction|stage1.outer_remaining.opening.OpFlagAssert|stage1.outer_remaining.opening.OpFlagDoNotUpdateUnexpandedPC|stage1.outer_remaining.opening.OpFlagAdvice|stage1.outer_remaining.opening.OpFlagIsCompressed|stage1.outer_remaining.opening.OpFlagIsFirstInSequence|stage1.outer_remaining.opening.OpFlagIsLastInSequence" }, +]; +pub const STAGE1_PROGRAM: Stage1VerifierProgramPlan = Stage1VerifierProgramPlan { + params: STAGE1_PARAMS, + transcript_squeezes: STAGE1_TRANSCRIPT_SQUEEZES, + claims: STAGE1_SUMCHECK_CLAIMS, + batches: STAGE1_SUMCHECK_BATCHES, + drivers: STAGE1_SUMCHECK_DRIVERS, + instance_results: STAGE1_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE1_SUMCHECK_EVALS, + opening_claims: STAGE1_OPENING_CLAIMS, + opening_batches: STAGE1_OPENING_BATCHES, +}; + +pub fn verify_stage1_outer( + proof: &Stage1Proof, + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + verify_stage1_outer_with_program(&STAGE1_PROGRAM, proof, transcript) +} + +pub fn verify_stage1_outer_with_program( + program: &'static Stage1VerifierProgramPlan, + proof: &Stage1Proof, + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage1Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut artifacts = Stage1ExecutionArtifacts::default(); + for squeeze in program.transcript_squeezes { + let values = transcript.challenge_vector(squeeze.count); + artifacts.challenge_vectors.push(Stage1ChallengeVector { + symbol: squeeze.symbol, + values, + }); + } + for (index, driver) in program.drivers.iter().enumerate() { + let proof = proof.sumchecks.get(index).ok_or(VerifyStage1Error::MissingProof { + driver: driver.symbol, + })?; + let output = verify_stage1_driver(program, driver, proof, &artifacts.sumchecks, transcript)?; + artifacts.sumchecks.push(output); + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage1_outer_verifier_program() -> &'static Stage1VerifierProgramPlan { + &STAGE1_PROGRAM +} + +fn verify_stage1_driver( + program: &'static Stage1VerifierProgramPlan, + driver: &'static Stage1SumcheckDriverPlan, + proof: &Stage1SumcheckOutput, + completed: &[Stage1SumcheckOutput], + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + if proof.driver != driver.symbol { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "driver symbol mismatch", + }); + } + let relation = driver.relation.unwrap_or(""); + match relation { + "jolt.stage1.outer.uniskip" => verify_outer_uniskip(program, driver, proof, transcript), + "jolt.stage1.outer.remaining" => { + verify_outer_remaining(program, driver, proof, completed, transcript) + } + relation => Err(VerifyStage1Error::UnsupportedRelation { relation }), + } +} + +fn verify_outer_uniskip( + program: &'static Stage1VerifierProgramPlan, + driver: &'static Stage1SumcheckDriverPlan, + proof: &Stage1SumcheckOutput, + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + let claim = SumcheckClaim::new(driver.num_rounds, driver.degree, Fr::from_u64(0)); + let round_proofs = proof + .proof + .round_polynomials + .iter() + .map(|poly| LabeledRoundPoly::new(poly, driver.round_label.as_bytes())) + .collect::>(); + let output = SumcheckVerifier::verify(&claim, &round_proofs, transcript) + .map_err(|error| VerifyStage1Error::Sumcheck { + driver: driver.symbol, + error, + })?; + let eval = output.value; + let point = output.point; + if !proof.point.is_empty() && proof.point != point { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "uniskip point mismatch", + }); + } + validate_eval_shape(program, driver, &proof.evals, Some(eval))?; + append_labeled_scalar(transcript, "opening_claim", &eval); + Ok(Stage1SumcheckOutput { + driver: driver.symbol, + point, + evals: driver_evals(program, driver.symbol, eval), + proof: proof.proof.clone(), + }) +} + +fn verify_outer_remaining( + program: &'static Stage1VerifierProgramPlan, + driver: &'static Stage1SumcheckDriverPlan, + proof: &Stage1SumcheckOutput, + completed: &[Stage1SumcheckOutput], + transcript: &mut T, +) -> Result, VerifyStage1Error> +where + T: Transcript, +{ + let input_claim = completed + .iter() + .find(|output| output.driver == "stage1.uniskip.sumcheck") + .and_then(|output| output.evals.first()) + .map(|eval| eval.value) + .ok_or(VerifyStage1Error::MissingDependency { + driver: driver.symbol, + dependency: "stage1.uniskip.eval", + })?; + append_labeled_scalar(transcript, driver.claim_label, &input_claim); + let batching_coeff = transcript.challenge(); + let claim = SumcheckClaim::new( + driver.num_rounds, + driver.degree, + input_claim * batching_coeff, + ); + let round_proofs = proof + .proof + .round_polynomials + .iter() + .map(|poly| CompressedLabeledRoundPoly::new(poly, driver.round_label.as_bytes())) + .collect::>(); + let output = SumcheckVerifier::verify(&claim, &round_proofs, transcript) + .map_err(|error| VerifyStage1Error::Sumcheck { + driver: driver.symbol, + error, + })?; + let point = output.point; + if !proof.point.is_empty() && proof.point != point { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "outer remaining point mismatch", + }); + } + validate_eval_shape(program, driver, &proof.evals, None)?; + append_opening_claims(transcript, &proof.evals); + Ok(Stage1SumcheckOutput { + driver: driver.symbol, + point, + evals: proof.evals.clone(), + proof: proof.proof.clone(), + }) +} + +fn driver_evals( + program: &'static Stage1VerifierProgramPlan, + driver: &'static str, + value: Fr, +) -> Vec> { + program + .evals + .iter() + .filter(|eval| eval.source == driver) + .map(|eval| Stage1NamedEval { + name: eval.name, + oracle: eval.oracle, + value, + }) + .collect() +} + +fn validate_eval_shape( + program: &'static Stage1VerifierProgramPlan, + driver: &'static Stage1SumcheckDriverPlan, + actual: &[Stage1NamedEval], + expected_value: Option, +) -> Result<(), VerifyStage1Error> { + let expected = program + .evals + .iter() + .filter(|eval| eval.source == driver.symbol) + .collect::>(); + if actual.len() != expected.len() { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "eval count mismatch", + }); + } + for (actual, expected) in actual.iter().zip(expected) { + if actual.name != expected.name { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "eval name mismatch", + }); + } + if actual.oracle != expected.oracle { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "eval oracle mismatch", + }); + } + if expected_value.is_some_and(|value| actual.value != value) { + return Err(VerifyStage1Error::InvalidProof { + driver: driver.symbol, + reason: "eval value mismatch", + }); + } + } + Ok(()) +} + +fn append_opening_claims(transcript: &mut T, evals: &[Stage1NamedEval]) +where + T: Transcript, +{ + for eval in evals { + append_labeled_scalar(transcript, "opening_claim", &eval.value); + } +} diff --git a/crates/jolt-verifier/src/stages/stage2.rs b/crates/jolt-verifier/src/stages/stage2.rs new file mode 100644 index 0000000000..4956bc9680 --- /dev/null +++ b/crates/jolt-verifier/src/stages/stage2.rs @@ -0,0 +1,1050 @@ +#![allow(dead_code)] + +use super::common::{append_labeled_scalar, batch_claims, eval_by_name, find_batch, find_plan, pow_field, require_operand_count, reverse_slice, single_operand}; +use jolt_field::{Field, Fr}; +use jolt_poly::lagrange::{lagrange_evals, lagrange_kernel_eval}; +use jolt_poly::{EqPolynomial, UnivariatePoly}; +use jolt_sumcheck::{CompressedLabeledRoundPoly, SumcheckClaim, SumcheckError, SumcheckVerifier}; +use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript}; + +pub type DefaultStage2Transcript = Blake2bTranscript; + +pub type Stage2NamedEval = super::common::StageNamedEval; +pub type Stage2SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage2ChallengeVector = super::common::StageChallengeVector; +pub type Stage2ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage2Proof = super::common::StageProof; +pub type Stage2OpeningInputValue = super::common::StageOpeningInputValue; +pub type Stage2VerifierProgramPlan = super::common::StageVerifierProgramPlanNoEqualities; + +pub use super::common::{ + FieldConstantPlan as Stage2FieldConstantPlan, FieldExprPlan as Stage2FieldExprPlan, + OpeningBatchPlan as Stage2OpeningBatchPlan, OpeningClaimPlan as Stage2OpeningClaimPlan, + OpeningInputPlan as Stage2OpeningInputPlan, PointConcatPlan as Stage2PointConcatPlan, + PointSlicePlan as Stage2PointSlicePlan, ProgramStepPlan as Stage2ProgramStepPlan, + StageParams as Stage2Params, SumcheckBatchPlan as Stage2SumcheckBatchPlan, + SumcheckEvalPlan as Stage2SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage2SumcheckInstanceResultPlan, + TranscriptSqueezePlan as Stage2TranscriptSqueezePlan, + SumcheckClaimPlan as Stage2SumcheckClaimPlan, + SumcheckDriverPlan as Stage2SumcheckDriverPlan, +}; + +#[derive(Clone, Copy, Debug)] +pub struct Stage2RamAccess { + pub remapped_address: Option, + pub read_value: u64, + pub write_value: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage2RamOutputLayout { + pub io_start: usize, + pub io_end: usize, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage2RamData<'a> { + pub log_k: usize, + pub start_address: u64, + pub initial_ram: &'a [u64], + pub final_ram: &'a [u64], + pub accesses: &'a [Stage2RamAccess], + pub output_layout: Option, +} + +#[derive(Clone, Debug, Default)] +struct Stage2ValueStore(super::common::ValueStore); + +#[derive(Debug)] +pub enum VerifyStage2Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + MissingRam { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage2Error); + +pub const STAGE2_PARAMS: Stage2Params = Stage2Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE2_PROGRAM_STEPS: &[Stage2ProgramStepPlan] = &[ + Stage2ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage2.product_virtual.tau_high" }, + Stage2ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage2.product_virtual.uniskip.sumcheck" }, + Stage2ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage2.ram_read_write.gamma" }, + Stage2ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage2.instruction_lookup.gamma" }, + Stage2ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage2.ram_output.r_address" }, + Stage2ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage2.sumcheck" }, +]; + +pub const STAGE2_TRANSCRIPT_SQUEEZES: &[Stage2TranscriptSqueezePlan] = &[ + Stage2TranscriptSqueezePlan { symbol: "stage2.product_virtual.tau_high", label: "product_virtual_tau_high", kind: "challenge_scalar", count: 1 }, + Stage2TranscriptSqueezePlan { symbol: "stage2.ram_read_write.gamma", label: "ram_read_write_gamma", kind: "challenge_scalar", count: 1 }, + Stage2TranscriptSqueezePlan { symbol: "stage2.instruction_lookup.gamma", label: "instruction_lookup_gamma", kind: "challenge_scalar", count: 1 }, + Stage2TranscriptSqueezePlan { symbol: "stage2.ram_output.r_address", label: "ram_output_r_address", kind: "challenge_vector", count: 14 }, +]; + +pub const STAGE2_OPENING_INPUTS: &[Stage2OpeningInputPlan] = &[ + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.Product", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.Product", oracle: "Product", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.ShouldBranch", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.ShouldBranch", oracle: "ShouldBranch", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.ShouldJump", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.ShouldJump", oracle: "ShouldJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RamReadValue", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RamReadValue", oracle: "RamReadValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RamWriteValue", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RamWriteValue", oracle: "RamWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.LookupOutput", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.LeftLookupOperand", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.LeftLookupOperand", oracle: "LeftLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RightLookupOperand", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RightLookupOperand", oracle: "RightLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.LeftInstructionInput", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RightInstructionInput", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage2OpeningInputPlan { symbol: "stage2.input.stage1.RamAddress", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RamAddress", oracle: "RamAddress", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, +]; + +pub const STAGE2_FIELD_CONSTANTS: &[Stage2FieldConstantPlan] = &[ + Stage2FieldConstantPlan { symbol: "stage2.ram_output.zero", field: "bn254_fr", value: 0 }, +]; + +pub const STAGE2_FIELD_EXPRS: &[Stage2FieldExprPlan] = &[ + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.weight.Product", kind: "op", formula: "poly.lagrange_basis_eval:-1:3:0", operands: "stage2.product_virtual.tau_high" }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.weight.ShouldBranch", kind: "op", formula: "poly.lagrange_basis_eval:-1:3:1", operands: "stage2.product_virtual.tau_high" }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.weight.ShouldJump", kind: "op", formula: "poly.lagrange_basis_eval:-1:3:2", operands: "stage2.product_virtual.tau_high" }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.term.Product", kind: "op", formula: "field.mul", operands: "stage2.product_virtual.uniskip.weight.Product|stage2.input.stage1.Product" }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.term.ShouldBranch", kind: "op", formula: "field.mul", operands: "stage2.product_virtual.uniskip.weight.ShouldBranch|stage2.input.stage1.ShouldBranch" }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.term.ShouldJump", kind: "op", formula: "field.mul", operands: "stage2.product_virtual.uniskip.weight.ShouldJump|stage2.input.stage1.ShouldJump" }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.partial.ProductShouldBranch", kind: "op", formula: "field.add", operands: "stage2.product_virtual.uniskip.term.Product|stage2.product_virtual.uniskip.term.ShouldBranch" }, + Stage2FieldExprPlan { symbol: "stage2.product_virtual.uniskip.claim_expr", kind: "op", formula: "field.add", operands: "stage2.product_virtual.uniskip.partial.ProductShouldBranch|stage2.product_virtual.uniskip.term.ShouldJump" }, + Stage2FieldExprPlan { symbol: "stage2.ram_read_write.term.RamWriteValue", kind: "op", formula: "field.mul", operands: "stage2.ram_read_write.gamma|stage2.input.stage1.RamWriteValue" }, + Stage2FieldExprPlan { symbol: "stage2.ram_read_write.claim_expr", kind: "op", formula: "field.add", operands: "stage2.input.stage1.RamReadValue|stage2.ram_read_write.term.RamWriteValue" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.gamma2", kind: "op", formula: "field.mul", operands: "stage2.instruction_lookup.gamma|stage2.instruction_lookup.gamma" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.gamma3", kind: "op", formula: "field.mul", operands: "stage2.instruction_lookup.gamma2|stage2.instruction_lookup.gamma" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.gamma4", kind: "op", formula: "field.mul", operands: "stage2.instruction_lookup.gamma2|stage2.instruction_lookup.gamma2" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.term.LeftLookupOperand", kind: "op", formula: "field.mul", operands: "stage2.instruction_lookup.gamma|stage2.input.stage1.LeftLookupOperand" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.term.RightLookupOperand", kind: "op", formula: "field.mul", operands: "stage2.instruction_lookup.gamma2|stage2.input.stage1.RightLookupOperand" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.term.LeftInstructionInput", kind: "op", formula: "field.mul", operands: "stage2.instruction_lookup.gamma3|stage2.input.stage1.LeftInstructionInput" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.term.RightInstructionInput", kind: "op", formula: "field.mul", operands: "stage2.instruction_lookup.gamma4|stage2.input.stage1.RightInstructionInput" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.partial.LookupOutputLeftOperand", kind: "op", formula: "field.add", operands: "stage2.input.stage1.LookupOutput|stage2.instruction_lookup.term.LeftLookupOperand" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.partial.RightOperand", kind: "op", formula: "field.add", operands: "stage2.instruction_lookup.partial.LookupOutputLeftOperand|stage2.instruction_lookup.term.RightLookupOperand" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.partial.LeftInstructionInput", kind: "op", formula: "field.add", operands: "stage2.instruction_lookup.partial.RightOperand|stage2.instruction_lookup.term.LeftInstructionInput" }, + Stage2FieldExprPlan { symbol: "stage2.instruction_lookup.claim_reduction.claim_expr", kind: "op", formula: "field.add", operands: "stage2.instruction_lookup.partial.LeftInstructionInput|stage2.instruction_lookup.term.RightInstructionInput" }, +]; +pub const STAGE2_SUMCHECK_CLAIMS: &[Stage2SumcheckClaimPlan] = &[ + Stage2SumcheckClaimPlan { symbol: "stage2.product_virtual.uniskip.input", stage: "stage2", domain: "jolt.stage2_uniskip_domain", num_rounds: 1, degree: 6, claim: "stage2.product_virtual.weighted_stage1_outputs", kernel: None, relation: Some("jolt.stage2.product_virtual.uniskip"), claim_value: "stage2.product_virtual.uniskip.claim_expr", input_openings: "stage2.input.stage1.Product|stage2.input.stage1.ShouldBranch|stage2.input.stage1.ShouldJump" }, + Stage2SumcheckClaimPlan { symbol: "stage2.ram_read_write.input", stage: "stage2", domain: "jolt.stage2_ram_rw_domain", num_rounds: 32, degree: 3, claim: "stage2.ram_read_write.weighted_values", kernel: None, relation: Some("jolt.stage2.ram.read_write"), claim_value: "stage2.ram_read_write.claim_expr", input_openings: "stage2.input.stage1.RamReadValue|stage2.input.stage1.RamWriteValue" }, + Stage2SumcheckClaimPlan { symbol: "stage2.product_virtual.remainder.input", stage: "stage2", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage2.product_virtual.uniskip.opening", kernel: None, relation: Some("jolt.stage2.product_virtual.remainder"), claim_value: "stage2.product_virtual.uniskip.eval.UnivariateSkip", input_openings: "stage2.product_virtual.uniskip.opening.UnivariateSkip" }, + Stage2SumcheckClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.input", stage: "stage2", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage2.instruction_lookup.weighted_operands", kernel: None, relation: Some("jolt.stage2.instruction_lookup.claim_reduction"), claim_value: "stage2.instruction_lookup.claim_reduction.claim_expr", input_openings: "stage2.input.stage1.LookupOutput|stage2.input.stage1.LeftLookupOperand|stage2.input.stage1.RightLookupOperand|stage2.input.stage1.LeftInstructionInput|stage2.input.stage1.RightInstructionInput" }, + Stage2SumcheckClaimPlan { symbol: "stage2.ram_raf.input", stage: "stage2", domain: "jolt.ram_address_domain", num_rounds: 14, degree: 2, claim: "stage2.ram_raf.ram_address", kernel: None, relation: Some("jolt.stage2.ram.raf_evaluation"), claim_value: "stage2.input.stage1.RamAddress", input_openings: "stage2.input.stage1.RamAddress" }, + Stage2SumcheckClaimPlan { symbol: "stage2.ram_output.input", stage: "stage2", domain: "jolt.ram_address_domain", num_rounds: 14, degree: 3, claim: "zero", kernel: None, relation: Some("jolt.stage2.ram.output_check"), claim_value: "stage2.ram_output.zero", input_openings: "" }, +]; +pub const STAGE2_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 1, +]; + +pub const STAGE2_SUMCHECK_BATCH_1_ROUND_SCHEDULE: &[usize] = &[ + 18, + 14, +]; + +pub const STAGE2_SUMCHECK_BATCHES: &[Stage2SumcheckBatchPlan] = &[ + Stage2SumcheckBatchPlan { symbol: "stage2.product_virtual.uniskip.batch", stage: "stage2", proof_slot: "stage2.product_virtual.uni_skip_first_round", policy: "single_instance", count: 1, ordered_claims: "stage2.product_virtual.uniskip.input", claim_operands: "stage2.product_virtual.uniskip.input", claim_label: "uniskip_claim", round_label: "uniskip_poly", round_schedule: STAGE2_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, + Stage2SumcheckBatchPlan { symbol: "stage2.batch", stage: "stage2", proof_slot: "stage2.sumcheck", policy: "jolt_core_stage2_aligned", count: 5, ordered_claims: "stage2.ram_read_write.input|stage2.product_virtual.remainder.input|stage2.instruction_lookup.claim_reduction.input|stage2.ram_raf.input|stage2.ram_output.input", claim_operands: "stage2.ram_read_write.input|stage2.product_virtual.remainder.input|stage2.instruction_lookup.claim_reduction.input|stage2.ram_raf.input|stage2.ram_output.input", claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE2_SUMCHECK_BATCH_1_ROUND_SCHEDULE }, +]; +pub const STAGE2_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 1, +]; + +pub const STAGE2_SUMCHECK_DRIVER_1_ROUND_SCHEDULE: &[usize] = &[ + 18, + 14, +]; + +pub const STAGE2_SUMCHECK_DRIVERS: &[Stage2SumcheckDriverPlan] = &[ + Stage2SumcheckDriverPlan { symbol: "stage2.product_virtual.uniskip.sumcheck", stage: "stage2", proof_slot: "stage2.product_virtual.uni_skip_first_round", kernel: None, relation: Some("jolt.stage2.product_virtual.uniskip"), batch: "stage2.product_virtual.uniskip.batch", policy: "univariate_skip", round_schedule: STAGE2_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "uniskip_claim", round_label: "uniskip_poly", num_rounds: 1, degree: 6 }, + Stage2SumcheckDriverPlan { symbol: "stage2.sumcheck", stage: "stage2", proof_slot: "stage2.sumcheck", kernel: None, relation: Some("jolt.stage2.batched"), batch: "stage2.batch", policy: "jolt_core_stage2_aligned", round_schedule: STAGE2_SUMCHECK_DRIVER_1_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 32, degree: 3 }, +]; +pub const STAGE2_SUMCHECK_INSTANCE_RESULTS: &[Stage2SumcheckInstanceResultPlan] = &[ + Stage2SumcheckInstanceResultPlan { symbol: "stage2.product_virtual.uniskip.instance", source: "stage2.product_virtual.uniskip.sumcheck", claim: "stage2.product_virtual.uniskip.input", relation: "jolt.stage2.product_virtual.uniskip", index: 0, point_arity: 1, num_rounds: 1, round_offset: 0, point_order: "as_is", degree: 6 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.ram_read_write.instance", source: "stage2.sumcheck", claim: "stage2.ram_read_write.input", relation: "jolt.stage2.ram.read_write", index: 0, point_arity: 32, num_rounds: 32, round_offset: 0, point_order: "reverse", degree: 3 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.product_virtual.remainder.instance", source: "stage2.sumcheck", claim: "stage2.product_virtual.remainder.input", relation: "jolt.stage2.product_virtual.remainder", index: 1, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 3 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.instruction_lookup.claim_reduction.instance", source: "stage2.sumcheck", claim: "stage2.instruction_lookup.claim_reduction.input", relation: "jolt.stage2.instruction_lookup.claim_reduction", index: 2, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 2 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.ram_raf.instance", source: "stage2.sumcheck", claim: "stage2.ram_raf.input", relation: "jolt.stage2.ram.raf_evaluation", index: 3, point_arity: 14, num_rounds: 14, round_offset: 18, point_order: "reverse", degree: 2 }, + Stage2SumcheckInstanceResultPlan { symbol: "stage2.ram_output.instance", source: "stage2.sumcheck", claim: "stage2.ram_output.input", relation: "jolt.stage2.ram.output_check", index: 4, point_arity: 14, num_rounds: 14, round_offset: 18, point_order: "reverse", degree: 3 }, +]; + +pub const STAGE2_SUMCHECK_EVALS: &[Stage2SumcheckEvalPlan] = &[ + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.uniskip.eval.UnivariateSkip", source: "stage2.product_virtual.uniskip.sumcheck", name: "stage2.product_virtual.uniskip.eval.UnivariateSkip", index: 0, oracle: "UnivariateSkip" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_read_write.eval.RamVal", source: "stage2.sumcheck", name: "stage2.ram_read_write.eval.RamVal", index: 0, oracle: "RamVal" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_read_write.eval.RamRa", source: "stage2.sumcheck", name: "stage2.ram_read_write.eval.RamRa", index: 1, oracle: "RamRa" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_read_write.eval.RamInc", source: "stage2.sumcheck", name: "stage2.ram_read_write.eval.RamInc", index: 2, oracle: "RamInc" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.LeftInstructionInput", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.LeftInstructionInput", index: 0, oracle: "LeftInstructionInput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.RightInstructionInput", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.RightInstructionInput", index: 1, oracle: "RightInstructionInput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.OpFlagJump", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.OpFlagJump", index: 2, oracle: "OpFlagJump" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.OpFlagWriteLookupOutputToRD", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.OpFlagWriteLookupOutputToRD", index: 3, oracle: "OpFlagWriteLookupOutputToRD" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.LookupOutput", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.LookupOutput", index: 4, oracle: "LookupOutput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.InstructionFlagBranch", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.InstructionFlagBranch", index: 5, oracle: "InstructionFlagBranch" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.NextIsNoop", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.NextIsNoop", index: 6, oracle: "NextIsNoop" }, + Stage2SumcheckEvalPlan { symbol: "stage2.product_virtual.remainder.eval.OpFlagVirtualInstruction", source: "stage2.sumcheck", name: "stage2.product_virtual.remainder.eval.OpFlagVirtualInstruction", index: 7, oracle: "OpFlagVirtualInstruction" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.LookupOutput", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.LookupOutput", index: 0, oracle: "LookupOutput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand", index: 1, oracle: "LeftLookupOperand" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand", index: 2, oracle: "RightLookupOperand" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput", index: 3, oracle: "LeftInstructionInput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput", source: "stage2.sumcheck", name: "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput", index: 4, oracle: "RightInstructionInput" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_raf.eval.RamRa", source: "stage2.sumcheck", name: "stage2.ram_raf.eval.RamRa", index: 0, oracle: "RamRa" }, + Stage2SumcheckEvalPlan { symbol: "stage2.ram_output.eval.RamValFinal", source: "stage2.sumcheck", name: "stage2.ram_output.eval.RamValFinal", index: 0, oracle: "RamValFinal" }, +]; + +pub const STAGE2_POINT_SLICES: &[Stage2PointSlicePlan] = &[ + Stage2PointSlicePlan { symbol: "stage2.ram_read_write.point.RamInc", source: "stage2.ram_read_write.instance", offset: 14, length: 18, input: "stage2.ram_read_write.instance" }, +]; + +pub const STAGE2_POINT_CONCATS: &[Stage2PointConcatPlan] = &[ + Stage2PointConcatPlan { symbol: "stage2.ram_raf.point.RamRa", layout: "address_then_cycle", arity: 32, inputs: "stage2.ram_raf.instance|stage2.input.stage1.RamAddress" }, +]; +pub const STAGE2_OPENING_CLAIMS: &[Stage2OpeningClaimPlan] = &[ + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.uniskip.opening.UnivariateSkip", oracle: "UnivariateSkip", domain: "jolt.stage2_uniskip_domain", point_arity: 1, claim_kind: "virtual", point_source: "stage2.product_virtual.uniskip.instance", eval_source: "stage2.product_virtual.uniskip.eval.UnivariateSkip" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_read_write.opening.RamVal", oracle: "RamVal", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage2.ram_read_write.instance", eval_source: "stage2.ram_read_write.eval.RamVal" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_read_write.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage2.ram_read_write.instance", eval_source: "stage2.ram_read_write.eval.RamRa" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_read_write.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage2.ram_read_write.point.RamInc", eval_source: "stage2.ram_read_write.eval.RamInc" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.LeftInstructionInput" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.RightInstructionInput" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.OpFlagJump", oracle: "OpFlagJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.OpFlagJump" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.OpFlagWriteLookupOutputToRD", oracle: "OpFlagWriteLookupOutputToRD", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.OpFlagWriteLookupOutputToRD" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.LookupOutput" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.InstructionFlagBranch", oracle: "InstructionFlagBranch", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.InstructionFlagBranch" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.NextIsNoop", oracle: "NextIsNoop", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.NextIsNoop" }, + Stage2OpeningClaimPlan { symbol: "stage2.product_virtual.remainder.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.product_virtual.remainder.instance", eval_source: "stage2.product_virtual.remainder.eval.OpFlagVirtualInstruction" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.LookupOutput" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand", oracle: "LeftLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand", oracle: "RightLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput" }, + Stage2OpeningClaimPlan { symbol: "stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage2.instruction_lookup.claim_reduction.instance", eval_source: "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_raf.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage2.ram_raf.point.RamRa", eval_source: "stage2.ram_raf.eval.RamRa" }, + Stage2OpeningClaimPlan { symbol: "stage2.ram_output.opening.RamValFinal", oracle: "RamValFinal", domain: "jolt.ram_address_domain", point_arity: 14, claim_kind: "virtual", point_source: "stage2.ram_output.instance", eval_source: "stage2.ram_output.eval.RamValFinal" }, +]; + +pub const STAGE2_OPENING_BATCHES: &[Stage2OpeningBatchPlan] = &[ + Stage2OpeningBatchPlan { symbol: "stage2.openings", stage: "stage2", proof_slot: "stage2.openings", policy: "jolt_stage2_output_order", count: 18, ordered_claims: "stage2.ram_read_write.opening.RamVal|stage2.ram_read_write.opening.RamRa|stage2.ram_read_write.opening.RamInc|stage2.product_virtual.remainder.opening.LeftInstructionInput|stage2.product_virtual.remainder.opening.RightInstructionInput|stage2.product_virtual.remainder.opening.OpFlagJump|stage2.product_virtual.remainder.opening.OpFlagWriteLookupOutputToRD|stage2.product_virtual.remainder.opening.LookupOutput|stage2.product_virtual.remainder.opening.InstructionFlagBranch|stage2.product_virtual.remainder.opening.NextIsNoop|stage2.product_virtual.remainder.opening.OpFlagVirtualInstruction|stage2.instruction_lookup.claim_reduction.opening.LookupOutput|stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand|stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand|stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput|stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput|stage2.ram_raf.opening.RamRa|stage2.ram_output.opening.RamValFinal", claim_operands: "stage2.ram_read_write.opening.RamVal|stage2.ram_read_write.opening.RamRa|stage2.ram_read_write.opening.RamInc|stage2.product_virtual.remainder.opening.LeftInstructionInput|stage2.product_virtual.remainder.opening.RightInstructionInput|stage2.product_virtual.remainder.opening.OpFlagJump|stage2.product_virtual.remainder.opening.OpFlagWriteLookupOutputToRD|stage2.product_virtual.remainder.opening.LookupOutput|stage2.product_virtual.remainder.opening.InstructionFlagBranch|stage2.product_virtual.remainder.opening.NextIsNoop|stage2.product_virtual.remainder.opening.OpFlagVirtualInstruction|stage2.instruction_lookup.claim_reduction.opening.LookupOutput|stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand|stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand|stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput|stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput|stage2.ram_raf.opening.RamRa|stage2.ram_output.opening.RamValFinal" }, +]; +pub const STAGE2_PROGRAM: Stage2VerifierProgramPlan = Stage2VerifierProgramPlan { + params: STAGE2_PARAMS, + steps: STAGE2_PROGRAM_STEPS, + transcript_squeezes: STAGE2_TRANSCRIPT_SQUEEZES, + opening_inputs: STAGE2_OPENING_INPUTS, + field_constants: STAGE2_FIELD_CONSTANTS, + field_exprs: STAGE2_FIELD_EXPRS, + claims: STAGE2_SUMCHECK_CLAIMS, + batches: STAGE2_SUMCHECK_BATCHES, + drivers: STAGE2_SUMCHECK_DRIVERS, + instance_results: STAGE2_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE2_SUMCHECK_EVALS, + point_slices: STAGE2_POINT_SLICES, + point_concats: STAGE2_POINT_CONCATS, + opening_claims: STAGE2_OPENING_CLAIMS, + opening_batches: STAGE2_OPENING_BATCHES, +}; + +const PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START: i64 = -1; +const PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE: usize = 3; + +pub fn verify_stage2( + proof: &Stage2Proof, + opening_inputs: &[Stage2OpeningInputValue], + ram: Option<&Stage2RamData<'_>>, + transcript: &mut T, +) -> Result, VerifyStage2Error> +where + T: Transcript, +{ + verify_stage2_with_program(&STAGE2_PROGRAM, proof, opening_inputs, ram, transcript) +} + +pub fn verify_stage2_with_program( + program: &'static Stage2VerifierProgramPlan, + proof: &Stage2Proof, + opening_inputs: &[Stage2OpeningInputValue], + ram: Option<&Stage2RamData<'_>>, + transcript: &mut T, +) -> Result, VerifyStage2Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage2Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = Stage2ValueStore::with_opening_inputs(program, opening_inputs)?; + store.seed_constants(program); + let mut artifacts = Stage2ExecutionArtifacts::default(); + if program.steps.is_empty() { + for squeeze in program.transcript_squeezes { + verify_stage2_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + for driver in program.drivers { + verify_stage2_driver(program, driver, proof, ram, &mut store, transcript, &mut artifacts)?; + } + } else { + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage2Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage2_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "sumcheck_driver" => { + let driver = find_plan(program.drivers, step.symbol).ok_or(VerifyStage2Error::MissingProof { + driver: step.symbol, + })?; + verify_stage2_driver(program, driver, proof, ram, &mut store, transcript, &mut artifacts)?; + } + _ => { + return Err(VerifyStage2Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage2 program step", + }); + } + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage2_verifier_program() -> &'static Stage2VerifierProgramPlan { + &STAGE2_PROGRAM +} + +fn verify_stage2_squeeze( + program: &'static Stage2VerifierProgramPlan, + squeeze: &'static Stage2TranscriptSqueezePlan, + store: &mut Stage2ValueStore, + transcript: &mut T, + artifacts: &mut Stage2ExecutionArtifacts, +) -> Result<(), VerifyStage2Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(program, squeeze, &values)?; + artifacts.challenge_vectors.push(Stage2ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn verify_stage2_driver( + program: &'static Stage2VerifierProgramPlan, + driver: &'static Stage2SumcheckDriverPlan, + proof: &Stage2Proof, + ram: Option<&Stage2RamData<'_>>, + store: &mut Stage2ValueStore, + transcript: &mut T, + artifacts: &mut Stage2ExecutionArtifacts, +) -> Result<(), VerifyStage2Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage2Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage2.product_virtual.uniskip" => { + verify_product_virtual_uniskip(program, driver, proof, store, transcript)? + } + "jolt.stage2.batched" => verify_batched_stage2(program, driver, proof, ram, store, transcript)?, + relation => return Err(VerifyStage2Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_product_virtual_uniskip( + program: &'static Stage2VerifierProgramPlan, + driver: &'static Stage2SumcheckDriverPlan, + proof: &Stage2SumcheckOutput, + store: &mut Stage2ValueStore, + transcript: &mut T, +) -> Result, VerifyStage2Error> +where + T: Transcript, +{ + validate_driver_symbol(driver, proof)?; + let [poly] = proof.proof.round_polynomials.as_slice() else { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "unexpected product uniskip round count", + }); + }; + if polynomial_degree(poly) > driver.degree { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "product uniskip polynomial exceeds degree bound", + }); + } + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claim = batch_claims(program.claims, batch)? + .into_iter() + .next() + .ok_or(VerifyStage2Error::MissingClaim { + batch: batch.symbol, + claim: "stage2.product_virtual.uniskip.input", + })?; + let input_claim = store.claim_value(program, claim)?; + if !product_uniskip_sum_matches(poly, input_claim) { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "product uniskip input claim mismatch", + }); + } + append_univariate_poly(transcript, driver.round_label, poly); + let r0 = transcript.challenge(); + if !proof.point.is_empty() && proof.point != [r0] { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "product uniskip point mismatch", + }); + } + let eval = poly.evaluate(r0); + append_labeled_scalar(transcript, "opening_claim", &eval); + let output = Stage2SumcheckOutput { + driver: driver.symbol, + point: vec![r0], + evals: driver_evals(program, driver.symbol, eval), + proof: proof.proof.clone(), + }; + verify_named_evals(driver.symbol, &output.evals, &proof.evals)?; + store.observe_sumcheck_output(program, &output)?; + Ok(output) +} + +fn verify_batched_stage2( + program: &'static Stage2VerifierProgramPlan, + driver: &'static Stage2SumcheckDriverPlan, + proof: &Stage2SumcheckOutput, + ram: Option<&Stage2RamData<'_>>, + store: &mut Stage2ValueStore, + transcript: &mut T, +) -> Result, VerifyStage2Error> +where + T: Transcript, +{ + validate_driver_symbol(driver, proof)?; + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let input_claims = store.batch_claim_values(program, batch)?; + for claim in &input_claims { + append_labeled_scalar(transcript, batch.claim_label, claim); + } + let batching_coeffs = transcript.challenge_vector(claims.len()); + let claimed_sum = input_claims + .iter() + .zip(claims.iter()) + .zip(&batching_coeffs) + .map(|((claim, plan), coefficient)| { + claim.mul_pow_2(driver.num_rounds - plan.num_rounds) * *coefficient + }) + .sum::(); + let claim = SumcheckClaim::new(driver.num_rounds, driver.degree, claimed_sum); + let round_proofs = proof + .proof + .round_polynomials + .iter() + .map(|poly| CompressedLabeledRoundPoly::new(poly, driver.round_label.as_bytes())) + .collect::>(); + let output = SumcheckVerifier::verify(&claim, &round_proofs, transcript) + .map_err(|error| VerifyStage2Error::Sumcheck { + driver: driver.symbol, + error, + })?; + if !proof.point.is_empty() && proof.point != output.point { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "batched point mismatch", + }); + } + let expected = + expected_batched_output_claim(program, driver, &*store, &proof.evals, &output.point, &batching_coeffs, ram)?; + if output.value != expected { + return Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "batched output claim mismatch", + }); + } + let verified = Stage2SumcheckOutput { + driver: driver.symbol, + point: output.point, + evals: proof.evals.clone(), + proof: proof.proof.clone(), + }; + store.observe_sumcheck_output(program, &verified)?; + super::common::append_opening_claims( + program.opening_inputs, + program.opening_claims, + program.opening_batches, + &mut store.0, + transcript, + &verified.evals, + |batch, claim| VerifyStage2Error::MissingClaim { batch, claim }, + |symbol| VerifyStage2Error::MissingValue { symbol }, + )?; + Ok(verified) +} + +impl Stage2ValueStore { + fn with_opening_inputs( + program: &'static Stage2VerifierProgramPlan, + inputs: &[Stage2OpeningInputValue], + ) -> Result { + Ok(Self(super::common::ValueStore::with_opening_inputs( + inputs, + program.opening_inputs, + )?)) + } + + fn seed_constants(&mut self, program: &'static Stage2VerifierProgramPlan) { + self.0.seed_constants(program.field_constants); + } + + fn observe_challenge_vector( + &mut self, + program: &'static Stage2VerifierProgramPlan, + plan: &'static Stage2TranscriptSqueezePlan, + values: &[F], + ) -> Result<(), VerifyStage2Error> { + self.0.observe_challenge_vector(plan, values, |input, expected, actual| { + VerifyStage2Error::InvalidInputLength { input, expected, actual } + })?; + self.evaluate_available_points(program)?; + self.evaluate_available_field_exprs(program)?; + Ok(()) + } + + fn observe_sumcheck_output( + &mut self, + program: &'static Stage2VerifierProgramPlan, + output: &Stage2SumcheckOutput, + ) -> Result<(), VerifyStage2Error> { + self.0.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + _ => { + return Err(VerifyStage2Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage2Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage2Error::MissingValue { symbol }, + )?; + self.evaluate_available_points(program)?; + self.evaluate_available_field_exprs(program)?; + Ok(()) + } + + fn claim_value( + &mut self, + program: &'static Stage2VerifierProgramPlan, + claim: &Stage2SumcheckClaimPlan, + ) -> Result { + self.evaluate_available_field_exprs(program)?; + self.scalar(claim.claim_value) + } + + fn batch_claim_values( + &mut self, + program: &'static Stage2VerifierProgramPlan, + batch: &Stage2SumcheckBatchPlan, + ) -> Result, VerifyStage2Error> { + super::common::symbol_list(batch.claim_operands) + .map(|symbol| { + let claim = find_plan(program.claims, symbol).ok_or(VerifyStage2Error::MissingClaim { + batch: batch.symbol, + claim: symbol, + })?; + self.claim_value(program, claim) + }) + .collect() + } + + fn evaluate_available_points( + &mut self, + program: &'static Stage2VerifierProgramPlan, + ) -> Result<(), VerifyStage2Error> { + self.0.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage2Error::InvalidInputLength { + input, + expected, + actual, + }, + ) + } + + fn evaluate_available_field_exprs( + &mut self, + program: &'static Stage2VerifierProgramPlan, + ) -> Result<(), VerifyStage2Error> { + self.0 + .evaluate_available_field_exprs(program.field_exprs, evaluate_stage2_field_expr) + } + + fn scalar(&self, symbol: &'static str) -> Result { + self.0 + .scalar_or(symbol, |symbol| VerifyStage2Error::MissingValue { symbol }) + } + + fn point(&self, symbol: &'static str) -> Result<&[F], VerifyStage2Error> { + self.0 + .point_or(symbol, |symbol| VerifyStage2Error::MissingValue { symbol }) + } + + fn try_point(&self, symbol: &str) -> Option<&[F]> { + self.0.try_point(symbol) + } +} + +fn evaluate_stage2_field_expr( + expr: &Stage2FieldExprPlan, + operands: &[F], +) -> Result { + match expr.formula { + "opening_eval" => Ok(single_operand(expr.symbol, operands)?), + "jolt_stage2_product_virtual_uniskip_input" => { + require_operand_count(expr.symbol, 4, operands.len())?; + let weights = lagrange_evals( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + operands[0], + ); + Ok(weights[0] * operands[1] + weights[1] * operands[2] + weights[2] * operands[3]) + } + "jolt_stage2_ram_read_write_input" => { + require_operand_count(expr.symbol, 3, operands.len())?; + Ok(operands[1] + operands[0] * operands[2]) + } + "jolt_stage2_instruction_lookup_input" => { + require_operand_count(expr.symbol, 6, operands.len())?; + let gamma = operands[0]; + let gamma2 = gamma.square(); + let gamma3 = gamma2 * gamma; + let gamma4 = gamma2.square(); + Ok(operands[1] + + gamma * operands[2] + + gamma2 * operands[3] + + gamma3 * operands[4] + + gamma4 * operands[5]) + } + "field.add" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] + operands[1]) + } + "field.sub" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] - operands[1]) + } + "field.mul" => { + require_operand_count(expr.symbol, 2, operands.len())?; + Ok(operands[0] * operands[1]) + } + "field.neg" => { + require_operand_count(expr.symbol, 1, operands.len())?; + Ok(-operands[0]) + } + formula => { + if let Some(exponent) = formula.strip_prefix("field.pow:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let exponent = exponent.parse::().map_err(|_| { + VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + return Ok(pow_field(operands[0], exponent)); + } + if let Some(spec) = formula.strip_prefix("poly.lagrange_basis_eval:") { + require_operand_count(expr.symbol, 1, operands.len())?; + let parts = spec.split(':').collect::>(); + if parts.len() != 3 { + return Err(VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }); + } + let domain_start = parts[0].parse::().map_err(|_| { + VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + let domain_size = parts[1].parse::().map_err(|_| { + VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + let index = parts[2].parse::().map_err(|_| { + VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + } + })?; + let weights = lagrange_evals(domain_start, domain_size, operands[0]); + return weights + .get(index) + .copied() + .ok_or(VerifyStage2Error::InvalidInputLength { + input: expr.symbol, + expected: index + 1, + actual: weights.len(), + }); + } + Err(VerifyStage2Error::UnsupportedFieldExpr { + symbol: expr.symbol, + formula, + }) + } + } +} + +fn expected_batched_output_claim( + program: &'static Stage2VerifierProgramPlan, + driver: &'static Stage2SumcheckDriverPlan, + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage2Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage2Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let value = match instance.relation { + "jolt.stage2.ram.read_write" => expected_ram_read_write(store, evals, local_point)?, + "jolt.stage2.product_virtual.remainder" => { + expected_product_remainder(store, evals, local_point)? + } + "jolt.stage2.instruction_lookup.claim_reduction" => { + expected_instruction_lookup(store, evals, local_point)? + } + "jolt.stage2.ram.raf_evaluation" => expected_ram_raf(evals, local_point, ram)?, + "jolt.stage2.ram.output_check" => expected_ram_output(store, evals, local_point, ram)?, + relation => return Err(VerifyStage2Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_ram_read_write( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[Fr], +) -> Result { + let r_cycle_stage1 = store.point("stage2.input.stage1.RamReadValue")?; + let log_t = r_cycle_stage1.len(); + let r_cycle = reverse_slice(&local_point[..log_t]); + let eq_eval = EqPolynomial::::mle(r_cycle_stage1, &r_cycle); + let gamma = store.scalar("stage2.ram_read_write.gamma")?; + let val = eval_by_name(evals, "stage2.ram_read_write.eval.RamVal")?; + let ra = eval_by_name(evals, "stage2.ram_read_write.eval.RamRa")?; + let inc = eval_by_name(evals, "stage2.ram_read_write.eval.RamInc")?; + Ok(eq_eval * ra * (val + gamma * (val + inc))) +} + +fn expected_product_remainder( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[Fr], +) -> Result { + let tau_low = store.point("stage2.input.stage1.Product")?; + let tau_high = store.scalar("stage2.product_virtual.tau_high")?; + let r0 = *store + .point("stage2.product_virtual.uniskip.sumcheck")? + .first() + .ok_or(VerifyStage2Error::MissingValue { + symbol: "stage2.product_virtual.uniskip.sumcheck", + })?; + let r_tail = reverse_slice(local_point); + let low = EqPolynomial::::mle(tau_low, &r_tail); + let high = lagrange_kernel_eval( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + tau_high, + r0, + ); + let weights = lagrange_evals( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START, + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE, + r0, + ); + let left = weights[0] + * eval_by_name(evals, "stage2.product_virtual.remainder.eval.LeftInstructionInput")? + + weights[1] * eval_by_name(evals, "stage2.product_virtual.remainder.eval.LookupOutput")? + + weights[2] * eval_by_name(evals, "stage2.product_virtual.remainder.eval.OpFlagJump")?; + let right = weights[0] + * eval_by_name(evals, "stage2.product_virtual.remainder.eval.RightInstructionInput")? + + weights[1] + * eval_by_name(evals, "stage2.product_virtual.remainder.eval.InstructionFlagBranch")? + + weights[2] + * (Fr::from_u64(1) + - eval_by_name(evals, "stage2.product_virtual.remainder.eval.NextIsNoop")?); + Ok(high * low * left * right) +} + +fn expected_instruction_lookup( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[Fr], +) -> Result { + let opening_point = reverse_slice(local_point); + let r_spartan = store.point("stage2.input.stage1.LookupOutput")?; + let eq_eval = EqPolynomial::::mle(&opening_point, r_spartan); + let gamma = store.scalar("stage2.instruction_lookup.gamma")?; + let gamma2 = gamma.square(); + let gamma3 = gamma2 * gamma; + let gamma4 = gamma2.square(); + let weighted = eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LookupOutput", + )? + gamma + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LeftLookupOperand", + )? + + gamma2 + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.RightLookupOperand", + )? + + gamma3 + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.LeftInstructionInput", + )? + + gamma4 + * eval_by_name( + evals, + "stage2.instruction_lookup.claim_reduction.eval.RightInstructionInput", + )?; + Ok(eq_eval * weighted) +} + +fn expected_ram_raf( + evals: &[Stage2NamedEval], + local_point: &[Fr], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let ram = ram.ok_or(VerifyStage2Error::MissingRam { + relation: "jolt.stage2.ram.raf_evaluation", + })?; + let address = reverse_slice(local_point); + let unmap = unmap_eval(ram.log_k, ram.start_address, &address); + Ok(unmap * eval_by_name(evals, "stage2.ram_raf.eval.RamRa")?) +} + +fn expected_ram_output( + store: &Stage2ValueStore, + evals: &[Stage2NamedEval], + local_point: &[Fr], + ram: Option<&Stage2RamData<'_>>, +) -> Result { + let ram = ram.ok_or(VerifyStage2Error::MissingRam { + relation: "jolt.stage2.ram.output_check", + })?; + let layout = ram.output_layout.ok_or(VerifyStage2Error::MissingRam { + relation: "jolt.stage2.ram.output_check.layout", + })?; + let r_address = store.point("stage2.ram_output.r_address")?; + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle(r_address, &opening_point); + let io_mask = range_mask_eval(layout.io_start, layout.io_end, &opening_point); + let val_io = sparse_final_ram_eval( + ram.final_ram, + layout.io_start, + layout.io_end, + &opening_point, + ); + let val_final = eval_by_name(evals, "stage2.ram_output.eval.RamValFinal")?; + Ok(eq_eval * io_mask * (val_final - val_io)) +} + +fn driver_evals( + program: &'static Stage2VerifierProgramPlan, + driver: &'static str, + value: Fr, +) -> Vec> { + program + .evals + .iter() + .filter(|eval| eval.source == driver) + .map(|eval| Stage2NamedEval { + name: eval.name, + oracle: eval.oracle, + value, + }) + .collect() +} + +fn verify_named_evals( + driver: &'static str, + expected: &[Stage2NamedEval], + actual: &[Stage2NamedEval], +) -> Result<(), VerifyStage2Error> { + if expected.len() != actual.len() { + return Err(VerifyStage2Error::InvalidProof { + driver, + reason: "eval count mismatch", + }); + } + for (expected, actual) in expected.iter().zip(actual) { + if expected.name != actual.name || expected.oracle != actual.oracle || expected.value != actual.value { + return Err(VerifyStage2Error::InvalidProof { + driver, + reason: "eval mismatch", + }); + } + } + Ok(()) +} + +fn validate_driver_symbol( + driver: &'static Stage2SumcheckDriverPlan, + proof: &Stage2SumcheckOutput, +) -> Result<(), VerifyStage2Error> { + if proof.driver == driver.symbol { + Ok(()) + } else { + Err(VerifyStage2Error::InvalidProof { + driver: driver.symbol, + reason: "driver symbol mismatch", + }) + } +} + +fn append_univariate_poly(transcript: &mut T, label: &'static str, poly: &UnivariatePoly) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + label.as_bytes(), + poly.coefficients().len() as u64, + )); + for coefficient in poly.coefficients() { + transcript.append(coefficient); + } +} + +fn product_uniskip_sum_matches(poly: &UnivariatePoly, claim: Fr) -> bool { + (0..PRODUCT_VIRTUAL_UNISKIP_DOMAIN_SIZE) + .map(|index| { + poly.evaluate(Fr::from_i64( + PRODUCT_VIRTUAL_UNISKIP_DOMAIN_START + index as i64, + )) + }) + .sum::() + == claim +} + +fn polynomial_degree(poly: &UnivariatePoly) -> usize { + poly.coefficients() + .iter() + .rposition(|coefficient| *coefficient != Fr::from_u64(0)) + .unwrap_or(0) +} + +fn unmap_eval(log_k: usize, start_address: u64, point: &[Fr]) -> Fr { + point + .iter() + .enumerate() + .fold(Fr::from_u64(start_address), |acc, (index, value)| { + acc + value.mul_pow_2(log_k - 1 - index).mul_u64(8) + }) +} + +fn range_mask_eval(start: usize, end: usize, point: &[Fr]) -> Fr { + eq_prefix_sum(end, point) - eq_prefix_sum(start, point) +} + +fn sparse_final_ram_eval(values: &[u64], start: usize, end: usize, point: &[Fr]) -> Fr { + values[start..end] + .iter() + .enumerate() + .filter(|(_, value)| **value != 0) + .map(|(offset, value)| Fr::from_u64(*value) * eq_eval_at_index(start + offset, point)) + .sum() +} + +fn eq_prefix_sum(end: usize, point: &[Fr]) -> Fr { + let domain_len = 1usize << point.len(); + if end >= domain_len { + return Fr::from_u64(1); + } + let mut sum = Fr::from_u64(0); + let mut prefix = Fr::from_u64(1); + for (bit, r) in point.iter().enumerate() { + let mask = 1usize << (point.len() - 1 - bit); + if end & mask == 0 { + prefix *= Fr::from_u64(1) - *r; + } else { + sum += prefix * (Fr::from_u64(1) - *r); + prefix *= *r; + } + } + sum +} + +fn eq_eval_at_index(index: usize, point: &[Fr]) -> Fr { + point.iter().enumerate().fold(Fr::from_u64(1), |acc, (bit, r)| { + let mask = 1usize << (point.len() - 1 - bit); + if index & mask == 0 { + acc * (Fr::from_u64(1) - *r) + } else { + acc * *r + } + }) +} diff --git a/crates/jolt-verifier/src/stages/stage3.rs b/crates/jolt-verifier/src/stages/stage3.rs new file mode 100644 index 0000000000..11942c327a --- /dev/null +++ b/crates/jolt-verifier/src/stages/stage3.rs @@ -0,0 +1,527 @@ +#![allow(dead_code)] + +use super::common::{batch_claims, eval_by_name, find_batch, find_plan, reverse_slice}; +use jolt_field::{Field, Fr}; +use jolt_poly::{EqPlusOnePolynomial, EqPolynomial}; +use jolt_sumcheck::SumcheckError; +use jolt_transcript::{Blake2bTranscript, Transcript}; + +pub type DefaultStage3Transcript = Blake2bTranscript; + +pub type Stage3NamedEval = super::common::StageNamedEval; +pub type Stage3SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage3ChallengeVector = super::common::StageChallengeVector; +pub type Stage3ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage3Proof = super::common::StageProof; +pub type Stage3OpeningInputValue = super::common::StageOpeningInputValue; +pub type Stage3VerifierProgramPlan = super::common::StageVerifierProgramPlan; + +pub use super::common::{ + FieldConstantPlan as Stage3FieldConstantPlan, FieldExprPlan as Stage3FieldExprPlan, + OpeningBatchPlan as Stage3OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage3OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage3OpeningClaimPlan, OpeningInputPlan as Stage3OpeningInputPlan, + PointConcatPlan as Stage3PointConcatPlan, PointSlicePlan as Stage3PointSlicePlan, + ProgramStepPlan as Stage3ProgramStepPlan, StageParams as Stage3Params, + SumcheckBatchPlan as Stage3SumcheckBatchPlan, SumcheckEvalPlan as Stage3SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage3SumcheckInstanceResultPlan, + TranscriptSqueezePlan as Stage3TranscriptSqueezePlan, + SumcheckClaimPlan as Stage3SumcheckClaimPlan, + SumcheckDriverPlan as Stage3SumcheckDriverPlan, +}; + +#[derive(Debug)] +pub enum VerifyStage3Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage3Error); + +pub const STAGE3_PARAMS: Stage3Params = Stage3Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE3_PROGRAM_STEPS: &[Stage3ProgramStepPlan] = &[ + Stage3ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage3.spartan_shift.gamma" }, + Stage3ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage3.instruction_input.gamma" }, + Stage3ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage3.registers.gamma" }, + Stage3ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage3.sumcheck" }, +]; + +pub const STAGE3_TRANSCRIPT_SQUEEZES: &[Stage3TranscriptSqueezePlan] = &[ + Stage3TranscriptSqueezePlan { symbol: "stage3.spartan_shift.gamma", label: "spartan_shift_gamma", kind: "challenge_scalar", count: 1 }, + Stage3TranscriptSqueezePlan { symbol: "stage3.instruction_input.gamma", label: "instruction_input_gamma", kind: "challenge_scalar", count: 1 }, + Stage3TranscriptSqueezePlan { symbol: "stage3.registers.gamma", label: "registers_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE3_OPENING_INPUTS: &[Stage3OpeningInputPlan] = &[ + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.NextUnexpandedPC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.NextUnexpandedPC", oracle: "NextUnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.NextPC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.NextPC", oracle: "NextPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.NextIsVirtual", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.NextIsVirtual", oracle: "NextIsVirtual", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.NextIsFirstInSequence", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.NextIsFirstInSequence", oracle: "NextIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.product_virtual.NextIsNoop", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.NextIsNoop", oracle: "NextIsNoop", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.product_virtual.LeftInstructionInput", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.product_virtual.RightInstructionInput", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.instruction_lookup.LeftInstructionInput", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.LeftInstructionInput", oracle: "LeftInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage2.instruction_lookup.RightInstructionInput", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.RightInstructionInput", oracle: "RightInstructionInput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.RdWriteValue", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.RdWriteValue", oracle: "RdWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.Rs1Value", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage3OpeningInputPlan { symbol: "stage3.input.stage1.Rs2Value", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, +]; + +pub const STAGE3_FIELD_CONSTANTS: &[Stage3FieldConstantPlan] = &[ + Stage3FieldConstantPlan { symbol: "stage3.field.one", field: "bn254_fr", value: 1 }, +]; + +pub const STAGE3_FIELD_EXPRS: &[Stage3FieldExprPlan] = &[ + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.gamma2", kind: "op", formula: "field.pow:2", operands: "stage3.spartan_shift.gamma" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.gamma3", kind: "op", formula: "field.mul", operands: "stage3.spartan_shift.gamma2|stage3.spartan_shift.gamma" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.gamma4", kind: "op", formula: "field.mul", operands: "stage3.spartan_shift.gamma2|stage3.spartan_shift.gamma2" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.term.NextPC", kind: "op", formula: "field.mul", operands: "stage3.spartan_shift.gamma|stage3.input.stage1.NextPC" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.term.NextIsVirtual", kind: "op", formula: "field.mul", operands: "stage3.spartan_shift.gamma2|stage3.input.stage1.NextIsVirtual" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.term.NextIsFirstInSequence", kind: "op", formula: "field.mul", operands: "stage3.spartan_shift.gamma3|stage3.input.stage1.NextIsFirstInSequence" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.one_minus.NextIsNoop", kind: "op", formula: "field.sub", operands: "stage3.field.one|stage3.input.stage2.product_virtual.NextIsNoop" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.term.NextIsNoop", kind: "op", formula: "field.mul", operands: "stage3.spartan_shift.gamma4|stage3.spartan_shift.one_minus.NextIsNoop" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.partial.NextUnexpandedPCNextPC", kind: "op", formula: "field.add", operands: "stage3.input.stage1.NextUnexpandedPC|stage3.spartan_shift.term.NextPC" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.partial.NextIsVirtual", kind: "op", formula: "field.add", operands: "stage3.spartan_shift.partial.NextUnexpandedPCNextPC|stage3.spartan_shift.term.NextIsVirtual" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.partial.NextIsFirstInSequence", kind: "op", formula: "field.add", operands: "stage3.spartan_shift.partial.NextIsVirtual|stage3.spartan_shift.term.NextIsFirstInSequence" }, + Stage3FieldExprPlan { symbol: "stage3.spartan_shift.claim_expr", kind: "op", formula: "field.add", operands: "stage3.spartan_shift.partial.NextIsFirstInSequence|stage3.spartan_shift.term.NextIsNoop" }, + Stage3FieldExprPlan { symbol: "stage3.instruction_input.term.LeftInstructionInput", kind: "op", formula: "field.mul", operands: "stage3.instruction_input.gamma|stage3.input.stage2.product_virtual.LeftInstructionInput" }, + Stage3FieldExprPlan { symbol: "stage3.instruction_input.claim_expr", kind: "op", formula: "field.add", operands: "stage3.input.stage2.product_virtual.RightInstructionInput|stage3.instruction_input.term.LeftInstructionInput" }, + Stage3FieldExprPlan { symbol: "stage3.registers.gamma2", kind: "op", formula: "field.pow:2", operands: "stage3.registers.gamma" }, + Stage3FieldExprPlan { symbol: "stage3.registers.term.Rs1Value", kind: "op", formula: "field.mul", operands: "stage3.registers.gamma|stage3.input.stage1.Rs1Value" }, + Stage3FieldExprPlan { symbol: "stage3.registers.term.Rs2Value", kind: "op", formula: "field.mul", operands: "stage3.registers.gamma2|stage3.input.stage1.Rs2Value" }, + Stage3FieldExprPlan { symbol: "stage3.registers.partial.RdWriteValueRs1Value", kind: "op", formula: "field.add", operands: "stage3.input.stage1.RdWriteValue|stage3.registers.term.Rs1Value" }, + Stage3FieldExprPlan { symbol: "stage3.registers.claim_expr", kind: "op", formula: "field.add", operands: "stage3.registers.partial.RdWriteValueRs1Value|stage3.registers.term.Rs2Value" }, +]; +pub const STAGE3_SUMCHECK_CLAIMS: &[Stage3SumcheckClaimPlan] = &[ + Stage3SumcheckClaimPlan { symbol: "stage3.spartan_shift.input", stage: "stage3", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage3.spartan_shift.weighted_next_values", kernel: None, relation: Some("jolt.stage3.spartan_shift"), claim_value: "stage3.spartan_shift.claim_expr", input_openings: "stage3.input.stage1.NextUnexpandedPC|stage3.input.stage1.NextPC|stage3.input.stage1.NextIsVirtual|stage3.input.stage1.NextIsFirstInSequence|stage3.input.stage2.product_virtual.NextIsNoop" }, + Stage3SumcheckClaimPlan { symbol: "stage3.instruction_input.input", stage: "stage3", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage3.instruction_input.weighted_inputs", kernel: None, relation: Some("jolt.stage3.instruction_input"), claim_value: "stage3.instruction_input.claim_expr", input_openings: "stage3.input.stage2.product_virtual.RightInstructionInput|stage3.input.stage2.product_virtual.LeftInstructionInput" }, + Stage3SumcheckClaimPlan { symbol: "stage3.registers_claim_reduction.input", stage: "stage3", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage3.registers.weighted_register_values", kernel: None, relation: Some("jolt.stage3.registers_claim_reduction"), claim_value: "stage3.registers.claim_expr", input_openings: "stage3.input.stage1.RdWriteValue|stage3.input.stage1.Rs1Value|stage3.input.stage1.Rs2Value" }, +]; +pub const STAGE3_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 18, +]; + +pub const STAGE3_SUMCHECK_BATCHES: &[Stage3SumcheckBatchPlan] = &[ + Stage3SumcheckBatchPlan { symbol: "stage3.batch", stage: "stage3", proof_slot: "stage3.sumcheck", policy: "jolt_core_stage3_aligned", count: 3, ordered_claims: "stage3.spartan_shift.input|stage3.instruction_input.input|stage3.registers_claim_reduction.input", claim_operands: "stage3.spartan_shift.input|stage3.instruction_input.input|stage3.registers_claim_reduction.input", claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE3_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE3_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 18, +]; + +pub const STAGE3_SUMCHECK_DRIVERS: &[Stage3SumcheckDriverPlan] = &[ + Stage3SumcheckDriverPlan { symbol: "stage3.sumcheck", stage: "stage3", proof_slot: "stage3.sumcheck", kernel: None, relation: Some("jolt.stage3.batched"), batch: "stage3.batch", policy: "jolt_core_stage3_aligned", round_schedule: STAGE3_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 18, degree: 3 }, +]; +pub const STAGE3_SUMCHECK_INSTANCE_RESULTS: &[Stage3SumcheckInstanceResultPlan] = &[ + Stage3SumcheckInstanceResultPlan { symbol: "stage3.spartan_shift.instance", source: "stage3.sumcheck", claim: "stage3.spartan_shift.input", relation: "jolt.stage3.spartan_shift", index: 0, point_arity: 18, num_rounds: 18, round_offset: 0, point_order: "reverse", degree: 2 }, + Stage3SumcheckInstanceResultPlan { symbol: "stage3.instruction_input.instance", source: "stage3.sumcheck", claim: "stage3.instruction_input.input", relation: "jolt.stage3.instruction_input", index: 1, point_arity: 18, num_rounds: 18, round_offset: 0, point_order: "reverse", degree: 3 }, + Stage3SumcheckInstanceResultPlan { symbol: "stage3.registers_claim_reduction.instance", source: "stage3.sumcheck", claim: "stage3.registers_claim_reduction.input", relation: "jolt.stage3.registers_claim_reduction", index: 2, point_arity: 18, num_rounds: 18, round_offset: 0, point_order: "reverse", degree: 2 }, +]; + +pub const STAGE3_SUMCHECK_EVALS: &[Stage3SumcheckEvalPlan] = &[ + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.UnexpandedPC", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.UnexpandedPC", index: 0, oracle: "UnexpandedPC" }, + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.PC", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.PC", index: 1, oracle: "PC" }, + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.OpFlagVirtualInstruction", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.OpFlagVirtualInstruction", index: 2, oracle: "OpFlagVirtualInstruction" }, + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.OpFlagIsFirstInSequence", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.OpFlagIsFirstInSequence", index: 3, oracle: "OpFlagIsFirstInSequence" }, + Stage3SumcheckEvalPlan { symbol: "stage3.spartan_shift.eval.InstructionFlagIsNoop", source: "stage3.sumcheck", name: "stage3.spartan_shift.eval.InstructionFlagIsNoop", index: 4, oracle: "InstructionFlagIsNoop" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", index: 5, oracle: "InstructionFlagLeftOperandIsRs1Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.Rs1Value", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.Rs1Value", index: 6, oracle: "Rs1Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", index: 7, oracle: "InstructionFlagLeftOperandIsPC" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.UnexpandedPC", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.UnexpandedPC", index: 8, oracle: "UnexpandedPC" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", index: 9, oracle: "InstructionFlagRightOperandIsRs2Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.Rs2Value", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.Rs2Value", index: 10, oracle: "Rs2Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", index: 11, oracle: "InstructionFlagRightOperandIsImm" }, + Stage3SumcheckEvalPlan { symbol: "stage3.instruction_input.eval.Imm", source: "stage3.sumcheck", name: "stage3.instruction_input.eval.Imm", index: 12, oracle: "Imm" }, + Stage3SumcheckEvalPlan { symbol: "stage3.registers_claim_reduction.eval.RdWriteValue", source: "stage3.sumcheck", name: "stage3.registers_claim_reduction.eval.RdWriteValue", index: 13, oracle: "RdWriteValue" }, + Stage3SumcheckEvalPlan { symbol: "stage3.registers_claim_reduction.eval.Rs1Value", source: "stage3.sumcheck", name: "stage3.registers_claim_reduction.eval.Rs1Value", index: 14, oracle: "Rs1Value" }, + Stage3SumcheckEvalPlan { symbol: "stage3.registers_claim_reduction.eval.Rs2Value", source: "stage3.sumcheck", name: "stage3.registers_claim_reduction.eval.Rs2Value", index: 15, oracle: "Rs2Value" }, +]; + +pub const STAGE3_POINT_SLICES: &[Stage3PointSlicePlan] = &[ + +]; + +pub const STAGE3_POINT_CONCATS: &[Stage3PointConcatPlan] = &[ + +]; +pub const STAGE3_OPENING_CLAIMS: &[Stage3OpeningClaimPlan] = &[ + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.UnexpandedPC" }, + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.PC", oracle: "PC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.PC" }, + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.OpFlagVirtualInstruction" }, + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.OpFlagIsFirstInSequence", oracle: "OpFlagIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.OpFlagIsFirstInSequence" }, + Stage3OpeningClaimPlan { symbol: "stage3.spartan_shift.opening.InstructionFlagIsNoop", oracle: "InstructionFlagIsNoop", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.spartan_shift.instance", eval_source: "stage3.spartan_shift.eval.InstructionFlagIsNoop" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value", oracle: "InstructionFlagLeftOperandIsRs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.Rs1Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC", oracle: "InstructionFlagLeftOperandIsPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.UnexpandedPC" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value", oracle: "InstructionFlagRightOperandIsRs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.Rs2Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.InstructionFlagRightOperandIsImm", oracle: "InstructionFlagRightOperandIsImm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm" }, + Stage3OpeningClaimPlan { symbol: "stage3.instruction_input.opening.Imm", oracle: "Imm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.instruction_input.instance", eval_source: "stage3.instruction_input.eval.Imm" }, + Stage3OpeningClaimPlan { symbol: "stage3.registers_claim_reduction.opening.RdWriteValue", oracle: "RdWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.registers_claim_reduction.instance", eval_source: "stage3.registers_claim_reduction.eval.RdWriteValue" }, + Stage3OpeningClaimPlan { symbol: "stage3.registers_claim_reduction.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.registers_claim_reduction.instance", eval_source: "stage3.registers_claim_reduction.eval.Rs1Value" }, + Stage3OpeningClaimPlan { symbol: "stage3.registers_claim_reduction.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage3.registers_claim_reduction.instance", eval_source: "stage3.registers_claim_reduction.eval.Rs2Value" }, +]; + +pub const STAGE3_OPENING_EQUALITIES: &[Stage3OpeningClaimEqualityPlan] = &[ + Stage3OpeningClaimEqualityPlan { symbol: "stage3.instruction_input.left_claim_consistency", mode: "point_and_eval", lhs: "stage3.input.stage2.product_virtual.LeftInstructionInput", rhs: "stage3.input.stage2.instruction_lookup.LeftInstructionInput" }, + Stage3OpeningClaimEqualityPlan { symbol: "stage3.instruction_input.right_claim_consistency", mode: "point_and_eval", lhs: "stage3.input.stage2.product_virtual.RightInstructionInput", rhs: "stage3.input.stage2.instruction_lookup.RightInstructionInput" }, +]; + +pub const STAGE3_OPENING_BATCHES: &[Stage3OpeningBatchPlan] = &[ + Stage3OpeningBatchPlan { symbol: "stage3.openings", stage: "stage3", proof_slot: "stage3.openings", policy: "jolt_stage3_output_order", count: 16, ordered_claims: "stage3.spartan_shift.opening.UnexpandedPC|stage3.spartan_shift.opening.PC|stage3.spartan_shift.opening.OpFlagVirtualInstruction|stage3.spartan_shift.opening.OpFlagIsFirstInSequence|stage3.spartan_shift.opening.InstructionFlagIsNoop|stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value|stage3.instruction_input.opening.Rs1Value|stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC|stage3.instruction_input.opening.UnexpandedPC|stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value|stage3.instruction_input.opening.Rs2Value|stage3.instruction_input.opening.InstructionFlagRightOperandIsImm|stage3.instruction_input.opening.Imm|stage3.registers_claim_reduction.opening.RdWriteValue|stage3.registers_claim_reduction.opening.Rs1Value|stage3.registers_claim_reduction.opening.Rs2Value", claim_operands: "stage3.spartan_shift.opening.UnexpandedPC|stage3.spartan_shift.opening.PC|stage3.spartan_shift.opening.OpFlagVirtualInstruction|stage3.spartan_shift.opening.OpFlagIsFirstInSequence|stage3.spartan_shift.opening.InstructionFlagIsNoop|stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value|stage3.instruction_input.opening.Rs1Value|stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC|stage3.instruction_input.opening.UnexpandedPC|stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value|stage3.instruction_input.opening.Rs2Value|stage3.instruction_input.opening.InstructionFlagRightOperandIsImm|stage3.instruction_input.opening.Imm|stage3.registers_claim_reduction.opening.RdWriteValue|stage3.registers_claim_reduction.opening.Rs1Value|stage3.registers_claim_reduction.opening.Rs2Value" }, +]; +pub const STAGE3_PROGRAM: Stage3VerifierProgramPlan = Stage3VerifierProgramPlan { + params: STAGE3_PARAMS, + steps: STAGE3_PROGRAM_STEPS, + transcript_squeezes: STAGE3_TRANSCRIPT_SQUEEZES, + opening_inputs: STAGE3_OPENING_INPUTS, + field_constants: STAGE3_FIELD_CONSTANTS, + field_exprs: STAGE3_FIELD_EXPRS, + claims: STAGE3_SUMCHECK_CLAIMS, + batches: STAGE3_SUMCHECK_BATCHES, + drivers: STAGE3_SUMCHECK_DRIVERS, + instance_results: STAGE3_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE3_SUMCHECK_EVALS, + point_slices: STAGE3_POINT_SLICES, + point_concats: STAGE3_POINT_CONCATS, + opening_claims: STAGE3_OPENING_CLAIMS, + opening_equalities: STAGE3_OPENING_EQUALITIES, + opening_batches: STAGE3_OPENING_BATCHES, +}; + +pub fn verify_stage3( + proof: &Stage3Proof, + opening_inputs: &[Stage3OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage3Error> +where + T: Transcript, +{ + verify_stage3_with_program(&STAGE3_PROGRAM, proof, opening_inputs, transcript) +} + +pub fn verify_stage3_with_program( + program: &'static Stage3VerifierProgramPlan, + proof: &Stage3Proof, + opening_inputs: &[Stage3OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage3Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage3Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + let mut artifacts = Stage3ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage3Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage3_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage3Error::MissingProof { + driver: step.symbol, + })?; + verify_stage3_driver(program, driver, proof, &mut store, transcript, &mut artifacts)?; + } + _ => { + return Err(VerifyStage3Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage3 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage3_verifier_program() -> &'static Stage3VerifierProgramPlan { + &STAGE3_PROGRAM +} + +fn verify_stage3_squeeze( + program: &'static Stage3VerifierProgramPlan, + squeeze: &'static Stage3TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage3ExecutionArtifacts, +) -> Result<(), VerifyStage3Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage3Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage3Error::from)?; + artifacts.challenge_vectors.push(Stage3ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn verify_stage3_driver( + program: &'static Stage3VerifierProgramPlan, + driver: &'static Stage3SumcheckDriverPlan, + proof: &Stage3Proof, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage3ExecutionArtifacts, +) -> Result<(), VerifyStage3Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage3Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage3.batched" => { + verify_batched_stage3(program, driver, proof, store, transcript)? + } + _ => { + return Err(VerifyStage3Error::UnsupportedRelation { + relation, + }); + } + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage3( + program: &'static Stage3VerifierProgramPlan, + driver: &'static Stage3SumcheckDriverPlan, + proof: &Stage3SumcheckOutput, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage3Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim(program, driver, store, evals, point, batching_coeffs) + }, + |store, verified| observe_stage3_sumcheck_output(program, store, verified), + |driver, error| VerifyStage3Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage3_sumcheck_output( + program: &'static Stage3VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage3SumcheckOutput, +) -> Result<(), VerifyStage3Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + _ => { + return Err(VerifyStage3Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage3Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage3Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage3Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage3Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage3Error::InvalidProof { driver, reason }, + |symbol| VerifyStage3Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage3VerifierProgramPlan, + driver: &'static Stage3SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage3NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage3Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage3Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let value = match instance.relation { + "jolt.stage3.spartan_shift" => { + expected_spartan_shift(store, evals, local_point)? + } + "jolt.stage3.instruction_input" => { + expected_instruction_input(store, evals, local_point)? + } + "jolt.stage3.registers_claim_reduction" => { + expected_registers(store, evals, local_point)? + } + _ => { + return Err(VerifyStage3Error::UnsupportedRelation { + relation: instance.relation, + }); + } + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_spartan_shift( + store: &super::common::ValueStore, + evals: &[Stage3NamedEval], + local_point: &[Fr], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_outer = + EqPlusOnePolynomial::::new(super::common::store_point(store, "stage3.input.stage1.NextPC")?.to_vec()) + .evaluate(&opening_point); + let eq_product = EqPlusOnePolynomial::::new( + super::common::store_point(store, "stage3.input.stage2.product_virtual.NextIsNoop")? + .to_vec(), + ) + .evaluate(&opening_point); + let weighted_outer = eval_by_name(evals, "stage3.spartan_shift.eval.UnexpandedPC")? + + super::common::store_scalar(store, "stage3.spartan_shift.gamma")? + * eval_by_name(evals, "stage3.spartan_shift.eval.PC")? + + super::common::store_scalar(store, "stage3.spartan_shift.gamma2")? + * eval_by_name(evals, "stage3.spartan_shift.eval.OpFlagVirtualInstruction")? + + super::common::store_scalar(store, "stage3.spartan_shift.gamma3")? + * eval_by_name(evals, "stage3.spartan_shift.eval.OpFlagIsFirstInSequence")?; + Ok(eq_outer * weighted_outer + + super::common::store_scalar(store, "stage3.spartan_shift.gamma4")? + * eq_product + * (Fr::from_u64(1) + - eval_by_name(evals, "stage3.spartan_shift.eval.InstructionFlagIsNoop")?)) +} + +fn expected_instruction_input( + store: &super::common::ValueStore, + evals: &[Stage3NamedEval], + local_point: &[Fr], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle( + &opening_point, + super::common::store_point(store, "stage3.input.stage2.product_virtual.LeftInstructionInput")?, + ); + let left = eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagLeftOperandIsRs1Value", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Rs1Value")? + + eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagLeftOperandIsPC", + )? * eval_by_name(evals, "stage3.instruction_input.eval.UnexpandedPC")?; + let right = eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagRightOperandIsRs2Value", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Rs2Value")? + + eval_by_name( + evals, + "stage3.instruction_input.eval.InstructionFlagRightOperandIsImm", + )? * eval_by_name(evals, "stage3.instruction_input.eval.Imm")?; + Ok(eq_eval * (right + super::common::store_scalar(store, "stage3.instruction_input.gamma")? * left)) +} + +fn expected_registers( + store: &super::common::ValueStore, + evals: &[Stage3NamedEval], + local_point: &[Fr], +) -> Result { + let opening_point = reverse_slice(local_point); + let eq_eval = EqPolynomial::::mle( + &opening_point, + super::common::store_point(store, "stage3.input.stage1.RdWriteValue")?, + ); + Ok(eq_eval + * (eval_by_name(evals, "stage3.registers_claim_reduction.eval.RdWriteValue")? + + super::common::store_scalar(store, "stage3.registers.gamma")? + * eval_by_name(evals, "stage3.registers_claim_reduction.eval.Rs1Value")? + + super::common::store_scalar(store, "stage3.registers.gamma2")? + * eval_by_name(evals, "stage3.registers_claim_reduction.eval.Rs2Value")?)) +} + diff --git a/crates/jolt-verifier/src/stages/stage4.rs b/crates/jolt-verifier/src/stages/stage4.rs new file mode 100644 index 0000000000..d161cc430e --- /dev/null +++ b/crates/jolt-verifier/src/stages/stage4.rs @@ -0,0 +1,553 @@ +#![allow(dead_code)] + +use super::common::{batch_claims, eval_by_name, find_batch, find_plan, lt_polynomial_eval, reverse_slice}; +use jolt_field::{Field, Fr}; +use jolt_poly::EqPolynomial; +use jolt_sumcheck::SumcheckError; +use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript}; + +pub type Stage4NamedEval = super::common::StageNamedEval; +pub type Stage4SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage4ChallengeVector = super::common::StageChallengeVector; +pub type Stage4ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage4Proof = super::common::StageProof; +pub type Stage4OpeningInputValue = super::common::StageOpeningInputValue; + +pub use super::common::{ + FieldConstantPlan as Stage4FieldConstantPlan, FieldExprPlan as Stage4FieldExprPlan, + KernelPlan as Stage4KernelPlan, OpeningBatchPlan as Stage4OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage4OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage4OpeningClaimPlan, OpeningInputPlan as Stage4OpeningInputPlan, + PointConcatPlan as Stage4PointConcatPlan, PointSlicePlan as Stage4PointSlicePlan, + ProgramStepPlan as Stage4ProgramStepPlan, StageParams as Stage4Params, + StageProgramPlanNoPointZeros as Stage4CpuProgramPlan, + SumcheckBatchPlan as Stage4SumcheckBatchPlan, + SumcheckClaimPlan as Stage4SumcheckClaimPlan, SumcheckDriverPlan as Stage4SumcheckDriverPlan, + SumcheckEvalPlan as Stage4SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage4SumcheckInstanceResultPlan, + TranscriptAbsorbBytesPlan as Stage4TranscriptAbsorbBytesPlan, + TranscriptSqueezePlan as Stage4TranscriptSqueezePlan, +}; + +pub type DefaultStage4Transcript = Blake2bTranscript; +pub type Stage4VerifierProgramPlan = Stage4CpuProgramPlan; + +#[derive(Debug)] +pub enum VerifyStage4Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage4Error); + +pub const STAGE4_PARAMS: Stage4Params = Stage4Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE4_PROGRAM_STEPS: &[Stage4ProgramStepPlan] = &[ + Stage4ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage4.registers_read_write.gamma" }, + Stage4ProgramStepPlan { kind: "transcript_absorb_bytes", symbol: "stage4.ram_val_check.domain_separator" }, + Stage4ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage4.ram_val_check.gamma" }, + Stage4ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage4.sumcheck" }, +]; + +pub const STAGE4_TRANSCRIPT_SQUEEZES: &[Stage4TranscriptSqueezePlan] = &[ + Stage4TranscriptSqueezePlan { symbol: "stage4.registers_read_write.gamma", label: "registers_read_write_gamma", kind: "challenge_scalar", count: 1 }, + Stage4TranscriptSqueezePlan { symbol: "stage4.ram_val_check.gamma", label: "ram_val_check_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE4_TRANSCRIPT_ABSORB_BYTES: &[Stage4TranscriptAbsorbBytesPlan] = &[ + Stage4TranscriptAbsorbBytesPlan { symbol: "stage4.ram_val_check.domain_separator", label: "ram_val_check_gamma", payload: "" }, +]; + +pub const STAGE4_OPENING_INPUTS: &[Stage4OpeningInputPlan] = &[ + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.registers.RdWriteValue", source_stage: "stage3", source_claim: "stage3.registers_claim_reduction.opening.RdWriteValue", oracle: "RdWriteValue", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.registers.Rs1Value", source_stage: "stage3", source_claim: "stage3.registers_claim_reduction.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.registers.Rs2Value", source_stage: "stage3", source_claim: "stage3.registers_claim_reduction.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.instruction.Rs1Value", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.Rs1Value", oracle: "Rs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage3.instruction.Rs2Value", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.Rs2Value", oracle: "Rs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage2.RamVal", source_stage: "stage2", source_claim: "stage2.ram_read_write.opening.RamVal", oracle: "RamVal", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.stage2.RamValFinal", source_stage: "stage2", source_claim: "stage2.ram_output.opening.RamValFinal", oracle: "RamValFinal", domain: "jolt.ram_address_domain", point_arity: 14, claim_kind: "virtual" }, + Stage4OpeningInputPlan { symbol: "stage4.input.initial_ram.RamValInit", source_stage: "stage4_precomputed", source_claim: "stage4.ram_val_check.initial_ram_eval", oracle: "RamValInit", domain: "jolt.ram_address_domain", point_arity: 14, claim_kind: "virtual" }, +]; + +pub const STAGE4_FIELD_CONSTANTS: &[Stage4FieldConstantPlan] = &[ + +]; + +pub const STAGE4_FIELD_EXPRS: &[Stage4FieldExprPlan] = &[ + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.gamma2", kind: "op", formula: "field.pow:2", operands: "stage4.registers_read_write.gamma" }, + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.term.Rs1Value", kind: "op", formula: "field.mul", operands: "stage4.registers_read_write.gamma|stage4.input.stage3.registers.Rs1Value" }, + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.term.Rs2Value", kind: "op", formula: "field.mul", operands: "stage4.registers_read_write.gamma2|stage4.input.stage3.registers.Rs2Value" }, + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.partial.RdWriteValueRs1Value", kind: "op", formula: "field.add", operands: "stage4.input.stage3.registers.RdWriteValue|stage4.registers_read_write.term.Rs1Value" }, + Stage4FieldExprPlan { symbol: "stage4.registers_read_write.claim_expr", kind: "op", formula: "field.add", operands: "stage4.registers_read_write.partial.RdWriteValueRs1Value|stage4.registers_read_write.term.Rs2Value" }, + Stage4FieldExprPlan { symbol: "stage4.ram_val_check.delta.RamVal", kind: "op", formula: "field.sub", operands: "stage4.input.stage2.RamVal|stage4.input.initial_ram.RamValInit" }, + Stage4FieldExprPlan { symbol: "stage4.ram_val_check.delta.RamValFinal", kind: "op", formula: "field.sub", operands: "stage4.input.stage2.RamValFinal|stage4.input.initial_ram.RamValInit" }, + Stage4FieldExprPlan { symbol: "stage4.ram_val_check.term.RamValFinal", kind: "op", formula: "field.mul", operands: "stage4.ram_val_check.gamma|stage4.ram_val_check.delta.RamValFinal" }, + Stage4FieldExprPlan { symbol: "stage4.ram_val_check.claim_expr", kind: "op", formula: "field.add", operands: "stage4.ram_val_check.delta.RamVal|stage4.ram_val_check.term.RamValFinal" }, +]; +pub const STAGE4_KERNELS: &[Stage4KernelPlan] = &[ + +]; + +pub const STAGE4_SUMCHECK_CLAIMS: &[Stage4SumcheckClaimPlan] = &[ + Stage4SumcheckClaimPlan { symbol: "stage4.registers_read_write.input", stage: "stage4", domain: "jolt.stage4_registers_rw_domain", num_rounds: 25, degree: 3, claim: "stage4.registers_read_write.weighted_values", kernel: None, relation: Some("jolt.stage4.registers_read_write"), claim_value: "stage4.registers_read_write.claim_expr", input_openings: "stage4.input.stage3.registers.RdWriteValue|stage4.input.stage3.registers.Rs1Value|stage4.input.stage3.registers.Rs2Value" }, + Stage4SumcheckClaimPlan { symbol: "stage4.ram_val_check.input", stage: "stage4", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage4.ram_val_check.weighted_values", kernel: None, relation: Some("jolt.stage4.ram_val_check"), claim_value: "stage4.ram_val_check.claim_expr", input_openings: "stage4.input.stage2.RamVal|stage4.input.stage2.RamValFinal|stage4.input.initial_ram.RamValInit" }, +]; +pub const STAGE4_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 18, + 7, +]; + +pub const STAGE4_SUMCHECK_BATCHES: &[Stage4SumcheckBatchPlan] = &[ + Stage4SumcheckBatchPlan { symbol: "stage4.batch", stage: "stage4", proof_slot: "stage4.sumcheck", policy: "jolt_core_stage4_aligned", count: 2, ordered_claims: "stage4.registers_read_write.input|stage4.ram_val_check.input", claim_operands: "stage4.registers_read_write.input|stage4.ram_val_check.input", claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE4_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE4_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 18, + 7, +]; + +pub const STAGE4_SUMCHECK_DRIVERS: &[Stage4SumcheckDriverPlan] = &[ + Stage4SumcheckDriverPlan { symbol: "stage4.sumcheck", stage: "stage4", proof_slot: "stage4.sumcheck", kernel: None, relation: Some("jolt.stage4.batched"), batch: "stage4.batch", policy: "jolt_core_stage4_aligned", round_schedule: STAGE4_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 25, degree: 3 }, +]; +pub const STAGE4_SUMCHECK_INSTANCE_RESULTS: &[Stage4SumcheckInstanceResultPlan] = &[ + Stage4SumcheckInstanceResultPlan { symbol: "stage4.registers_read_write.instance", source: "stage4.sumcheck", claim: "stage4.registers_read_write.input", relation: "jolt.stage4.registers_read_write", index: 0, point_arity: 25, num_rounds: 25, round_offset: 0, point_order: "stage4_registers_rw", degree: 3 }, + Stage4SumcheckInstanceResultPlan { symbol: "stage4.ram_val_check.instance", source: "stage4.sumcheck", claim: "stage4.ram_val_check.input", relation: "jolt.stage4.ram_val_check", index: 1, point_arity: 18, num_rounds: 18, round_offset: 7, point_order: "reverse", degree: 3 }, +]; + +pub const STAGE4_SUMCHECK_EVALS: &[Stage4SumcheckEvalPlan] = &[ + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.RegistersVal", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.RegistersVal", index: 0, oracle: "RegistersVal" }, + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.Rs1Ra", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.Rs1Ra", index: 1, oracle: "Rs1Ra" }, + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.Rs2Ra", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.Rs2Ra", index: 2, oracle: "Rs2Ra" }, + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.RdWa", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.RdWa", index: 3, oracle: "RdWa" }, + Stage4SumcheckEvalPlan { symbol: "stage4.registers_read_write.eval.RdInc", source: "stage4.sumcheck", name: "stage4.registers_read_write.eval.RdInc", index: 4, oracle: "RdInc" }, + Stage4SumcheckEvalPlan { symbol: "stage4.ram_val_check.eval.RamRa", source: "stage4.sumcheck", name: "stage4.ram_val_check.eval.RamRa", index: 0, oracle: "RamRa" }, + Stage4SumcheckEvalPlan { symbol: "stage4.ram_val_check.eval.RamInc", source: "stage4.sumcheck", name: "stage4.ram_val_check.eval.RamInc", index: 1, oracle: "RamInc" }, +]; + +pub const STAGE4_POINT_SLICES: &[Stage4PointSlicePlan] = &[ + Stage4PointSlicePlan { symbol: "stage4.registers_read_write.point.RdInc", source: "stage4.registers_read_write.instance", offset: 7, length: 18, input: "stage4.registers_read_write.instance" }, + Stage4PointSlicePlan { symbol: "stage4.ram_val_check.point.RamAddress", source: "stage4.input.stage2.RamVal", offset: 0, length: 14, input: "stage4.input.stage2.RamVal" }, +]; + +pub const STAGE4_POINT_CONCATS: &[Stage4PointConcatPlan] = &[ + Stage4PointConcatPlan { symbol: "stage4.ram_val_check.point.RamRa", layout: "address_then_cycle", arity: 32, inputs: "stage4.ram_val_check.point.RamAddress|stage4.ram_val_check.instance" }, +]; +pub const STAGE4_OPENING_CLAIMS: &[Stage4OpeningClaimPlan] = &[ + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.RegistersVal", oracle: "RegistersVal", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage4.registers_read_write.instance", eval_source: "stage4.registers_read_write.eval.RegistersVal" }, + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.Rs1Ra", oracle: "Rs1Ra", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage4.registers_read_write.instance", eval_source: "stage4.registers_read_write.eval.Rs1Ra" }, + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.Rs2Ra", oracle: "Rs2Ra", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage4.registers_read_write.instance", eval_source: "stage4.registers_read_write.eval.Rs2Ra" }, + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.RdWa", oracle: "RdWa", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage4.registers_read_write.instance", eval_source: "stage4.registers_read_write.eval.RdWa" }, + Stage4OpeningClaimPlan { symbol: "stage4.registers_read_write.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage4.registers_read_write.point.RdInc", eval_source: "stage4.registers_read_write.eval.RdInc" }, + Stage4OpeningClaimPlan { symbol: "stage4.ram_val_check.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage4.ram_val_check.point.RamRa", eval_source: "stage4.ram_val_check.eval.RamRa" }, + Stage4OpeningClaimPlan { symbol: "stage4.ram_val_check.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage4.ram_val_check.instance", eval_source: "stage4.ram_val_check.eval.RamInc" }, +]; + +pub const STAGE4_OPENING_EQUALITIES: &[Stage4OpeningClaimEqualityPlan] = &[ + Stage4OpeningClaimEqualityPlan { symbol: "stage4.registers.rs1_claim_consistency", mode: "point_and_eval", lhs: "stage4.input.stage3.registers.Rs1Value", rhs: "stage4.input.stage3.instruction.Rs1Value" }, + Stage4OpeningClaimEqualityPlan { symbol: "stage4.registers.rs2_claim_consistency", mode: "point_and_eval", lhs: "stage4.input.stage3.registers.Rs2Value", rhs: "stage4.input.stage3.instruction.Rs2Value" }, +]; + +pub const STAGE4_OPENING_BATCHES: &[Stage4OpeningBatchPlan] = &[ + Stage4OpeningBatchPlan { symbol: "stage4.openings", stage: "stage4", proof_slot: "stage4.openings", policy: "jolt_stage4_output_order", count: 7, ordered_claims: "stage4.registers_read_write.opening.RegistersVal|stage4.registers_read_write.opening.Rs1Ra|stage4.registers_read_write.opening.Rs2Ra|stage4.registers_read_write.opening.RdWa|stage4.registers_read_write.opening.RdInc|stage4.ram_val_check.opening.RamRa|stage4.ram_val_check.opening.RamInc", claim_operands: "stage4.registers_read_write.opening.RegistersVal|stage4.registers_read_write.opening.Rs1Ra|stage4.registers_read_write.opening.Rs2Ra|stage4.registers_read_write.opening.RdWa|stage4.registers_read_write.opening.RdInc|stage4.ram_val_check.opening.RamRa|stage4.ram_val_check.opening.RamInc" }, +]; +pub const STAGE4_PROGRAM: Stage4VerifierProgramPlan = Stage4CpuProgramPlan { + role: "verifier", + params: STAGE4_PARAMS, + steps: STAGE4_PROGRAM_STEPS, + transcript_squeezes: STAGE4_TRANSCRIPT_SQUEEZES, + transcript_absorb_bytes: STAGE4_TRANSCRIPT_ABSORB_BYTES, + opening_inputs: STAGE4_OPENING_INPUTS, + field_constants: STAGE4_FIELD_CONSTANTS, + field_exprs: STAGE4_FIELD_EXPRS, + kernels: STAGE4_KERNELS, + claims: STAGE4_SUMCHECK_CLAIMS, + batches: STAGE4_SUMCHECK_BATCHES, + drivers: STAGE4_SUMCHECK_DRIVERS, + instance_results: STAGE4_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE4_SUMCHECK_EVALS, + point_slices: STAGE4_POINT_SLICES, + point_concats: STAGE4_POINT_CONCATS, + opening_claims: STAGE4_OPENING_CLAIMS, + opening_equalities: STAGE4_OPENING_EQUALITIES, + opening_batches: STAGE4_OPENING_BATCHES, +}; + +pub fn verify_stage4( + proof: &Stage4Proof, + opening_inputs: &[Stage4OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage4Error> +where + T: Transcript, +{ + verify_stage4_with_program(&STAGE4_PROGRAM, proof, opening_inputs, transcript) +} + +pub fn verify_stage4_with_program( + program: &'static Stage4VerifierProgramPlan, + proof: &Stage4Proof, + opening_inputs: &[Stage4OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage4Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage4Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + let mut artifacts = Stage4ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage4Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage4_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "transcript_absorb_bytes" => { + let absorb = find_plan(program.transcript_absorb_bytes, step.symbol).ok_or( + VerifyStage4Error::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage4_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage4Error::MissingProof { + driver: step.symbol, + })?; + verify_stage4_driver(program, driver, proof, &mut store, transcript, &mut artifacts)?; + } + _ => { + return Err(VerifyStage4Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage4 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage4_verifier_program() -> &'static Stage4VerifierProgramPlan { + &STAGE4_PROGRAM +} + +fn verify_stage4_squeeze( + program: &'static Stage4VerifierProgramPlan, + squeeze: &'static Stage4TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage4ExecutionArtifacts, +) -> Result<(), VerifyStage4Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage4Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage4Error::from)?; + artifacts.challenge_vectors.push(Stage4ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn absorb_stage4_bytes(absorb: &'static Stage4TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn verify_stage4_driver( + program: &'static Stage4VerifierProgramPlan, + driver: &'static Stage4SumcheckDriverPlan, + proof: &Stage4Proof, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage4ExecutionArtifacts, +) -> Result<(), VerifyStage4Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage4Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage4.batched" => { + verify_batched_stage4(program, driver, proof, store, transcript)? + } + _ => return Err(VerifyStage4Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage4( + program: &'static Stage4VerifierProgramPlan, + driver: &'static Stage4SumcheckDriverPlan, + proof: &Stage4SumcheckOutput, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage4Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim(program, driver, store, evals, point, batching_coeffs) + }, + |store, verified| observe_stage4_sumcheck_output(program, store, verified), + |driver, error| VerifyStage4Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage4_sumcheck_output( + program: &'static Stage4VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage4SumcheckOutput, +) -> Result<(), VerifyStage4Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "stage4_registers_rw" => { + point = normalize_stage4_registers_rw_point(program, output.driver, &point)?; + } + _ => { + return Err(VerifyStage4Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage4Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage4Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage4Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage4Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage4Error::InvalidProof { driver, reason }, + |symbol| VerifyStage4Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage4VerifierProgramPlan, + driver: &'static Stage4SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage4NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage4Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage4Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(""); + let value = match relation { + "jolt.stage4.registers_read_write" => { + expected_registers_read_write(store, evals, local_point)? + } + "jolt.stage4.ram_val_check" => { + expected_ram_val_check(store, evals, local_point)? + } + _ => return Err(VerifyStage4Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_registers_read_write( + store: &super::common::ValueStore, + evals: &[Stage4NamedEval], + local_point: &[Fr], +) -> Result { + let trace_point = super::common::store_point(store, "stage4.input.stage3.registers.RdWriteValue")?; + let r_cycle = normalize_stage4_registers_rw_cycle_point( + local_point, + trace_point.len(), + "stage4.registers_read_write.instance", + )?; + let eq_eval = EqPolynomial::::mle(&r_cycle, trace_point); + let registers_val = eval_by_name( + evals, + "stage4.registers_read_write.eval.RegistersVal", + )?; + let rs1_ra = eval_by_name(evals, "stage4.registers_read_write.eval.Rs1Ra")?; + let rs2_ra = eval_by_name(evals, "stage4.registers_read_write.eval.Rs2Ra")?; + let rd_wa = eval_by_name(evals, "stage4.registers_read_write.eval.RdWa")?; + let rd_inc = eval_by_name(evals, "stage4.registers_read_write.eval.RdInc")?; + let gamma = super::common::store_scalar(store, "stage4.registers_read_write.gamma")?; + Ok(eq_eval + * (rd_wa * (registers_val + rd_inc) + + gamma * (rs1_ra * registers_val + gamma * rs2_ra * registers_val))) +} + +fn expected_ram_val_check( + store: &super::common::ValueStore, + evals: &[Stage4NamedEval], + local_point: &[Fr], +) -> Result { + let ram_val_point = super::common::store_point(store, "stage4.input.stage2.RamVal")?; + let r_cycle_prime = reverse_slice(local_point); + let r_cycle = suffix_point( + ram_val_point, + r_cycle_prime.len(), + "stage4.input.stage2.RamVal", + )?; + let lt_eval = lt_polynomial_eval(&r_cycle_prime, r_cycle); + let gamma = super::common::store_scalar(store, "stage4.ram_val_check.gamma")?; + let ram_ra = eval_by_name(evals, "stage4.ram_val_check.eval.RamRa")?; + let ram_inc = eval_by_name(evals, "stage4.ram_val_check.eval.RamInc")?; + Ok(ram_inc * ram_ra * (lt_eval + gamma)) +} + +fn suffix_point<'a>( + point: &'a [Fr], + length: usize, + input: &'static str, +) -> Result<&'a [Fr], VerifyStage4Error> { + point + .get(point.len().saturating_sub(length)..) + .filter(|suffix| suffix.len() == length) + .ok_or(VerifyStage4Error::InvalidInputLength { + input, + expected: length, + actual: point.len(), + }) +} + +fn normalize_stage4_registers_rw_point( + program: &'static Stage4VerifierProgramPlan, + driver: &'static str, + point: &[F], +) -> Result, VerifyStage4Error> { + let driver_plan = find_plan(program.drivers, driver).ok_or(VerifyStage4Error::MissingProof { + driver, + })?; + if driver_plan.round_schedule.len() != 2 { + return Err(VerifyStage4Error::InvalidProof { + driver, + reason: "stage4 registers point normalization requires [cycle, address] schedule", + }); + } + let cycle_rounds = driver_plan.round_schedule[0]; + let address_rounds = driver_plan.round_schedule[1]; + if point.len() != cycle_rounds + address_rounds { + return Err(VerifyStage4Error::InvalidInputLength { + input: "stage4.registers_read_write.instance", + expected: cycle_rounds + address_rounds, + actual: point.len(), + }); + } + let (cycle, address) = point.split_at(cycle_rounds); + Ok(address + .iter() + .rev() + .copied() + .chain(cycle.iter().rev().copied()) + .collect()) +} + +fn normalize_stage4_registers_rw_cycle_point( + point: &[F], + cycle_rounds: usize, + input: &'static str, +) -> Result, VerifyStage4Error> { + let cycle = point + .get(..cycle_rounds) + .filter(|cycle| cycle.len() == cycle_rounds) + .ok_or(VerifyStage4Error::InvalidInputLength { + input, + expected: cycle_rounds, + actual: point.len(), + })?; + Ok(cycle.iter().rev().copied().collect()) +} + diff --git a/crates/jolt-verifier/src/stages/stage5.rs b/crates/jolt-verifier/src/stages/stage5.rs new file mode 100644 index 0000000000..9115c05755 --- /dev/null +++ b/crates/jolt-verifier/src/stages/stage5.rs @@ -0,0 +1,663 @@ +#![allow(dead_code)] + +use super::common::{batch_claims, eval_by_name, find_batch, find_plan, identity_polynomial_eval, indexed_evals_by_prefix, indexed_evals_by_prefix_any, lt_polynomial_eval, normalize_instruction_read_raf_point, operand_polynomial_eval, reverse_slice, suffix_point}; +use jolt_field::{Field, Fr}; +use jolt_lookup_tables::LookupTableKind; +use jolt_poly::EqPolynomial; +use jolt_sumcheck::SumcheckError; +use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript}; + +pub type Stage5NamedEval = super::common::StageNamedEval; +pub type Stage5SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage5ChallengeVector = super::common::StageChallengeVector; +pub type Stage5ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage5Proof = super::common::StageProof; +pub type Stage5OpeningInputValue = super::common::StageOpeningInputValue; + +pub use super::common::{ + FieldConstantPlan as Stage5FieldConstantPlan, FieldExprPlan as Stage5FieldExprPlan, + KernelPlan as Stage5KernelPlan, OpeningBatchPlan as Stage5OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage5OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage5OpeningClaimPlan, OpeningInputPlan as Stage5OpeningInputPlan, + PointConcatPlan as Stage5PointConcatPlan, PointSlicePlan as Stage5PointSlicePlan, + ProgramStepPlan as Stage5ProgramStepPlan, StageParams as Stage5Params, + StageProgramPlanNoPointZeros as Stage5CpuProgramPlan, + SumcheckBatchPlan as Stage5SumcheckBatchPlan, + SumcheckClaimPlan as Stage5SumcheckClaimPlan, SumcheckDriverPlan as Stage5SumcheckDriverPlan, + SumcheckEvalPlan as Stage5SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage5SumcheckInstanceResultPlan, + TranscriptAbsorbBytesPlan as Stage5TranscriptAbsorbBytesPlan, + TranscriptSqueezePlan as Stage5TranscriptSqueezePlan, +}; + +pub type DefaultStage5Transcript = Blake2bTranscript; +pub type Stage5VerifierProgramPlan = Stage5CpuProgramPlan; + +#[derive(Debug)] +pub enum VerifyStage5Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage5Error); + +pub const STAGE5_PARAMS: Stage5Params = Stage5Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE5_PROGRAM_STEPS: &[Stage5ProgramStepPlan] = &[ + Stage5ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage5.instruction_read_raf.gamma" }, + Stage5ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage5.ram_ra_claim_reduction.gamma" }, + Stage5ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage5.sumcheck" }, +]; + +pub const STAGE5_TRANSCRIPT_SQUEEZES: &[Stage5TranscriptSqueezePlan] = &[ + Stage5TranscriptSqueezePlan { symbol: "stage5.instruction_read_raf.gamma", label: "instruction_read_raf_gamma", kind: "challenge_scalar", count: 1 }, + Stage5TranscriptSqueezePlan { symbol: "stage5.ram_ra_claim_reduction.gamma", label: "ram_ra_claim_reduction_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE5_TRANSCRIPT_ABSORB_BYTES: &[Stage5TranscriptAbsorbBytesPlan] = &[ + +]; + +pub const STAGE5_OPENING_INPUTS: &[Stage5OpeningInputPlan] = &[ + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.instruction.LookupOutput", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.product_virtual.LookupOutput", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.instruction.LeftLookupOperand", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.LeftLookupOperand", oracle: "LeftLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.instruction.RightLookupOperand", source_stage: "stage2", source_claim: "stage2.instruction_lookup.claim_reduction.opening.RightLookupOperand", oracle: "RightLookupOperand", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.ram_raf.RamRa", source_stage: "stage2", source_claim: "stage2.ram_raf.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage2.ram_read_write.RamRa", source_stage: "stage2", source_claim: "stage2.ram_read_write.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage4.ram_val_check.RamRa", source_stage: "stage4", source_claim: "stage4.ram_val_check.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage5OpeningInputPlan { symbol: "stage5.input.stage4.registers.RegistersVal", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.RegistersVal", oracle: "RegistersVal", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, +]; + +pub const STAGE5_FIELD_CONSTANTS: &[Stage5FieldConstantPlan] = &[ + +]; + +pub const STAGE5_FIELD_EXPRS: &[Stage5FieldExprPlan] = &[ + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.gamma2", kind: "op", formula: "field.pow:2", operands: "stage5.instruction_read_raf.gamma" }, + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.term.LeftLookupOperand", kind: "op", formula: "field.mul", operands: "stage5.instruction_read_raf.gamma|stage5.input.stage2.instruction.LeftLookupOperand" }, + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.term.RightLookupOperand", kind: "op", formula: "field.mul", operands: "stage5.instruction_read_raf.gamma2|stage5.input.stage2.instruction.RightLookupOperand" }, + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.partial.LookupOutputLeftOperand", kind: "op", formula: "field.add", operands: "stage5.input.stage2.instruction.LookupOutput|stage5.instruction_read_raf.term.LeftLookupOperand" }, + Stage5FieldExprPlan { symbol: "stage5.instruction_read_raf.claim_expr", kind: "op", formula: "field.add", operands: "stage5.instruction_read_raf.partial.LookupOutputLeftOperand|stage5.instruction_read_raf.term.RightLookupOperand" }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.gamma2", kind: "op", formula: "field.pow:2", operands: "stage5.ram_ra_claim_reduction.gamma" }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.term.RamRaReadWrite", kind: "op", formula: "field.mul", operands: "stage5.ram_ra_claim_reduction.gamma|stage5.input.stage2.ram_read_write.RamRa" }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.term.RamRaValCheck", kind: "op", formula: "field.mul", operands: "stage5.ram_ra_claim_reduction.gamma2|stage5.input.stage4.ram_val_check.RamRa" }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.partial.RafReadWrite", kind: "op", formula: "field.add", operands: "stage5.input.stage2.ram_raf.RamRa|stage5.ram_ra_claim_reduction.term.RamRaReadWrite" }, + Stage5FieldExprPlan { symbol: "stage5.ram_ra_claim_reduction.claim_expr", kind: "op", formula: "field.add", operands: "stage5.ram_ra_claim_reduction.partial.RafReadWrite|stage5.ram_ra_claim_reduction.term.RamRaValCheck" }, +]; +pub const STAGE5_KERNELS: &[Stage5KernelPlan] = &[ + +]; + +pub const STAGE5_SUMCHECK_CLAIMS: &[Stage5SumcheckClaimPlan] = &[ + Stage5SumcheckClaimPlan { symbol: "stage5.instruction_read_raf.input", stage: "stage5", domain: "jolt.stage5_instruction_read_raf_domain", num_rounds: 146, degree: 10, claim: "stage5.instruction_read_raf.weighted_lookup_values", kernel: None, relation: Some("jolt.stage5.instruction_read_raf"), claim_value: "stage5.instruction_read_raf.claim_expr", input_openings: "stage5.input.stage2.instruction.LookupOutput|stage5.input.stage2.instruction.LeftLookupOperand|stage5.input.stage2.instruction.RightLookupOperand" }, + Stage5SumcheckClaimPlan { symbol: "stage5.ram_ra_claim_reduction.input", stage: "stage5", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage5.ram_ra_claim_reduction.weighted_ram_ra", kernel: None, relation: Some("jolt.stage5.ram_ra_claim_reduction"), claim_value: "stage5.ram_ra_claim_reduction.claim_expr", input_openings: "stage5.input.stage2.ram_raf.RamRa|stage5.input.stage2.ram_read_write.RamRa|stage5.input.stage4.ram_val_check.RamRa" }, + Stage5SumcheckClaimPlan { symbol: "stage5.registers_val_evaluation.input", stage: "stage5", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage5.registers_val_evaluation.registers_val", kernel: None, relation: Some("jolt.stage5.registers_val_evaluation"), claim_value: "stage5.input.stage4.registers.RegistersVal", input_openings: "stage5.input.stage4.registers.RegistersVal" }, +]; +pub const STAGE5_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 128, + 18, +]; + +pub const STAGE5_SUMCHECK_BATCHES: &[Stage5SumcheckBatchPlan] = &[ + Stage5SumcheckBatchPlan { symbol: "stage5.batch", stage: "stage5", proof_slot: "stage5.sumcheck", policy: "jolt_core_stage5_aligned", count: 3, ordered_claims: "stage5.instruction_read_raf.input|stage5.ram_ra_claim_reduction.input|stage5.registers_val_evaluation.input", claim_operands: "stage5.instruction_read_raf.input|stage5.ram_ra_claim_reduction.input|stage5.registers_val_evaluation.input", claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE5_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE5_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 128, + 18, +]; + +pub const STAGE5_SUMCHECK_DRIVERS: &[Stage5SumcheckDriverPlan] = &[ + Stage5SumcheckDriverPlan { symbol: "stage5.sumcheck", stage: "stage5", proof_slot: "stage5.sumcheck", kernel: None, relation: Some("jolt.stage5.batched"), batch: "stage5.batch", policy: "jolt_core_stage5_aligned", round_schedule: STAGE5_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 146, degree: 10 }, +]; +pub const STAGE5_SUMCHECK_INSTANCE_RESULTS: &[Stage5SumcheckInstanceResultPlan] = &[ + Stage5SumcheckInstanceResultPlan { symbol: "stage5.instruction_read_raf.instance", source: "stage5.sumcheck", claim: "stage5.instruction_read_raf.input", relation: "jolt.stage5.instruction_read_raf", index: 0, point_arity: 146, num_rounds: 146, round_offset: 0, point_order: "instruction_read_raf", degree: 10 }, + Stage5SumcheckInstanceResultPlan { symbol: "stage5.ram_ra_claim_reduction.instance", source: "stage5.sumcheck", claim: "stage5.ram_ra_claim_reduction.input", relation: "jolt.stage5.ram_ra_claim_reduction", index: 1, point_arity: 18, num_rounds: 18, round_offset: 128, point_order: "reverse", degree: 2 }, + Stage5SumcheckInstanceResultPlan { symbol: "stage5.registers_val_evaluation.instance", source: "stage5.sumcheck", claim: "stage5.registers_val_evaluation.input", relation: "jolt.stage5.registers_val_evaluation", index: 2, point_arity: 18, num_rounds: 18, round_offset: 128, point_order: "reverse", degree: 3 }, +]; + +pub const STAGE5_SUMCHECK_EVALS: &[Stage5SumcheckEvalPlan] = &[ + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_0", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_0", index: 0, oracle: "LookupTableFlag_0" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_1", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_1", index: 1, oracle: "LookupTableFlag_1" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_2", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_2", index: 2, oracle: "LookupTableFlag_2" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_3", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_3", index: 3, oracle: "LookupTableFlag_3" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_4", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_4", index: 4, oracle: "LookupTableFlag_4" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_5", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_5", index: 5, oracle: "LookupTableFlag_5" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_6", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_6", index: 6, oracle: "LookupTableFlag_6" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_7", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_7", index: 7, oracle: "LookupTableFlag_7" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_8", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_8", index: 8, oracle: "LookupTableFlag_8" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_9", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_9", index: 9, oracle: "LookupTableFlag_9" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_10", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_10", index: 10, oracle: "LookupTableFlag_10" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_11", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_11", index: 11, oracle: "LookupTableFlag_11" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_12", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_12", index: 12, oracle: "LookupTableFlag_12" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_13", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_13", index: 13, oracle: "LookupTableFlag_13" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_14", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_14", index: 14, oracle: "LookupTableFlag_14" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_15", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_15", index: 15, oracle: "LookupTableFlag_15" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_16", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_16", index: 16, oracle: "LookupTableFlag_16" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_17", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_17", index: 17, oracle: "LookupTableFlag_17" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_18", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_18", index: 18, oracle: "LookupTableFlag_18" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_19", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_19", index: 19, oracle: "LookupTableFlag_19" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_20", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_20", index: 20, oracle: "LookupTableFlag_20" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_21", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_21", index: 21, oracle: "LookupTableFlag_21" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_22", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_22", index: 22, oracle: "LookupTableFlag_22" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_23", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_23", index: 23, oracle: "LookupTableFlag_23" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_24", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_24", index: 24, oracle: "LookupTableFlag_24" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_25", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_25", index: 25, oracle: "LookupTableFlag_25" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_26", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_26", index: 26, oracle: "LookupTableFlag_26" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_27", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_27", index: 27, oracle: "LookupTableFlag_27" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_28", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_28", index: 28, oracle: "LookupTableFlag_28" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_29", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_29", index: 29, oracle: "LookupTableFlag_29" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_30", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_30", index: 30, oracle: "LookupTableFlag_30" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_31", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_31", index: 31, oracle: "LookupTableFlag_31" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_32", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_32", index: 32, oracle: "LookupTableFlag_32" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_33", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_33", index: 33, oracle: "LookupTableFlag_33" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_34", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_34", index: 34, oracle: "LookupTableFlag_34" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_35", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_35", index: 35, oracle: "LookupTableFlag_35" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_36", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_36", index: 36, oracle: "LookupTableFlag_36" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_37", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_37", index: 37, oracle: "LookupTableFlag_37" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_38", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_38", index: 38, oracle: "LookupTableFlag_38" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.LookupTableFlag_39", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.LookupTableFlag_39", index: 39, oracle: "LookupTableFlag_39" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_0", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_0", index: 40, oracle: "InstructionRa_0" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_1", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_1", index: 41, oracle: "InstructionRa_1" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_2", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_2", index: 42, oracle: "InstructionRa_2" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_3", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_3", index: 43, oracle: "InstructionRa_3" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_4", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_4", index: 44, oracle: "InstructionRa_4" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_5", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_5", index: 45, oracle: "InstructionRa_5" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_6", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_6", index: 46, oracle: "InstructionRa_6" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRa_7", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRa_7", index: 47, oracle: "InstructionRa_7" }, + Stage5SumcheckEvalPlan { symbol: "stage5.instruction_read_raf.eval.InstructionRafFlag", source: "stage5.sumcheck", name: "stage5.instruction_read_raf.eval.InstructionRafFlag", index: 48, oracle: "InstructionRafFlag" }, + Stage5SumcheckEvalPlan { symbol: "stage5.ram_ra_claim_reduction.eval.RamRa", source: "stage5.sumcheck", name: "stage5.ram_ra_claim_reduction.eval.RamRa", index: 0, oracle: "RamRa" }, + Stage5SumcheckEvalPlan { symbol: "stage5.registers_val_evaluation.eval.RdInc", source: "stage5.sumcheck", name: "stage5.registers_val_evaluation.eval.RdInc", index: 0, oracle: "RdInc" }, + Stage5SumcheckEvalPlan { symbol: "stage5.registers_val_evaluation.eval.RdWa", source: "stage5.sumcheck", name: "stage5.registers_val_evaluation.eval.RdWa", index: 1, oracle: "RdWa" }, +]; + +pub const STAGE5_POINT_SLICES: &[Stage5PointSlicePlan] = &[ + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.Cycle", source: "stage5.instruction_read_raf.instance", offset: 128, length: 18, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_0.address", source: "stage5.instruction_read_raf.instance", offset: 0, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_1.address", source: "stage5.instruction_read_raf.instance", offset: 16, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_2.address", source: "stage5.instruction_read_raf.instance", offset: 32, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_3.address", source: "stage5.instruction_read_raf.instance", offset: 48, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_4.address", source: "stage5.instruction_read_raf.instance", offset: 64, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_5.address", source: "stage5.instruction_read_raf.instance", offset: 80, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_6.address", source: "stage5.instruction_read_raf.instance", offset: 96, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_7.address", source: "stage5.instruction_read_raf.instance", offset: 112, length: 16, input: "stage5.instruction_read_raf.instance" }, + Stage5PointSlicePlan { symbol: "stage5.ram_ra_claim_reduction.point.RamAddress", source: "stage5.input.stage2.ram_raf.RamRa", offset: 0, length: 14, input: "stage5.input.stage2.ram_raf.RamRa" }, + Stage5PointSlicePlan { symbol: "stage5.registers_val_evaluation.point.RegisterAddress", source: "stage5.input.stage4.registers.RegistersVal", offset: 0, length: 7, input: "stage5.input.stage4.registers.RegistersVal" }, +]; + +pub const STAGE5_POINT_CONCATS: &[Stage5PointConcatPlan] = &[ + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_0", layout: "address_chunk_then_cycle", arity: 34, inputs: "stage5.instruction_read_raf.point.InstructionRa_0.address|stage5.instruction_read_raf.point.Cycle" }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_1", layout: "address_chunk_then_cycle", arity: 34, inputs: "stage5.instruction_read_raf.point.InstructionRa_1.address|stage5.instruction_read_raf.point.Cycle" }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_2", layout: "address_chunk_then_cycle", arity: 34, inputs: "stage5.instruction_read_raf.point.InstructionRa_2.address|stage5.instruction_read_raf.point.Cycle" }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_3", layout: "address_chunk_then_cycle", arity: 34, inputs: "stage5.instruction_read_raf.point.InstructionRa_3.address|stage5.instruction_read_raf.point.Cycle" }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_4", layout: "address_chunk_then_cycle", arity: 34, inputs: "stage5.instruction_read_raf.point.InstructionRa_4.address|stage5.instruction_read_raf.point.Cycle" }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_5", layout: "address_chunk_then_cycle", arity: 34, inputs: "stage5.instruction_read_raf.point.InstructionRa_5.address|stage5.instruction_read_raf.point.Cycle" }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_6", layout: "address_chunk_then_cycle", arity: 34, inputs: "stage5.instruction_read_raf.point.InstructionRa_6.address|stage5.instruction_read_raf.point.Cycle" }, + Stage5PointConcatPlan { symbol: "stage5.instruction_read_raf.point.InstructionRa_7", layout: "address_chunk_then_cycle", arity: 34, inputs: "stage5.instruction_read_raf.point.InstructionRa_7.address|stage5.instruction_read_raf.point.Cycle" }, + Stage5PointConcatPlan { symbol: "stage5.ram_ra_claim_reduction.point.RamRa", layout: "address_then_cycle", arity: 32, inputs: "stage5.ram_ra_claim_reduction.point.RamAddress|stage5.ram_ra_claim_reduction.instance" }, + Stage5PointConcatPlan { symbol: "stage5.registers_val_evaluation.point.RdWa", layout: "register_address_then_cycle", arity: 25, inputs: "stage5.registers_val_evaluation.point.RegisterAddress|stage5.registers_val_evaluation.instance" }, +]; +pub const STAGE5_OPENING_CLAIMS: &[Stage5OpeningClaimPlan] = &[ + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_0", oracle: "LookupTableFlag_0", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_0" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_1", oracle: "LookupTableFlag_1", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_1" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_2", oracle: "LookupTableFlag_2", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_2" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_3", oracle: "LookupTableFlag_3", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_3" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_4", oracle: "LookupTableFlag_4", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_4" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_5", oracle: "LookupTableFlag_5", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_5" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_6", oracle: "LookupTableFlag_6", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_6" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_7", oracle: "LookupTableFlag_7", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_7" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_8", oracle: "LookupTableFlag_8", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_8" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_9", oracle: "LookupTableFlag_9", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_9" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_10", oracle: "LookupTableFlag_10", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_10" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_11", oracle: "LookupTableFlag_11", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_11" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_12", oracle: "LookupTableFlag_12", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_12" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_13", oracle: "LookupTableFlag_13", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_13" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_14", oracle: "LookupTableFlag_14", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_14" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_15", oracle: "LookupTableFlag_15", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_15" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_16", oracle: "LookupTableFlag_16", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_16" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_17", oracle: "LookupTableFlag_17", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_17" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_18", oracle: "LookupTableFlag_18", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_18" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_19", oracle: "LookupTableFlag_19", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_19" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_20", oracle: "LookupTableFlag_20", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_20" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_21", oracle: "LookupTableFlag_21", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_21" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_22", oracle: "LookupTableFlag_22", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_22" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_23", oracle: "LookupTableFlag_23", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_23" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_24", oracle: "LookupTableFlag_24", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_24" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_25", oracle: "LookupTableFlag_25", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_25" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_26", oracle: "LookupTableFlag_26", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_26" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_27", oracle: "LookupTableFlag_27", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_27" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_28", oracle: "LookupTableFlag_28", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_28" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_29", oracle: "LookupTableFlag_29", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_29" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_30", oracle: "LookupTableFlag_30", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_30" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_31", oracle: "LookupTableFlag_31", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_31" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_32", oracle: "LookupTableFlag_32", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_32" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_33", oracle: "LookupTableFlag_33", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_33" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_34", oracle: "LookupTableFlag_34", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_34" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_35", oracle: "LookupTableFlag_35", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_35" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_36", oracle: "LookupTableFlag_36", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_36" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_37", oracle: "LookupTableFlag_37", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_37" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_38", oracle: "LookupTableFlag_38", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_38" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.LookupTableFlag_39", oracle: "LookupTableFlag_39", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.LookupTableFlag_39" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_0", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_0" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_1", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_1" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_2", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_2" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_3", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_3" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_4", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_4" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_5", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_5" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_6", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_6" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.InstructionRa_7", eval_source: "stage5.instruction_read_raf.eval.InstructionRa_7" }, + Stage5OpeningClaimPlan { symbol: "stage5.instruction_read_raf.opening.InstructionRafFlag", oracle: "InstructionRafFlag", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage5.instruction_read_raf.point.Cycle", eval_source: "stage5.instruction_read_raf.eval.InstructionRafFlag" }, + Stage5OpeningClaimPlan { symbol: "stage5.ram_ra_claim_reduction.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual", point_source: "stage5.ram_ra_claim_reduction.point.RamRa", eval_source: "stage5.ram_ra_claim_reduction.eval.RamRa" }, + Stage5OpeningClaimPlan { symbol: "stage5.registers_val_evaluation.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage5.registers_val_evaluation.instance", eval_source: "stage5.registers_val_evaluation.eval.RdInc" }, + Stage5OpeningClaimPlan { symbol: "stage5.registers_val_evaluation.opening.RdWa", oracle: "RdWa", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual", point_source: "stage5.registers_val_evaluation.point.RdWa", eval_source: "stage5.registers_val_evaluation.eval.RdWa" }, +]; + +pub const STAGE5_OPENING_EQUALITIES: &[Stage5OpeningClaimEqualityPlan] = &[ + Stage5OpeningClaimEqualityPlan { symbol: "stage5.instruction.lookup_output_claim_consistency", mode: "point_and_eval", lhs: "stage5.input.stage2.instruction.LookupOutput", rhs: "stage5.input.stage2.product_virtual.LookupOutput" }, +]; + +pub const STAGE5_OPENING_BATCHES: &[Stage5OpeningBatchPlan] = &[ + Stage5OpeningBatchPlan { symbol: "stage5.openings", stage: "stage5", proof_slot: "stage5.openings", policy: "jolt_stage5_output_order", count: 52, ordered_claims: "stage5.instruction_read_raf.opening.LookupTableFlag_0|stage5.instruction_read_raf.opening.LookupTableFlag_1|stage5.instruction_read_raf.opening.LookupTableFlag_2|stage5.instruction_read_raf.opening.LookupTableFlag_3|stage5.instruction_read_raf.opening.LookupTableFlag_4|stage5.instruction_read_raf.opening.LookupTableFlag_5|stage5.instruction_read_raf.opening.LookupTableFlag_6|stage5.instruction_read_raf.opening.LookupTableFlag_7|stage5.instruction_read_raf.opening.LookupTableFlag_8|stage5.instruction_read_raf.opening.LookupTableFlag_9|stage5.instruction_read_raf.opening.LookupTableFlag_10|stage5.instruction_read_raf.opening.LookupTableFlag_11|stage5.instruction_read_raf.opening.LookupTableFlag_12|stage5.instruction_read_raf.opening.LookupTableFlag_13|stage5.instruction_read_raf.opening.LookupTableFlag_14|stage5.instruction_read_raf.opening.LookupTableFlag_15|stage5.instruction_read_raf.opening.LookupTableFlag_16|stage5.instruction_read_raf.opening.LookupTableFlag_17|stage5.instruction_read_raf.opening.LookupTableFlag_18|stage5.instruction_read_raf.opening.LookupTableFlag_19|stage5.instruction_read_raf.opening.LookupTableFlag_20|stage5.instruction_read_raf.opening.LookupTableFlag_21|stage5.instruction_read_raf.opening.LookupTableFlag_22|stage5.instruction_read_raf.opening.LookupTableFlag_23|stage5.instruction_read_raf.opening.LookupTableFlag_24|stage5.instruction_read_raf.opening.LookupTableFlag_25|stage5.instruction_read_raf.opening.LookupTableFlag_26|stage5.instruction_read_raf.opening.LookupTableFlag_27|stage5.instruction_read_raf.opening.LookupTableFlag_28|stage5.instruction_read_raf.opening.LookupTableFlag_29|stage5.instruction_read_raf.opening.LookupTableFlag_30|stage5.instruction_read_raf.opening.LookupTableFlag_31|stage5.instruction_read_raf.opening.LookupTableFlag_32|stage5.instruction_read_raf.opening.LookupTableFlag_33|stage5.instruction_read_raf.opening.LookupTableFlag_34|stage5.instruction_read_raf.opening.LookupTableFlag_35|stage5.instruction_read_raf.opening.LookupTableFlag_36|stage5.instruction_read_raf.opening.LookupTableFlag_37|stage5.instruction_read_raf.opening.LookupTableFlag_38|stage5.instruction_read_raf.opening.LookupTableFlag_39|stage5.instruction_read_raf.opening.InstructionRa_0|stage5.instruction_read_raf.opening.InstructionRa_1|stage5.instruction_read_raf.opening.InstructionRa_2|stage5.instruction_read_raf.opening.InstructionRa_3|stage5.instruction_read_raf.opening.InstructionRa_4|stage5.instruction_read_raf.opening.InstructionRa_5|stage5.instruction_read_raf.opening.InstructionRa_6|stage5.instruction_read_raf.opening.InstructionRa_7|stage5.instruction_read_raf.opening.InstructionRafFlag|stage5.ram_ra_claim_reduction.opening.RamRa|stage5.registers_val_evaluation.opening.RdInc|stage5.registers_val_evaluation.opening.RdWa", claim_operands: "stage5.instruction_read_raf.opening.LookupTableFlag_0|stage5.instruction_read_raf.opening.LookupTableFlag_1|stage5.instruction_read_raf.opening.LookupTableFlag_2|stage5.instruction_read_raf.opening.LookupTableFlag_3|stage5.instruction_read_raf.opening.LookupTableFlag_4|stage5.instruction_read_raf.opening.LookupTableFlag_5|stage5.instruction_read_raf.opening.LookupTableFlag_6|stage5.instruction_read_raf.opening.LookupTableFlag_7|stage5.instruction_read_raf.opening.LookupTableFlag_8|stage5.instruction_read_raf.opening.LookupTableFlag_9|stage5.instruction_read_raf.opening.LookupTableFlag_10|stage5.instruction_read_raf.opening.LookupTableFlag_11|stage5.instruction_read_raf.opening.LookupTableFlag_12|stage5.instruction_read_raf.opening.LookupTableFlag_13|stage5.instruction_read_raf.opening.LookupTableFlag_14|stage5.instruction_read_raf.opening.LookupTableFlag_15|stage5.instruction_read_raf.opening.LookupTableFlag_16|stage5.instruction_read_raf.opening.LookupTableFlag_17|stage5.instruction_read_raf.opening.LookupTableFlag_18|stage5.instruction_read_raf.opening.LookupTableFlag_19|stage5.instruction_read_raf.opening.LookupTableFlag_20|stage5.instruction_read_raf.opening.LookupTableFlag_21|stage5.instruction_read_raf.opening.LookupTableFlag_22|stage5.instruction_read_raf.opening.LookupTableFlag_23|stage5.instruction_read_raf.opening.LookupTableFlag_24|stage5.instruction_read_raf.opening.LookupTableFlag_25|stage5.instruction_read_raf.opening.LookupTableFlag_26|stage5.instruction_read_raf.opening.LookupTableFlag_27|stage5.instruction_read_raf.opening.LookupTableFlag_28|stage5.instruction_read_raf.opening.LookupTableFlag_29|stage5.instruction_read_raf.opening.LookupTableFlag_30|stage5.instruction_read_raf.opening.LookupTableFlag_31|stage5.instruction_read_raf.opening.LookupTableFlag_32|stage5.instruction_read_raf.opening.LookupTableFlag_33|stage5.instruction_read_raf.opening.LookupTableFlag_34|stage5.instruction_read_raf.opening.LookupTableFlag_35|stage5.instruction_read_raf.opening.LookupTableFlag_36|stage5.instruction_read_raf.opening.LookupTableFlag_37|stage5.instruction_read_raf.opening.LookupTableFlag_38|stage5.instruction_read_raf.opening.LookupTableFlag_39|stage5.instruction_read_raf.opening.InstructionRa_0|stage5.instruction_read_raf.opening.InstructionRa_1|stage5.instruction_read_raf.opening.InstructionRa_2|stage5.instruction_read_raf.opening.InstructionRa_3|stage5.instruction_read_raf.opening.InstructionRa_4|stage5.instruction_read_raf.opening.InstructionRa_5|stage5.instruction_read_raf.opening.InstructionRa_6|stage5.instruction_read_raf.opening.InstructionRa_7|stage5.instruction_read_raf.opening.InstructionRafFlag|stage5.ram_ra_claim_reduction.opening.RamRa|stage5.registers_val_evaluation.opening.RdInc|stage5.registers_val_evaluation.opening.RdWa" }, +]; +pub const STAGE5_PROGRAM: Stage5VerifierProgramPlan = Stage5CpuProgramPlan { + role: "verifier", + params: STAGE5_PARAMS, + steps: STAGE5_PROGRAM_STEPS, + transcript_squeezes: STAGE5_TRANSCRIPT_SQUEEZES, + transcript_absorb_bytes: STAGE5_TRANSCRIPT_ABSORB_BYTES, + opening_inputs: STAGE5_OPENING_INPUTS, + field_constants: STAGE5_FIELD_CONSTANTS, + field_exprs: STAGE5_FIELD_EXPRS, + kernels: STAGE5_KERNELS, + claims: STAGE5_SUMCHECK_CLAIMS, + batches: STAGE5_SUMCHECK_BATCHES, + drivers: STAGE5_SUMCHECK_DRIVERS, + instance_results: STAGE5_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE5_SUMCHECK_EVALS, + point_slices: STAGE5_POINT_SLICES, + point_concats: STAGE5_POINT_CONCATS, + opening_claims: STAGE5_OPENING_CLAIMS, + opening_equalities: STAGE5_OPENING_EQUALITIES, + opening_batches: STAGE5_OPENING_BATCHES, +}; + +pub fn verify_stage5( + proof: &Stage5Proof, + opening_inputs: &[Stage5OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage5Error> +where + T: Transcript, +{ + verify_stage5_with_program(&STAGE5_PROGRAM, proof, opening_inputs, transcript) +} + +pub fn verify_stage5_with_program( + program: &'static Stage5VerifierProgramPlan, + proof: &Stage5Proof, + opening_inputs: &[Stage5OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage5Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage5Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + let mut artifacts = Stage5ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage5Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage5_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "transcript_absorb_bytes" => { + let absorb = find_plan(program.transcript_absorb_bytes, step.symbol).ok_or( + VerifyStage5Error::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage5_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage5Error::MissingProof { + driver: step.symbol, + })?; + verify_stage5_driver(program, driver, proof, &mut store, transcript, &mut artifacts)?; + } + _ => { + return Err(VerifyStage5Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage5 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage5_verifier_program() -> &'static Stage5VerifierProgramPlan { + &STAGE5_PROGRAM +} + +fn verify_stage5_squeeze( + program: &'static Stage5VerifierProgramPlan, + squeeze: &'static Stage5TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage5ExecutionArtifacts, +) -> Result<(), VerifyStage5Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage5Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage5Error::from)?; + artifacts.challenge_vectors.push(Stage5ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn absorb_stage5_bytes(absorb: &'static Stage5TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn verify_stage5_driver( + program: &'static Stage5VerifierProgramPlan, + driver: &'static Stage5SumcheckDriverPlan, + proof: &Stage5Proof, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage5ExecutionArtifacts, +) -> Result<(), VerifyStage5Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage5Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage5.batched" => { + verify_batched_stage5(program, driver, proof, store, transcript)? + } + _ => return Err(VerifyStage5Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage5( + program: &'static Stage5VerifierProgramPlan, + driver: &'static Stage5SumcheckDriverPlan, + proof: &Stage5SumcheckOutput, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage5Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim(program, driver, store, evals, point, batching_coeffs) + }, + |store, verified| observe_stage5_sumcheck_output(program, store, verified), + |driver, error| VerifyStage5Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage5_sumcheck_output( + program: &'static Stage5VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage5SumcheckOutput, +) -> Result<(), VerifyStage5Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "instruction_read_raf" => { + point = normalize_instruction_read_raf_point(&point, "stage5.instruction_read_raf.point")?; + } + _ => { + return Err(VerifyStage5Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage5Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage5Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage5Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage5Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage5Error::InvalidProof { driver, reason }, + |symbol| VerifyStage5Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage5VerifierProgramPlan, + driver: &'static Stage5SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage5NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage5Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage5Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(""); + let value = match relation { + "jolt.stage5.instruction_read_raf" => { + expected_instruction_read_raf(store, evals, local_point)? + } + "jolt.stage5.ram_ra_claim_reduction" => { + expected_ram_ra_claim_reduction(store, evals, local_point)? + } + "jolt.stage5.registers_val_evaluation" => { + expected_registers_val_evaluation(store, evals, local_point)? + } + _ => return Err(VerifyStage5Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_instruction_read_raf( + store: &super::common::ValueStore, + evals: &[Stage5NamedEval], + local_point: &[Fr], +) -> Result { + const LOG_K: usize = 128; + const XLEN: usize = 64; + + if local_point.len() < LOG_K { + return Err(VerifyStage5Error::InvalidInputLength { + input: "stage5.instruction_read_raf.point", + expected: LOG_K, + actual: local_point.len(), + }); + } + + let (r_address_prime, r_cycle) = local_point.split_at(LOG_K); + let r_cycle_prime = reverse_slice(r_cycle); + let r_reduction = super::common::store_point(store, "stage5.input.stage2.instruction.LookupOutput")?; + let eq_eval_r_reduction = EqPolynomial::::mle(r_reduction, &r_cycle_prime); + + let left_operand_eval = operand_polynomial_eval(r_address_prime, true); + let right_operand_eval = operand_polynomial_eval(r_address_prime, false); + let identity_poly_eval = identity_polynomial_eval(r_address_prime); + + let table_values = LookupTableKind::::all() + .iter() + .map(|table| table.evaluate_mle::(r_address_prime)) + .collect::>(); + let table_flag_claims = indexed_evals_by_prefix( + evals, + "stage5.instruction_read_raf.eval.LookupTableFlag_", + table_values.len(), + )?; + let val_claim = table_values + .into_iter() + .zip(table_flag_claims) + .map(|(table_value, flag_claim)| table_value * flag_claim) + .sum::(); + + let ra_claim = indexed_evals_by_prefix_any( + evals, + "stage5.instruction_read_raf.eval.InstructionRa_", + )? + .into_iter() + .product::(); + let raf_flag_claim = eval_by_name( + evals, + "stage5.instruction_read_raf.eval.InstructionRafFlag", + )?; + let gamma = super::common::store_scalar(store, "stage5.instruction_read_raf.gamma")?; + + let raf_claim = (Fr::from_u64(1) - raf_flag_claim) + * (left_operand_eval + gamma * right_operand_eval) + + raf_flag_claim * gamma * identity_poly_eval; + Ok(eq_eval_r_reduction * ra_claim * (val_claim + gamma * raf_claim)) +} + +fn expected_ram_ra_claim_reduction( + store: &super::common::ValueStore, + evals: &[Stage5NamedEval], + local_point: &[Fr], +) -> Result { + let r_cycle_reduced = reverse_slice(local_point); + let r_cycle_raf = suffix_point( + super::common::store_point(store, "stage5.input.stage2.ram_raf.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage2.ram_raf.RamRa", + )?; + let r_cycle_rw = suffix_point( + super::common::store_point(store, "stage5.input.stage2.ram_read_write.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage2.ram_read_write.RamRa", + )?; + let r_cycle_val = suffix_point( + super::common::store_point(store, "stage5.input.stage4.ram_val_check.RamRa")?, + r_cycle_reduced.len(), + "stage5.input.stage4.ram_val_check.RamRa", + )?; + let gamma = super::common::store_scalar(store, "stage5.ram_ra_claim_reduction.gamma")?; + let eq_combined = EqPolynomial::::mle(r_cycle_raf, &r_cycle_reduced) + + gamma * EqPolynomial::::mle(r_cycle_rw, &r_cycle_reduced) + + gamma.square() * EqPolynomial::::mle(r_cycle_val, &r_cycle_reduced); + let ram_ra = eval_by_name(evals, "stage5.ram_ra_claim_reduction.eval.RamRa")?; + Ok(eq_combined * ram_ra) +} + +fn expected_registers_val_evaluation( + store: &super::common::ValueStore, + evals: &[Stage5NamedEval], + local_point: &[Fr], +) -> Result { + let registers_val_point = super::common::store_point(store, "stage5.input.stage4.registers.RegistersVal")?; + let r_cycle = suffix_point( + registers_val_point, + local_point.len(), + "stage5.input.stage4.registers.RegistersVal", + )?; + let r_reduced = reverse_slice(local_point); + let lt_eval = lt_polynomial_eval(&r_reduced, r_cycle); + let rd_inc = eval_by_name(evals, "stage5.registers_val_evaluation.eval.RdInc")?; + let rd_wa = eval_by_name(evals, "stage5.registers_val_evaluation.eval.RdWa")?; + Ok(rd_inc * rd_wa * lt_eval) +} + diff --git a/crates/jolt-verifier/src/stages/stage6.rs b/crates/jolt-verifier/src/stages/stage6.rs new file mode 100644 index 0000000000..9b4752f856 --- /dev/null +++ b/crates/jolt-verifier/src/stages/stage6.rs @@ -0,0 +1,993 @@ +#![allow(dead_code)] + +use super::common::{batch_claims, expected_stage67_booleanity, expected_stage67_bytecode_read_raf, expected_stage67_hamming_booleanity, expected_stage67_inc_claim_reduction, expected_stage67_instruction_ra_virtual, expected_stage67_ram_ra_virtual, find_batch, find_plan, normalize_bytecode_read_raf_point, normalize_instruction_read_raf_point, stage67_trace_rounds, Stage67BytecodeEntry, Stage67BytecodeSymbols, Stage67RelationSymbols}; +use jolt_field::{Field, Fr}; +use jolt_sumcheck::SumcheckError; +use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript}; + +pub type Stage6NamedEval = super::common::StageNamedEval; +pub type Stage6SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage6ChallengeVector = super::common::StageChallengeVector; +pub type Stage6ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage6Proof = super::common::StageProof; +pub type Stage6OpeningInputValue = super::common::StageOpeningInputValue; + +pub use super::common::{ + FieldConstantPlan as Stage6FieldConstantPlan, FieldExprPlan as Stage6FieldExprPlan, + KernelPlan as Stage6KernelPlan, OpeningBatchPlan as Stage6OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage6OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage6OpeningClaimPlan, OpeningInputPlan as Stage6OpeningInputPlan, + PointConcatPlan as Stage6PointConcatPlan, PointSlicePlan as Stage6PointSlicePlan, + PointZeroPlan as Stage6PointZeroPlan, ProgramStepPlan as Stage6ProgramStepPlan, + StageParams as Stage6Params, StageProgramPlan as Stage6CpuProgramPlan, + SumcheckBatchPlan as Stage6SumcheckBatchPlan, + SumcheckClaimPlan as Stage6SumcheckClaimPlan, SumcheckDriverPlan as Stage6SumcheckDriverPlan, + SumcheckEvalPlan as Stage6SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage6SumcheckInstanceResultPlan, + TranscriptAbsorbBytesPlan as Stage6TranscriptAbsorbBytesPlan, + TranscriptSqueezePlan as Stage6TranscriptSqueezePlan, +}; + +pub type DefaultStage6Transcript = Blake2bTranscript; +pub type Stage6VerifierProgramPlan = Stage6CpuProgramPlan; + +#[derive(Clone, Debug)] +pub struct Stage6BytecodeEntry { + pub address: Fr, + pub imm: Fr, + pub circuit_flags: [bool; 14], + pub rd: Option, + pub rs1: Option, + pub rs2: Option, + pub lookup_table: Option, + pub is_interleaved: bool, + pub is_branch: bool, + pub left_is_rs1: bool, + pub left_is_pc: bool, + pub right_is_rs2: bool, + pub right_is_imm: bool, + pub is_noop: bool, +} + +impl Stage67BytecodeEntry for Stage6BytecodeEntry { + fn address(&self) -> Fr { self.address } + fn imm(&self) -> Fr { self.imm } + fn circuit_flags(&self) -> &[bool; 14] { &self.circuit_flags } + fn rd(&self) -> Option { self.rd } + fn rs1(&self) -> Option { self.rs1 } + fn rs2(&self) -> Option { self.rs2 } + fn lookup_table(&self) -> Option { self.lookup_table } + fn is_interleaved(&self) -> bool { self.is_interleaved } + fn is_branch(&self) -> bool { self.is_branch } + fn left_is_rs1(&self) -> bool { self.left_is_rs1 } + fn left_is_pc(&self) -> bool { self.left_is_pc } + fn right_is_rs2(&self) -> bool { self.right_is_rs2 } + fn right_is_imm(&self) -> bool { self.right_is_imm } + fn is_noop(&self) -> bool { self.is_noop } +} + + +#[derive(Clone, Debug)] +pub struct Stage6BytecodeReadRafData { + pub entries: Vec, + pub entry_bytecode_index: usize, + pub num_lookup_tables: usize, +} + +#[derive(Clone, Debug)] +pub struct Stage6VerifierData { + pub bytecode_read_raf: Option, +} + +const STAGE6_RELATION_SYMBOLS: Stage67RelationSymbols = Stage67RelationSymbols { + hamming_booleanity_relation: "jolt.stage6.hamming_booleanity", + hamming_booleanity_instance: "stage6.hamming_booleanity.instance", + booleanity_point: "stage6.booleanity.point", + stage5_instruction_ra0: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + booleanity_combined_point: "stage6.booleanity.combined_point", + booleanity_gamma: "stage6.booleanity.gamma", + booleanity_instruction_ra_prefix: "stage6.booleanity.eval.InstructionRa_", + booleanity_bytecode_ra_prefix: "stage6.booleanity.eval.BytecodeRa_", + booleanity_ram_ra_prefix: "stage6.booleanity.eval.RamRa_", + hamming_weight_eval: "stage6.hamming_booleanity.eval.HammingWeight", + hamming_lookup_output: "stage6.input.stage1.LookupOutput", + ram_ra_virtual_cycle: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + ram_ra_virtual_eval_prefix: "stage6.ram_ra_virtual.eval.RamRa_", + instruction_ra_virtual_cycle: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", + instruction_ra_virtual_eval_prefix: "stage6.instruction_ra_virtual.eval.InstructionRa_", + instruction_ra_virtual_input_prefix: "stage6.input.stage5.instruction_read_raf.InstructionRa_", + instruction_ra_virtual_gamma: "stage6.instruction_ra_virtual.gamma", + inc_ram_stage2: "stage6.input.stage2.ram_read_write.RamInc", + inc_ram_stage4: "stage6.input.stage4.ram_val_check.RamInc", + inc_rd_stage4: "stage6.input.stage4.registers_read_write.RdInc", + inc_rd_stage5: "stage6.input.stage5.registers_val_evaluation.RdInc", + inc_gamma: "stage6.inc_claim_reduction.gamma", + inc_ram_eval: "stage6.inc_claim_reduction.eval.RamInc", + inc_rd_eval: "stage6.inc_claim_reduction.eval.RdInc", +}; + +const STAGE6_BYTECODE_SYMBOLS: Stage67BytecodeSymbols = Stage67BytecodeSymbols { + point: "stage6.bytecode_read_raf.point", + gamma: "stage6.bytecode_read_raf.gamma", + bytecode_ra_eval_prefix: "stage6.bytecode_read_raf.eval.BytecodeRa_", + entries: "stage6.bytecode_read_raf.entries", + entry_bytecode_index: "stage6.bytecode_read_raf.entry_bytecode_index", + stage_gammas: [ + "stage6.bytecode_read_raf.stage1_gamma", + "stage6.bytecode_read_raf.stage2_gamma", + "stage6.bytecode_read_raf.stage3_gamma", + "stage6.bytecode_read_raf.stage4_gamma", + "stage6.bytecode_read_raf.stage5_gamma", + ], + stage_cycle_points: [ + "stage6.input.stage1.Imm", + "stage6.input.stage2.OpFlagJump", + "stage6.input.stage3.spartan_shift.UnexpandedPC", + "stage6.input.stage4.Rs1Ra", + "stage6.input.stage5.registers_val_evaluation.RdWa", + ], + stage4_register_point: "stage6.input.stage4.Rs1Ra", + stage5_register_point: "stage6.input.stage5.registers_val_evaluation.RdWa", + entry_rd: "stage6.bytecode.entry.rd", + entry_rs1: "stage6.bytecode.entry.rs1", + entry_rs2: "stage6.bytecode.entry.rs2", + entry_lookup_table: "stage6.bytecode.entry.lookup_table", +}; + +#[derive(Debug)] +pub enum VerifyStage6Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage6Error); + +pub const STAGE6_PARAMS: Stage6Params = Stage6Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE6_PROGRAM_STEPS: &[Stage6ProgramStepPlan] = &[ + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage1_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage2_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage3_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage4_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.bytecode_read_raf.stage5_gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.booleanity.gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.instruction_ra_virtual.gamma" }, + Stage6ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage6.inc_claim_reduction.gamma" }, + Stage6ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage6.sumcheck" }, +]; + +pub const STAGE6_TRANSCRIPT_SQUEEZES: &[Stage6TranscriptSqueezePlan] = &[ + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.gamma", label: "bc_raf_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage1_gamma", label: "bc_raf_stage1_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage2_gamma", label: "bc_raf_stage2_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage3_gamma", label: "bc_raf_stage3_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage4_gamma", label: "bc_raf_stage4_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.bytecode_read_raf.stage5_gamma", label: "bc_raf_stage5_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.booleanity.gamma", label: "booleanity_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.instruction_ra_virtual.gamma", label: "inst_ra_virtual_gamma", kind: "challenge_scalar", count: 1 }, + Stage6TranscriptSqueezePlan { symbol: "stage6.inc_claim_reduction.gamma", label: "inc_reduction_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE6_TRANSCRIPT_ABSORB_BYTES: &[Stage6TranscriptAbsorbBytesPlan] = &[ + +]; + +pub const STAGE6_OPENING_INPUTS: &[Stage6OpeningInputPlan] = &[ + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.UnexpandedPC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.Imm", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.Imm", oracle: "Imm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagAddOperands", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagAddOperands", oracle: "OpFlagAddOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagSubtractOperands", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagSubtractOperands", oracle: "OpFlagSubtractOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagMultiplyOperands", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagMultiplyOperands", oracle: "OpFlagMultiplyOperands", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagLoad", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagLoad", oracle: "OpFlagLoad", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagStore", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagStore", oracle: "OpFlagStore", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagJump", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagJump", oracle: "OpFlagJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagWriteLookupOutputToRD", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagWriteLookupOutputToRD", oracle: "OpFlagWriteLookupOutputToRD", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagVirtualInstruction", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagAssert", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagAssert", oracle: "OpFlagAssert", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagDoNotUpdateUnexpandedPC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagDoNotUpdateUnexpandedPC", oracle: "OpFlagDoNotUpdateUnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagAdvice", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagAdvice", oracle: "OpFlagAdvice", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagIsCompressed", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagIsCompressed", oracle: "OpFlagIsCompressed", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagIsFirstInSequence", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagIsFirstInSequence", oracle: "OpFlagIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.OpFlagIsLastInSequence", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.OpFlagIsLastInSequence", oracle: "OpFlagIsLastInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.OpFlagJump", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.OpFlagJump", oracle: "OpFlagJump", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.InstructionFlagBranch", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.InstructionFlagBranch", oracle: "InstructionFlagBranch", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.OpFlagWriteLookupOutputToRD", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.OpFlagWriteLookupOutputToRD", oracle: "OpFlagWriteLookupOutputToRD", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.OpFlagVirtualInstruction", source_stage: "stage2", source_claim: "stage2.product_virtual.remainder.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.Imm", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.Imm", oracle: "Imm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.UnexpandedPC", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.UnexpandedPC", oracle: "UnexpandedPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsRs1Value", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.InstructionFlagLeftOperandIsRs1Value", oracle: "InstructionFlagLeftOperandIsRs1Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsPC", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.InstructionFlagLeftOperandIsPC", oracle: "InstructionFlagLeftOperandIsPC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsRs2Value", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.InstructionFlagRightOperandIsRs2Value", oracle: "InstructionFlagRightOperandIsRs2Value", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsImm", source_stage: "stage3", source_claim: "stage3.instruction_input.opening.InstructionFlagRightOperandIsImm", oracle: "InstructionFlagRightOperandIsImm", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.InstructionFlagIsNoop", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.InstructionFlagIsNoop", oracle: "InstructionFlagIsNoop", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.OpFlagVirtualInstruction", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.OpFlagVirtualInstruction", oracle: "OpFlagVirtualInstruction", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.OpFlagIsFirstInSequence", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.OpFlagIsFirstInSequence", oracle: "OpFlagIsFirstInSequence", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.RdWa", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.RdWa", oracle: "RdWa", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.Rs1Ra", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.Rs1Ra", oracle: "Rs1Ra", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.Rs2Ra", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.Rs2Ra", oracle: "Rs2Ra", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.registers_val_evaluation.RdWa", source_stage: "stage5", source_claim: "stage5.registers_val_evaluation.opening.RdWa", oracle: "RdWa", domain: "jolt.stage4_registers_rw_domain", point_arity: 25, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.InstructionRafFlag", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRafFlag", oracle: "InstructionRafFlag", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_0", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_0", oracle: "LookupTableFlag_0", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_1", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_1", oracle: "LookupTableFlag_1", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_2", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_2", oracle: "LookupTableFlag_2", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_3", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_3", oracle: "LookupTableFlag_3", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_4", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_4", oracle: "LookupTableFlag_4", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_5", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_5", oracle: "LookupTableFlag_5", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_6", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_6", oracle: "LookupTableFlag_6", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_7", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_7", oracle: "LookupTableFlag_7", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_8", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_8", oracle: "LookupTableFlag_8", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_9", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_9", oracle: "LookupTableFlag_9", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_10", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_10", oracle: "LookupTableFlag_10", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_11", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_11", oracle: "LookupTableFlag_11", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_12", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_12", oracle: "LookupTableFlag_12", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_13", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_13", oracle: "LookupTableFlag_13", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_14", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_14", oracle: "LookupTableFlag_14", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_15", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_15", oracle: "LookupTableFlag_15", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_16", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_16", oracle: "LookupTableFlag_16", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_17", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_17", oracle: "LookupTableFlag_17", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_18", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_18", oracle: "LookupTableFlag_18", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_19", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_19", oracle: "LookupTableFlag_19", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_20", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_20", oracle: "LookupTableFlag_20", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_21", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_21", oracle: "LookupTableFlag_21", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_22", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_22", oracle: "LookupTableFlag_22", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_23", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_23", oracle: "LookupTableFlag_23", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_24", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_24", oracle: "LookupTableFlag_24", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_25", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_25", oracle: "LookupTableFlag_25", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_26", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_26", oracle: "LookupTableFlag_26", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_27", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_27", oracle: "LookupTableFlag_27", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_28", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_28", oracle: "LookupTableFlag_28", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_29", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_29", oracle: "LookupTableFlag_29", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_30", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_30", oracle: "LookupTableFlag_30", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_31", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_31", oracle: "LookupTableFlag_31", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_32", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_32", oracle: "LookupTableFlag_32", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_33", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_33", oracle: "LookupTableFlag_33", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_34", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_34", oracle: "LookupTableFlag_34", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_35", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_35", oracle: "LookupTableFlag_35", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_36", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_36", oracle: "LookupTableFlag_36", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_37", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_37", oracle: "LookupTableFlag_37", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_38", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_38", oracle: "LookupTableFlag_38", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.LookupTableFlag_39", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.LookupTableFlag_39", oracle: "LookupTableFlag_39", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.PC", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.PC", oracle: "PC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage3.spartan_shift.PC", source_stage: "stage3", source_claim: "stage3.spartan_shift.opening.PC", oracle: "PC", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", source_stage: "stage5", source_claim: "stage5.ram_ra_claim_reduction.opening.RamRa", oracle: "RamRa", domain: "jolt.stage2_ram_rw_domain", point_arity: 32, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", source_stage: "stage5", source_claim: "stage5.instruction_read_raf.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.stage5_instruction_ra_chunk_domain", point_arity: 34, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage1.LookupOutput", source_stage: "stage1", source_claim: "stage1.outer_remaining.opening.LookupOutput", oracle: "LookupOutput", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage2.ram_read_write.RamInc", source_stage: "stage2", source_claim: "stage2.ram_read_write.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.ram_val_check.RamInc", source_stage: "stage4", source_claim: "stage4.ram_val_check.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage4.registers_read_write.RdInc", source_stage: "stage4", source_claim: "stage4.registers_read_write.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed" }, + Stage6OpeningInputPlan { symbol: "stage6.input.stage5.registers_val_evaluation.RdInc", source_stage: "stage5", source_claim: "stage5.registers_val_evaluation.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed" }, +]; + +pub const STAGE6_FIELD_CONSTANTS: &[Stage6FieldConstantPlan] = &[ + Stage6FieldConstantPlan { symbol: "stage6.zero", field: "bn254_fr", value: 0 }, +]; + +macro_rules! stage6_field_expr { + ($symbol:literal, $formula:literal, $operands:literal) => { + Stage6FieldExprPlan { symbol: $symbol, kind: "op", formula: $formula, operands: $operands } + }; +} + +#[rustfmt::skip] +pub const STAGE6_FIELD_EXPRS: &[Stage6FieldExprPlan] = &[ + stage6_field_expr!("stage6.booleanity.gamma_sq_0", "field.pow:0", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_1", "field.pow:2", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_2", "field.pow:4", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_3", "field.pow:6", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_4", "field.pow:8", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_5", "field.pow:10", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_6", "field.pow:12", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_7", "field.pow:14", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_sq_8", "field.pow:16", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_9", "field.pow:18", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_10", "field.pow:20", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_11", "field.pow:22", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_12", "field.pow:24", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_13", "field.pow:26", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_14", "field.pow:28", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_15", "field.pow:30", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_sq_16", "field.pow:32", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_17", "field.pow:34", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_18", "field.pow:36", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_19", "field.pow:38", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_20", "field.pow:40", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_21", "field.pow:42", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_22", "field.pow:44", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_23", "field.pow:46", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_sq_24", "field.pow:48", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_25", "field.pow:50", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_26", "field.pow:52", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_27", "field.pow:54", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_28", "field.pow:56", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_29", "field.pow:58", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_30", "field.pow:60", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_31", "field.pow:62", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_sq_32", "field.pow:64", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_33", "field.pow:66", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_34", "field.pow:68", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_35", "field.pow:70", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_36", "field.pow:72", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_37", "field.pow:74", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_38", "field.pow:76", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_sq_39", "field.pow:78", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_pow_0", "field.pow:0", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_1", "field.pow:1", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_2", "field.pow:2", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_3", "field.pow:3", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_4", "field.pow:4", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_5", "field.pow:5", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_6", "field.pow:6", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_7", "field.pow:7", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_pow_8", "field.pow:8", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_9", "field.pow:9", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_10", "field.pow:10", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_11", "field.pow:11", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_12", "field.pow:12", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_13", "field.pow:13", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_14", "field.pow:14", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_15", "field.pow:15", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_pow_16", "field.pow:16", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_17", "field.pow:17", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_18", "field.pow:18", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_19", "field.pow:19", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_20", "field.pow:20", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_21", "field.pow:21", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_22", "field.pow:22", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_23", "field.pow:23", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_pow_24", "field.pow:24", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_25", "field.pow:25", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_26", "field.pow:26", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_27", "field.pow:27", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_28", "field.pow:28", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_29", "field.pow:29", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_30", "field.pow:30", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_31", "field.pow:31", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.booleanity.gamma_pow_32", "field.pow:32", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_33", "field.pow:33", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_34", "field.pow:34", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_35", "field.pow:35", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_36", "field.pow:36", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_37", "field.pow:37", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_38", "field.pow:38", "stage6.booleanity.gamma"), stage6_field_expr!("stage6.booleanity.gamma_pow_39", "field.pow:39", "stage6.booleanity.gamma"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term1.stage_gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term1.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term1.stage_gamma_pow|stage6.input.stage1.Imm"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term2.stage_gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term2.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term2.stage_gamma_pow|stage6.input.stage1.OpFlagAddOperands"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term3.stage_gamma_pow", "field.pow:3", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term3.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term3.stage_gamma_pow|stage6.input.stage1.OpFlagSubtractOperands"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term4.stage_gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term4.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term4.stage_gamma_pow|stage6.input.stage1.OpFlagMultiplyOperands"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term5.stage_gamma_pow", "field.pow:5", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term5.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term5.stage_gamma_pow|stage6.input.stage1.OpFlagLoad"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term6.stage_gamma_pow", "field.pow:6", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term6.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term6.stage_gamma_pow|stage6.input.stage1.OpFlagStore"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term7.stage_gamma_pow", "field.pow:7", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term7.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term7.stage_gamma_pow|stage6.input.stage1.OpFlagJump"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term8.stage_gamma_pow", "field.pow:8", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term8.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term8.stage_gamma_pow|stage6.input.stage1.OpFlagWriteLookupOutputToRD"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term9.stage_gamma_pow", "field.pow:9", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term9.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term9.stage_gamma_pow|stage6.input.stage1.OpFlagVirtualInstruction"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term10.stage_gamma_pow", "field.pow:10", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term10.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term10.stage_gamma_pow|stage6.input.stage1.OpFlagAssert"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term11.stage_gamma_pow", "field.pow:11", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term11.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term11.stage_gamma_pow|stage6.input.stage1.OpFlagDoNotUpdateUnexpandedPC"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term12.stage_gamma_pow", "field.pow:12", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term12.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term12.stage_gamma_pow|stage6.input.stage1.OpFlagAdvice"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term13.stage_gamma_pow", "field.pow:13", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term13.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term13.stage_gamma_pow|stage6.input.stage1.OpFlagIsCompressed"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term14.stage_gamma_pow", "field.pow:14", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term14.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term14.stage_gamma_pow|stage6.input.stage1.OpFlagIsFirstInSequence"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term15.stage_gamma_pow", "field.pow:15", "stage6.bytecode_read_raf.stage1_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term15.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term15.stage_gamma_pow|stage6.input.stage1.OpFlagIsLastInSequence"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term16.gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term16.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term16.gamma_pow|stage6.input.stage2.OpFlagJump"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term17.stage_gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.stage2_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term17.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term17.stage_gamma_pow|stage6.input.stage2.InstructionFlagBranch"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term17.gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term17.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term17.gamma_pow|stage6.bytecode_read_raf.claim.term17.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term18.stage_gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.stage2_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term18.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term18.stage_gamma_pow|stage6.input.stage2.OpFlagWriteLookupOutputToRD"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term18.gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term18.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term18.gamma_pow|stage6.bytecode_read_raf.claim.term18.stage_gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term19.stage_gamma_pow", "field.pow:3", "stage6.bytecode_read_raf.stage2_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term19.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term19.stage_gamma_pow|stage6.input.stage2.OpFlagVirtualInstruction"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term19.gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term19.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term19.gamma_pow|stage6.bytecode_read_raf.claim.term19.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term20.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term20.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term20.gamma_pow|stage6.input.stage3.instruction_input.Imm"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term21.stage_gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.stage3_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term21.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term21.stage_gamma_pow|stage6.input.stage3.spartan_shift.UnexpandedPC"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term21.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term21.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term21.gamma_pow|stage6.bytecode_read_raf.claim.term21.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term22.stage_gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.stage3_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term22.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term22.stage_gamma_pow|stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsRs1Value"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term22.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term22.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term22.gamma_pow|stage6.bytecode_read_raf.claim.term22.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term23.stage_gamma_pow", "field.pow:3", "stage6.bytecode_read_raf.stage3_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term23.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term23.stage_gamma_pow|stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsPC"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term23.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term23.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term23.gamma_pow|stage6.bytecode_read_raf.claim.term23.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term24.stage_gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.stage3_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term24.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term24.stage_gamma_pow|stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsRs2Value"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term24.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term24.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term24.gamma_pow|stage6.bytecode_read_raf.claim.term24.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term25.stage_gamma_pow", "field.pow:5", "stage6.bytecode_read_raf.stage3_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term25.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term25.stage_gamma_pow|stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsImm"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term25.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term25.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term25.gamma_pow|stage6.bytecode_read_raf.claim.term25.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term26.stage_gamma_pow", "field.pow:6", "stage6.bytecode_read_raf.stage3_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term26.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term26.stage_gamma_pow|stage6.input.stage3.spartan_shift.InstructionFlagIsNoop"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term26.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term26.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term26.gamma_pow|stage6.bytecode_read_raf.claim.term26.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term27.stage_gamma_pow", "field.pow:7", "stage6.bytecode_read_raf.stage3_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term27.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term27.stage_gamma_pow|stage6.input.stage3.spartan_shift.OpFlagVirtualInstruction"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term27.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term27.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term27.gamma_pow|stage6.bytecode_read_raf.claim.term27.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term28.stage_gamma_pow", "field.pow:8", "stage6.bytecode_read_raf.stage3_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term28.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term28.stage_gamma_pow|stage6.input.stage3.spartan_shift.OpFlagIsFirstInSequence"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term28.gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term28.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term28.gamma_pow|stage6.bytecode_read_raf.claim.term28.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term29.gamma_pow", "field.pow:3", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term29.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term29.gamma_pow|stage6.input.stage4.RdWa"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term30.stage_gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.stage4_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term30.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term30.stage_gamma_pow|stage6.input.stage4.Rs1Ra"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term30.gamma_pow", "field.pow:3", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term30.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term30.gamma_pow|stage6.bytecode_read_raf.claim.term30.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term31.stage_gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.stage4_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term31.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term31.stage_gamma_pow|stage6.input.stage4.Rs2Ra"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term31.gamma_pow", "field.pow:3", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term31.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term31.gamma_pow|stage6.bytecode_read_raf.claim.term31.stage_gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term32.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term32.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term32.gamma_pow|stage6.input.stage5.registers_val_evaluation.RdWa"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term33.stage_gamma_pow", "field.pow:1", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term33.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term33.stage_gamma_pow|stage6.input.stage5.InstructionRafFlag"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term33.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term33.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term33.gamma_pow|stage6.bytecode_read_raf.claim.term33.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term34.stage_gamma_pow", "field.pow:2", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term34.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term34.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_0"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term34.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term34.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term34.gamma_pow|stage6.bytecode_read_raf.claim.term34.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term35.stage_gamma_pow", "field.pow:3", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term35.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term35.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_1"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term35.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term35.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term35.gamma_pow|stage6.bytecode_read_raf.claim.term35.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term36.stage_gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term36.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term36.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_2"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term36.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term36.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term36.gamma_pow|stage6.bytecode_read_raf.claim.term36.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term37.stage_gamma_pow", "field.pow:5", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term37.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term37.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_3"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term37.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term37.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term37.gamma_pow|stage6.bytecode_read_raf.claim.term37.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term38.stage_gamma_pow", "field.pow:6", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term38.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term38.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_4"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term38.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term38.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term38.gamma_pow|stage6.bytecode_read_raf.claim.term38.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term39.stage_gamma_pow", "field.pow:7", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term39.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term39.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_5"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term39.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term39.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term39.gamma_pow|stage6.bytecode_read_raf.claim.term39.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term40.stage_gamma_pow", "field.pow:8", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term40.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term40.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_6"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term40.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term40.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term40.gamma_pow|stage6.bytecode_read_raf.claim.term40.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term41.stage_gamma_pow", "field.pow:9", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term41.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term41.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_7"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term41.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term41.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term41.gamma_pow|stage6.bytecode_read_raf.claim.term41.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term42.stage_gamma_pow", "field.pow:10", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term42.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term42.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_8"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term42.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term42.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term42.gamma_pow|stage6.bytecode_read_raf.claim.term42.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term43.stage_gamma_pow", "field.pow:11", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term43.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term43.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_9"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term43.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term43.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term43.gamma_pow|stage6.bytecode_read_raf.claim.term43.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term44.stage_gamma_pow", "field.pow:12", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term44.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term44.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_10"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term44.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term44.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term44.gamma_pow|stage6.bytecode_read_raf.claim.term44.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term45.stage_gamma_pow", "field.pow:13", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term45.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term45.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_11"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term45.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term45.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term45.gamma_pow|stage6.bytecode_read_raf.claim.term45.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term46.stage_gamma_pow", "field.pow:14", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term46.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term46.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_12"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term46.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term46.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term46.gamma_pow|stage6.bytecode_read_raf.claim.term46.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term47.stage_gamma_pow", "field.pow:15", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term47.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term47.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_13"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term47.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term47.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term47.gamma_pow|stage6.bytecode_read_raf.claim.term47.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term48.stage_gamma_pow", "field.pow:16", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term48.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term48.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_14"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term48.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term48.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term48.gamma_pow|stage6.bytecode_read_raf.claim.term48.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term49.stage_gamma_pow", "field.pow:17", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term49.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term49.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_15"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term49.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term49.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term49.gamma_pow|stage6.bytecode_read_raf.claim.term49.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term50.stage_gamma_pow", "field.pow:18", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term50.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term50.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_16"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term50.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term50.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term50.gamma_pow|stage6.bytecode_read_raf.claim.term50.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term51.stage_gamma_pow", "field.pow:19", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term51.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term51.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_17"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term51.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term51.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term51.gamma_pow|stage6.bytecode_read_raf.claim.term51.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term52.stage_gamma_pow", "field.pow:20", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term52.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term52.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_18"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term52.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term52.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term52.gamma_pow|stage6.bytecode_read_raf.claim.term52.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term53.stage_gamma_pow", "field.pow:21", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term53.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term53.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_19"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term53.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term53.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term53.gamma_pow|stage6.bytecode_read_raf.claim.term53.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term54.stage_gamma_pow", "field.pow:22", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term54.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term54.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_20"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term54.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term54.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term54.gamma_pow|stage6.bytecode_read_raf.claim.term54.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term55.stage_gamma_pow", "field.pow:23", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term55.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term55.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_21"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term55.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term55.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term55.gamma_pow|stage6.bytecode_read_raf.claim.term55.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term56.stage_gamma_pow", "field.pow:24", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term56.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term56.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_22"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term56.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term56.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term56.gamma_pow|stage6.bytecode_read_raf.claim.term56.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term57.stage_gamma_pow", "field.pow:25", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term57.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term57.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_23"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term57.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term57.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term57.gamma_pow|stage6.bytecode_read_raf.claim.term57.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term58.stage_gamma_pow", "field.pow:26", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term58.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term58.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_24"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term58.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term58.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term58.gamma_pow|stage6.bytecode_read_raf.claim.term58.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term59.stage_gamma_pow", "field.pow:27", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term59.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term59.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_25"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term59.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term59.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term59.gamma_pow|stage6.bytecode_read_raf.claim.term59.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term60.stage_gamma_pow", "field.pow:28", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term60.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term60.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_26"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term60.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term60.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term60.gamma_pow|stage6.bytecode_read_raf.claim.term60.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term61.stage_gamma_pow", "field.pow:29", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term61.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term61.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_27"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term61.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term61.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term61.gamma_pow|stage6.bytecode_read_raf.claim.term61.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term62.stage_gamma_pow", "field.pow:30", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term62.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term62.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_28"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term62.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term62.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term62.gamma_pow|stage6.bytecode_read_raf.claim.term62.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term63.stage_gamma_pow", "field.pow:31", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term63.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term63.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_29"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term63.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term63.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term63.gamma_pow|stage6.bytecode_read_raf.claim.term63.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term64.stage_gamma_pow", "field.pow:32", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term64.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term64.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_30"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term64.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term64.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term64.gamma_pow|stage6.bytecode_read_raf.claim.term64.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term65.stage_gamma_pow", "field.pow:33", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term65.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term65.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_31"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term65.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term65.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term65.gamma_pow|stage6.bytecode_read_raf.claim.term65.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term66.stage_gamma_pow", "field.pow:34", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term66.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term66.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_32"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term66.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term66.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term66.gamma_pow|stage6.bytecode_read_raf.claim.term66.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term67.stage_gamma_pow", "field.pow:35", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term67.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term67.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_33"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term67.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term67.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term67.gamma_pow|stage6.bytecode_read_raf.claim.term67.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term68.stage_gamma_pow", "field.pow:36", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term68.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term68.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_34"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term68.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term68.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term68.gamma_pow|stage6.bytecode_read_raf.claim.term68.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term69.stage_gamma_pow", "field.pow:37", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term69.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term69.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_35"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term69.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term69.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term69.gamma_pow|stage6.bytecode_read_raf.claim.term69.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term70.stage_gamma_pow", "field.pow:38", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term70.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term70.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_36"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term70.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term70.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term70.gamma_pow|stage6.bytecode_read_raf.claim.term70.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term71.stage_gamma_pow", "field.pow:39", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term71.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term71.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_37"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term71.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term71.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term71.gamma_pow|stage6.bytecode_read_raf.claim.term71.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term72.stage_gamma_pow", "field.pow:40", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term72.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term72.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_38"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term72.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term72.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term72.gamma_pow|stage6.bytecode_read_raf.claim.term72.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term73.stage_gamma_pow", "field.pow:41", "stage6.bytecode_read_raf.stage5_gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term73.stage_gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term73.stage_gamma_pow|stage6.input.stage5.LookupTableFlag_39"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term73.gamma_pow", "field.pow:4", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term73.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term73.gamma_pow|stage6.bytecode_read_raf.claim.term73.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term74.gamma_pow", "field.pow:5", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term74.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term74.gamma_pow|stage6.input.stage1.PC"), + stage6_field_expr!("stage6.bytecode_read_raf.claim.term75.gamma_pow", "field.pow:6", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim.term75.gamma_term", "field.mul", "stage6.bytecode_read_raf.claim.term75.gamma_pow|stage6.input.stage3.spartan_shift.PC"), stage6_field_expr!("stage6.bytecode_read_raf.claim.entry_constant", "field.pow:7", "stage6.bytecode_read_raf.gamma"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial0", "field.add", "stage6.input.stage1.UnexpandedPC|stage6.bytecode_read_raf.claim.term1.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial1", "field.add", "stage6.bytecode_read_raf.claim_expr.partial0|stage6.bytecode_read_raf.claim.term2.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial2", "field.add", "stage6.bytecode_read_raf.claim_expr.partial1|stage6.bytecode_read_raf.claim.term3.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial3", "field.add", "stage6.bytecode_read_raf.claim_expr.partial2|stage6.bytecode_read_raf.claim.term4.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial4", "field.add", "stage6.bytecode_read_raf.claim_expr.partial3|stage6.bytecode_read_raf.claim.term5.stage_gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial5", "field.add", "stage6.bytecode_read_raf.claim_expr.partial4|stage6.bytecode_read_raf.claim.term6.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial6", "field.add", "stage6.bytecode_read_raf.claim_expr.partial5|stage6.bytecode_read_raf.claim.term7.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial7", "field.add", "stage6.bytecode_read_raf.claim_expr.partial6|stage6.bytecode_read_raf.claim.term8.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial8", "field.add", "stage6.bytecode_read_raf.claim_expr.partial7|stage6.bytecode_read_raf.claim.term9.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial9", "field.add", "stage6.bytecode_read_raf.claim_expr.partial8|stage6.bytecode_read_raf.claim.term10.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial10", "field.add", "stage6.bytecode_read_raf.claim_expr.partial9|stage6.bytecode_read_raf.claim.term11.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial11", "field.add", "stage6.bytecode_read_raf.claim_expr.partial10|stage6.bytecode_read_raf.claim.term12.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial12", "field.add", "stage6.bytecode_read_raf.claim_expr.partial11|stage6.bytecode_read_raf.claim.term13.stage_gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial13", "field.add", "stage6.bytecode_read_raf.claim_expr.partial12|stage6.bytecode_read_raf.claim.term14.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial14", "field.add", "stage6.bytecode_read_raf.claim_expr.partial13|stage6.bytecode_read_raf.claim.term15.stage_gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial15", "field.add", "stage6.bytecode_read_raf.claim_expr.partial14|stage6.bytecode_read_raf.claim.term16.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial16", "field.add", "stage6.bytecode_read_raf.claim_expr.partial15|stage6.bytecode_read_raf.claim.term17.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial17", "field.add", "stage6.bytecode_read_raf.claim_expr.partial16|stage6.bytecode_read_raf.claim.term18.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial18", "field.add", "stage6.bytecode_read_raf.claim_expr.partial17|stage6.bytecode_read_raf.claim.term19.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial19", "field.add", "stage6.bytecode_read_raf.claim_expr.partial18|stage6.bytecode_read_raf.claim.term20.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial20", "field.add", "stage6.bytecode_read_raf.claim_expr.partial19|stage6.bytecode_read_raf.claim.term21.gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial21", "field.add", "stage6.bytecode_read_raf.claim_expr.partial20|stage6.bytecode_read_raf.claim.term22.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial22", "field.add", "stage6.bytecode_read_raf.claim_expr.partial21|stage6.bytecode_read_raf.claim.term23.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial23", "field.add", "stage6.bytecode_read_raf.claim_expr.partial22|stage6.bytecode_read_raf.claim.term24.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial24", "field.add", "stage6.bytecode_read_raf.claim_expr.partial23|stage6.bytecode_read_raf.claim.term25.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial25", "field.add", "stage6.bytecode_read_raf.claim_expr.partial24|stage6.bytecode_read_raf.claim.term26.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial26", "field.add", "stage6.bytecode_read_raf.claim_expr.partial25|stage6.bytecode_read_raf.claim.term27.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial27", "field.add", "stage6.bytecode_read_raf.claim_expr.partial26|stage6.bytecode_read_raf.claim.term28.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial28", "field.add", "stage6.bytecode_read_raf.claim_expr.partial27|stage6.bytecode_read_raf.claim.term29.gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial29", "field.add", "stage6.bytecode_read_raf.claim_expr.partial28|stage6.bytecode_read_raf.claim.term30.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial30", "field.add", "stage6.bytecode_read_raf.claim_expr.partial29|stage6.bytecode_read_raf.claim.term31.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial31", "field.add", "stage6.bytecode_read_raf.claim_expr.partial30|stage6.bytecode_read_raf.claim.term32.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial32", "field.add", "stage6.bytecode_read_raf.claim_expr.partial31|stage6.bytecode_read_raf.claim.term33.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial33", "field.add", "stage6.bytecode_read_raf.claim_expr.partial32|stage6.bytecode_read_raf.claim.term34.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial34", "field.add", "stage6.bytecode_read_raf.claim_expr.partial33|stage6.bytecode_read_raf.claim.term35.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial35", "field.add", "stage6.bytecode_read_raf.claim_expr.partial34|stage6.bytecode_read_raf.claim.term36.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial36", "field.add", "stage6.bytecode_read_raf.claim_expr.partial35|stage6.bytecode_read_raf.claim.term37.gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial37", "field.add", "stage6.bytecode_read_raf.claim_expr.partial36|stage6.bytecode_read_raf.claim.term38.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial38", "field.add", "stage6.bytecode_read_raf.claim_expr.partial37|stage6.bytecode_read_raf.claim.term39.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial39", "field.add", "stage6.bytecode_read_raf.claim_expr.partial38|stage6.bytecode_read_raf.claim.term40.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial40", "field.add", "stage6.bytecode_read_raf.claim_expr.partial39|stage6.bytecode_read_raf.claim.term41.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial41", "field.add", "stage6.bytecode_read_raf.claim_expr.partial40|stage6.bytecode_read_raf.claim.term42.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial42", "field.add", "stage6.bytecode_read_raf.claim_expr.partial41|stage6.bytecode_read_raf.claim.term43.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial43", "field.add", "stage6.bytecode_read_raf.claim_expr.partial42|stage6.bytecode_read_raf.claim.term44.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial44", "field.add", "stage6.bytecode_read_raf.claim_expr.partial43|stage6.bytecode_read_raf.claim.term45.gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial45", "field.add", "stage6.bytecode_read_raf.claim_expr.partial44|stage6.bytecode_read_raf.claim.term46.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial46", "field.add", "stage6.bytecode_read_raf.claim_expr.partial45|stage6.bytecode_read_raf.claim.term47.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial47", "field.add", "stage6.bytecode_read_raf.claim_expr.partial46|stage6.bytecode_read_raf.claim.term48.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial48", "field.add", "stage6.bytecode_read_raf.claim_expr.partial47|stage6.bytecode_read_raf.claim.term49.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial49", "field.add", "stage6.bytecode_read_raf.claim_expr.partial48|stage6.bytecode_read_raf.claim.term50.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial50", "field.add", "stage6.bytecode_read_raf.claim_expr.partial49|stage6.bytecode_read_raf.claim.term51.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial51", "field.add", "stage6.bytecode_read_raf.claim_expr.partial50|stage6.bytecode_read_raf.claim.term52.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial52", "field.add", "stage6.bytecode_read_raf.claim_expr.partial51|stage6.bytecode_read_raf.claim.term53.gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial53", "field.add", "stage6.bytecode_read_raf.claim_expr.partial52|stage6.bytecode_read_raf.claim.term54.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial54", "field.add", "stage6.bytecode_read_raf.claim_expr.partial53|stage6.bytecode_read_raf.claim.term55.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial55", "field.add", "stage6.bytecode_read_raf.claim_expr.partial54|stage6.bytecode_read_raf.claim.term56.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial56", "field.add", "stage6.bytecode_read_raf.claim_expr.partial55|stage6.bytecode_read_raf.claim.term57.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial57", "field.add", "stage6.bytecode_read_raf.claim_expr.partial56|stage6.bytecode_read_raf.claim.term58.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial58", "field.add", "stage6.bytecode_read_raf.claim_expr.partial57|stage6.bytecode_read_raf.claim.term59.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial59", "field.add", "stage6.bytecode_read_raf.claim_expr.partial58|stage6.bytecode_read_raf.claim.term60.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial60", "field.add", "stage6.bytecode_read_raf.claim_expr.partial59|stage6.bytecode_read_raf.claim.term61.gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial61", "field.add", "stage6.bytecode_read_raf.claim_expr.partial60|stage6.bytecode_read_raf.claim.term62.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial62", "field.add", "stage6.bytecode_read_raf.claim_expr.partial61|stage6.bytecode_read_raf.claim.term63.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial63", "field.add", "stage6.bytecode_read_raf.claim_expr.partial62|stage6.bytecode_read_raf.claim.term64.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial64", "field.add", "stage6.bytecode_read_raf.claim_expr.partial63|stage6.bytecode_read_raf.claim.term65.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial65", "field.add", "stage6.bytecode_read_raf.claim_expr.partial64|stage6.bytecode_read_raf.claim.term66.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial66", "field.add", "stage6.bytecode_read_raf.claim_expr.partial65|stage6.bytecode_read_raf.claim.term67.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial67", "field.add", "stage6.bytecode_read_raf.claim_expr.partial66|stage6.bytecode_read_raf.claim.term68.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial68", "field.add", "stage6.bytecode_read_raf.claim_expr.partial67|stage6.bytecode_read_raf.claim.term69.gamma_term"), + stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial69", "field.add", "stage6.bytecode_read_raf.claim_expr.partial68|stage6.bytecode_read_raf.claim.term70.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial70", "field.add", "stage6.bytecode_read_raf.claim_expr.partial69|stage6.bytecode_read_raf.claim.term71.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial71", "field.add", "stage6.bytecode_read_raf.claim_expr.partial70|stage6.bytecode_read_raf.claim.term72.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial72", "field.add", "stage6.bytecode_read_raf.claim_expr.partial71|stage6.bytecode_read_raf.claim.term73.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial73", "field.add", "stage6.bytecode_read_raf.claim_expr.partial72|stage6.bytecode_read_raf.claim.term74.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial74", "field.add", "stage6.bytecode_read_raf.claim_expr.partial73|stage6.bytecode_read_raf.claim.term75.gamma_term"), stage6_field_expr!("stage6.bytecode_read_raf.claim_expr.partial75", "field.add", "stage6.bytecode_read_raf.claim_expr.partial74|stage6.bytecode_read_raf.claim.entry_constant"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term1.gamma_pow", "field.pow:1", "stage6.instruction_ra_virtual.gamma"), + stage6_field_expr!("stage6.instruction_ra_virtual.claim.term1.gamma_term", "field.mul", "stage6.instruction_ra_virtual.claim.term1.gamma_pow|stage6.input.stage5.instruction_read_raf.InstructionRa_1"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term2.gamma_pow", "field.pow:2", "stage6.instruction_ra_virtual.gamma"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term2.gamma_term", "field.mul", "stage6.instruction_ra_virtual.claim.term2.gamma_pow|stage6.input.stage5.instruction_read_raf.InstructionRa_2"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term3.gamma_pow", "field.pow:3", "stage6.instruction_ra_virtual.gamma"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term3.gamma_term", "field.mul", "stage6.instruction_ra_virtual.claim.term3.gamma_pow|stage6.input.stage5.instruction_read_raf.InstructionRa_3"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term4.gamma_pow", "field.pow:4", "stage6.instruction_ra_virtual.gamma"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term4.gamma_term", "field.mul", "stage6.instruction_ra_virtual.claim.term4.gamma_pow|stage6.input.stage5.instruction_read_raf.InstructionRa_4"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term5.gamma_pow", "field.pow:5", "stage6.instruction_ra_virtual.gamma"), + stage6_field_expr!("stage6.instruction_ra_virtual.claim.term5.gamma_term", "field.mul", "stage6.instruction_ra_virtual.claim.term5.gamma_pow|stage6.input.stage5.instruction_read_raf.InstructionRa_5"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term6.gamma_pow", "field.pow:6", "stage6.instruction_ra_virtual.gamma"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term6.gamma_term", "field.mul", "stage6.instruction_ra_virtual.claim.term6.gamma_pow|stage6.input.stage5.instruction_read_raf.InstructionRa_6"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term7.gamma_pow", "field.pow:7", "stage6.instruction_ra_virtual.gamma"), stage6_field_expr!("stage6.instruction_ra_virtual.claim.term7.gamma_term", "field.mul", "stage6.instruction_ra_virtual.claim.term7.gamma_pow|stage6.input.stage5.instruction_read_raf.InstructionRa_7"), stage6_field_expr!("stage6.instruction_ra_virtual.claim_expr.partial0", "field.add", "stage6.input.stage5.instruction_read_raf.InstructionRa_0|stage6.instruction_ra_virtual.claim.term1.gamma_term"), stage6_field_expr!("stage6.instruction_ra_virtual.claim_expr.partial1", "field.add", "stage6.instruction_ra_virtual.claim_expr.partial0|stage6.instruction_ra_virtual.claim.term2.gamma_term"), stage6_field_expr!("stage6.instruction_ra_virtual.claim_expr.partial2", "field.add", "stage6.instruction_ra_virtual.claim_expr.partial1|stage6.instruction_ra_virtual.claim.term3.gamma_term"), + stage6_field_expr!("stage6.instruction_ra_virtual.claim_expr.partial3", "field.add", "stage6.instruction_ra_virtual.claim_expr.partial2|stage6.instruction_ra_virtual.claim.term4.gamma_term"), stage6_field_expr!("stage6.instruction_ra_virtual.claim_expr.partial4", "field.add", "stage6.instruction_ra_virtual.claim_expr.partial3|stage6.instruction_ra_virtual.claim.term5.gamma_term"), stage6_field_expr!("stage6.instruction_ra_virtual.claim_expr.partial5", "field.add", "stage6.instruction_ra_virtual.claim_expr.partial4|stage6.instruction_ra_virtual.claim.term6.gamma_term"), stage6_field_expr!("stage6.instruction_ra_virtual.claim_expr.partial6", "field.add", "stage6.instruction_ra_virtual.claim_expr.partial5|stage6.instruction_ra_virtual.claim.term7.gamma_term"), stage6_field_expr!("stage6.inc_claim_reduction.claim.ram_inc_stage4.gamma_pow", "field.pow:1", "stage6.inc_claim_reduction.gamma"), stage6_field_expr!("stage6.inc_claim_reduction.claim.ram_inc_stage4.gamma_term", "field.mul", "stage6.inc_claim_reduction.claim.ram_inc_stage4.gamma_pow|stage6.input.stage4.ram_val_check.RamInc"), stage6_field_expr!("stage6.inc_claim_reduction.claim.rd_inc_stage4.gamma_pow", "field.pow:2", "stage6.inc_claim_reduction.gamma"), stage6_field_expr!("stage6.inc_claim_reduction.claim.rd_inc_stage4.gamma_term", "field.mul", "stage6.inc_claim_reduction.claim.rd_inc_stage4.gamma_pow|stage6.input.stage4.registers_read_write.RdInc"), + stage6_field_expr!("stage6.inc_claim_reduction.claim.rd_inc_stage5.gamma_pow", "field.pow:3", "stage6.inc_claim_reduction.gamma"), stage6_field_expr!("stage6.inc_claim_reduction.claim.rd_inc_stage5.gamma_term", "field.mul", "stage6.inc_claim_reduction.claim.rd_inc_stage5.gamma_pow|stage6.input.stage5.registers_val_evaluation.RdInc"), stage6_field_expr!("stage6.inc_claim_reduction.claim_expr.partial0", "field.add", "stage6.input.stage2.ram_read_write.RamInc|stage6.inc_claim_reduction.claim.ram_inc_stage4.gamma_term"), stage6_field_expr!("stage6.inc_claim_reduction.claim_expr.partial1", "field.add", "stage6.inc_claim_reduction.claim_expr.partial0|stage6.inc_claim_reduction.claim.rd_inc_stage4.gamma_term"), stage6_field_expr!("stage6.inc_claim_reduction.claim_expr.partial2", "field.add", "stage6.inc_claim_reduction.claim_expr.partial1|stage6.inc_claim_reduction.claim.rd_inc_stage5.gamma_term"), +]; +pub const STAGE6_KERNELS: &[Stage6KernelPlan] = &[ + +]; + +pub const STAGE6_SUMCHECK_CLAIMS: &[Stage6SumcheckClaimPlan] = &[ + Stage6SumcheckClaimPlan { symbol: "stage6.bytecode_read_raf.input", stage: "stage6", domain: "jolt.stage6_bytecode_read_raf_domain", num_rounds: 32, degree: 5, claim: "stage6.bytecode_read_raf.weighted_prior_stage_values", kernel: None, relation: Some("jolt.stage6.bytecode_read_raf"), claim_value: "stage6.bytecode_read_raf.claim_expr.partial75", input_openings: "stage6.input.stage1.UnexpandedPC|stage6.input.stage1.Imm|stage6.input.stage1.OpFlagAddOperands|stage6.input.stage1.OpFlagSubtractOperands|stage6.input.stage1.OpFlagMultiplyOperands|stage6.input.stage1.OpFlagLoad|stage6.input.stage1.OpFlagStore|stage6.input.stage1.OpFlagJump|stage6.input.stage1.OpFlagWriteLookupOutputToRD|stage6.input.stage1.OpFlagVirtualInstruction|stage6.input.stage1.OpFlagAssert|stage6.input.stage1.OpFlagDoNotUpdateUnexpandedPC|stage6.input.stage1.OpFlagAdvice|stage6.input.stage1.OpFlagIsCompressed|stage6.input.stage1.OpFlagIsFirstInSequence|stage6.input.stage1.OpFlagIsLastInSequence|stage6.input.stage2.OpFlagJump|stage6.input.stage2.InstructionFlagBranch|stage6.input.stage2.OpFlagWriteLookupOutputToRD|stage6.input.stage2.OpFlagVirtualInstruction|stage6.input.stage3.instruction_input.Imm|stage6.input.stage3.spartan_shift.UnexpandedPC|stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsRs1Value|stage6.input.stage3.instruction_input.InstructionFlagLeftOperandIsPC|stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsRs2Value|stage6.input.stage3.instruction_input.InstructionFlagRightOperandIsImm|stage6.input.stage3.spartan_shift.InstructionFlagIsNoop|stage6.input.stage3.spartan_shift.OpFlagVirtualInstruction|stage6.input.stage3.spartan_shift.OpFlagIsFirstInSequence|stage6.input.stage4.RdWa|stage6.input.stage4.Rs1Ra|stage6.input.stage4.Rs2Ra|stage6.input.stage5.registers_val_evaluation.RdWa|stage6.input.stage5.InstructionRafFlag|stage6.input.stage5.LookupTableFlag_0|stage6.input.stage5.LookupTableFlag_1|stage6.input.stage5.LookupTableFlag_2|stage6.input.stage5.LookupTableFlag_3|stage6.input.stage5.LookupTableFlag_4|stage6.input.stage5.LookupTableFlag_5|stage6.input.stage5.LookupTableFlag_6|stage6.input.stage5.LookupTableFlag_7|stage6.input.stage5.LookupTableFlag_8|stage6.input.stage5.LookupTableFlag_9|stage6.input.stage5.LookupTableFlag_10|stage6.input.stage5.LookupTableFlag_11|stage6.input.stage5.LookupTableFlag_12|stage6.input.stage5.LookupTableFlag_13|stage6.input.stage5.LookupTableFlag_14|stage6.input.stage5.LookupTableFlag_15|stage6.input.stage5.LookupTableFlag_16|stage6.input.stage5.LookupTableFlag_17|stage6.input.stage5.LookupTableFlag_18|stage6.input.stage5.LookupTableFlag_19|stage6.input.stage5.LookupTableFlag_20|stage6.input.stage5.LookupTableFlag_21|stage6.input.stage5.LookupTableFlag_22|stage6.input.stage5.LookupTableFlag_23|stage6.input.stage5.LookupTableFlag_24|stage6.input.stage5.LookupTableFlag_25|stage6.input.stage5.LookupTableFlag_26|stage6.input.stage5.LookupTableFlag_27|stage6.input.stage5.LookupTableFlag_28|stage6.input.stage5.LookupTableFlag_29|stage6.input.stage5.LookupTableFlag_30|stage6.input.stage5.LookupTableFlag_31|stage6.input.stage5.LookupTableFlag_32|stage6.input.stage5.LookupTableFlag_33|stage6.input.stage5.LookupTableFlag_34|stage6.input.stage5.LookupTableFlag_35|stage6.input.stage5.LookupTableFlag_36|stage6.input.stage5.LookupTableFlag_37|stage6.input.stage5.LookupTableFlag_38|stage6.input.stage5.LookupTableFlag_39|stage6.input.stage1.PC|stage6.input.stage3.spartan_shift.PC" }, + Stage6SumcheckClaimPlan { symbol: "stage6.booleanity.input", stage: "stage6", domain: "jolt.stage6_booleanity_domain", num_rounds: 22, degree: 3, claim: "stage6.booleanity.zero", kernel: None, relation: Some("jolt.stage6.booleanity"), claim_value: "stage6.zero", input_openings: "" }, + Stage6SumcheckClaimPlan { symbol: "stage6.hamming_booleanity.input", stage: "stage6", domain: "jolt.trace_domain", num_rounds: 18, degree: 3, claim: "stage6.hamming_booleanity.zero", kernel: None, relation: Some("jolt.stage6.hamming_booleanity"), claim_value: "stage6.zero", input_openings: "stage6.input.stage1.LookupOutput" }, + Stage6SumcheckClaimPlan { symbol: "stage6.ram_ra_virtual.input", stage: "stage6", domain: "jolt.trace_domain", num_rounds: 18, degree: 5, claim: "stage6.ram_ra_virtual.weighted_ram_ra", kernel: None, relation: Some("jolt.stage6.ram_ra_virtual"), claim_value: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", input_openings: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6SumcheckClaimPlan { symbol: "stage6.instruction_ra_virtual.input", stage: "stage6", domain: "jolt.trace_domain", num_rounds: 18, degree: 5, claim: "stage6.instruction_ra_virtual.weighted_instruction_ra", kernel: None, relation: Some("jolt.stage6.instruction_ra_virtual"), claim_value: "stage6.instruction_ra_virtual.claim_expr.partial6", input_openings: "stage6.input.stage5.instruction_read_raf.InstructionRa_0|stage6.input.stage5.instruction_read_raf.InstructionRa_1|stage6.input.stage5.instruction_read_raf.InstructionRa_2|stage6.input.stage5.instruction_read_raf.InstructionRa_3|stage6.input.stage5.instruction_read_raf.InstructionRa_4|stage6.input.stage5.instruction_read_raf.InstructionRa_5|stage6.input.stage5.instruction_read_raf.InstructionRa_6|stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, + Stage6SumcheckClaimPlan { symbol: "stage6.inc_claim_reduction.input", stage: "stage6", domain: "jolt.trace_domain", num_rounds: 18, degree: 2, claim: "stage6.inc_claim_reduction.weighted_increments", kernel: None, relation: Some("jolt.stage6.inc_claim_reduction"), claim_value: "stage6.inc_claim_reduction.claim_expr.partial2", input_openings: "stage6.input.stage2.ram_read_write.RamInc|stage6.input.stage4.ram_val_check.RamInc|stage6.input.stage4.registers_read_write.RdInc|stage6.input.stage5.registers_val_evaluation.RdInc" }, +]; +pub const STAGE6_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 14, + 18, +]; + +pub const STAGE6_SUMCHECK_BATCHES: &[Stage6SumcheckBatchPlan] = &[ + Stage6SumcheckBatchPlan { symbol: "stage6.batch", stage: "stage6", proof_slot: "stage6.sumcheck", policy: "jolt_core_stage6_aligned", count: 6, ordered_claims: "stage6.bytecode_read_raf.input|stage6.booleanity.input|stage6.hamming_booleanity.input|stage6.ram_ra_virtual.input|stage6.instruction_ra_virtual.input|stage6.inc_claim_reduction.input", claim_operands: "stage6.bytecode_read_raf.input|stage6.booleanity.input|stage6.hamming_booleanity.input|stage6.ram_ra_virtual.input|stage6.instruction_ra_virtual.input|stage6.inc_claim_reduction.input", claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE6_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE6_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 14, + 18, +]; + +pub const STAGE6_SUMCHECK_DRIVERS: &[Stage6SumcheckDriverPlan] = &[ + Stage6SumcheckDriverPlan { symbol: "stage6.sumcheck", stage: "stage6", proof_slot: "stage6.sumcheck", kernel: None, relation: Some("jolt.stage6.batched"), batch: "stage6.batch", policy: "jolt_core_stage6_aligned", round_schedule: STAGE6_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 32, degree: 5 }, +]; +pub const STAGE6_SUMCHECK_INSTANCE_RESULTS: &[Stage6SumcheckInstanceResultPlan] = &[ + Stage6SumcheckInstanceResultPlan { symbol: "stage6.bytecode_read_raf.instance", source: "stage6.sumcheck", claim: "stage6.bytecode_read_raf.input", relation: "jolt.stage6.bytecode_read_raf", index: 0, point_arity: 32, num_rounds: 32, round_offset: 0, point_order: "bytecode_read_raf", degree: 5 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.booleanity.instance", source: "stage6.sumcheck", claim: "stage6.booleanity.input", relation: "jolt.stage6.booleanity", index: 1, point_arity: 22, num_rounds: 22, round_offset: 10, point_order: "stage6_booleanity", degree: 3 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.hamming_booleanity.instance", source: "stage6.sumcheck", claim: "stage6.hamming_booleanity.input", relation: "jolt.stage6.hamming_booleanity", index: 2, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 3 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.ram_ra_virtual.instance", source: "stage6.sumcheck", claim: "stage6.ram_ra_virtual.input", relation: "jolt.stage6.ram_ra_virtual", index: 3, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 5 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.instruction_ra_virtual.instance", source: "stage6.sumcheck", claim: "stage6.instruction_ra_virtual.input", relation: "jolt.stage6.instruction_ra_virtual", index: 4, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 5 }, + Stage6SumcheckInstanceResultPlan { symbol: "stage6.inc_claim_reduction.instance", source: "stage6.sumcheck", claim: "stage6.inc_claim_reduction.input", relation: "jolt.stage6.inc_claim_reduction", index: 5, point_arity: 18, num_rounds: 18, round_offset: 14, point_order: "reverse", degree: 2 }, +]; + +macro_rules! stage6_sumcheck_eval { + ($symbol:literal, $source:literal, $name:literal, $index:literal, $oracle:literal) => { + Stage6SumcheckEvalPlan { symbol: $symbol, source: $source, name: $name, index: $index, oracle: $oracle } + }; +} + +#[rustfmt::skip] +pub const STAGE6_SUMCHECK_EVALS: &[Stage6SumcheckEvalPlan] = &[ + stage6_sumcheck_eval!("stage6.bytecode_read_raf.eval.BytecodeRa_0", "stage6.sumcheck", "stage6.bytecode_read_raf.eval.BytecodeRa_0", 0, "BytecodeRa_0"), stage6_sumcheck_eval!("stage6.bytecode_read_raf.eval.BytecodeRa_1", "stage6.sumcheck", "stage6.bytecode_read_raf.eval.BytecodeRa_1", 1, "BytecodeRa_1"), stage6_sumcheck_eval!("stage6.bytecode_read_raf.eval.BytecodeRa_2", "stage6.sumcheck", "stage6.bytecode_read_raf.eval.BytecodeRa_2", 2, "BytecodeRa_2"), stage6_sumcheck_eval!("stage6.bytecode_read_raf.eval.BytecodeRa_3", "stage6.sumcheck", "stage6.bytecode_read_raf.eval.BytecodeRa_3", 3, "BytecodeRa_3"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_0", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_0", 0, "InstructionRa_0"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_1", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_1", 1, "InstructionRa_1"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_2", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_2", 2, "InstructionRa_2"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_3", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_3", 3, "InstructionRa_3"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_4", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_4", 4, "InstructionRa_4"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_5", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_5", 5, "InstructionRa_5"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_6", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_6", 6, "InstructionRa_6"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_7", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_7", 7, "InstructionRa_7"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_8", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_8", 8, "InstructionRa_8"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_9", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_9", 9, "InstructionRa_9"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_10", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_10", 10, "InstructionRa_10"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_11", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_11", 11, "InstructionRa_11"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_12", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_12", 12, "InstructionRa_12"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_13", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_13", 13, "InstructionRa_13"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_14", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_14", 14, "InstructionRa_14"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_15", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_15", 15, "InstructionRa_15"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_16", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_16", 16, "InstructionRa_16"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_17", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_17", 17, "InstructionRa_17"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_18", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_18", 18, "InstructionRa_18"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_19", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_19", 19, "InstructionRa_19"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_20", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_20", 20, "InstructionRa_20"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_21", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_21", 21, "InstructionRa_21"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_22", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_22", 22, "InstructionRa_22"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_23", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_23", 23, "InstructionRa_23"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_24", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_24", 24, "InstructionRa_24"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_25", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_25", 25, "InstructionRa_25"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_26", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_26", 26, "InstructionRa_26"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_27", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_27", 27, "InstructionRa_27"), + stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_28", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_28", 28, "InstructionRa_28"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_29", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_29", 29, "InstructionRa_29"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_30", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_30", 30, "InstructionRa_30"), stage6_sumcheck_eval!("stage6.booleanity.eval.InstructionRa_31", "stage6.sumcheck", "stage6.booleanity.eval.InstructionRa_31", 31, "InstructionRa_31"), + stage6_sumcheck_eval!("stage6.booleanity.eval.BytecodeRa_0", "stage6.sumcheck", "stage6.booleanity.eval.BytecodeRa_0", 32, "BytecodeRa_0"), stage6_sumcheck_eval!("stage6.booleanity.eval.BytecodeRa_1", "stage6.sumcheck", "stage6.booleanity.eval.BytecodeRa_1", 33, "BytecodeRa_1"), stage6_sumcheck_eval!("stage6.booleanity.eval.BytecodeRa_2", "stage6.sumcheck", "stage6.booleanity.eval.BytecodeRa_2", 34, "BytecodeRa_2"), stage6_sumcheck_eval!("stage6.booleanity.eval.BytecodeRa_3", "stage6.sumcheck", "stage6.booleanity.eval.BytecodeRa_3", 35, "BytecodeRa_3"), + stage6_sumcheck_eval!("stage6.booleanity.eval.RamRa_0", "stage6.sumcheck", "stage6.booleanity.eval.RamRa_0", 36, "RamRa_0"), stage6_sumcheck_eval!("stage6.booleanity.eval.RamRa_1", "stage6.sumcheck", "stage6.booleanity.eval.RamRa_1", 37, "RamRa_1"), stage6_sumcheck_eval!("stage6.booleanity.eval.RamRa_2", "stage6.sumcheck", "stage6.booleanity.eval.RamRa_2", 38, "RamRa_2"), stage6_sumcheck_eval!("stage6.booleanity.eval.RamRa_3", "stage6.sumcheck", "stage6.booleanity.eval.RamRa_3", 39, "RamRa_3"), + stage6_sumcheck_eval!("stage6.hamming_booleanity.eval.HammingWeight", "stage6.sumcheck", "stage6.hamming_booleanity.eval.HammingWeight", 0, "HammingWeight"), stage6_sumcheck_eval!("stage6.ram_ra_virtual.eval.RamRa_0", "stage6.sumcheck", "stage6.ram_ra_virtual.eval.RamRa_0", 0, "RamRa_0"), stage6_sumcheck_eval!("stage6.ram_ra_virtual.eval.RamRa_1", "stage6.sumcheck", "stage6.ram_ra_virtual.eval.RamRa_1", 1, "RamRa_1"), stage6_sumcheck_eval!("stage6.ram_ra_virtual.eval.RamRa_2", "stage6.sumcheck", "stage6.ram_ra_virtual.eval.RamRa_2", 2, "RamRa_2"), + stage6_sumcheck_eval!("stage6.ram_ra_virtual.eval.RamRa_3", "stage6.sumcheck", "stage6.ram_ra_virtual.eval.RamRa_3", 3, "RamRa_3"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_0", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_0", 0, "InstructionRa_0"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_1", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_1", 1, "InstructionRa_1"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_2", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_2", 2, "InstructionRa_2"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_3", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_3", 3, "InstructionRa_3"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_4", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_4", 4, "InstructionRa_4"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_5", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_5", 5, "InstructionRa_5"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_6", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_6", 6, "InstructionRa_6"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_7", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_7", 7, "InstructionRa_7"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_8", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_8", 8, "InstructionRa_8"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_9", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_9", 9, "InstructionRa_9"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_10", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_10", 10, "InstructionRa_10"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_11", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_11", 11, "InstructionRa_11"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_12", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_12", 12, "InstructionRa_12"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_13", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_13", 13, "InstructionRa_13"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_14", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_14", 14, "InstructionRa_14"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_15", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_15", 15, "InstructionRa_15"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_16", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_16", 16, "InstructionRa_16"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_17", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_17", 17, "InstructionRa_17"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_18", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_18", 18, "InstructionRa_18"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_19", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_19", 19, "InstructionRa_19"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_20", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_20", 20, "InstructionRa_20"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_21", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_21", 21, "InstructionRa_21"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_22", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_22", 22, "InstructionRa_22"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_23", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_23", 23, "InstructionRa_23"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_24", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_24", 24, "InstructionRa_24"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_25", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_25", 25, "InstructionRa_25"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_26", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_26", 26, "InstructionRa_26"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_27", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_27", 27, "InstructionRa_27"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_28", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_28", 28, "InstructionRa_28"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_29", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_29", 29, "InstructionRa_29"), stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_30", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_30", 30, "InstructionRa_30"), + stage6_sumcheck_eval!("stage6.instruction_ra_virtual.eval.InstructionRa_31", "stage6.sumcheck", "stage6.instruction_ra_virtual.eval.InstructionRa_31", 31, "InstructionRa_31"), stage6_sumcheck_eval!("stage6.inc_claim_reduction.eval.RamInc", "stage6.sumcheck", "stage6.inc_claim_reduction.eval.RamInc", 0, "RamInc"), stage6_sumcheck_eval!("stage6.inc_claim_reduction.eval.RdInc", "stage6.sumcheck", "stage6.inc_claim_reduction.eval.RdInc", 1, "RdInc"), +]; + +pub const STAGE6_POINT_ZEROS: &[Stage6PointZeroPlan] = &[ + Stage6PointZeroPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_0.address.zero_pad", field: "bn254_fr", arity: 2 }, + Stage6PointZeroPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_0.address.zero_pad", field: "bn254_fr", arity: 2 }, +]; + +pub const STAGE6_POINT_SLICES: &[Stage6PointSlicePlan] = &[ + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.Cycle", source: "stage6.bytecode_read_raf.instance", offset: 14, length: 18, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_0.address.source", source: "stage6.bytecode_read_raf.instance", offset: 0, length: 2, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_1.address", source: "stage6.bytecode_read_raf.instance", offset: 2, length: 4, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_2.address", source: "stage6.bytecode_read_raf.instance", offset: 6, length: 4, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_3.address", source: "stage6.bytecode_read_raf.instance", offset: 10, length: 4, input: "stage6.bytecode_read_raf.instance" }, + Stage6PointSlicePlan { symbol: "stage6.ram_ra_virtual.point.RamRa_0.address.source", source: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", offset: 0, length: 2, input: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6PointSlicePlan { symbol: "stage6.ram_ra_virtual.point.RamRa_1.address", source: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", offset: 2, length: 4, input: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6PointSlicePlan { symbol: "stage6.ram_ra_virtual.point.RamRa_2.address", source: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", offset: 6, length: 4, input: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6PointSlicePlan { symbol: "stage6.ram_ra_virtual.point.RamRa_3.address", source: "stage6.input.stage5.ram_ra_claim_reduction.RamRa", offset: 10, length: 4, input: "stage6.input.stage5.ram_ra_claim_reduction.RamRa" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_0.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_1.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_2.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_3.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_0", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_0" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_4.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_1" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_5.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_1" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_6.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_1" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_7.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_1", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_1" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_8.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_2" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_9.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_2" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_10.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_2" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_11.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_2", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_2" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_12.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_3" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_13.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_3" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_14.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_3" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_15.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_3", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_3" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_16.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_4" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_17.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_4" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_18.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_4" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_19.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_4", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_4" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_20.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_5" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_21.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_5" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_22.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_5" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_23.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_5", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_5" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_24.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_6" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_25.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_6" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_26.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_6" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_27.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_6", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_6" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_28.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", offset: 0, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_29.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", offset: 4, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_30.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", offset: 8, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, + Stage6PointSlicePlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_31.address", source: "stage6.input.stage5.instruction_read_raf.InstructionRa_7", offset: 12, length: 4, input: "stage6.input.stage5.instruction_read_raf.InstructionRa_7" }, +]; + +pub const STAGE6_POINT_CONCATS: &[Stage6PointConcatPlan] = &[ + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_0.address", layout: "left_zero_padded_address_chunk", arity: 4, inputs: "stage6.bytecode_read_raf.point.BytecodeRa_0.address.zero_pad|stage6.bytecode_read_raf.point.BytecodeRa_0.address.source" }, + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_0", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.bytecode_read_raf.point.BytecodeRa_0.address|stage6.bytecode_read_raf.point.Cycle" }, + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_1", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.bytecode_read_raf.point.BytecodeRa_1.address|stage6.bytecode_read_raf.point.Cycle" }, + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_2", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.bytecode_read_raf.point.BytecodeRa_2.address|stage6.bytecode_read_raf.point.Cycle" }, + Stage6PointConcatPlan { symbol: "stage6.bytecode_read_raf.point.BytecodeRa_3", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.bytecode_read_raf.point.BytecodeRa_3.address|stage6.bytecode_read_raf.point.Cycle" }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_0.address", layout: "left_zero_padded_address_chunk", arity: 4, inputs: "stage6.ram_ra_virtual.point.RamRa_0.address.zero_pad|stage6.ram_ra_virtual.point.RamRa_0.address.source" }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_0", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.ram_ra_virtual.point.RamRa_0.address|stage6.ram_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_1", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.ram_ra_virtual.point.RamRa_1.address|stage6.ram_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_2", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.ram_ra_virtual.point.RamRa_2.address|stage6.ram_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.ram_ra_virtual.point.RamRa_3", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.ram_ra_virtual.point.RamRa_3.address|stage6.ram_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_0", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_0.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_1", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_1.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_2", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_2.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_3", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_3.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_4", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_4.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_5", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_5.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_6", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_6.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_7", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_7.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_8", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_8.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_9", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_9.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_10", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_10.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_11", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_11.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_12", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_12.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_13", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_13.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_14", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_14.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_15", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_15.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_16", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_16.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_17", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_17.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_18", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_18.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_19", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_19.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_20", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_20.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_21", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_21.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_22", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_22.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_23", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_23.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_24", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_24.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_25", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_25.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_26", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_26.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_27", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_27.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_28", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_28.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_29", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_29.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_30", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_30.address|stage6.instruction_ra_virtual.instance" }, + Stage6PointConcatPlan { symbol: "stage6.instruction_ra_virtual.point.InstructionRa_31", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage6.instruction_ra_virtual.point.InstructionRa_31.address|stage6.instruction_ra_virtual.instance" }, +]; +pub const STAGE6_OPENING_CLAIMS: &[Stage6OpeningClaimPlan] = &[ + Stage6OpeningClaimPlan { symbol: "stage6.bytecode_read_raf.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.bytecode_read_raf.point.BytecodeRa_0", eval_source: "stage6.bytecode_read_raf.eval.BytecodeRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.bytecode_read_raf.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.bytecode_read_raf.point.BytecodeRa_1", eval_source: "stage6.bytecode_read_raf.eval.BytecodeRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.bytecode_read_raf.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.bytecode_read_raf.point.BytecodeRa_2", eval_source: "stage6.bytecode_read_raf.eval.BytecodeRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.bytecode_read_raf.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.bytecode_read_raf.point.BytecodeRa_3", eval_source: "stage6.bytecode_read_raf.eval.BytecodeRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_4" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_5" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_6" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_7" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_8" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_9" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_10" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_11" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_12" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_13" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_14" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_15" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_16" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_17" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_18" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_19" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_20" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_21" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_22" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_23" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_24" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_25" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_26" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_27" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_28" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_29" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_30" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.InstructionRa_31" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.BytecodeRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.BytecodeRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.BytecodeRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.BytecodeRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.RamRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.RamRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.RamRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.booleanity.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.booleanity.instance", eval_source: "stage6.booleanity.eval.RamRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.hamming_booleanity.opening.HammingWeight", oracle: "HammingWeight", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual", point_source: "stage6.hamming_booleanity.instance", eval_source: "stage6.hamming_booleanity.eval.HammingWeight" }, + Stage6OpeningClaimPlan { symbol: "stage6.ram_ra_virtual.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.ram_ra_virtual.point.RamRa_0", eval_source: "stage6.ram_ra_virtual.eval.RamRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.ram_ra_virtual.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.ram_ra_virtual.point.RamRa_1", eval_source: "stage6.ram_ra_virtual.eval.RamRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.ram_ra_virtual.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.ram_ra_virtual.point.RamRa_2", eval_source: "stage6.ram_ra_virtual.eval.RamRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.ram_ra_virtual.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.ram_ra_virtual.point.RamRa_3", eval_source: "stage6.ram_ra_virtual.eval.RamRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_0", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_0" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_1", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_1" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_2", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_2" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_3", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_3" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_4", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_4" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_5", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_5" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_6", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_6" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_7", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_7" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_8", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_8" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_9", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_9" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_10", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_10" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_11", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_11" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_12", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_12" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_13", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_13" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_14", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_14" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_15", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_15" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_16", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_16" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_17", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_17" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_18", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_18" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_19", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_19" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_20", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_20" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_21", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_21" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_22", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_22" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_23", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_23" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_24", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_24" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_25", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_25" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_26", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_26" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_27", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_27" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_28", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_28" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_29", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_29" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_30", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_30" }, + Stage6OpeningClaimPlan { symbol: "stage6.instruction_ra_virtual.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage6.instruction_ra_virtual.point.InstructionRa_31", eval_source: "stage6.instruction_ra_virtual.eval.InstructionRa_31" }, + Stage6OpeningClaimPlan { symbol: "stage6.inc_claim_reduction.opening.RamInc", oracle: "RamInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage6.inc_claim_reduction.instance", eval_source: "stage6.inc_claim_reduction.eval.RamInc" }, + Stage6OpeningClaimPlan { symbol: "stage6.inc_claim_reduction.opening.RdInc", oracle: "RdInc", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "committed", point_source: "stage6.inc_claim_reduction.instance", eval_source: "stage6.inc_claim_reduction.eval.RdInc" }, +]; + +pub const STAGE6_OPENING_EQUALITIES: &[Stage6OpeningClaimEqualityPlan] = &[ + +]; + +pub const STAGE6_OPENING_BATCHES: &[Stage6OpeningBatchPlan] = &[ + Stage6OpeningBatchPlan { symbol: "stage6.openings", stage: "stage6", proof_slot: "stage6.openings", policy: "jolt_stage6_output_order", count: 83, ordered_claims: "stage6.bytecode_read_raf.opening.BytecodeRa_0|stage6.bytecode_read_raf.opening.BytecodeRa_1|stage6.bytecode_read_raf.opening.BytecodeRa_2|stage6.bytecode_read_raf.opening.BytecodeRa_3|stage6.booleanity.opening.InstructionRa_0|stage6.booleanity.opening.InstructionRa_1|stage6.booleanity.opening.InstructionRa_2|stage6.booleanity.opening.InstructionRa_3|stage6.booleanity.opening.InstructionRa_4|stage6.booleanity.opening.InstructionRa_5|stage6.booleanity.opening.InstructionRa_6|stage6.booleanity.opening.InstructionRa_7|stage6.booleanity.opening.InstructionRa_8|stage6.booleanity.opening.InstructionRa_9|stage6.booleanity.opening.InstructionRa_10|stage6.booleanity.opening.InstructionRa_11|stage6.booleanity.opening.InstructionRa_12|stage6.booleanity.opening.InstructionRa_13|stage6.booleanity.opening.InstructionRa_14|stage6.booleanity.opening.InstructionRa_15|stage6.booleanity.opening.InstructionRa_16|stage6.booleanity.opening.InstructionRa_17|stage6.booleanity.opening.InstructionRa_18|stage6.booleanity.opening.InstructionRa_19|stage6.booleanity.opening.InstructionRa_20|stage6.booleanity.opening.InstructionRa_21|stage6.booleanity.opening.InstructionRa_22|stage6.booleanity.opening.InstructionRa_23|stage6.booleanity.opening.InstructionRa_24|stage6.booleanity.opening.InstructionRa_25|stage6.booleanity.opening.InstructionRa_26|stage6.booleanity.opening.InstructionRa_27|stage6.booleanity.opening.InstructionRa_28|stage6.booleanity.opening.InstructionRa_29|stage6.booleanity.opening.InstructionRa_30|stage6.booleanity.opening.InstructionRa_31|stage6.booleanity.opening.BytecodeRa_0|stage6.booleanity.opening.BytecodeRa_1|stage6.booleanity.opening.BytecodeRa_2|stage6.booleanity.opening.BytecodeRa_3|stage6.booleanity.opening.RamRa_0|stage6.booleanity.opening.RamRa_1|stage6.booleanity.opening.RamRa_2|stage6.booleanity.opening.RamRa_3|stage6.hamming_booleanity.opening.HammingWeight|stage6.ram_ra_virtual.opening.RamRa_0|stage6.ram_ra_virtual.opening.RamRa_1|stage6.ram_ra_virtual.opening.RamRa_2|stage6.ram_ra_virtual.opening.RamRa_3|stage6.instruction_ra_virtual.opening.InstructionRa_0|stage6.instruction_ra_virtual.opening.InstructionRa_1|stage6.instruction_ra_virtual.opening.InstructionRa_2|stage6.instruction_ra_virtual.opening.InstructionRa_3|stage6.instruction_ra_virtual.opening.InstructionRa_4|stage6.instruction_ra_virtual.opening.InstructionRa_5|stage6.instruction_ra_virtual.opening.InstructionRa_6|stage6.instruction_ra_virtual.opening.InstructionRa_7|stage6.instruction_ra_virtual.opening.InstructionRa_8|stage6.instruction_ra_virtual.opening.InstructionRa_9|stage6.instruction_ra_virtual.opening.InstructionRa_10|stage6.instruction_ra_virtual.opening.InstructionRa_11|stage6.instruction_ra_virtual.opening.InstructionRa_12|stage6.instruction_ra_virtual.opening.InstructionRa_13|stage6.instruction_ra_virtual.opening.InstructionRa_14|stage6.instruction_ra_virtual.opening.InstructionRa_15|stage6.instruction_ra_virtual.opening.InstructionRa_16|stage6.instruction_ra_virtual.opening.InstructionRa_17|stage6.instruction_ra_virtual.opening.InstructionRa_18|stage6.instruction_ra_virtual.opening.InstructionRa_19|stage6.instruction_ra_virtual.opening.InstructionRa_20|stage6.instruction_ra_virtual.opening.InstructionRa_21|stage6.instruction_ra_virtual.opening.InstructionRa_22|stage6.instruction_ra_virtual.opening.InstructionRa_23|stage6.instruction_ra_virtual.opening.InstructionRa_24|stage6.instruction_ra_virtual.opening.InstructionRa_25|stage6.instruction_ra_virtual.opening.InstructionRa_26|stage6.instruction_ra_virtual.opening.InstructionRa_27|stage6.instruction_ra_virtual.opening.InstructionRa_28|stage6.instruction_ra_virtual.opening.InstructionRa_29|stage6.instruction_ra_virtual.opening.InstructionRa_30|stage6.instruction_ra_virtual.opening.InstructionRa_31|stage6.inc_claim_reduction.opening.RamInc|stage6.inc_claim_reduction.opening.RdInc", claim_operands: "stage6.bytecode_read_raf.opening.BytecodeRa_0|stage6.bytecode_read_raf.opening.BytecodeRa_1|stage6.bytecode_read_raf.opening.BytecodeRa_2|stage6.bytecode_read_raf.opening.BytecodeRa_3|stage6.booleanity.opening.InstructionRa_0|stage6.booleanity.opening.InstructionRa_1|stage6.booleanity.opening.InstructionRa_2|stage6.booleanity.opening.InstructionRa_3|stage6.booleanity.opening.InstructionRa_4|stage6.booleanity.opening.InstructionRa_5|stage6.booleanity.opening.InstructionRa_6|stage6.booleanity.opening.InstructionRa_7|stage6.booleanity.opening.InstructionRa_8|stage6.booleanity.opening.InstructionRa_9|stage6.booleanity.opening.InstructionRa_10|stage6.booleanity.opening.InstructionRa_11|stage6.booleanity.opening.InstructionRa_12|stage6.booleanity.opening.InstructionRa_13|stage6.booleanity.opening.InstructionRa_14|stage6.booleanity.opening.InstructionRa_15|stage6.booleanity.opening.InstructionRa_16|stage6.booleanity.opening.InstructionRa_17|stage6.booleanity.opening.InstructionRa_18|stage6.booleanity.opening.InstructionRa_19|stage6.booleanity.opening.InstructionRa_20|stage6.booleanity.opening.InstructionRa_21|stage6.booleanity.opening.InstructionRa_22|stage6.booleanity.opening.InstructionRa_23|stage6.booleanity.opening.InstructionRa_24|stage6.booleanity.opening.InstructionRa_25|stage6.booleanity.opening.InstructionRa_26|stage6.booleanity.opening.InstructionRa_27|stage6.booleanity.opening.InstructionRa_28|stage6.booleanity.opening.InstructionRa_29|stage6.booleanity.opening.InstructionRa_30|stage6.booleanity.opening.InstructionRa_31|stage6.booleanity.opening.BytecodeRa_0|stage6.booleanity.opening.BytecodeRa_1|stage6.booleanity.opening.BytecodeRa_2|stage6.booleanity.opening.BytecodeRa_3|stage6.booleanity.opening.RamRa_0|stage6.booleanity.opening.RamRa_1|stage6.booleanity.opening.RamRa_2|stage6.booleanity.opening.RamRa_3|stage6.hamming_booleanity.opening.HammingWeight|stage6.ram_ra_virtual.opening.RamRa_0|stage6.ram_ra_virtual.opening.RamRa_1|stage6.ram_ra_virtual.opening.RamRa_2|stage6.ram_ra_virtual.opening.RamRa_3|stage6.instruction_ra_virtual.opening.InstructionRa_0|stage6.instruction_ra_virtual.opening.InstructionRa_1|stage6.instruction_ra_virtual.opening.InstructionRa_2|stage6.instruction_ra_virtual.opening.InstructionRa_3|stage6.instruction_ra_virtual.opening.InstructionRa_4|stage6.instruction_ra_virtual.opening.InstructionRa_5|stage6.instruction_ra_virtual.opening.InstructionRa_6|stage6.instruction_ra_virtual.opening.InstructionRa_7|stage6.instruction_ra_virtual.opening.InstructionRa_8|stage6.instruction_ra_virtual.opening.InstructionRa_9|stage6.instruction_ra_virtual.opening.InstructionRa_10|stage6.instruction_ra_virtual.opening.InstructionRa_11|stage6.instruction_ra_virtual.opening.InstructionRa_12|stage6.instruction_ra_virtual.opening.InstructionRa_13|stage6.instruction_ra_virtual.opening.InstructionRa_14|stage6.instruction_ra_virtual.opening.InstructionRa_15|stage6.instruction_ra_virtual.opening.InstructionRa_16|stage6.instruction_ra_virtual.opening.InstructionRa_17|stage6.instruction_ra_virtual.opening.InstructionRa_18|stage6.instruction_ra_virtual.opening.InstructionRa_19|stage6.instruction_ra_virtual.opening.InstructionRa_20|stage6.instruction_ra_virtual.opening.InstructionRa_21|stage6.instruction_ra_virtual.opening.InstructionRa_22|stage6.instruction_ra_virtual.opening.InstructionRa_23|stage6.instruction_ra_virtual.opening.InstructionRa_24|stage6.instruction_ra_virtual.opening.InstructionRa_25|stage6.instruction_ra_virtual.opening.InstructionRa_26|stage6.instruction_ra_virtual.opening.InstructionRa_27|stage6.instruction_ra_virtual.opening.InstructionRa_28|stage6.instruction_ra_virtual.opening.InstructionRa_29|stage6.instruction_ra_virtual.opening.InstructionRa_30|stage6.instruction_ra_virtual.opening.InstructionRa_31|stage6.inc_claim_reduction.opening.RamInc|stage6.inc_claim_reduction.opening.RdInc" }, +]; +pub const STAGE6_PROGRAM: Stage6VerifierProgramPlan = Stage6CpuProgramPlan { + role: "verifier", + params: STAGE6_PARAMS, + steps: STAGE6_PROGRAM_STEPS, + transcript_squeezes: STAGE6_TRANSCRIPT_SQUEEZES, + transcript_absorb_bytes: STAGE6_TRANSCRIPT_ABSORB_BYTES, + opening_inputs: STAGE6_OPENING_INPUTS, + field_constants: STAGE6_FIELD_CONSTANTS, + field_exprs: STAGE6_FIELD_EXPRS, + kernels: STAGE6_KERNELS, + claims: STAGE6_SUMCHECK_CLAIMS, + batches: STAGE6_SUMCHECK_BATCHES, + drivers: STAGE6_SUMCHECK_DRIVERS, + instance_results: STAGE6_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE6_SUMCHECK_EVALS, + point_zeros: STAGE6_POINT_ZEROS, + point_slices: STAGE6_POINT_SLICES, + point_concats: STAGE6_POINT_CONCATS, + opening_claims: STAGE6_OPENING_CLAIMS, + opening_equalities: STAGE6_OPENING_EQUALITIES, + opening_batches: STAGE6_OPENING_BATCHES, +}; + +pub fn verify_stage6( + proof: &Stage6Proof, + opening_inputs: &[Stage6OpeningInputValue], + verifier_data: Option<&Stage6VerifierData>, + transcript: &mut T, +) -> Result, VerifyStage6Error> +where + T: Transcript, +{ + verify_stage6_with_program(&STAGE6_PROGRAM, proof, opening_inputs, verifier_data, transcript) +} + +pub fn verify_stage6_with_program( + program: &'static Stage6VerifierProgramPlan, + proof: &Stage6Proof, + opening_inputs: &[Stage6OpeningInputValue], + verifier_data: Option<&Stage6VerifierData>, + transcript: &mut T, +) -> Result, VerifyStage6Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage6Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + store.seed_point_zeros(program.point_zeros); + let mut artifacts = Stage6ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage6Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage6_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "transcript_absorb_bytes" => { + let absorb = find_plan(program.transcript_absorb_bytes, step.symbol).ok_or( + VerifyStage6Error::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage6_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage6Error::MissingProof { + driver: step.symbol, + })?; + verify_stage6_driver( + program, + driver, + proof, + verifier_data, + &mut store, + transcript, + &mut artifacts, + )?; + } + _ => { + return Err(VerifyStage6Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage6 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage6_verifier_program() -> &'static Stage6VerifierProgramPlan { + &STAGE6_PROGRAM +} + +fn verify_stage6_squeeze( + program: &'static Stage6VerifierProgramPlan, + squeeze: &'static Stage6TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage6ExecutionArtifacts, +) -> Result<(), VerifyStage6Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage6Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage6Error::from)?; + artifacts.challenge_vectors.push(Stage6ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn absorb_stage6_bytes(absorb: &'static Stage6TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn verify_stage6_driver( + program: &'static Stage6VerifierProgramPlan, + driver: &'static Stage6SumcheckDriverPlan, + proof: &Stage6Proof, + verifier_data: Option<&Stage6VerifierData>, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage6ExecutionArtifacts, +) -> Result<(), VerifyStage6Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage6Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage6.batched" => { + verify_batched_stage6(program, driver, proof, verifier_data, store, transcript)? + } + _ => return Err(VerifyStage6Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage6( + program: &'static Stage6VerifierProgramPlan, + driver: &'static Stage6SumcheckDriverPlan, + proof: &Stage6SumcheckOutput, + verifier_data: Option<&Stage6VerifierData>, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage6Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim( + program, + driver, + verifier_data, + store, + evals, + point, + batching_coeffs, + ) + }, + |store, verified| observe_stage6_sumcheck_output(program, store, verified), + |driver, error| VerifyStage6Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage6_sumcheck_output( + program: &'static Stage6VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage6SumcheckOutput, +) -> Result<(), VerifyStage6Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "bytecode_read_raf" => point = normalize_bytecode_read_raf_point(&point, stage6_trace_rounds(program)?, "stage6.bytecode_read_raf.point")?, + "stage6_booleanity" => {} + "instruction_read_raf" => point = normalize_instruction_read_raf_point(&point, "stage6.instruction_read_raf.point")?, + _ => { + return Err(VerifyStage6Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage6Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage6Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage6Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage6Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage6Error::InvalidProof { driver, reason }, + |symbol| VerifyStage6Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage6VerifierProgramPlan, + driver: &'static Stage6SumcheckDriverPlan, + verifier_data: Option<&Stage6VerifierData>, + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage6Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage6Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(""); + let value = match relation { + "jolt.stage6.bytecode_read_raf" => { + let data = verifier_data + .and_then(|data| data.bytecode_read_raf.as_ref()) + .ok_or(VerifyStage6Error::MissingValue { + symbol: "stage6.bytecode_read_raf.data", + })?; + expected_bytecode_read_raf(program, data, store, evals, local_point)? + } + "jolt.stage6.booleanity" => { + expected_booleanity(program, store, evals, local_point)? + } + "jolt.stage6.hamming_booleanity" => { + expected_hamming_booleanity(store, evals, local_point)? + } + "jolt.stage6.ram_ra_virtual" => { + expected_ram_ra_virtual(store, evals, local_point)? + } + "jolt.stage6.instruction_ra_virtual" => { + expected_instruction_ra_virtual(program, store, evals, local_point)? + } + "jolt.stage6.inc_claim_reduction" => { + expected_inc_claim_reduction(store, evals, local_point)? + } + _ => return Err(VerifyStage6Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_bytecode_read_raf( + program: &'static Stage6VerifierProgramPlan, + data: &Stage6BytecodeReadRafData, + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + let log_t = stage6_trace_rounds(program)?; + Ok(expected_stage67_bytecode_read_raf( + &data.entries, + data.entry_bytecode_index, + data.num_lookup_tables, + store, + evals, + local_point, + log_t, + &STAGE6_BYTECODE_SYMBOLS, + )?) +} + +fn expected_booleanity( + program: &'static Stage6VerifierProgramPlan, + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + let log_t = stage6_trace_rounds(program)?; + Ok(expected_stage67_booleanity(store, evals, local_point, log_t, &STAGE6_RELATION_SYMBOLS)?) +} + +fn expected_hamming_booleanity( + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + Ok(expected_stage67_hamming_booleanity(store, evals, local_point, &STAGE6_RELATION_SYMBOLS)?) +} + +fn expected_ram_ra_virtual( + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + Ok(expected_stage67_ram_ra_virtual(store, evals, local_point, &STAGE6_RELATION_SYMBOLS)?) +} + +fn expected_instruction_ra_virtual( + program: &'static Stage6VerifierProgramPlan, + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + Ok(expected_stage67_instruction_ra_virtual(program.opening_inputs, store, evals, local_point, &STAGE6_RELATION_SYMBOLS)?) +} + +fn expected_inc_claim_reduction( + store: &super::common::ValueStore, + evals: &[Stage6NamedEval], + local_point: &[Fr], +) -> Result { + Ok(expected_stage67_inc_claim_reduction(store, evals, local_point, &STAGE6_RELATION_SYMBOLS)?) +} + +fn stage6_trace_rounds( + program: &'static Stage6VerifierProgramPlan, +) -> Result { + Ok(stage67_trace_rounds(program.instance_results, &STAGE6_RELATION_SYMBOLS)?) +} diff --git a/crates/jolt-verifier/src/stages/stage7.rs b/crates/jolt-verifier/src/stages/stage7.rs new file mode 100644 index 0000000000..f80d153c4e --- /dev/null +++ b/crates/jolt-verifier/src/stages/stage7.rs @@ -0,0 +1,686 @@ +#![allow(dead_code)] + +use super::common::{batch_claims, eval_by_name, find_batch, find_plan, normalize_bytecode_read_raf_point, normalize_instruction_read_raf_point, reverse_slice}; +use jolt_field::{Field, Fr}; +use jolt_poly::EqPolynomial; +use jolt_sumcheck::SumcheckError; +use jolt_transcript::{Blake2bTranscript, LabelWithCount, Transcript}; + +pub type Stage7NamedEval = super::common::StageNamedEval; +pub type Stage7SumcheckOutput = super::common::StageSumcheckOutput; +pub type Stage7ChallengeVector = super::common::StageChallengeVector; +pub type Stage7ExecutionArtifacts = super::common::StageExecutionArtifacts; +pub type Stage7Proof = super::common::StageProof; +pub type Stage7OpeningInputValue = super::common::StageOpeningInputValue; + +pub use super::common::{ + FieldConstantPlan as Stage7FieldConstantPlan, FieldExprPlan as Stage7FieldExprPlan, + KernelPlan as Stage7KernelPlan, OpeningBatchPlan as Stage7OpeningBatchPlan, + OpeningClaimEqualityPlan as Stage7OpeningClaimEqualityPlan, + OpeningClaimPlan as Stage7OpeningClaimPlan, OpeningInputPlan as Stage7OpeningInputPlan, + PointConcatPlan as Stage7PointConcatPlan, PointSlicePlan as Stage7PointSlicePlan, + PointZeroPlan as Stage7PointZeroPlan, ProgramStepPlan as Stage7ProgramStepPlan, + StageParams as Stage7Params, StageProgramPlan as Stage7CpuProgramPlan, + SumcheckBatchPlan as Stage7SumcheckBatchPlan, + SumcheckClaimPlan as Stage7SumcheckClaimPlan, SumcheckDriverPlan as Stage7SumcheckDriverPlan, + SumcheckEvalPlan as Stage7SumcheckEvalPlan, + SumcheckInstanceResultPlan as Stage7SumcheckInstanceResultPlan, + TranscriptAbsorbBytesPlan as Stage7TranscriptAbsorbBytesPlan, + TranscriptSqueezePlan as Stage7TranscriptSqueezePlan, +}; + +pub type DefaultStage7Transcript = Blake2bTranscript; +pub type Stage7VerifierProgramPlan = Stage7CpuProgramPlan; + +#[derive(Debug)] +pub enum VerifyStage7Error { + UnexpectedProofCount { expected: usize, got: usize }, + MissingProof { driver: &'static str }, + MissingBatch { driver: &'static str, batch: &'static str }, + MissingClaim { batch: &'static str, claim: &'static str }, + MissingValue { symbol: &'static str }, + InvalidInputLength { input: &'static str, expected: usize, actual: usize }, + InvalidProof { driver: &'static str, reason: &'static str }, + UnsupportedFieldExpr { symbol: &'static str, formula: &'static str }, + UnsupportedRelation { relation: &'static str }, + Sumcheck { driver: &'static str, error: SumcheckError }, +} + +super::common::impl_runtime_plan_error_conversion!(VerifyStage7Error); + +pub const STAGE7_PARAMS: Stage7Params = Stage7Params { + field: "bn254_fr", + pcs: "dory", + transcript: "blake2b_transcript", +}; +pub const STAGE7_PROGRAM_STEPS: &[Stage7ProgramStepPlan] = &[ + Stage7ProgramStepPlan { kind: "transcript_squeeze", symbol: "stage7.hamming_weight_claim_reduction.gamma" }, + Stage7ProgramStepPlan { kind: "sumcheck_driver", symbol: "stage7.sumcheck" }, +]; + +pub const STAGE7_TRANSCRIPT_SQUEEZES: &[Stage7TranscriptSqueezePlan] = &[ + Stage7TranscriptSqueezePlan { symbol: "stage7.hamming_weight_claim_reduction.gamma", label: "hamming_weight_claim_reduction_gamma", kind: "challenge_scalar", count: 1 }, +]; + +pub const STAGE7_TRANSCRIPT_ABSORB_BYTES: &[Stage7TranscriptAbsorbBytesPlan] = &[ + +]; + +pub const STAGE7_OPENING_INPUTS: &[Stage7OpeningInputPlan] = &[ + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.hamming_booleanity.HammingWeight", source_stage: "stage6", source_claim: "stage6.hamming_booleanity.opening.HammingWeight", oracle: "HammingWeight", domain: "jolt.trace_domain", point_arity: 18, claim_kind: "virtual" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_0", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_0", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_1", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_1", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_2", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_2", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_3", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_3", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_4", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_4", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_5", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_5", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_6", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_6", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_7", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_7", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_8", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_8", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_9", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_9", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_10", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_10", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_11", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_11", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_12", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_12", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_13", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_13", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_14", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_14", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_15", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_15", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_16", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_16", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_17", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_17", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_18", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_18", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_19", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_19", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_20", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_20", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_21", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_21", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_22", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_22", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_23", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_23", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_24", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_24", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_25", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_25", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_26", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_26", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_27", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_27", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_28", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_28", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_29", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_29", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_30", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_30", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.InstructionRa_31", source_stage: "stage6", source_claim: "stage6.booleanity.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.instruction_ra_virtual.InstructionRa_31", source_stage: "stage6", source_claim: "stage6.instruction_ra_virtual.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.BytecodeRa_0", source_stage: "stage6", source_claim: "stage6.booleanity.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.bytecode_read_raf.BytecodeRa_0", source_stage: "stage6", source_claim: "stage6.bytecode_read_raf.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.BytecodeRa_1", source_stage: "stage6", source_claim: "stage6.booleanity.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.bytecode_read_raf.BytecodeRa_1", source_stage: "stage6", source_claim: "stage6.bytecode_read_raf.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.BytecodeRa_2", source_stage: "stage6", source_claim: "stage6.booleanity.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.bytecode_read_raf.BytecodeRa_2", source_stage: "stage6", source_claim: "stage6.bytecode_read_raf.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.BytecodeRa_3", source_stage: "stage6", source_claim: "stage6.booleanity.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.bytecode_read_raf.BytecodeRa_3", source_stage: "stage6", source_claim: "stage6.bytecode_read_raf.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.RamRa_0", source_stage: "stage6", source_claim: "stage6.booleanity.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_0", source_stage: "stage6", source_claim: "stage6.ram_ra_virtual.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.RamRa_1", source_stage: "stage6", source_claim: "stage6.booleanity.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_1", source_stage: "stage6", source_claim: "stage6.ram_ra_virtual.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.RamRa_2", source_stage: "stage6", source_claim: "stage6.booleanity.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_2", source_stage: "stage6", source_claim: "stage6.ram_ra_virtual.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.booleanity.RamRa_3", source_stage: "stage6", source_claim: "stage6.booleanity.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage7OpeningInputPlan { symbol: "stage7.input.stage6.ram_ra_virtual.RamRa_3", source_stage: "stage6", source_claim: "stage6.ram_ra_virtual.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, +]; + +pub const STAGE7_FIELD_CONSTANTS: &[Stage7FieldConstantPlan] = &[ + Stage7FieldConstantPlan { symbol: "stage7.field.one", field: "bn254_fr", value: 1 }, +]; + +macro_rules! stage7_field_expr { + ($symbol:literal, $formula:literal, $operands:literal) => { + Stage7FieldExprPlan { symbol: $symbol, kind: "op", formula: $formula, operands: $operands } + }; +} + +#[rustfmt::skip] +pub const STAGE7_FIELD_EXPRS: &[Stage7FieldExprPlan] = &[ + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.0.booleanity.gamma_pow", "field.pow:1", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.0.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.0.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_0"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.0.virtualization.gamma_pow", "field.pow:2", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.0.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.0.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_0"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.1.hw.gamma_pow", "field.pow:3", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.1.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.1.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.1.booleanity.gamma_pow", "field.pow:4", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.1.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.1.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_1"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.1.virtualization.gamma_pow", "field.pow:5", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.1.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.1.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_1"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.2.hw.gamma_pow", "field.pow:6", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.2.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.2.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.2.booleanity.gamma_pow", "field.pow:7", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.2.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.2.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_2"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.2.virtualization.gamma_pow", "field.pow:8", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.2.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.2.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_2"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.3.hw.gamma_pow", "field.pow:9", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.3.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.3.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.3.booleanity.gamma_pow", "field.pow:10", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.3.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.3.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_3"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.3.virtualization.gamma_pow", "field.pow:11", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.3.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.3.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_3"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.4.hw.gamma_pow", "field.pow:12", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.4.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.4.hw.gamma_pow|stage7.field.one"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.4.booleanity.gamma_pow", "field.pow:13", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.4.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.4.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_4"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.4.virtualization.gamma_pow", "field.pow:14", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.4.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.4.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_4"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.5.hw.gamma_pow", "field.pow:15", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.5.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.5.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.5.booleanity.gamma_pow", "field.pow:16", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.5.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.5.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_5"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.5.virtualization.gamma_pow", "field.pow:17", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.5.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.5.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_5"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.6.hw.gamma_pow", "field.pow:18", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.6.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.6.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.6.booleanity.gamma_pow", "field.pow:19", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.6.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.6.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_6"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.6.virtualization.gamma_pow", "field.pow:20", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.6.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.6.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_6"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.7.hw.gamma_pow", "field.pow:21", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.7.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.7.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.7.booleanity.gamma_pow", "field.pow:22", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.7.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.7.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_7"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.7.virtualization.gamma_pow", "field.pow:23", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.7.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.7.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_7"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.8.hw.gamma_pow", "field.pow:24", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.8.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.8.hw.gamma_pow|stage7.field.one"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.8.booleanity.gamma_pow", "field.pow:25", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.8.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.8.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_8"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.8.virtualization.gamma_pow", "field.pow:26", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.8.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.8.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_8"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.9.hw.gamma_pow", "field.pow:27", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.9.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.9.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.9.booleanity.gamma_pow", "field.pow:28", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.9.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.9.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_9"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.9.virtualization.gamma_pow", "field.pow:29", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.9.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.9.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_9"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.10.hw.gamma_pow", "field.pow:30", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.10.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.10.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.10.booleanity.gamma_pow", "field.pow:31", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.10.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.10.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_10"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.10.virtualization.gamma_pow", "field.pow:32", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.10.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.10.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_10"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.11.hw.gamma_pow", "field.pow:33", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.11.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.11.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.11.booleanity.gamma_pow", "field.pow:34", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.11.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.11.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_11"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.11.virtualization.gamma_pow", "field.pow:35", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.11.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.11.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_11"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.12.hw.gamma_pow", "field.pow:36", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.12.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.12.hw.gamma_pow|stage7.field.one"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.12.booleanity.gamma_pow", "field.pow:37", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.12.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.12.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_12"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.12.virtualization.gamma_pow", "field.pow:38", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.12.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.12.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_12"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.13.hw.gamma_pow", "field.pow:39", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.13.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.13.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.13.booleanity.gamma_pow", "field.pow:40", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.13.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.13.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_13"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.13.virtualization.gamma_pow", "field.pow:41", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.13.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.13.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_13"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.14.hw.gamma_pow", "field.pow:42", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.14.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.14.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.14.booleanity.gamma_pow", "field.pow:43", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.14.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.14.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_14"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.14.virtualization.gamma_pow", "field.pow:44", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.14.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.14.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_14"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.15.hw.gamma_pow", "field.pow:45", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.15.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.15.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.15.booleanity.gamma_pow", "field.pow:46", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.15.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.15.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_15"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.15.virtualization.gamma_pow", "field.pow:47", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.15.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.15.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_15"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.16.hw.gamma_pow", "field.pow:48", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.16.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.16.hw.gamma_pow|stage7.field.one"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.16.booleanity.gamma_pow", "field.pow:49", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.16.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.16.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_16"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.16.virtualization.gamma_pow", "field.pow:50", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.16.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.16.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_16"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.17.hw.gamma_pow", "field.pow:51", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.17.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.17.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.17.booleanity.gamma_pow", "field.pow:52", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.17.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.17.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_17"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.17.virtualization.gamma_pow", "field.pow:53", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.17.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.17.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_17"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.18.hw.gamma_pow", "field.pow:54", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.18.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.18.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.18.booleanity.gamma_pow", "field.pow:55", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.18.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.18.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_18"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.18.virtualization.gamma_pow", "field.pow:56", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.18.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.18.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_18"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.19.hw.gamma_pow", "field.pow:57", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.19.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.19.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.19.booleanity.gamma_pow", "field.pow:58", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.19.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.19.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_19"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.19.virtualization.gamma_pow", "field.pow:59", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.19.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.19.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_19"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.20.hw.gamma_pow", "field.pow:60", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.20.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.20.hw.gamma_pow|stage7.field.one"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.20.booleanity.gamma_pow", "field.pow:61", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.20.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.20.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_20"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.20.virtualization.gamma_pow", "field.pow:62", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.20.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.20.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_20"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.21.hw.gamma_pow", "field.pow:63", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.21.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.21.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.21.booleanity.gamma_pow", "field.pow:64", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.21.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.21.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_21"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.21.virtualization.gamma_pow", "field.pow:65", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.21.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.21.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_21"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.22.hw.gamma_pow", "field.pow:66", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.22.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.22.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.22.booleanity.gamma_pow", "field.pow:67", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.22.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.22.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_22"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.22.virtualization.gamma_pow", "field.pow:68", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.22.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.22.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_22"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.23.hw.gamma_pow", "field.pow:69", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.23.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.23.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.23.booleanity.gamma_pow", "field.pow:70", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.23.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.23.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_23"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.23.virtualization.gamma_pow", "field.pow:71", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.23.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.23.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_23"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.24.hw.gamma_pow", "field.pow:72", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.24.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.24.hw.gamma_pow|stage7.field.one"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.24.booleanity.gamma_pow", "field.pow:73", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.24.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.24.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_24"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.24.virtualization.gamma_pow", "field.pow:74", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.24.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.24.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_24"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.25.hw.gamma_pow", "field.pow:75", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.25.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.25.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.25.booleanity.gamma_pow", "field.pow:76", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.25.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.25.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_25"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.25.virtualization.gamma_pow", "field.pow:77", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.25.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.25.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_25"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.26.hw.gamma_pow", "field.pow:78", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.26.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.26.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.26.booleanity.gamma_pow", "field.pow:79", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.26.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.26.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_26"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.26.virtualization.gamma_pow", "field.pow:80", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.26.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.26.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_26"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.27.hw.gamma_pow", "field.pow:81", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.27.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.27.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.27.booleanity.gamma_pow", "field.pow:82", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.27.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.27.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_27"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.27.virtualization.gamma_pow", "field.pow:83", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.27.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.27.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_27"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.28.hw.gamma_pow", "field.pow:84", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.28.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.28.hw.gamma_pow|stage7.field.one"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.28.booleanity.gamma_pow", "field.pow:85", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.28.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.28.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_28"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.28.virtualization.gamma_pow", "field.pow:86", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.28.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.28.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_28"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.29.hw.gamma_pow", "field.pow:87", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.29.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.29.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.29.booleanity.gamma_pow", "field.pow:88", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.29.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.29.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_29"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.29.virtualization.gamma_pow", "field.pow:89", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.29.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.29.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_29"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.30.hw.gamma_pow", "field.pow:90", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.30.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.30.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.30.booleanity.gamma_pow", "field.pow:91", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.30.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.30.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_30"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.30.virtualization.gamma_pow", "field.pow:92", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.30.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.30.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_30"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.31.hw.gamma_pow", "field.pow:93", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.31.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.31.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.31.booleanity.gamma_pow", "field.pow:94", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.31.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.31.booleanity.gamma_pow|stage7.input.stage6.booleanity.InstructionRa_31"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.31.virtualization.gamma_pow", "field.pow:95", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.31.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.31.virtualization.gamma_pow|stage7.input.stage6.instruction_ra_virtual.InstructionRa_31"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.32.hw.gamma_pow", "field.pow:96", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.32.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.32.hw.gamma_pow|stage7.field.one"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.32.booleanity.gamma_pow", "field.pow:97", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.32.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.32.booleanity.gamma_pow|stage7.input.stage6.booleanity.BytecodeRa_0"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.32.virtualization.gamma_pow", "field.pow:98", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.32.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.32.virtualization.gamma_pow|stage7.input.stage6.bytecode_read_raf.BytecodeRa_0"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.33.hw.gamma_pow", "field.pow:99", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.33.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.33.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.33.booleanity.gamma_pow", "field.pow:100", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.33.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.33.booleanity.gamma_pow|stage7.input.stage6.booleanity.BytecodeRa_1"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.33.virtualization.gamma_pow", "field.pow:101", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.33.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.33.virtualization.gamma_pow|stage7.input.stage6.bytecode_read_raf.BytecodeRa_1"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.34.hw.gamma_pow", "field.pow:102", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.34.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.34.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.34.booleanity.gamma_pow", "field.pow:103", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.34.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.34.booleanity.gamma_pow|stage7.input.stage6.booleanity.BytecodeRa_2"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.34.virtualization.gamma_pow", "field.pow:104", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.34.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.34.virtualization.gamma_pow|stage7.input.stage6.bytecode_read_raf.BytecodeRa_2"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.35.hw.gamma_pow", "field.pow:105", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.35.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.35.hw.gamma_pow|stage7.field.one"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.35.booleanity.gamma_pow", "field.pow:106", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.35.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.35.booleanity.gamma_pow|stage7.input.stage6.booleanity.BytecodeRa_3"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.35.virtualization.gamma_pow", "field.pow:107", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.35.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.35.virtualization.gamma_pow|stage7.input.stage6.bytecode_read_raf.BytecodeRa_3"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.36.hw.gamma_pow", "field.pow:108", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.36.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.36.hw.gamma_pow|stage7.input.stage6.hamming_booleanity.HammingWeight"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.36.booleanity.gamma_pow", "field.pow:109", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.36.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.36.booleanity.gamma_pow|stage7.input.stage6.booleanity.RamRa_0"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.36.virtualization.gamma_pow", "field.pow:110", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.36.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.36.virtualization.gamma_pow|stage7.input.stage6.ram_ra_virtual.RamRa_0"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.37.hw.gamma_pow", "field.pow:111", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.37.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.37.hw.gamma_pow|stage7.input.stage6.hamming_booleanity.HammingWeight"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.37.booleanity.gamma_pow", "field.pow:112", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.37.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.37.booleanity.gamma_pow|stage7.input.stage6.booleanity.RamRa_1"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.37.virtualization.gamma_pow", "field.pow:113", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.37.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.37.virtualization.gamma_pow|stage7.input.stage6.ram_ra_virtual.RamRa_1"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.38.hw.gamma_pow", "field.pow:114", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.38.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.38.hw.gamma_pow|stage7.input.stage6.hamming_booleanity.HammingWeight"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.38.booleanity.gamma_pow", "field.pow:115", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.38.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.38.booleanity.gamma_pow|stage7.input.stage6.booleanity.RamRa_2"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.38.virtualization.gamma_pow", "field.pow:116", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.38.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.38.virtualization.gamma_pow|stage7.input.stage6.ram_ra_virtual.RamRa_2"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.39.hw.gamma_pow", "field.pow:117", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.39.hw.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.39.hw.gamma_pow|stage7.input.stage6.hamming_booleanity.HammingWeight"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.39.booleanity.gamma_pow", "field.pow:118", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.39.booleanity.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.39.booleanity.gamma_pow|stage7.input.stage6.booleanity.RamRa_3"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.39.virtualization.gamma_pow", "field.pow:119", "stage7.hamming_weight_claim_reduction.gamma"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim.39.virtualization.gamma_term", "field.mul", "stage7.hamming_weight_claim_reduction.claim.39.virtualization.gamma_pow|stage7.input.stage6.ram_ra_virtual.RamRa_3"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial0", "field.add", "stage7.field.one|stage7.hamming_weight_claim_reduction.claim.0.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial1", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial0|stage7.hamming_weight_claim_reduction.claim.0.virtualization.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial2", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial1|stage7.hamming_weight_claim_reduction.claim.1.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial3", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial2|stage7.hamming_weight_claim_reduction.claim.1.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial4", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial3|stage7.hamming_weight_claim_reduction.claim.1.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial5", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial4|stage7.hamming_weight_claim_reduction.claim.2.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial6", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial5|stage7.hamming_weight_claim_reduction.claim.2.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial7", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial6|stage7.hamming_weight_claim_reduction.claim.2.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial8", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial7|stage7.hamming_weight_claim_reduction.claim.3.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial9", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial8|stage7.hamming_weight_claim_reduction.claim.3.booleanity.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial10", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial9|stage7.hamming_weight_claim_reduction.claim.3.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial11", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial10|stage7.hamming_weight_claim_reduction.claim.4.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial12", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial11|stage7.hamming_weight_claim_reduction.claim.4.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial13", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial12|stage7.hamming_weight_claim_reduction.claim.4.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial14", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial13|stage7.hamming_weight_claim_reduction.claim.5.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial15", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial14|stage7.hamming_weight_claim_reduction.claim.5.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial16", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial15|stage7.hamming_weight_claim_reduction.claim.5.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial17", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial16|stage7.hamming_weight_claim_reduction.claim.6.hw.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial18", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial17|stage7.hamming_weight_claim_reduction.claim.6.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial19", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial18|stage7.hamming_weight_claim_reduction.claim.6.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial20", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial19|stage7.hamming_weight_claim_reduction.claim.7.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial21", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial20|stage7.hamming_weight_claim_reduction.claim.7.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial22", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial21|stage7.hamming_weight_claim_reduction.claim.7.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial23", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial22|stage7.hamming_weight_claim_reduction.claim.8.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial24", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial23|stage7.hamming_weight_claim_reduction.claim.8.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial25", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial24|stage7.hamming_weight_claim_reduction.claim.8.virtualization.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial26", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial25|stage7.hamming_weight_claim_reduction.claim.9.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial27", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial26|stage7.hamming_weight_claim_reduction.claim.9.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial28", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial27|stage7.hamming_weight_claim_reduction.claim.9.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial29", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial28|stage7.hamming_weight_claim_reduction.claim.10.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial30", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial29|stage7.hamming_weight_claim_reduction.claim.10.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial31", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial30|stage7.hamming_weight_claim_reduction.claim.10.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial32", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial31|stage7.hamming_weight_claim_reduction.claim.11.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial33", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial32|stage7.hamming_weight_claim_reduction.claim.11.booleanity.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial34", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial33|stage7.hamming_weight_claim_reduction.claim.11.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial35", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial34|stage7.hamming_weight_claim_reduction.claim.12.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial36", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial35|stage7.hamming_weight_claim_reduction.claim.12.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial37", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial36|stage7.hamming_weight_claim_reduction.claim.12.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial38", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial37|stage7.hamming_weight_claim_reduction.claim.13.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial39", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial38|stage7.hamming_weight_claim_reduction.claim.13.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial40", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial39|stage7.hamming_weight_claim_reduction.claim.13.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial41", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial40|stage7.hamming_weight_claim_reduction.claim.14.hw.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial42", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial41|stage7.hamming_weight_claim_reduction.claim.14.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial43", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial42|stage7.hamming_weight_claim_reduction.claim.14.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial44", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial43|stage7.hamming_weight_claim_reduction.claim.15.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial45", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial44|stage7.hamming_weight_claim_reduction.claim.15.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial46", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial45|stage7.hamming_weight_claim_reduction.claim.15.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial47", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial46|stage7.hamming_weight_claim_reduction.claim.16.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial48", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial47|stage7.hamming_weight_claim_reduction.claim.16.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial49", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial48|stage7.hamming_weight_claim_reduction.claim.16.virtualization.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial50", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial49|stage7.hamming_weight_claim_reduction.claim.17.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial51", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial50|stage7.hamming_weight_claim_reduction.claim.17.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial52", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial51|stage7.hamming_weight_claim_reduction.claim.17.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial53", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial52|stage7.hamming_weight_claim_reduction.claim.18.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial54", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial53|stage7.hamming_weight_claim_reduction.claim.18.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial55", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial54|stage7.hamming_weight_claim_reduction.claim.18.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial56", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial55|stage7.hamming_weight_claim_reduction.claim.19.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial57", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial56|stage7.hamming_weight_claim_reduction.claim.19.booleanity.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial58", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial57|stage7.hamming_weight_claim_reduction.claim.19.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial59", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial58|stage7.hamming_weight_claim_reduction.claim.20.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial60", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial59|stage7.hamming_weight_claim_reduction.claim.20.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial61", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial60|stage7.hamming_weight_claim_reduction.claim.20.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial62", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial61|stage7.hamming_weight_claim_reduction.claim.21.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial63", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial62|stage7.hamming_weight_claim_reduction.claim.21.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial64", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial63|stage7.hamming_weight_claim_reduction.claim.21.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial65", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial64|stage7.hamming_weight_claim_reduction.claim.22.hw.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial66", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial65|stage7.hamming_weight_claim_reduction.claim.22.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial67", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial66|stage7.hamming_weight_claim_reduction.claim.22.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial68", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial67|stage7.hamming_weight_claim_reduction.claim.23.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial69", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial68|stage7.hamming_weight_claim_reduction.claim.23.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial70", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial69|stage7.hamming_weight_claim_reduction.claim.23.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial71", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial70|stage7.hamming_weight_claim_reduction.claim.24.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial72", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial71|stage7.hamming_weight_claim_reduction.claim.24.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial73", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial72|stage7.hamming_weight_claim_reduction.claim.24.virtualization.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial74", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial73|stage7.hamming_weight_claim_reduction.claim.25.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial75", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial74|stage7.hamming_weight_claim_reduction.claim.25.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial76", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial75|stage7.hamming_weight_claim_reduction.claim.25.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial77", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial76|stage7.hamming_weight_claim_reduction.claim.26.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial78", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial77|stage7.hamming_weight_claim_reduction.claim.26.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial79", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial78|stage7.hamming_weight_claim_reduction.claim.26.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial80", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial79|stage7.hamming_weight_claim_reduction.claim.27.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial81", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial80|stage7.hamming_weight_claim_reduction.claim.27.booleanity.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial82", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial81|stage7.hamming_weight_claim_reduction.claim.27.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial83", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial82|stage7.hamming_weight_claim_reduction.claim.28.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial84", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial83|stage7.hamming_weight_claim_reduction.claim.28.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial85", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial84|stage7.hamming_weight_claim_reduction.claim.28.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial86", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial85|stage7.hamming_weight_claim_reduction.claim.29.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial87", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial86|stage7.hamming_weight_claim_reduction.claim.29.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial88", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial87|stage7.hamming_weight_claim_reduction.claim.29.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial89", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial88|stage7.hamming_weight_claim_reduction.claim.30.hw.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial90", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial89|stage7.hamming_weight_claim_reduction.claim.30.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial91", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial90|stage7.hamming_weight_claim_reduction.claim.30.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial92", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial91|stage7.hamming_weight_claim_reduction.claim.31.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial93", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial92|stage7.hamming_weight_claim_reduction.claim.31.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial94", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial93|stage7.hamming_weight_claim_reduction.claim.31.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial95", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial94|stage7.hamming_weight_claim_reduction.claim.32.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial96", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial95|stage7.hamming_weight_claim_reduction.claim.32.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial97", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial96|stage7.hamming_weight_claim_reduction.claim.32.virtualization.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial98", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial97|stage7.hamming_weight_claim_reduction.claim.33.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial99", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial98|stage7.hamming_weight_claim_reduction.claim.33.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial100", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial99|stage7.hamming_weight_claim_reduction.claim.33.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial101", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial100|stage7.hamming_weight_claim_reduction.claim.34.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial102", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial101|stage7.hamming_weight_claim_reduction.claim.34.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial103", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial102|stage7.hamming_weight_claim_reduction.claim.34.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial104", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial103|stage7.hamming_weight_claim_reduction.claim.35.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial105", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial104|stage7.hamming_weight_claim_reduction.claim.35.booleanity.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial106", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial105|stage7.hamming_weight_claim_reduction.claim.35.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial107", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial106|stage7.hamming_weight_claim_reduction.claim.36.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial108", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial107|stage7.hamming_weight_claim_reduction.claim.36.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial109", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial108|stage7.hamming_weight_claim_reduction.claim.36.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial110", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial109|stage7.hamming_weight_claim_reduction.claim.37.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial111", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial110|stage7.hamming_weight_claim_reduction.claim.37.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial112", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial111|stage7.hamming_weight_claim_reduction.claim.37.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial113", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial112|stage7.hamming_weight_claim_reduction.claim.38.hw.gamma_term"), + stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial114", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial113|stage7.hamming_weight_claim_reduction.claim.38.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial115", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial114|stage7.hamming_weight_claim_reduction.claim.38.virtualization.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial116", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial115|stage7.hamming_weight_claim_reduction.claim.39.hw.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial117", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial116|stage7.hamming_weight_claim_reduction.claim.39.booleanity.gamma_term"), stage7_field_expr!("stage7.hamming_weight_claim_reduction.claim_expr.partial118", "field.add", "stage7.hamming_weight_claim_reduction.claim_expr.partial117|stage7.hamming_weight_claim_reduction.claim.39.virtualization.gamma_term"), +]; +pub const STAGE7_KERNELS: &[Stage7KernelPlan] = &[ + +]; + +pub const STAGE7_SUMCHECK_CLAIMS: &[Stage7SumcheckClaimPlan] = &[ + Stage7SumcheckClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.input", stage: "stage7", domain: "jolt.stage7_hamming_weight_claim_reduction_domain", num_rounds: 4, degree: 2, claim: "stage7.hamming_weight_claim_reduction.weighted_stage6_claims", kernel: None, relation: Some("jolt.stage7.hamming_weight_claim_reduction"), claim_value: "stage7.hamming_weight_claim_reduction.claim_expr.partial118", input_openings: "stage7.input.stage6.hamming_booleanity.HammingWeight|stage7.input.stage6.booleanity.InstructionRa_0|stage7.input.stage6.instruction_ra_virtual.InstructionRa_0|stage7.input.stage6.booleanity.InstructionRa_1|stage7.input.stage6.instruction_ra_virtual.InstructionRa_1|stage7.input.stage6.booleanity.InstructionRa_2|stage7.input.stage6.instruction_ra_virtual.InstructionRa_2|stage7.input.stage6.booleanity.InstructionRa_3|stage7.input.stage6.instruction_ra_virtual.InstructionRa_3|stage7.input.stage6.booleanity.InstructionRa_4|stage7.input.stage6.instruction_ra_virtual.InstructionRa_4|stage7.input.stage6.booleanity.InstructionRa_5|stage7.input.stage6.instruction_ra_virtual.InstructionRa_5|stage7.input.stage6.booleanity.InstructionRa_6|stage7.input.stage6.instruction_ra_virtual.InstructionRa_6|stage7.input.stage6.booleanity.InstructionRa_7|stage7.input.stage6.instruction_ra_virtual.InstructionRa_7|stage7.input.stage6.booleanity.InstructionRa_8|stage7.input.stage6.instruction_ra_virtual.InstructionRa_8|stage7.input.stage6.booleanity.InstructionRa_9|stage7.input.stage6.instruction_ra_virtual.InstructionRa_9|stage7.input.stage6.booleanity.InstructionRa_10|stage7.input.stage6.instruction_ra_virtual.InstructionRa_10|stage7.input.stage6.booleanity.InstructionRa_11|stage7.input.stage6.instruction_ra_virtual.InstructionRa_11|stage7.input.stage6.booleanity.InstructionRa_12|stage7.input.stage6.instruction_ra_virtual.InstructionRa_12|stage7.input.stage6.booleanity.InstructionRa_13|stage7.input.stage6.instruction_ra_virtual.InstructionRa_13|stage7.input.stage6.booleanity.InstructionRa_14|stage7.input.stage6.instruction_ra_virtual.InstructionRa_14|stage7.input.stage6.booleanity.InstructionRa_15|stage7.input.stage6.instruction_ra_virtual.InstructionRa_15|stage7.input.stage6.booleanity.InstructionRa_16|stage7.input.stage6.instruction_ra_virtual.InstructionRa_16|stage7.input.stage6.booleanity.InstructionRa_17|stage7.input.stage6.instruction_ra_virtual.InstructionRa_17|stage7.input.stage6.booleanity.InstructionRa_18|stage7.input.stage6.instruction_ra_virtual.InstructionRa_18|stage7.input.stage6.booleanity.InstructionRa_19|stage7.input.stage6.instruction_ra_virtual.InstructionRa_19|stage7.input.stage6.booleanity.InstructionRa_20|stage7.input.stage6.instruction_ra_virtual.InstructionRa_20|stage7.input.stage6.booleanity.InstructionRa_21|stage7.input.stage6.instruction_ra_virtual.InstructionRa_21|stage7.input.stage6.booleanity.InstructionRa_22|stage7.input.stage6.instruction_ra_virtual.InstructionRa_22|stage7.input.stage6.booleanity.InstructionRa_23|stage7.input.stage6.instruction_ra_virtual.InstructionRa_23|stage7.input.stage6.booleanity.InstructionRa_24|stage7.input.stage6.instruction_ra_virtual.InstructionRa_24|stage7.input.stage6.booleanity.InstructionRa_25|stage7.input.stage6.instruction_ra_virtual.InstructionRa_25|stage7.input.stage6.booleanity.InstructionRa_26|stage7.input.stage6.instruction_ra_virtual.InstructionRa_26|stage7.input.stage6.booleanity.InstructionRa_27|stage7.input.stage6.instruction_ra_virtual.InstructionRa_27|stage7.input.stage6.booleanity.InstructionRa_28|stage7.input.stage6.instruction_ra_virtual.InstructionRa_28|stage7.input.stage6.booleanity.InstructionRa_29|stage7.input.stage6.instruction_ra_virtual.InstructionRa_29|stage7.input.stage6.booleanity.InstructionRa_30|stage7.input.stage6.instruction_ra_virtual.InstructionRa_30|stage7.input.stage6.booleanity.InstructionRa_31|stage7.input.stage6.instruction_ra_virtual.InstructionRa_31|stage7.input.stage6.booleanity.BytecodeRa_0|stage7.input.stage6.bytecode_read_raf.BytecodeRa_0|stage7.input.stage6.booleanity.BytecodeRa_1|stage7.input.stage6.bytecode_read_raf.BytecodeRa_1|stage7.input.stage6.booleanity.BytecodeRa_2|stage7.input.stage6.bytecode_read_raf.BytecodeRa_2|stage7.input.stage6.booleanity.BytecodeRa_3|stage7.input.stage6.bytecode_read_raf.BytecodeRa_3|stage7.input.stage6.booleanity.RamRa_0|stage7.input.stage6.ram_ra_virtual.RamRa_0|stage7.input.stage6.booleanity.RamRa_1|stage7.input.stage6.ram_ra_virtual.RamRa_1|stage7.input.stage6.booleanity.RamRa_2|stage7.input.stage6.ram_ra_virtual.RamRa_2|stage7.input.stage6.booleanity.RamRa_3|stage7.input.stage6.ram_ra_virtual.RamRa_3" }, +]; +pub const STAGE7_SUMCHECK_BATCH_0_ROUND_SCHEDULE: &[usize] = &[ + 4, +]; + +pub const STAGE7_SUMCHECK_BATCHES: &[Stage7SumcheckBatchPlan] = &[ + Stage7SumcheckBatchPlan { symbol: "stage7.batch", stage: "stage7", proof_slot: "stage7.sumcheck", policy: "jolt_core_stage7_aligned", count: 1, ordered_claims: "stage7.hamming_weight_claim_reduction.input", claim_operands: "stage7.hamming_weight_claim_reduction.input", claim_label: "sumcheck_claim", round_label: "sumcheck_poly", round_schedule: STAGE7_SUMCHECK_BATCH_0_ROUND_SCHEDULE }, +]; +pub const STAGE7_SUMCHECK_DRIVER_0_ROUND_SCHEDULE: &[usize] = &[ + 4, +]; + +pub const STAGE7_SUMCHECK_DRIVERS: &[Stage7SumcheckDriverPlan] = &[ + Stage7SumcheckDriverPlan { symbol: "stage7.sumcheck", stage: "stage7", proof_slot: "stage7.sumcheck", kernel: None, relation: Some("jolt.stage7.batched"), batch: "stage7.batch", policy: "jolt_core_stage7_aligned", round_schedule: STAGE7_SUMCHECK_DRIVER_0_ROUND_SCHEDULE, claim_label: "sumcheck_claim", round_label: "sumcheck_poly", num_rounds: 4, degree: 2 }, +]; +pub const STAGE7_SUMCHECK_INSTANCE_RESULTS: &[Stage7SumcheckInstanceResultPlan] = &[ + Stage7SumcheckInstanceResultPlan { symbol: "stage7.hamming_weight_claim_reduction.instance", source: "stage7.sumcheck", claim: "stage7.hamming_weight_claim_reduction.input", relation: "jolt.stage7.hamming_weight_claim_reduction", index: 0, point_arity: 4, num_rounds: 4, round_offset: 0, point_order: "reverse", degree: 2 }, +]; + +macro_rules! stage7_sumcheck_eval { + ($symbol:literal, $source:literal, $name:literal, $index:literal, $oracle:literal) => { + Stage7SumcheckEvalPlan { symbol: $symbol, source: $source, name: $name, index: $index, oracle: $oracle } + }; +} + +#[rustfmt::skip] +pub const STAGE7_SUMCHECK_EVALS: &[Stage7SumcheckEvalPlan] = &[ + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", 0, "InstructionRa_0"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_1", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_1", 1, "InstructionRa_1"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_2", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_2", 2, "InstructionRa_2"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_3", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_3", 3, "InstructionRa_3"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_4", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_4", 4, "InstructionRa_4"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_5", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_5", 5, "InstructionRa_5"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_6", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_6", 6, "InstructionRa_6"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_7", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_7", 7, "InstructionRa_7"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_8", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_8", 8, "InstructionRa_8"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_9", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_9", 9, "InstructionRa_9"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_10", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_10", 10, "InstructionRa_10"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_11", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_11", 11, "InstructionRa_11"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_12", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_12", 12, "InstructionRa_12"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_13", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_13", 13, "InstructionRa_13"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_14", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_14", 14, "InstructionRa_14"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_15", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_15", 15, "InstructionRa_15"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_16", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_16", 16, "InstructionRa_16"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_17", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_17", 17, "InstructionRa_17"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_18", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_18", 18, "InstructionRa_18"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_19", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_19", 19, "InstructionRa_19"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_20", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_20", 20, "InstructionRa_20"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_21", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_21", 21, "InstructionRa_21"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_22", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_22", 22, "InstructionRa_22"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_23", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_23", 23, "InstructionRa_23"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_24", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_24", 24, "InstructionRa_24"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_25", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_25", 25, "InstructionRa_25"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_26", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_26", 26, "InstructionRa_26"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_27", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_27", 27, "InstructionRa_27"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_28", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_28", 28, "InstructionRa_28"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_29", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_29", 29, "InstructionRa_29"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_30", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_30", 30, "InstructionRa_30"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.InstructionRa_31", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.InstructionRa_31", 31, "InstructionRa_31"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0", 32, "BytecodeRa_0"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1", 33, "BytecodeRa_1"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2", 34, "BytecodeRa_2"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3", 35, "BytecodeRa_3"), + stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.RamRa_0", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.RamRa_0", 36, "RamRa_0"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.RamRa_1", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.RamRa_1", 37, "RamRa_1"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.RamRa_2", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.RamRa_2", 38, "RamRa_2"), stage7_sumcheck_eval!("stage7.hamming_weight_claim_reduction.eval.RamRa_3", "stage7.sumcheck", "stage7.hamming_weight_claim_reduction.eval.RamRa_3", 39, "RamRa_3"), +]; + +pub const STAGE7_POINT_ZEROS: &[Stage7PointZeroPlan] = &[ + +]; + +pub const STAGE7_POINT_SLICES: &[Stage7PointSlicePlan] = &[ + Stage7PointSlicePlan { symbol: "stage7.hamming_weight_claim_reduction.point.cycle", source: "stage7.input.stage6.booleanity.InstructionRa_0", offset: 4, length: 18, input: "stage7.input.stage6.booleanity.InstructionRa_0" }, +]; + +pub const STAGE7_POINT_CONCATS: &[Stage7PointConcatPlan] = &[ + Stage7PointConcatPlan { symbol: "stage7.hamming_weight_claim_reduction.point", layout: "address_chunk_then_cycle", arity: 22, inputs: "stage7.hamming_weight_claim_reduction.instance|stage7.hamming_weight_claim_reduction.point.cycle" }, +]; +pub const STAGE7_OPENING_CLAIMS: &[Stage7OpeningClaimPlan] = &[ + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_1" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_2" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_3" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_4" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_5" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_6" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_7" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_8" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_9" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_10" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_11" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_12" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_13" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_14" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_15" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_16" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_17" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_18" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_19" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_20" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_21" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_22" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_23" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_24" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_25" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_26" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_27" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_28" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_29" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_30" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_31" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_0" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_1" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_2" }, + Stage7OpeningClaimPlan { symbol: "stage7.hamming_weight_claim_reduction.opening.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed", point_source: "stage7.hamming_weight_claim_reduction.point", eval_source: "stage7.hamming_weight_claim_reduction.eval.RamRa_3" }, +]; + +pub const STAGE7_OPENING_EQUALITIES: &[Stage7OpeningClaimEqualityPlan] = &[ + +]; + +pub const STAGE7_OPENING_BATCHES: &[Stage7OpeningBatchPlan] = &[ + Stage7OpeningBatchPlan { symbol: "stage7.openings", stage: "stage7", proof_slot: "stage7.openings", policy: "jolt_stage7_output_order", count: 40, ordered_claims: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0|stage7.hamming_weight_claim_reduction.opening.InstructionRa_1|stage7.hamming_weight_claim_reduction.opening.InstructionRa_2|stage7.hamming_weight_claim_reduction.opening.InstructionRa_3|stage7.hamming_weight_claim_reduction.opening.InstructionRa_4|stage7.hamming_weight_claim_reduction.opening.InstructionRa_5|stage7.hamming_weight_claim_reduction.opening.InstructionRa_6|stage7.hamming_weight_claim_reduction.opening.InstructionRa_7|stage7.hamming_weight_claim_reduction.opening.InstructionRa_8|stage7.hamming_weight_claim_reduction.opening.InstructionRa_9|stage7.hamming_weight_claim_reduction.opening.InstructionRa_10|stage7.hamming_weight_claim_reduction.opening.InstructionRa_11|stage7.hamming_weight_claim_reduction.opening.InstructionRa_12|stage7.hamming_weight_claim_reduction.opening.InstructionRa_13|stage7.hamming_weight_claim_reduction.opening.InstructionRa_14|stage7.hamming_weight_claim_reduction.opening.InstructionRa_15|stage7.hamming_weight_claim_reduction.opening.InstructionRa_16|stage7.hamming_weight_claim_reduction.opening.InstructionRa_17|stage7.hamming_weight_claim_reduction.opening.InstructionRa_18|stage7.hamming_weight_claim_reduction.opening.InstructionRa_19|stage7.hamming_weight_claim_reduction.opening.InstructionRa_20|stage7.hamming_weight_claim_reduction.opening.InstructionRa_21|stage7.hamming_weight_claim_reduction.opening.InstructionRa_22|stage7.hamming_weight_claim_reduction.opening.InstructionRa_23|stage7.hamming_weight_claim_reduction.opening.InstructionRa_24|stage7.hamming_weight_claim_reduction.opening.InstructionRa_25|stage7.hamming_weight_claim_reduction.opening.InstructionRa_26|stage7.hamming_weight_claim_reduction.opening.InstructionRa_27|stage7.hamming_weight_claim_reduction.opening.InstructionRa_28|stage7.hamming_weight_claim_reduction.opening.InstructionRa_29|stage7.hamming_weight_claim_reduction.opening.InstructionRa_30|stage7.hamming_weight_claim_reduction.opening.InstructionRa_31|stage7.hamming_weight_claim_reduction.opening.BytecodeRa_0|stage7.hamming_weight_claim_reduction.opening.BytecodeRa_1|stage7.hamming_weight_claim_reduction.opening.BytecodeRa_2|stage7.hamming_weight_claim_reduction.opening.BytecodeRa_3|stage7.hamming_weight_claim_reduction.opening.RamRa_0|stage7.hamming_weight_claim_reduction.opening.RamRa_1|stage7.hamming_weight_claim_reduction.opening.RamRa_2|stage7.hamming_weight_claim_reduction.opening.RamRa_3", claim_operands: "stage7.hamming_weight_claim_reduction.opening.InstructionRa_0|stage7.hamming_weight_claim_reduction.opening.InstructionRa_1|stage7.hamming_weight_claim_reduction.opening.InstructionRa_2|stage7.hamming_weight_claim_reduction.opening.InstructionRa_3|stage7.hamming_weight_claim_reduction.opening.InstructionRa_4|stage7.hamming_weight_claim_reduction.opening.InstructionRa_5|stage7.hamming_weight_claim_reduction.opening.InstructionRa_6|stage7.hamming_weight_claim_reduction.opening.InstructionRa_7|stage7.hamming_weight_claim_reduction.opening.InstructionRa_8|stage7.hamming_weight_claim_reduction.opening.InstructionRa_9|stage7.hamming_weight_claim_reduction.opening.InstructionRa_10|stage7.hamming_weight_claim_reduction.opening.InstructionRa_11|stage7.hamming_weight_claim_reduction.opening.InstructionRa_12|stage7.hamming_weight_claim_reduction.opening.InstructionRa_13|stage7.hamming_weight_claim_reduction.opening.InstructionRa_14|stage7.hamming_weight_claim_reduction.opening.InstructionRa_15|stage7.hamming_weight_claim_reduction.opening.InstructionRa_16|stage7.hamming_weight_claim_reduction.opening.InstructionRa_17|stage7.hamming_weight_claim_reduction.opening.InstructionRa_18|stage7.hamming_weight_claim_reduction.opening.InstructionRa_19|stage7.hamming_weight_claim_reduction.opening.InstructionRa_20|stage7.hamming_weight_claim_reduction.opening.InstructionRa_21|stage7.hamming_weight_claim_reduction.opening.InstructionRa_22|stage7.hamming_weight_claim_reduction.opening.InstructionRa_23|stage7.hamming_weight_claim_reduction.opening.InstructionRa_24|stage7.hamming_weight_claim_reduction.opening.InstructionRa_25|stage7.hamming_weight_claim_reduction.opening.InstructionRa_26|stage7.hamming_weight_claim_reduction.opening.InstructionRa_27|stage7.hamming_weight_claim_reduction.opening.InstructionRa_28|stage7.hamming_weight_claim_reduction.opening.InstructionRa_29|stage7.hamming_weight_claim_reduction.opening.InstructionRa_30|stage7.hamming_weight_claim_reduction.opening.InstructionRa_31|stage7.hamming_weight_claim_reduction.opening.BytecodeRa_0|stage7.hamming_weight_claim_reduction.opening.BytecodeRa_1|stage7.hamming_weight_claim_reduction.opening.BytecodeRa_2|stage7.hamming_weight_claim_reduction.opening.BytecodeRa_3|stage7.hamming_weight_claim_reduction.opening.RamRa_0|stage7.hamming_weight_claim_reduction.opening.RamRa_1|stage7.hamming_weight_claim_reduction.opening.RamRa_2|stage7.hamming_weight_claim_reduction.opening.RamRa_3" }, +]; +pub const STAGE7_PROGRAM: Stage7VerifierProgramPlan = Stage7CpuProgramPlan { + role: "verifier", + params: STAGE7_PARAMS, + steps: STAGE7_PROGRAM_STEPS, + transcript_squeezes: STAGE7_TRANSCRIPT_SQUEEZES, + transcript_absorb_bytes: STAGE7_TRANSCRIPT_ABSORB_BYTES, + opening_inputs: STAGE7_OPENING_INPUTS, + field_constants: STAGE7_FIELD_CONSTANTS, + field_exprs: STAGE7_FIELD_EXPRS, + kernels: STAGE7_KERNELS, + claims: STAGE7_SUMCHECK_CLAIMS, + batches: STAGE7_SUMCHECK_BATCHES, + drivers: STAGE7_SUMCHECK_DRIVERS, + instance_results: STAGE7_SUMCHECK_INSTANCE_RESULTS, + evals: STAGE7_SUMCHECK_EVALS, + point_zeros: STAGE7_POINT_ZEROS, + point_slices: STAGE7_POINT_SLICES, + point_concats: STAGE7_POINT_CONCATS, + opening_claims: STAGE7_OPENING_CLAIMS, + opening_equalities: STAGE7_OPENING_EQUALITIES, + opening_batches: STAGE7_OPENING_BATCHES, +}; + +pub fn verify_stage7( + proof: &Stage7Proof, + opening_inputs: &[Stage7OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage7Error> +where + T: Transcript, +{ + verify_stage7_with_program(&STAGE7_PROGRAM, proof, opening_inputs, transcript) +} + +pub fn verify_stage7_with_program( + program: &'static Stage7VerifierProgramPlan, + proof: &Stage7Proof, + opening_inputs: &[Stage7OpeningInputValue], + transcript: &mut T, +) -> Result, VerifyStage7Error> +where + T: Transcript, +{ + if proof.sumchecks.len() != program.drivers.len() { + return Err(VerifyStage7Error::UnexpectedProofCount { + expected: program.drivers.len(), + got: proof.sumchecks.len(), + }); + } + let mut store = + super::common::ValueStore::with_opening_inputs(opening_inputs, program.opening_inputs)?; + store.seed_constants(program.field_constants); + store.seed_point_zeros(program.point_zeros); + let mut artifacts = Stage7ExecutionArtifacts::default(); + for step in program.steps { + match step.kind { + "transcript_squeeze" => { + let squeeze = + find_plan(program.transcript_squeezes, step.symbol).ok_or(VerifyStage7Error::MissingValue { + symbol: step.symbol, + })?; + verify_stage7_squeeze(program, squeeze, &mut store, transcript, &mut artifacts)?; + } + "transcript_absorb_bytes" => { + let absorb = find_plan(program.transcript_absorb_bytes, step.symbol).ok_or( + VerifyStage7Error::MissingValue { + symbol: step.symbol, + }, + )?; + absorb_stage7_bytes(absorb, transcript); + } + "sumcheck_driver" => { + let driver = + find_plan(program.drivers, step.symbol).ok_or(VerifyStage7Error::MissingProof { + driver: step.symbol, + })?; + verify_stage7_driver( + program, + driver, + proof, + &mut store, + transcript, + &mut artifacts, + )?; + } + _ => { + return Err(VerifyStage7Error::InvalidProof { + driver: step.symbol, + reason: "unsupported stage7 program step", + }); + } + } + } + artifacts + .opening_batches + .extend(program.opening_batches.iter()); + Ok(artifacts) +} + +pub fn stage7_verifier_program() -> &'static Stage7VerifierProgramPlan { + &STAGE7_PROGRAM +} + +fn verify_stage7_squeeze( + program: &'static Stage7VerifierProgramPlan, + squeeze: &'static Stage7TranscriptSqueezePlan, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage7ExecutionArtifacts, +) -> Result<(), VerifyStage7Error> +where + T: Transcript, +{ + let values = transcript.challenge_vector(squeeze.count); + store.observe_challenge_vector(squeeze, &values, |input, expected, actual| { + VerifyStage7Error::InvalidInputLength { + input, + expected, + actual, + } + })?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage7Error::from)?; + artifacts.challenge_vectors.push(Stage7ChallengeVector { + symbol: squeeze.symbol, + values, + }); + Ok(()) +} + +fn absorb_stage7_bytes(absorb: &'static Stage7TranscriptAbsorbBytesPlan, transcript: &mut T) +where + T: Transcript, +{ + transcript.append(&LabelWithCount( + absorb.label.as_bytes(), + absorb.payload.len() as u64, + )); + transcript.append_bytes(absorb.payload.as_bytes()); +} + +fn verify_stage7_driver( + program: &'static Stage7VerifierProgramPlan, + driver: &'static Stage7SumcheckDriverPlan, + proof: &Stage7Proof, + store: &mut super::common::ValueStore, + transcript: &mut T, + artifacts: &mut Stage7ExecutionArtifacts, +) -> Result<(), VerifyStage7Error> +where + T: Transcript, +{ + let proof = proof + .sumchecks + .get(artifacts.sumchecks.len()) + .ok_or(VerifyStage7Error::MissingProof { + driver: driver.symbol, + })?; + let relation = driver.relation.unwrap_or(""); + let output = match relation { + "jolt.stage7.batched" => { + verify_batched_stage7(program, driver, proof, store, transcript)? + } + _ => return Err(VerifyStage7Error::UnsupportedRelation { relation }), + }; + artifacts.sumchecks.push(output); + Ok(()) +} + +fn verify_batched_stage7( + program: &'static Stage7VerifierProgramPlan, + driver: &'static Stage7SumcheckDriverPlan, + proof: &Stage7SumcheckOutput, + store: &mut super::common::ValueStore, + transcript: &mut T, +) -> Result, VerifyStage7Error> +where + T: Transcript, +{ + super::common::verify_batched_sumcheck( + driver, + proof, + program.claims, + program.batches, + program.field_exprs, + program.opening_inputs, + program.opening_claims, + program.opening_batches, + store, + transcript, + |store, evals, point, batching_coeffs| { + expected_batched_output_claim(program, driver, store, evals, point, batching_coeffs) + }, + |store, verified| observe_stage7_sumcheck_output(program, store, verified), + |driver, error| VerifyStage7Error::Sumcheck { driver, error }, + ) +} + +fn observe_stage7_sumcheck_output( + program: &'static Stage7VerifierProgramPlan, + store: &mut super::common::ValueStore, + output: &Stage7SumcheckOutput, +) -> Result<(), VerifyStage7Error> { + store.observe_sumcheck_output( + program.instance_results, + program.evals, + output, + |instance, mut point| { + match instance.point_order { + "as_is" => {} + "reverse" => point.reverse(), + "bytecode_read_raf" => point = normalize_bytecode_read_raf_point(&point, stage7_trace_rounds(program)?, "stage7.bytecode_read_raf.point")?, + "stage7_booleanity" => {} + "instruction_read_raf" => point = normalize_instruction_read_raf_point(&point, "stage7.instruction_read_raf.point")?, + _ => { + return Err(VerifyStage7Error::InvalidProof { + driver: output.driver, + reason: "unsupported point order", + }); + } + } + Ok(point) + }, + |input, expected, actual| VerifyStage7Error::InvalidInputLength { + input, + expected, + actual, + }, + |symbol| VerifyStage7Error::MissingValue { symbol }, + )?; + store.evaluate_available_points( + program.point_slices, + program.point_concats, + |input, expected, actual| VerifyStage7Error::InvalidInputLength { + input, + expected, + actual, + }, + )?; + store + .evaluate_available_field_exprs(program.field_exprs, super::common::evaluate_field_expr) + .map_err(VerifyStage7Error::from)?; + store.verify_opening_equalities( + program.opening_equalities, + |driver, reason| VerifyStage7Error::InvalidProof { driver, reason }, + |symbol| VerifyStage7Error::MissingValue { symbol }, + ) +} + +fn expected_batched_output_claim( + program: &'static Stage7VerifierProgramPlan, + driver: &'static Stage7SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage7NamedEval], + point: &[Fr], + batching_coeffs: &[Fr], +) -> Result { + let batch = find_batch(program.batches, driver.symbol, driver.batch)?; + let claims = batch_claims(program.claims, batch)?; + let mut expected = Fr::from_u64(0); + for (claim, coefficient) in claims.iter().zip(batching_coeffs) { + let instance = program + .instance_results + .iter() + .find(|instance| instance.claim == claim.symbol && instance.source == driver.symbol) + .ok_or(VerifyStage7Error::MissingClaim { + batch: batch.symbol, + claim: claim.symbol, + })?; + let local_point = point + .get(instance.round_offset..instance.round_offset + instance.num_rounds) + .ok_or(VerifyStage7Error::InvalidInputLength { + input: instance.symbol, + expected: instance.round_offset + instance.num_rounds, + actual: point.len(), + })?; + let relation = claim.relation.unwrap_or(""); + let value = match relation { + "jolt.stage7.hamming_weight_claim_reduction" => { + expected_hamming_weight_claim_reduction(program, driver, store, evals, local_point)? + } + _ => return Err(VerifyStage7Error::UnsupportedRelation { relation }), + }; + expected += *coefficient * value; + } + Ok(expected) +} + +fn expected_hamming_weight_claim_reduction( + program: &'static Stage7VerifierProgramPlan, + driver: &'static Stage7SumcheckDriverPlan, + store: &super::common::ValueStore, + evals: &[Stage7NamedEval], + local_point: &[Fr], +) -> Result { + let rho_rev = reverse_slice(local_point); + let booleanity_point = super::common::store_point(store, "stage7.input.stage6.booleanity.InstructionRa_0")?; + let r_addr_bool = + booleanity_point + .get(..local_point.len()) + .ok_or(VerifyStage7Error::InvalidInputLength { + input: "stage7.input.stage6.booleanity.InstructionRa_0", + expected: local_point.len(), + actual: booleanity_point.len(), + })?; + let eq_bool = EqPolynomial::::mle(&rho_rev, r_addr_bool); + let gamma = super::common::store_scalar(store, "stage7.hamming_weight_claim_reduction.gamma")?; + let mut gamma_power = Fr::from_u64(1); + let mut expected = Fr::from_u64(0); + let mut eval_plans = program + .evals + .iter() + .filter(|eval| eval.source == driver.symbol) + .collect::>(); + eval_plans.sort_by_key(|eval| eval.index); + for eval_plan in eval_plans { + let g_i = eval_by_name(evals, eval_plan.name)?; + let virt_point = + stage7_virtualization_point(store, eval_plan.oracle, local_point.len())?; + let eq_virt = EqPolynomial::::mle(&rho_rev, virt_point); + expected += g_i * (gamma_power + gamma_power * gamma * eq_bool + + gamma_power * gamma.square() * eq_virt); + gamma_power *= gamma; + gamma_power *= gamma; + gamma_power *= gamma; + } + Ok(expected) +} + +fn stage7_virtualization_point<'a>( + store: &'a super::common::ValueStore, + oracle: &str, + log_k_chunk: usize, +) -> Result<&'a [Fr], VerifyStage7Error> { + let symbol = if oracle.starts_with("InstructionRa_") { + format!("stage7.input.stage6.instruction_ra_virtual.{oracle}") + } else if oracle.starts_with("BytecodeRa_") { + format!("stage7.input.stage6.bytecode_read_raf.{oracle}") + } else if oracle.starts_with("RamRa_") { + format!("stage7.input.stage6.ram_ra_virtual.{oracle}") + } else { + return Err(VerifyStage7Error::MissingValue { + symbol: "stage7.hamming_weight_claim_reduction.oracle", + }); + }; + let point = store.try_point(&symbol).ok_or(VerifyStage7Error::MissingValue { + symbol: "stage7.hamming_weight_claim_reduction.virtualization_point", + })?; + point + .get(..log_k_chunk) + .ok_or(VerifyStage7Error::InvalidInputLength { + input: "stage7.hamming_weight_claim_reduction.virtualization_point", + expected: log_k_chunk, + actual: point.len(), + }) +} + +fn stage7_trace_rounds( + program: &'static Stage7VerifierProgramPlan, +) -> Result { + program + .instance_results + .iter() + .find(|instance| instance.relation == "jolt.stage7.hamming_booleanity") + .map(|instance| instance.num_rounds) + .ok_or(VerifyStage7Error::MissingValue { + symbol: "stage7.hamming_booleanity.instance", + }) +} diff --git a/crates/jolt-verifier/src/stages/stage8.rs b/crates/jolt-verifier/src/stages/stage8.rs new file mode 100644 index 0000000000..497a8b7469 --- /dev/null +++ b/crates/jolt-verifier/src/stages/stage8.rs @@ -0,0 +1,175 @@ +#![allow(clippy::too_many_lines)] + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8Params { + pub field: &'static str, + pub pcs: &'static str, + pub transcript: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8OpeningInputPlan { + pub symbol: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, + pub oracle: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub claim_kind: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8OpeningClaimPlan { + pub symbol: &'static str, + pub oracle: &'static str, + pub family: &'static str, + pub domain: &'static str, + pub point_arity: usize, + pub point_source: &'static str, + pub eval_source: &'static str, + pub source_stage: &'static str, + pub source_claim: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8OpeningBatchPlan { + pub symbol: &'static str, + pub proof_slot: &'static str, + pub policy: &'static str, + pub count: usize, + pub ordered_claims: &'static [&'static str], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8PcsProofPlan { + pub symbol: &'static str, + pub mode: &'static str, + pub pcs: &'static str, + pub proof_slot: &'static str, + pub transcript_label: &'static str, + pub batch: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage8EvaluationProgramPlan { + pub role: &'static str, + pub function: &'static str, + pub params: Stage8Params, + pub evaluation_point_source: Stage8OpeningInputPlan, + pub opening_inputs: &'static [Stage8OpeningInputPlan], + pub opening_claims: &'static [Stage8OpeningClaimPlan], + pub opening_batch: Stage8OpeningBatchPlan, + pub pcs_proof: Stage8PcsProofPlan, +} + +pub const STAGE8_PARAMS: Stage8Params = Stage8Params { field: "bn254_fr", pcs: "dory", transcript: "blake2b_transcript" }; + +pub const STAGE8_EVALUATION_POINT_SOURCE: Stage8OpeningInputPlan = Stage8OpeningInputPlan { symbol: "stage8.evaluation.point_source", source_stage: "stage7", source_claim: "stage7.input.stage6.booleanity.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }; + +pub const STAGE8_OPENING_INPUTS: &[Stage8OpeningInputPlan] = &[ + Stage8OpeningInputPlan { symbol: "stage8.evaluation.point_source", source_stage: "stage7", source_claim: "stage7.input.stage6.booleanity.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage6.RamInc", source_stage: "stage6", source_claim: "stage6.inc_claim_reduction.eval.RamInc", oracle: "RamInc", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage6.RdInc", source_stage: "stage6", source_claim: "stage6.inc_claim_reduction.eval.RdInc", oracle: "RdInc", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0", oracle: "InstructionRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_1", oracle: "InstructionRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_2", oracle: "InstructionRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_3", oracle: "InstructionRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_4", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_4", oracle: "InstructionRa_4", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_5", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_5", oracle: "InstructionRa_5", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_6", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_6", oracle: "InstructionRa_6", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_7", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_7", oracle: "InstructionRa_7", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_8", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_8", oracle: "InstructionRa_8", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_9", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_9", oracle: "InstructionRa_9", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_10", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_10", oracle: "InstructionRa_10", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_11", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_11", oracle: "InstructionRa_11", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_12", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_12", oracle: "InstructionRa_12", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_13", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_13", oracle: "InstructionRa_13", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_14", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_14", oracle: "InstructionRa_14", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_15", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_15", oracle: "InstructionRa_15", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_16", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_16", oracle: "InstructionRa_16", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_17", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_17", oracle: "InstructionRa_17", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_18", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_18", oracle: "InstructionRa_18", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_19", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_19", oracle: "InstructionRa_19", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_20", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_20", oracle: "InstructionRa_20", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_21", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_21", oracle: "InstructionRa_21", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_22", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_22", oracle: "InstructionRa_22", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_23", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_23", oracle: "InstructionRa_23", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_24", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_24", oracle: "InstructionRa_24", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_25", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_25", oracle: "InstructionRa_25", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_26", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_26", oracle: "InstructionRa_26", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_27", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_27", oracle: "InstructionRa_27", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_28", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_28", oracle: "InstructionRa_28", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_29", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_29", oracle: "InstructionRa_29", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_30", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_30", oracle: "InstructionRa_30", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.InstructionRa_31", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_31", oracle: "InstructionRa_31", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.BytecodeRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0", oracle: "BytecodeRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.BytecodeRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1", oracle: "BytecodeRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.BytecodeRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2", oracle: "BytecodeRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.BytecodeRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3", oracle: "BytecodeRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.RamRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_0", oracle: "RamRa_0", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.RamRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_1", oracle: "RamRa_1", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.RamRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_2", oracle: "RamRa_2", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, + Stage8OpeningInputPlan { symbol: "stage8.input.stage7.RamRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_3", oracle: "RamRa_3", domain: "jolt.main_witness_commit_domain", point_arity: 22, claim_kind: "committed" }, +]; + +pub const STAGE8_OPENING_CLAIMS: &[Stage8OpeningClaimPlan] = &[ + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamInc", oracle: "RamInc", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage6.RamInc", eval_source: "stage8.input.stage6.RamInc", source_stage: "stage6", source_claim: "stage6.inc_claim_reduction.eval.RamInc" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RdInc", oracle: "RdInc", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage6.RdInc", eval_source: "stage8.input.stage6.RdInc", source_stage: "stage6", source_claim: "stage6.inc_claim_reduction.eval.RdInc" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_0", oracle: "InstructionRa_0", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_0", eval_source: "stage8.input.stage7.InstructionRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_0" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_1", oracle: "InstructionRa_1", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_1", eval_source: "stage8.input.stage7.InstructionRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_1" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_2", oracle: "InstructionRa_2", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_2", eval_source: "stage8.input.stage7.InstructionRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_2" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_3", oracle: "InstructionRa_3", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_3", eval_source: "stage8.input.stage7.InstructionRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_3" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_4", oracle: "InstructionRa_4", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_4", eval_source: "stage8.input.stage7.InstructionRa_4", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_4" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_5", oracle: "InstructionRa_5", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_5", eval_source: "stage8.input.stage7.InstructionRa_5", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_5" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_6", oracle: "InstructionRa_6", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_6", eval_source: "stage8.input.stage7.InstructionRa_6", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_6" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_7", oracle: "InstructionRa_7", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_7", eval_source: "stage8.input.stage7.InstructionRa_7", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_7" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_8", oracle: "InstructionRa_8", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_8", eval_source: "stage8.input.stage7.InstructionRa_8", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_8" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_9", oracle: "InstructionRa_9", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_9", eval_source: "stage8.input.stage7.InstructionRa_9", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_9" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_10", oracle: "InstructionRa_10", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_10", eval_source: "stage8.input.stage7.InstructionRa_10", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_10" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_11", oracle: "InstructionRa_11", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_11", eval_source: "stage8.input.stage7.InstructionRa_11", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_11" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_12", oracle: "InstructionRa_12", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_12", eval_source: "stage8.input.stage7.InstructionRa_12", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_12" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_13", oracle: "InstructionRa_13", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_13", eval_source: "stage8.input.stage7.InstructionRa_13", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_13" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_14", oracle: "InstructionRa_14", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_14", eval_source: "stage8.input.stage7.InstructionRa_14", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_14" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_15", oracle: "InstructionRa_15", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_15", eval_source: "stage8.input.stage7.InstructionRa_15", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_15" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_16", oracle: "InstructionRa_16", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_16", eval_source: "stage8.input.stage7.InstructionRa_16", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_16" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_17", oracle: "InstructionRa_17", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_17", eval_source: "stage8.input.stage7.InstructionRa_17", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_17" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_18", oracle: "InstructionRa_18", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_18", eval_source: "stage8.input.stage7.InstructionRa_18", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_18" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_19", oracle: "InstructionRa_19", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_19", eval_source: "stage8.input.stage7.InstructionRa_19", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_19" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_20", oracle: "InstructionRa_20", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_20", eval_source: "stage8.input.stage7.InstructionRa_20", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_20" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_21", oracle: "InstructionRa_21", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_21", eval_source: "stage8.input.stage7.InstructionRa_21", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_21" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_22", oracle: "InstructionRa_22", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_22", eval_source: "stage8.input.stage7.InstructionRa_22", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_22" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_23", oracle: "InstructionRa_23", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_23", eval_source: "stage8.input.stage7.InstructionRa_23", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_23" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_24", oracle: "InstructionRa_24", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_24", eval_source: "stage8.input.stage7.InstructionRa_24", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_24" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_25", oracle: "InstructionRa_25", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_25", eval_source: "stage8.input.stage7.InstructionRa_25", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_25" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_26", oracle: "InstructionRa_26", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_26", eval_source: "stage8.input.stage7.InstructionRa_26", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_26" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_27", oracle: "InstructionRa_27", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_27", eval_source: "stage8.input.stage7.InstructionRa_27", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_27" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_28", oracle: "InstructionRa_28", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_28", eval_source: "stage8.input.stage7.InstructionRa_28", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_28" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_29", oracle: "InstructionRa_29", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_29", eval_source: "stage8.input.stage7.InstructionRa_29", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_29" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_30", oracle: "InstructionRa_30", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_30", eval_source: "stage8.input.stage7.InstructionRa_30", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_30" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.InstructionRa_31", oracle: "InstructionRa_31", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.InstructionRa_31", eval_source: "stage8.input.stage7.InstructionRa_31", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.InstructionRa_31" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.BytecodeRa_0", oracle: "BytecodeRa_0", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.BytecodeRa_0", eval_source: "stage8.input.stage7.BytecodeRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_0" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.BytecodeRa_1", oracle: "BytecodeRa_1", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.BytecodeRa_1", eval_source: "stage8.input.stage7.BytecodeRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_1" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.BytecodeRa_2", oracle: "BytecodeRa_2", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.BytecodeRa_2", eval_source: "stage8.input.stage7.BytecodeRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_2" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.BytecodeRa_3", oracle: "BytecodeRa_3", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.BytecodeRa_3", eval_source: "stage8.input.stage7.BytecodeRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.BytecodeRa_3" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamRa_0", oracle: "RamRa_0", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.RamRa_0", eval_source: "stage8.input.stage7.RamRa_0", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_0" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamRa_1", oracle: "RamRa_1", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.RamRa_1", eval_source: "stage8.input.stage7.RamRa_1", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_1" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamRa_2", oracle: "RamRa_2", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.RamRa_2", eval_source: "stage8.input.stage7.RamRa_2", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_2" }, + Stage8OpeningClaimPlan { symbol: "stage8.evaluation.opening.RamRa_3", oracle: "RamRa_3", family: "jolt.main_witness_polys", domain: "jolt.main_witness_commit_domain", point_arity: 22, point_source: "stage8.input.stage7.RamRa_3", eval_source: "stage8.input.stage7.RamRa_3", source_stage: "stage7", source_claim: "stage7.hamming_weight_claim_reduction.eval.RamRa_3" }, +]; + +pub const STAGE8_OPENING_BATCH_ORDERED_CLAIMS: &[&str] = &["stage8.evaluation.opening.RamInc", "stage8.evaluation.opening.RdInc", "stage8.evaluation.opening.InstructionRa_0", "stage8.evaluation.opening.InstructionRa_1", "stage8.evaluation.opening.InstructionRa_2", "stage8.evaluation.opening.InstructionRa_3", "stage8.evaluation.opening.InstructionRa_4", "stage8.evaluation.opening.InstructionRa_5", "stage8.evaluation.opening.InstructionRa_6", "stage8.evaluation.opening.InstructionRa_7", "stage8.evaluation.opening.InstructionRa_8", "stage8.evaluation.opening.InstructionRa_9", "stage8.evaluation.opening.InstructionRa_10", "stage8.evaluation.opening.InstructionRa_11", "stage8.evaluation.opening.InstructionRa_12", "stage8.evaluation.opening.InstructionRa_13", "stage8.evaluation.opening.InstructionRa_14", "stage8.evaluation.opening.InstructionRa_15", "stage8.evaluation.opening.InstructionRa_16", "stage8.evaluation.opening.InstructionRa_17", "stage8.evaluation.opening.InstructionRa_18", "stage8.evaluation.opening.InstructionRa_19", "stage8.evaluation.opening.InstructionRa_20", "stage8.evaluation.opening.InstructionRa_21", "stage8.evaluation.opening.InstructionRa_22", "stage8.evaluation.opening.InstructionRa_23", "stage8.evaluation.opening.InstructionRa_24", "stage8.evaluation.opening.InstructionRa_25", "stage8.evaluation.opening.InstructionRa_26", "stage8.evaluation.opening.InstructionRa_27", "stage8.evaluation.opening.InstructionRa_28", "stage8.evaluation.opening.InstructionRa_29", "stage8.evaluation.opening.InstructionRa_30", "stage8.evaluation.opening.InstructionRa_31", "stage8.evaluation.opening.BytecodeRa_0", "stage8.evaluation.opening.BytecodeRa_1", "stage8.evaluation.opening.BytecodeRa_2", "stage8.evaluation.opening.BytecodeRa_3", "stage8.evaluation.opening.RamRa_0", "stage8.evaluation.opening.RamRa_1", "stage8.evaluation.opening.RamRa_2", "stage8.evaluation.opening.RamRa_3"]; + +pub const STAGE8_OPENING_BATCH: Stage8OpeningBatchPlan = Stage8OpeningBatchPlan { symbol: "stage8.evaluation.openings", proof_slot: "stage8.evaluation", policy: "jolt_stage8_joint_rlc", count: 42, ordered_claims: STAGE8_OPENING_BATCH_ORDERED_CLAIMS }; + +pub const STAGE8_PCS_PROOF: Stage8PcsProofPlan = Stage8PcsProofPlan { symbol: "stage8.evaluation.proof", mode: "verify", pcs: "dory", proof_slot: "stage8.evaluation", transcript_label: "rlc_claims", batch: "stage8.evaluation.openings" }; + +pub const STAGE8_PROGRAM: Stage8EvaluationProgramPlan = Stage8EvaluationProgramPlan { + role: "verifier", + function: "jolt.stage8", + params: STAGE8_PARAMS, + evaluation_point_source: STAGE8_EVALUATION_POINT_SOURCE, + opening_inputs: STAGE8_OPENING_INPUTS, + opening_claims: STAGE8_OPENING_CLAIMS, + opening_batch: STAGE8_OPENING_BATCH, + pcs_proof: STAGE8_PCS_PROOF, +}; diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs new file mode 100644 index 0000000000..5d1b43bc66 --- /dev/null +++ b/crates/jolt-verifier/src/verifier.rs @@ -0,0 +1,494 @@ +use std::collections::BTreeMap; + +use jolt_dory::{DoryCommitment, DoryProof, DoryScheme, DoryVerifierSetup}; +use jolt_field::{Field, Fr}; +use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, OpeningsError}; +use jolt_poly::EqPolynomial; +use jolt_transcript::{AppendToTranscript, LabelWithCount, Transcript}; + +use crate::stages::{commitment as commitment_stage, stage1_outer as stage1_outer_stage, stage2 as stage2_stage, stage3 as stage3_stage, stage4 as stage4_stage, stage5 as stage5_stage, stage6 as stage6_stage, stage7 as stage7_stage, stage8 as stage8_stage}; + +pub type JoltNamedEval = crate::stages::common::StageNamedEval; +pub type JoltSumcheckOutput = crate::stages::common::StageSumcheckOutput; +pub type JoltStageProof = crate::stages::common::StageProof; + +#[derive(Clone, Debug)] +pub struct JoltProof { + pub commitments: Vec>, + pub stage1_outer: JoltStageProof, + pub stage2: JoltStageProof, + pub stage3: JoltStageProof, + pub stage4: JoltStageProof, + pub stage5: JoltStageProof, + pub stage6: JoltStageProof, + pub stage7: JoltStageProof, + pub evaluation: Option, +} + +pub type JoltStage2RamAccess = crate::stages::stage2::Stage2RamAccess; +pub type JoltStage2RamOutputLayout = crate::stages::stage2::Stage2RamOutputLayout; +pub type JoltStage2RamData<'a> = crate::stages::stage2::Stage2RamData<'a>; +pub type JoltStageChallengeVector = crate::stages::common::StageChallengeVector; +pub type JoltStageExecutionArtifacts = crate::stages::common::StageExecutionArtifacts; +pub type JoltStageOpeningInputValue = crate::stages::common::StageOpeningInputValue; + +#[derive(Clone, Debug)] +pub struct JoltEvaluationProof { + pub joint_opening_proof: DoryProof, +} + +#[derive(Clone, Copy)] +pub struct JoltVerifierInputs<'a> { + pub stage2_openings: &'a [stage2_stage::Stage2OpeningInputValue], + pub stage2_ram: Option<&'a stage2_stage::Stage2RamData<'a>>, + pub stage3_openings: &'a [stage3_stage::Stage3OpeningInputValue], + pub stage4_openings: &'a [stage4_stage::Stage4OpeningInputValue], + pub stage5_openings: &'a [stage5_stage::Stage5OpeningInputValue], + pub stage6_openings: &'a [stage6_stage::Stage6OpeningInputValue], + pub stage6_data: Option<&'a stage6_stage::Stage6VerifierData>, + pub stage7_openings: &'a [stage7_stage::Stage7OpeningInputValue], + pub evaluation_setup: Option<&'a DoryVerifierSetup>, +} + +#[derive(Clone, Copy, Debug)] +pub struct JoltVerifierPrograms { + pub commitment: &'static commitment_stage::CommitmentVerifierProgramPlan, + pub stage1_outer: &'static stage1_outer_stage::Stage1VerifierProgramPlan, + pub stage2: &'static stage2_stage::Stage2VerifierProgramPlan, + pub stage3: &'static stage3_stage::Stage3VerifierProgramPlan, + pub stage4: &'static stage4_stage::Stage4VerifierProgramPlan, + pub stage5: &'static stage5_stage::Stage5VerifierProgramPlan, + pub stage6: &'static stage6_stage::Stage6VerifierProgramPlan, + pub stage7: &'static stage7_stage::Stage7VerifierProgramPlan, + pub stage8: &'static stage8_stage::Stage8EvaluationProgramPlan, +} + +pub fn default_verifier_programs() -> JoltVerifierPrograms { + JoltVerifierPrograms { + commitment: &commitment_stage::COMMITMENT_PROGRAM, + stage1_outer: &stage1_outer_stage::STAGE1_PROGRAM, + stage2: &stage2_stage::STAGE2_PROGRAM, + stage3: &stage3_stage::STAGE3_PROGRAM, + stage4: &stage4_stage::STAGE4_PROGRAM, + stage5: &stage5_stage::STAGE5_PROGRAM, + stage6: &stage6_stage::STAGE6_PROGRAM, + stage7: &stage7_stage::STAGE7_PROGRAM, + stage8: &stage8_stage::STAGE8_PROGRAM, + } +} + +#[derive(Clone, Debug)] +pub struct JoltVerificationArtifacts { + pub commitment: commitment_stage::CommitmentArtifacts, + pub stage1_outer: stage1_outer_stage::Stage1ExecutionArtifacts, + pub stage2: stage2_stage::Stage2ExecutionArtifacts, + pub stage3: stage3_stage::Stage3ExecutionArtifacts, + pub stage4: stage4_stage::Stage4ExecutionArtifacts, + pub stage5: stage5_stage::Stage5ExecutionArtifacts, + pub stage6: stage6_stage::Stage6ExecutionArtifacts, + pub stage7: stage7_stage::Stage7ExecutionArtifacts, +} + +#[derive(Debug)] +pub enum JoltVerifyError { + Commitment(commitment_stage::CommitmentPhaseError), + Stage1Outer(stage1_outer_stage::VerifyStage1Error), + Stage2(stage2_stage::VerifyStage2Error), + Stage3(stage3_stage::VerifyStage3Error), + Stage4(stage4_stage::VerifyStage4Error), + Stage5(stage5_stage::VerifyStage5Error), + Stage6(stage6_stage::VerifyStage6Error), + Stage7(stage7_stage::VerifyStage7Error), + Evaluation(JoltEvaluationProofError), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum JoltVerifierTarget { + ThroughStage5, + ThroughStage6, + ThroughStage7, + Full, +} + +impl JoltVerifierTarget { + fn verifies_stage6(self) -> bool { matches!(self, Self::ThroughStage6 | Self::ThroughStage7 | Self::Full) } + fn verifies_stage7(self) -> bool { matches!(self, Self::ThroughStage7 | Self::Full) } + fn verifies_evaluation(self) -> bool { matches!(self, Self::Full) } + fn allows_optional_evaluation(self) -> bool { matches!(self, Self::ThroughStage7 | Self::Full) } +} + +#[derive(Debug)] +pub enum JoltEvaluationProofError { + MissingProof, + MissingVerifierSetup, + MissingStageEval { stage: &'static str, eval: &'static str }, + MissingStage7RaEval, + MissingStage7EvaluationPoint, + MissingCommitment { oracle: &'static str }, + InvalidPointLength { + artifact: &'static str, + expected: usize, + actual: usize, + }, + Opening(OpeningsError), +} + +macro_rules! define_jolt_verify_error_from { + ($module:ident, $error_ty:ident, $variant:ident) => { + impl From<$module::$error_ty> for JoltVerifyError { + fn from(error: $module::$error_ty) -> Self { + Self::$variant(error) + } + } + }; +} + +define_jolt_verify_error_from!(commitment_stage, CommitmentPhaseError, Commitment); +define_jolt_verify_error_from!(stage1_outer_stage, VerifyStage1Error, Stage1Outer); +define_jolt_verify_error_from!(stage2_stage, VerifyStage2Error, Stage2); +define_jolt_verify_error_from!(stage3_stage, VerifyStage3Error, Stage3); +define_jolt_verify_error_from!(stage4_stage, VerifyStage4Error, Stage4); +define_jolt_verify_error_from!(stage5_stage, VerifyStage5Error, Stage5); +define_jolt_verify_error_from!(stage6_stage, VerifyStage6Error, Stage6); +define_jolt_verify_error_from!(stage7_stage, VerifyStage7Error, Stage7); + +impl From for JoltVerifyError { + fn from(error: JoltEvaluationProofError) -> Self { + Self::Evaluation(error) + } +} + +impl From for JoltEvaluationProofError { + fn from(error: OpeningsError) -> Self { + Self::Opening(error) + } +} + +pub fn verify_jolt>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { + verify_jolt_with_programs(proof, inputs, default_verifier_programs(), transcript) +} + +pub fn verify_jolt_prefix>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { verify_jolt_prefix_with_programs(proof, inputs, default_verifier_programs(), transcript) } + +pub fn verify_jolt_through_stage5>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { verify_jolt_through_stage5_with_programs(proof, inputs, default_verifier_programs(), transcript) } + +pub fn verify_jolt_through_stage6>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { verify_jolt_through_stage6_with_programs(proof, inputs, default_verifier_programs(), transcript) } + +pub fn verify_jolt_through_stage7>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, transcript: &mut T) -> Result { verify_jolt_through_stage7_with_programs(proof, inputs, default_verifier_programs(), transcript) } + +pub fn verify_jolt_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { + verify_jolt_with_programs_inner(proof, inputs, programs, transcript, JoltVerifierTarget::Full) +} + +pub fn verify_jolt_through_stage5_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { verify_jolt_with_programs_inner(proof, inputs, programs, transcript, JoltVerifierTarget::ThroughStage5) } + +pub fn verify_jolt_through_stage6_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { verify_jolt_with_programs_inner(proof, inputs, programs, transcript, JoltVerifierTarget::ThroughStage6) } + +pub fn verify_jolt_through_stage7_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { verify_jolt_with_programs_inner(proof, inputs, programs, transcript, JoltVerifierTarget::ThroughStage7) } + +pub fn verify_jolt_prefix_with_programs>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T) -> Result { verify_jolt_through_stage7_with_programs(proof, inputs, programs, transcript) } + +fn verify_jolt_with_programs_inner>(proof: &JoltProof, inputs: JoltVerifierInputs<'_>, programs: JoltVerifierPrograms, transcript: &mut T, target: JoltVerifierTarget) -> Result { + let _verify_span = tracing::info_span!("bolt.verify").entered(); + let commitment = commitment_stage::verify_commitment_phase_with_program(programs.commitment, &proof.commitments, transcript)?; + let stage1_outer = stage1_outer_stage::verify_stage1_outer_with_program(programs.stage1_outer, &proof.stage1_outer, transcript)?; + let stage2 = stage2_stage::verify_stage2_with_program(programs.stage2, &proof.stage2, inputs.stage2_openings, inputs.stage2_ram, transcript)?; + let stage3 = stage3_stage::verify_stage3_with_program(programs.stage3, &proof.stage3, inputs.stage3_openings, transcript)?; + let stage4 = stage4_stage::verify_stage4_with_program(programs.stage4, &proof.stage4, inputs.stage4_openings, transcript)?; + let stage5 = stage5_stage::verify_stage5_with_program(programs.stage5, &proof.stage5, inputs.stage5_openings, transcript)?; + let stage6 = if target.verifies_stage6() { + stage6_stage::verify_stage6_with_program(programs.stage6, &proof.stage6, inputs.stage6_openings, inputs.stage6_data, transcript)? + } else { + stage6_stage::Stage6ExecutionArtifacts::default() + }; + let stage7 = if target.verifies_stage7() { + stage7_stage::verify_stage7_with_program(programs.stage7, &proof.stage7, inputs.stage7_openings, transcript)? + } else { + stage7_stage::Stage7ExecutionArtifacts::default() + }; + if target.allows_optional_evaluation() { + match (&proof.evaluation, inputs.evaluation_setup) { + (Some(evaluation), Some(setup)) => { + verify_jolt_evaluation_proof( + programs.stage8, + evaluation, + &commitment, + &proof.stage6, + &proof.stage7, + inputs.stage7_openings, + setup, + transcript, + )?; + } + (Some(_), None) => return Err(JoltEvaluationProofError::MissingVerifierSetup.into()), + (None, Some(_)) => return Err(JoltEvaluationProofError::MissingProof.into()), + (None, None) if target.verifies_evaluation() => return Err(JoltEvaluationProofError::MissingProof.into()), + (None, None) => {} + } + } + + Ok(JoltVerificationArtifacts { + commitment, + stage1_outer, + stage2, + stage3, + stage4, + stage5, + stage6, + stage7, + }) +} + +impl<'a> JoltVerifierInputs<'a> { + pub fn through_stage5(mut self) -> Self { self.stage6_openings = &[]; self.stage7_openings = &[]; self.evaluation_setup = None; self } + pub fn through_stage6(mut self) -> Self { self.stage7_openings = &[]; self.evaluation_setup = None; self } + pub fn through_stage7(mut self) -> Self { self.evaluation_setup = None; self } + pub fn full(mut self, evaluation_setup: &'a DoryVerifierSetup) -> Self { self.evaluation_setup = Some(evaluation_setup); self } +} + +pub type JoltStage6BytecodeEntry = crate::stages::stage6::Stage6BytecodeEntry; +pub type JoltStage6BytecodeReadRafData = crate::stages::stage6::Stage6BytecodeReadRafData; +pub type JoltStage6VerifierData = crate::stages::stage6::Stage6VerifierData; + +#[expect( + clippy::too_many_arguments, + reason = "generated verifier entry point follows the Jolt proof artifact boundary" +)] +pub fn verify_jolt_evaluation_proof( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + proof: &JoltEvaluationProof, + commitments: &commitment_stage::CommitmentArtifacts, + stage6: &JoltStageProof, + stage7: &JoltStageProof, + stage7_openings: &[stage7_stage::Stage7OpeningInputValue], + verifier_setup: &DoryVerifierSetup, + transcript: &mut T, +) -> Result<(), JoltEvaluationProofError> +where + T: Transcript, +{ + let _state_span = tracing::info_span!("bolt.verify.evaluation_state").entered(); + let state = + evaluation_proof_state(program, commitments, stage6, stage7, stage7_openings, transcript)?; + drop(_state_span); + let _dory_verify_span = tracing::info_span!("bolt.verify.dory_verify").entered(); + ::verify( + &state.joint_commitment, + &state.opening_point, + state.joint_claim, + &proof.joint_opening_proof, + verifier_setup, + transcript, + )?; + drop(_dory_verify_span); + let _bind_span = tracing::info_span!("bolt.verify.bind_opening_inputs").entered(); + ::bind_opening_inputs( + transcript, + &state.opening_point, + &state.joint_claim, + ); + drop(_bind_span); + Ok(()) +} + +struct EvaluationProofState { + opening_point: Vec, + joint_claim: Fr, + joint_commitment: DoryCommitment, +} + +struct EvaluationClaim { + oracle: &'static str, + value: Fr, +} + +fn evaluation_proof_state( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + commitments: &commitment_stage::CommitmentArtifacts, + stage6: &JoltStageProof, + stage7: &JoltStageProof, + stage7_openings: &[stage7_stage::Stage7OpeningInputValue], + transcript: &mut T, +) -> Result +where + T: Transcript, +{ + let (sumcheck_address_point, stage7_values) = stage7_claim_values(program, stage7)?; + let address_point = reverse_point(&sumcheck_address_point); + let opening_point = stage7_evaluation_opening_point(program, &address_point, stage7_openings)?; + let lagrange_factor = EqPolynomial::::zero_selector(&address_point); + let claims = evaluation_claims(program, stage6, &stage7_values, lagrange_factor)?; + + append_rlc_claims(transcript, &claims); + let gamma_powers = gamma_powers(transcript, claims.len()); + let joint_claim = claims + .iter() + .zip(&gamma_powers) + .map(|(claim, gamma)| claim.value * *gamma) + .sum(); + let joint_commitment = joint_commitment(commitments, &claims, &gamma_powers)?; + + Ok(EvaluationProofState { + opening_point, + joint_claim, + joint_commitment, + }) +} + +fn stage_eval( + proof: &JoltStageProof, + stage: &'static str, + eval_name: &'static str, +) -> Result { + for output in &proof.sumchecks { + if let Some(eval) = output.evals.iter().find(|eval| eval.name == eval_name) { + return Ok(eval.value); + } + } + Err(JoltEvaluationProofError::MissingStageEval { + stage, + eval: eval_name, + }) +} + +fn evaluation_claims( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + stage6: &JoltStageProof, + stage7_values: &BTreeMap<&'static str, Fr>, + lagrange_factor: Fr, +) -> Result, JoltEvaluationProofError> { + let mut claims = Vec::with_capacity(program.opening_claims.len()); + for plan in program.opening_claims { + let value = match plan.source_stage { + "stage6" => stage_eval(stage6, plan.source_stage, plan.source_claim)? * lagrange_factor, + "stage7" => *stage7_values.get(plan.source_claim).ok_or( + JoltEvaluationProofError::MissingStageEval { + stage: plan.source_stage, + eval: plan.source_claim, + }, + )?, + _ => { + return Err(JoltEvaluationProofError::MissingStageEval { + stage: plan.source_stage, + eval: plan.source_claim, + }); + } + }; + claims.push(EvaluationClaim { + oracle: plan.oracle, + value, + }); + } + Ok(claims) +} + +fn stage7_claim_values( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + proof: &JoltStageProof, +) -> Result<(Vec, BTreeMap<&'static str, Fr>), JoltEvaluationProofError> { + let stage7_plans = program + .opening_claims + .iter() + .filter(|plan| plan.source_stage == "stage7") + .collect::>(); + for output in &proof.sumchecks { + let mut values = BTreeMap::new(); + for plan in &stage7_plans { + if let Some(eval) = output.evals.iter().find(|eval| eval.name == plan.source_claim) { + let _ = values.insert(plan.source_claim, eval.value); + } + } + if values.len() == stage7_plans.len() { + return Ok((output.point.clone(), values)); + } + } + Err(JoltEvaluationProofError::MissingStage7RaEval) +} + +fn reverse_point(point: &[Fr]) -> Vec { + point.iter().rev().copied().collect() +} + +fn stage7_evaluation_opening_point( + program: &'static stage8_stage::Stage8EvaluationProgramPlan, + address_point: &[Fr], + stage7_openings: &[stage7_stage::Stage7OpeningInputValue], +) -> Result, JoltEvaluationProofError> { + let cycle_source_symbol = program.evaluation_point_source.source_claim; + let cycle_source = stage7_openings + .iter() + .find(|input| input.symbol == cycle_source_symbol) + .ok_or(JoltEvaluationProofError::MissingStage7EvaluationPoint)?; + if cycle_source.point.len() < address_point.len() { + return Err(JoltEvaluationProofError::InvalidPointLength { + artifact: cycle_source_symbol, + expected: address_point.len(), + actual: cycle_source.point.len(), + }); + } + let mut point = Vec::with_capacity(cycle_source.point.len()); + point.extend_from_slice(address_point); + point.extend_from_slice(&cycle_source.point[address_point.len()..]); + Ok(point) +} + +fn append_rlc_claims(transcript: &mut T, claims: &[EvaluationClaim]) +where + T: Transcript, +{ + transcript.append(&LabelWithCount(b"rlc_claims", claims.len() as u64)); + for claim in claims { + claim.value.append_to_transcript(transcript); + } +} + +fn gamma_powers(transcript: &mut T, count: usize) -> Vec +where + T: Transcript, +{ + let gamma = transcript.challenge(); + let mut powers = Vec::with_capacity(count); + let mut power = Fr::from_u64(1); + for _ in 0..count { + powers.push(power); + power *= gamma; + } + powers +} + +fn joint_commitment( + commitments: &commitment_stage::CommitmentArtifacts, + claims: &[EvaluationClaim], + gamma_powers: &[Fr], +) -> Result { + let mut coefficients = BTreeMap::<&'static str, Fr>::new(); + for (claim, gamma) in claims.iter().zip(gamma_powers) { + let coefficient = coefficients.entry(claim.oracle).or_insert(Fr::from_u64(0)); + *coefficient += *gamma; + } + let mut commitment_values = Vec::with_capacity(coefficients.len()); + let mut scalars = Vec::with_capacity(coefficients.len()); + for (oracle, coefficient) in coefficients { + commitment_values.push(commitment_for_oracle(commitments, oracle)?); + scalars.push(coefficient); + } + Ok(::combine( + &commitment_values, + &scalars, + )) +} + +fn commitment_for_oracle( + commitments: &commitment_stage::CommitmentArtifacts, + oracle: &'static str, +) -> Result { + for (record, commitment) in commitments.records.iter().zip(&commitments.commitments) { + if record.oracle == oracle { + return commitment + .clone() + .ok_or(JoltEvaluationProofError::MissingCommitment { oracle }); + } + } + Err(JoltEvaluationProofError::MissingCommitment { oracle }) +} + diff --git a/crates/jolt-witness/Cargo.toml b/crates/jolt-witness/Cargo.toml new file mode 100644 index 0000000000..2ec9481490 --- /dev/null +++ b/crates/jolt-witness/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "jolt-witness" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Primitive oracle construction kernels for Bolt-generated Jolt code" + +[lints] +workspace = true + +[dependencies] +jolt-field = { path = "../jolt-field" } +jolt-poly = { path = "../jolt-poly" } diff --git a/crates/jolt-witness/src/lib.rs b/crates/jolt-witness/src/lib.rs new file mode 100644 index 0000000000..4f95953936 --- /dev/null +++ b/crates/jolt-witness/src/lib.rs @@ -0,0 +1,1119 @@ +//! Primitive oracle construction kernels for Bolt-generated Jolt code. +//! +//! This crate is intentionally not a runtime/provider abstraction. Generated +//! code calls these kernels after the Bolt lowering pipeline has made oracle +//! generation explicit in IR. + +use jolt_field::Field; +use jolt_poly::EqPolynomial; + +pub const NUM_DENSE_TRACE_COLUMNS: usize = 2; +pub const NUM_ONE_HOT_TRACE_SOURCES: usize = 3; + +/// Per-cycle primitive inputs consumed by Bolt oracle generation. +#[derive(Clone, Copy, Debug)] +pub struct CycleInput { + pub dense: [i128; NUM_DENSE_TRACE_COLUMNS], + pub one_hot: [Option; NUM_ONE_HOT_TRACE_SOURCES], +} + +impl CycleInput { + pub const PADDING: Self = Self { + dense: [0; NUM_DENSE_TRACE_COLUMNS], + one_hot: [Some(0), Some(0), None], + }; +} + +impl Default for CycleInput { + fn default() -> Self { + Self::PADDING + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CommitmentTraceSources { + pub rd_inc: Vec, + pub ram_inc: Vec, + pub instruction_keys: Vec>, + pub ram_addresses: Vec>, + pub bytecode_indices: Vec>, +} + +impl CommitmentTraceSources { + pub fn from_cycle_inputs(cycle_inputs: &[CycleInput]) -> Self { + Self { + rd_inc: cycle_inputs.iter().map(|cycle| cycle.dense[0]).collect(), + ram_inc: cycle_inputs.iter().map(|cycle| cycle.dense[1]).collect(), + instruction_keys: one_hot_cycle_column(cycle_inputs, 0), + ram_addresses: one_hot_cycle_column(cycle_inputs, 2), + bytecode_indices: one_hot_cycle_column(cycle_inputs, 1), + } + } +} + +pub fn commitment_trace_sources(cycle_inputs: &[CycleInput]) -> CommitmentTraceSources { + CommitmentTraceSources::from_cycle_inputs(cycle_inputs) +} + +/// Returns a dense trace source by its generated oracle source name. +pub fn dense_cycle_source(cycle_inputs: &[CycleInput], source: &str) -> Vec { + let slot = match source { + "trace.rd_inc" => 0, + "trace.ram_inc" => 1, + _ => unreachable!("unsupported dense source `{source}`"), + }; + cycle_inputs.iter().map(|cycle| cycle.dense[slot]).collect() +} + +/// Returns a one-hot trace source by its generated oracle source name. +pub fn one_hot_cycle_source(cycle_inputs: &[CycleInput], source: &str) -> Vec> { + let slot = match source { + "trace.instruction_keys" => 0, + "trace.bytecode_indices" => 1, + "trace.ram_addresses" => 2, + _ => unreachable!("unsupported one-hot source `{source}`"), + }; + one_hot_cycle_column(cycle_inputs, slot) +} + +/// Maps generated one-hot padding policy names to the corresponding value. +pub fn one_hot_padding_value(padding: &str) -> Option { + match padding { + "zero" => Some(0), + "none" => None, + _ => unreachable!("unsupported padding `{padding}`"), + } +} + +/// Converts an i128 trace column to field elements and pads it to `target_len`. +/// +/// The input is normally trace-length data; commitment domains can be larger +/// than the trace domain, so generated code asks for the final committed length. +pub fn dense_i128_column_to_field(values: &[i128], target_len: usize) -> Vec { + assert!( + values.len() <= target_len, + "dense trace column has {} values, target length is {target_len}", + values.len() + ); + let mut output: Vec = values.iter().map(|&value| F::from_i128(value)).collect(); + output.resize(target_len, F::zero()); + output +} + +/// Pads an optional field-valued oracle to `target_len`. +/// +/// `None` stays `None`; zero-skipping policy is deliberately left to the +/// generated commitment code because skip semantics are protocol metadata. +pub fn optional_field_oracle(values: Option<&[F]>, target_len: usize) -> Option> { + values.map(|values| pad_field_oracle(values, target_len)) +} + +/// Pads a field-valued oracle to `target_len`. +pub fn pad_field_oracle(values: &[F], target_len: usize) -> Vec { + assert!( + values.len() <= target_len, + "field oracle has {} values, target length is {target_len}", + values.len() + ); + let mut output = values.to_vec(); + output.resize(target_len, F::zero()); + output +} + +/// Deterministic placeholder data for optional advice oracles in synthetic tests. +pub fn deterministic_oracle_data(oracle: &str, num_vars: usize) -> Vec { + let seed = oracle.bytes().fold(17u64, |state, byte| { + state.wrapping_mul(131).wrapping_add(byte as u64) + }); + (0..(1usize << num_vars)) + .map(|index| F::from_u64(seed.wrapping_add(index as u64 + 1))) + .collect() +} + +/// Returns synthetic data for non-advice oracles and `None` for optional advice. +pub fn optional_oracle_data(oracle: &str, num_vars: usize) -> Option> { + match oracle { + "UntrustedAdvice" | "TrustedAdvice" => None, + _ => Some(deterministic_oracle_data(oracle, num_vars)), + } +} + +/// Builds sparse per-cycle one-hot chunk indices. +/// +/// The returned vector has one entry per trace cycle. `None` means no one-hot +/// entry is active for that cycle. Chunk `0` is the most significant chunk, +/// matching jolt-core's committed RA decomposition. +pub fn one_hot_chunk_indices( + values: &[Option], + chunk: usize, + num_chunks: usize, + chunk_bits: usize, + trace_len: usize, + padding_value: Option, +) -> Vec> { + assert!( + values.len() <= trace_len, + "one-hot source has {} values, trace length is {trace_len}", + values.len() + ); + assert!( + chunk < num_chunks, + "chunk index {chunk} out of bounds for {num_chunks} chunks" + ); + assert!( + chunk_bits <= u8::BITS as usize, + "chunk_bits must fit in one byte" + ); + assert!( + chunk_bits * num_chunks <= u128::BITS as usize, + "one-hot chunks must fit in u128 source values" + ); + + let chunk_domain = 1usize << chunk_bits; + let shift = chunk_bits * (num_chunks - 1 - chunk); + let mask = (chunk_domain - 1) as u128; + let mut output = Vec::with_capacity(trace_len); + + for cycle in 0..trace_len { + let value = values.get(cycle).copied().flatten().or(padding_value); + output.push(value.map(|value| ((value >> shift) & mask) as u8)); + } + + output +} + +/// Builds one address-major one-hot chunk polynomial. +/// +/// Layout is `output[chunk_value * trace_len + cycle]`. Chunk `0` is the most +/// significant chunk, matching jolt-core's committed RA decomposition. +pub fn one_hot_chunk_address_major( + values: &[Option], + chunk: usize, + num_chunks: usize, + chunk_bits: usize, + trace_len: usize, + padding_value: Option, +) -> Vec { + let indices = one_hot_chunk_indices( + values, + chunk, + num_chunks, + chunk_bits, + trace_len, + padding_value, + ); + one_hot_address_major_from_indices(&indices, chunk_bits) +} + +/// Builds one address-major one-hot chunk polynomial from sparse per-cycle indices. +/// +/// Layout is `output[chunk_value * trace_len + cycle]`. +pub fn one_hot_address_major_from_indices( + indices: &[Option], + chunk_bits: usize, +) -> Vec { + assert!( + chunk_bits < usize::BITS as usize, + "chunk_bits must fit in usize" + ); + + let chunk_domain = 1usize << chunk_bits; + let mut output = vec![F::zero(); chunk_domain * indices.len()]; + + for (cycle, index) in indices.iter().enumerate() { + if let Some(index) = index { + let index = usize::from(*index); + assert!( + index < chunk_domain, + "one-hot index {index} exceeds chunk domain {chunk_domain}" + ); + output[index * indices.len() + cycle] = F::one(); + } + } + + output +} + +/// Builds one cycle-major one-hot chunk polynomial from sparse per-cycle indices. +/// +/// Layout is `output[cycle * chunk_domain + chunk_value]`. +pub fn one_hot_cycle_major_from_indices( + indices: &[Option], + chunk_bits: usize, +) -> Vec { + assert!( + chunk_bits < usize::BITS as usize, + "chunk_bits must fit in usize" + ); + + let chunk_domain = 1usize << chunk_bits; + let mut output = vec![F::zero(); chunk_domain * indices.len()]; + + for (cycle, index) in indices.iter().enumerate() { + if let Some(index) = index { + let index = usize::from(*index); + assert!( + index < chunk_domain, + "one-hot index {index} exceeds chunk domain {chunk_domain}" + ); + output[cycle * chunk_domain + index] = F::one(); + } + } + + output +} + +/// Evaluates one-hot per-cycle indices at an address-chunk point. +/// +/// The returned vector has one field element per cycle. Skipped entries +/// evaluate to zero. +pub fn one_hot_evals_at_chunk_point(indices: &[Option], point: &[F]) -> Vec { + let eq_table = EqPolynomial::::evals(point, None); + indices + .iter() + .map(|index| { + index.map_or(F::zero(), |index| { + let index = usize::from(index); + assert!( + index < eq_table.len(), + "one-hot index {index} exceeds chunk point domain {}", + eq_table.len() + ); + eq_table[index] + }) + }) + .collect() +} + +/// Returns most-significant-first chunk widths for a bitstring split by `chunk_bits`. +/// +/// If the high chunk is partial, it appears first. The result is padded with +/// full-width chunks until it reaches `chunk_count`. +pub fn msb_chunk_bit_widths( + total_bits: usize, + chunk_bits: usize, + chunk_count: usize, +) -> Vec { + assert!(chunk_bits > 0, "chunk_bits must be nonzero"); + let first_chunk_bits = total_bits % chunk_bits; + let mut widths = Vec::with_capacity(chunk_count); + if first_chunk_bits != 0 { + widths.push(first_chunk_bits); + } + while widths.len() < chunk_count { + widths.push(chunk_bits); + } + widths +} + +/// Splits a most-significant-first point into fixed-width chunks. +/// +/// The high chunk is left-padded with zero challenges if the point length is +/// not a multiple of `chunk_bits`. +pub fn msb_point_chunks(point: &[F], chunk_bits: usize) -> Vec> { + assert!(chunk_bits > 0, "chunk_bits must be nonzero"); + let mut padded = Vec::new(); + let remainder = point.len() % chunk_bits; + if remainder != 0 { + padded.resize(chunk_bits - remainder, F::zero()); + } + padded.extend_from_slice(point); + padded + .chunks(chunk_bits) + .map(|chunk| chunk.to_vec()) + .collect() +} + +/// Computes `post - pre` in the field for a `u64` value transition. +pub fn u64_increment(pre: u64, post: u64) -> F { + if post >= pre { + F::from_u64(post - pre) + } else { + -F::from_u64(pre - post) + } +} + +/// Computes a field increment column from `(pre, post)` `u64` transitions. +pub fn u64_increment_column(transitions: impl IntoIterator) -> Vec { + transitions + .into_iter() + .map(|(pre, post)| u64_increment(pre, post)) + .collect() +} + +/// Computes a field increment column where a missing write contributes zero. +pub fn optional_u64_increment_column( + transitions: impl IntoIterator>, +) -> Vec { + transitions + .into_iter() + .map(|transition| transition.map_or_else(F::zero, |(pre, post)| u64_increment(pre, post))) + .collect() +} + +/// Materializes an optional `usize` source column. +pub fn optional_usize_column( + values: impl IntoIterator>, +) -> Vec> { + values.into_iter().collect() +} + +#[derive(Clone, Debug)] +pub struct Stage45SparseTraceWitness { + pub rd_inc: Vec, + pub ram_addresses: Vec>, + pub ram_inc: Vec, + pub rd_write_addresses: Vec>, +} + +pub fn stage4_5_sparse_trace_witness( + register_writes: impl IntoIterator>, + ram_accesses: impl IntoIterator, u64, u64)>, +) -> Stage45SparseTraceWitness { + let mut rd_inc = Vec::new(); + let mut rd_write_addresses = Vec::new(); + for write in register_writes { + if let Some((address, pre_value, post_value)) = write { + rd_inc.push(u64_increment(pre_value, post_value)); + rd_write_addresses.push(Some(address)); + } else { + rd_inc.push(F::zero()); + rd_write_addresses.push(None); + } + } + + let mut ram_addresses = Vec::new(); + let mut ram_inc = Vec::new(); + for (address, read_value, write_value) in ram_accesses { + ram_addresses.push(address); + ram_inc.push(u64_increment(read_value, write_value)); + } + + Stage45SparseTraceWitness { + rd_inc, + ram_addresses, + ram_inc, + rd_write_addresses, + } +} + +/// Evaluates a `u64`-valued multilinear extension at `point`. +pub fn mle_eval_u64(values: &[u64], point: &[F]) -> F { + EqPolynomial::::evals(point, None) + .iter() + .zip(values) + .map(|(&weight, &value)| weight * F::from_u64(value)) + .sum() +} + +/// Builds the Stage 4 `RamValInit` opening from the initial RAM image. +/// +/// Stage 4 consumes this at the same address point as `RamValFinal`. +pub fn stage4_ram_val_init_opening( + initial_ram_state: &[u64], + ram_val_final_point: &[F], +) -> (Vec, F) { + ( + ram_val_final_point.to_vec(), + mle_eval_u64(initial_ram_state, ram_val_final_point), + ) +} + +/// Reverses a challenge point. +pub fn reverse_point(point: &[F]) -> Vec { + point.iter().rev().copied().collect() +} + +/// Returns the last `len` point coordinates in reverse order. +pub fn reversed_suffix(point: &[F], len: usize) -> Vec { + let Some(start) = point.len().checked_sub(len) else { + unreachable!("point is shorter than suffix length {len}"); + }; + point[start..].iter().rev().copied().collect() +} + +/// Normalizes Stage 4 register read/write points to address-major order. +pub fn normalized_stage4_registers_rw_point( + log_t: usize, + register_log_k: usize, + point: &[F], +) -> Vec { + let expected = log_t + register_log_k; + assert_eq!( + point.len(), + expected, + "Stage 4 registers point length mismatch" + ); + let (cycle, address) = point.split_at(log_t); + address + .iter() + .rev() + .copied() + .chain(cycle.iter().rev().copied()) + .collect() +} + +/// Extracts the Stage 5 instruction read-RAF cycle point. +pub fn stage5_instruction_cycle_point( + stage5_point: &[F], + instruction_ra_virtual_d: usize, + ra_virtual_log_k_chunk: usize, + log_t: usize, +) -> Vec { + let address_len = instruction_ra_virtual_d * ra_virtual_log_k_chunk; + let end = address_len + log_t; + assert!( + end <= stage5_point.len(), + "Stage 5 point is shorter than instruction address plus cycle arity" + ); + reverse_point(&stage5_point[address_len..end]) +} + +/// Builds a Stage 5 instruction RA opening point for one virtual address chunk. +pub fn stage5_instruction_ra_point( + stage5_point: &[F], + instruction_ra_virtual_d: usize, + ra_virtual_log_k_chunk: usize, + log_t: usize, + index: usize, +) -> Vec { + let start = index * ra_virtual_log_k_chunk; + let end = start + ra_virtual_log_k_chunk; + assert!( + end <= stage5_point.len(), + "Stage 5 point is shorter than instruction RA chunk {index}" + ); + let mut point = stage5_point[start..end].to_vec(); + point.extend(stage5_instruction_cycle_point( + stage5_point, + instruction_ra_virtual_d, + ra_virtual_log_k_chunk, + log_t, + )); + point +} + +/// Builds the Stage 5 RAM RA opening point from its input address and cycle point. +pub fn stage5_ram_ra_point( + stage5_input_point: &[F], + stage5_point: &[F], + log_k_ram: usize, + log_t: usize, +) -> Vec { + assert!( + stage5_input_point.len() >= log_k_ram, + "Stage 5 RAM RA input point is shorter than RAM address arity" + ); + let mut point = stage5_input_point[..log_k_ram].to_vec(); + point.extend(reversed_suffix(stage5_point, log_t)); + point +} + +/// Builds the Stage 5 RegistersVal opening point from address and cycle points. +pub fn stage5_registers_val_point( + stage5_input_point: &[F], + stage5_point: &[F], + register_log_k: usize, + log_t: usize, +) -> Vec { + assert!( + stage5_input_point.len() >= register_log_k, + "Stage 5 RegistersVal input point is shorter than register address arity" + ); + let mut point = stage5_input_point[..register_log_k].to_vec(); + point.extend(reversed_suffix(stage5_point, log_t)); + point +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Stage6WitnessParams { + pub trace_len: usize, + pub log_k_chunk: usize, + pub log_k_bytecode: usize, + pub log_k_ram: usize, + pub lookups_ra_virtual_log_k_chunk: usize, + pub instruction_d: usize, + pub instruction_ra_virtual_d: usize, + pub bytecode_d: usize, + pub ram_d: usize, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage6BytecodeEntry { + pub address: F, + pub imm: F, + pub circuit_flags: [bool; 14], + pub rd: Option, + pub rs1: Option, + pub rs2: Option, + pub lookup_table: Option, + pub is_interleaved: bool, + pub is_branch: bool, + pub left_is_rs1: bool, + pub left_is_pc: bool, + pub right_is_rs2: bool, + pub right_is_imm: bool, + pub is_noop: bool, +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage6OpeningInputRef<'a, F: Field> { + pub symbol: &'a str, + pub point: &'a [F], +} + +#[derive(Clone, Copy, Debug)] +pub struct Stage6WitnessInputs<'a, F: Field> { + pub params: Stage6WitnessParams, + pub cycle_inputs: &'a [CycleInput], + pub opening_inputs: &'a [Stage6OpeningInputRef<'a, F>], +} + +#[derive(Clone, Debug)] +pub struct Stage6WitnessPolynomials { + pub instruction_ra_indices: Vec>>, + pub bytecode_ra_indices: Vec>>, + pub ram_ra_indices: Vec>>, + pub instruction_ra_booleanity: Vec>, + pub bytecode_ra_booleanity: Vec>, + pub ram_ra_booleanity: Vec>, + pub bytecode_ra_read_raf: Vec>, + pub bytecode_ra_read_raf_chunk_lens: Vec, + pub instruction_ra_virtual: Vec>, + pub ram_ra_virtual: Vec>, + pub hamming_weight: Vec, + pub ram_inc: Vec, + pub rd_inc: Vec, +} + +#[derive(Clone, Debug)] +pub struct Stage6WitnessSlices<'a, F: Field> { + pub booleanity_chunks: Vec<&'a [F]>, + pub booleanity_index_chunks: Vec<&'a [Option]>, + pub bytecode_ra_read_raf_chunks: Vec<&'a [F]>, + pub bytecode_ra_read_raf_chunk_lens: Vec, + pub ram_ra_virtual_chunks: Vec<&'a [F]>, + pub instruction_ra_virtual_chunks: Vec<&'a [F]>, + pub instruction_ra_index_chunks: Vec<&'a [Option]>, + pub bytecode_ra_index_chunks: Vec<&'a [Option]>, + pub ram_ra_index_chunks: Vec<&'a [Option]>, +} + +impl Stage6WitnessPolynomials { + /// Returns borrowed slices in the order expected by the generated Stage 6/7 kernels. + pub fn slices(&self) -> Stage6WitnessSlices<'_, F> { + let mut booleanity_chunks = field_slices(&self.instruction_ra_booleanity); + booleanity_chunks.extend(field_slices(&self.bytecode_ra_booleanity)); + booleanity_chunks.extend(field_slices(&self.ram_ra_booleanity)); + + let mut booleanity_index_chunks = index_slices(&self.instruction_ra_indices); + booleanity_index_chunks.extend(index_slices(&self.bytecode_ra_indices)); + booleanity_index_chunks.extend(index_slices(&self.ram_ra_indices)); + + Stage6WitnessSlices { + booleanity_chunks, + booleanity_index_chunks, + bytecode_ra_read_raf_chunks: field_slices(&self.bytecode_ra_read_raf), + bytecode_ra_read_raf_chunk_lens: self.bytecode_ra_read_raf_chunk_lens.clone(), + ram_ra_virtual_chunks: field_slices(&self.ram_ra_virtual), + instruction_ra_virtual_chunks: field_slices(&self.instruction_ra_virtual), + instruction_ra_index_chunks: index_slices(&self.instruction_ra_indices), + bytecode_ra_index_chunks: index_slices(&self.bytecode_ra_indices), + ram_ra_index_chunks: index_slices(&self.ram_ra_indices), + } + } +} + +pub fn stage6_witness_polynomials( + inputs: Stage6WitnessInputs<'_, F>, +) -> Stage6WitnessPolynomials { + let params = inputs.params; + let trace_len = params.trace_len; + assert!( + inputs.cycle_inputs.len() <= trace_len, + "cycle input length {} exceeds trace length {trace_len}", + inputs.cycle_inputs.len() + ); + + let instruction_keys = one_hot_cycle_column(inputs.cycle_inputs, 0); + let bytecode_indices_source = one_hot_cycle_column(inputs.cycle_inputs, 1); + let ram_addresses = one_hot_cycle_column(inputs.cycle_inputs, 2); + + let instruction_indices = (0..params.instruction_d) + .map(|index| { + one_hot_chunk_indices( + &instruction_keys, + index, + params.instruction_d, + params.log_k_chunk, + trace_len, + Some(0), + ) + }) + .collect::>(); + let bytecode_indices = (0..params.bytecode_d) + .map(|index| { + one_hot_chunk_indices( + &bytecode_indices_source, + index, + params.bytecode_d, + params.log_k_chunk, + trace_len, + Some(0), + ) + }) + .collect::>(); + let ram_indices = (0..params.ram_d) + .map(|index| { + one_hot_chunk_indices( + &ram_addresses, + index, + params.ram_d, + params.log_k_chunk, + trace_len, + None, + ) + }) + .collect::>(); + + let bytecode_ra_read_raf_chunk_lens = + msb_chunk_bit_widths(params.log_k_bytecode, params.log_k_chunk, params.bytecode_d); + + let ram_address_chunks = stage6_ram_virtual_address_chunks(params, inputs.opening_inputs); + assert_eq!( + ram_address_chunks.len(), + params.ram_d, + "RAM Stage 6 address chunk count mismatch" + ); + let ram_ra_virtual = ram_indices + .iter() + .zip(&ram_address_chunks) + .map(|(indices, point)| one_hot_evals_at_chunk_point(indices, point)) + .collect::>(); + + let instruction_address_chunks = + stage6_instruction_virtual_address_chunks(params, inputs.opening_inputs); + assert_eq!( + instruction_address_chunks.len(), + params.instruction_d, + "instruction Stage 6 address chunk count mismatch" + ); + let instruction_ra_virtual = instruction_indices + .iter() + .zip(&instruction_address_chunks) + .map(|(indices, point)| one_hot_evals_at_chunk_point(indices, point)) + .collect::>(); + + Stage6WitnessPolynomials { + instruction_ra_indices: instruction_indices, + bytecode_ra_indices: bytecode_indices, + ram_ra_indices: ram_indices, + instruction_ra_booleanity: Vec::new(), + bytecode_ra_booleanity: Vec::new(), + ram_ra_booleanity: Vec::new(), + bytecode_ra_read_raf: Vec::new(), + bytecode_ra_read_raf_chunk_lens, + instruction_ra_virtual, + ram_ra_virtual, + hamming_weight: hamming_weight_from_cycle_inputs(inputs.cycle_inputs, trace_len), + ram_inc: dense_cycle_column_to_field(inputs.cycle_inputs, 1, trace_len), + rd_inc: dense_cycle_column_to_field(inputs.cycle_inputs, 0, trace_len), + } +} + +fn field_slices(values: &[Vec]) -> Vec<&[F]> { + values.iter().map(Vec::as_slice).collect() +} + +fn index_slices(values: &[Vec>]) -> Vec<&[Option]> { + values.iter().map(Vec::as_slice).collect() +} + +fn one_hot_cycle_column(cycle_inputs: &[CycleInput], slot: usize) -> Vec> { + cycle_inputs + .iter() + .map(|cycle| cycle.one_hot[slot]) + .collect() +} + +fn dense_cycle_column_to_field( + cycle_inputs: &[CycleInput], + slot: usize, + trace_len: usize, +) -> Vec { + assert!( + cycle_inputs.len() <= trace_len, + "cycle input length {} exceeds trace length {trace_len}", + cycle_inputs.len() + ); + let mut output = cycle_inputs + .iter() + .map(|cycle| F::from_i128(cycle.dense[slot])) + .collect::>(); + output.resize(trace_len, F::zero()); + output +} + +fn hamming_weight_from_cycle_inputs( + cycle_inputs: &[CycleInput], + trace_len: usize, +) -> Vec { + assert!( + cycle_inputs.len() <= trace_len, + "cycle input length {} exceeds trace length {trace_len}", + cycle_inputs.len() + ); + let mut output = cycle_inputs + .iter() + .map(|cycle| { + if cycle.one_hot[2].is_some() { + F::one() + } else { + F::zero() + } + }) + .collect::>(); + output.resize(trace_len, F::zero()); + output +} + +fn stage6_ram_virtual_address_chunks( + params: Stage6WitnessParams, + opening_inputs: &[Stage6OpeningInputRef<'_, F>], +) -> Vec> { + let point = stage6_opening_point( + opening_inputs, + "stage6.input.stage5.ram_ra_claim_reduction.RamRa", + ); + assert!( + point.len() >= params.log_k_ram, + "RAM RA opening point is shorter than the RAM address arity" + ); + msb_point_chunks(&point[..params.log_k_ram], params.log_k_chunk) +} + +fn stage6_instruction_virtual_address_chunks( + params: Stage6WitnessParams, + opening_inputs: &[Stage6OpeningInputRef<'_, F>], +) -> Vec> { + let mut address = Vec::with_capacity(params.instruction_d * params.log_k_chunk); + for index in 0..params.instruction_ra_virtual_d { + let symbol = format!("stage6.input.stage5.instruction_read_raf.InstructionRa_{index}"); + let point = stage6_opening_point(opening_inputs, &symbol); + assert!( + point.len() >= params.lookups_ra_virtual_log_k_chunk, + "instruction RA opening point is shorter than the virtual address chunk arity" + ); + address.extend_from_slice(&point[..params.lookups_ra_virtual_log_k_chunk]); + } + msb_point_chunks(&address, params.log_k_chunk) +} + +fn stage6_opening_point<'a, F: Field>( + opening_inputs: &'a [Stage6OpeningInputRef<'_, F>], + symbol: &str, +) -> &'a [F] { + let Some(input) = opening_inputs.iter().find(|input| input.symbol == symbol) else { + unreachable!("missing Stage 6 opening input `{symbol}`"); + }; + input.point +} + +#[cfg(test)] +mod tests { + use super::*; + use jolt_field::{Field, Fr}; + + fn fr(value: u64) -> Fr { + Fr::from_u64(value) + } + + #[test] + fn dense_column_converts_and_pads() { + let output = dense_i128_column_to_field::(&[5, -3], 4); + assert_eq!(output.len(), 4); + assert_eq!(output[0], Fr::from_i128(5)); + assert_eq!(output[1], Fr::from_i128(-3)); + assert_eq!(output[2], Fr::from_u64(0)); + assert_eq!(output[3], Fr::from_u64(0)); + } + + #[test] + fn cycle_sources_select_generated_trace_columns() { + let cycle_inputs = [ + CycleInput { + dense: [3, -2], + one_hot: [Some(7), Some(5), None], + }, + CycleInput { + dense: [8, 11], + one_hot: [Some(1), Some(4), Some(9)], + }, + ]; + let sources = commitment_trace_sources(&cycle_inputs); + assert_eq!(sources.rd_inc, vec![3, 8]); + assert_eq!(sources.ram_inc, vec![-2, 11]); + assert_eq!(sources.instruction_keys, vec![Some(7), Some(1)]); + assert_eq!(sources.ram_addresses, vec![None, Some(9)]); + assert_eq!(sources.bytecode_indices, vec![Some(5), Some(4)]); + assert_eq!( + dense_cycle_source(&cycle_inputs, "trace.rd_inc"), + vec![3, 8] + ); + assert_eq!( + dense_cycle_source(&cycle_inputs, "trace.ram_inc"), + vec![-2, 11] + ); + assert_eq!( + one_hot_cycle_source(&cycle_inputs, "trace.instruction_keys"), + vec![Some(7), Some(1)] + ); + assert_eq!( + one_hot_cycle_source(&cycle_inputs, "trace.bytecode_indices"), + vec![Some(5), Some(4)] + ); + assert_eq!( + one_hot_cycle_source(&cycle_inputs, "trace.ram_addresses"), + vec![None, Some(9)] + ); + } + + #[test] + fn increment_columns_compute_signed_field_deltas() { + assert_eq!(u64_increment::(2, 9), Fr::from_u64(7)); + assert_eq!(u64_increment::(9, 2), -Fr::from_u64(7)); + assert_eq!( + u64_increment_column::([(5, 8), (8, 3)]), + vec![Fr::from_u64(3), -Fr::from_u64(5)] + ); + assert_eq!( + optional_u64_increment_column::([Some((5, 8)), None, Some((8, 3))]), + vec![Fr::from_u64(3), Fr::from_u64(0), -Fr::from_u64(5)] + ); + } + + #[test] + fn stage4_5_sparse_trace_witness_groups_sparse_columns() { + let witness = stage4_5_sparse_trace_witness::( + [Some((2, 5, 8)), None, Some((3, 9, 4))], + [(Some(7), 10, 12), (None, 3, 3), (Some(8), 1, 0)], + ); + + assert_eq!(witness.rd_inc, vec![fr(3), fr(0), -fr(5)]); + assert_eq!(witness.rd_write_addresses, vec![Some(2), None, Some(3)]); + assert_eq!(witness.ram_addresses, vec![Some(7), None, Some(8)]); + assert_eq!(witness.ram_inc, vec![fr(2), fr(0), -fr(1)]); + } + + #[test] + fn mle_eval_u64_matches_boolean_hypercube_points() { + let values = [10, 20, 30, 40]; + let point = [Fr::from_u64(1), Fr::from_u64(0)]; + assert_eq!(mle_eval_u64(&values, &point), Fr::from_u64(30)); + } + + #[test] + fn stage4_ram_val_init_opening_uses_final_ram_point() { + let values = [10, 20, 30, 40]; + let point = [Fr::from_u64(1), Fr::from_u64(0)]; + let (opening_point, eval) = stage4_ram_val_init_opening(&values, &point); + + assert_eq!(opening_point, point); + assert_eq!(eval, Fr::from_u64(30)); + } + + #[test] + fn point_helpers_normalize_stage_points() { + let point = [fr(1), fr(2), fr(3), fr(4), fr(5)]; + assert_eq!( + reverse_point(&point), + vec![fr(5), fr(4), fr(3), fr(2), fr(1)] + ); + assert_eq!(reversed_suffix(&point, 3), vec![fr(5), fr(4), fr(3)]); + assert_eq!( + normalized_stage4_registers_rw_point(2, 3, &point), + vec![fr(5), fr(4), fr(3), fr(2), fr(1)] + ); + } + + #[test] + fn stage5_point_helpers_compose_address_and_cycle_points() { + let stage5_point = [fr(10), fr(11), fr(12), fr(13), fr(14), fr(15)]; + let input_point = [fr(20), fr(21), fr(22), fr(23)]; + assert_eq!( + stage5_instruction_cycle_point(&stage5_point, 2, 2, 2), + vec![fr(15), fr(14)] + ); + assert_eq!( + stage5_instruction_ra_point(&stage5_point, 2, 2, 2, 1), + vec![fr(12), fr(13), fr(15), fr(14)] + ); + assert_eq!( + stage5_ram_ra_point(&input_point, &stage5_point, 3, 2), + vec![fr(20), fr(21), fr(22), fr(15), fr(14)] + ); + assert_eq!( + stage5_registers_val_point(&input_point, &stage5_point, 2, 2), + vec![fr(20), fr(21), fr(15), fr(14)] + ); + } + + #[test] + fn one_hot_chunks_are_address_major_and_msb_first() { + let values = [Some(0xABu128), Some(0x12), None]; + let output = one_hot_chunk_address_major::(&values, 0, 2, 4, 4, Some(0)); + + assert_eq!(output.len(), 16 * 4); + assert_eq!(output[0xA * 4], Fr::from_u64(1)); + assert_eq!(output[5], Fr::from_u64(1)); + assert_eq!(output[2], Fr::from_u64(1)); + assert_eq!(output[3], Fr::from_u64(1)); + } + + #[test] + fn one_hot_address_major_from_indices_skips_none_entries() { + let output = one_hot_address_major_from_indices::(&[Some(2), None, Some(1)], 2); + + assert_eq!(output.len(), 12); + assert_eq!(output[2 * 3], Fr::from_u64(1)); + assert_eq!(output[5], Fr::from_u64(1)); + assert_eq!( + output + .iter() + .enumerate() + .filter(|(_, value)| **value == Fr::from_u64(1)) + .map(|(index, _)| index) + .collect::>(), + vec![5, 6] + ); + } + + #[test] + fn one_hot_cycle_major_from_indices_skips_none_entries() { + let output = one_hot_cycle_major_from_indices::(&[Some(2), None, Some(1)], 2); + + assert_eq!(output.len(), 12); + assert_eq!(output[2], Fr::from_u64(1)); + assert_eq!(output[2 * 4 + 1], Fr::from_u64(1)); + assert_eq!( + output + .iter() + .enumerate() + .filter(|(_, value)| **value == Fr::from_u64(1)) + .map(|(index, _)| index) + .collect::>(), + vec![2, 9] + ); + } + + #[test] + fn one_hot_evals_at_chunk_point_evaluates_sparse_indices() { + let point = [Fr::from_u64(5), Fr::from_u64(7)]; + let eq = EqPolynomial::::evals(&point, None); + let output = one_hot_evals_at_chunk_point(&[Some(0), Some(3), None], &point); + + assert_eq!(output, vec![eq[0], eq[3], Fr::from_u64(0)]); + } + + #[test] + fn stage6_witness_slices_preserve_kernel_order() { + let witness = Stage6WitnessPolynomials { + instruction_ra_indices: vec![vec![Some(1)]], + bytecode_ra_indices: vec![vec![Some(2)]], + ram_ra_indices: vec![vec![None]], + instruction_ra_booleanity: vec![vec![fr(10)]], + bytecode_ra_booleanity: vec![vec![fr(20)]], + ram_ra_booleanity: vec![vec![fr(30)]], + bytecode_ra_read_raf: vec![vec![fr(40)]], + bytecode_ra_read_raf_chunk_lens: vec![1], + instruction_ra_virtual: vec![vec![fr(50)]], + ram_ra_virtual: vec![vec![fr(60)]], + hamming_weight: vec![fr(70)], + ram_inc: vec![fr(80)], + rd_inc: vec![fr(90)], + }; + + let slices = witness.slices(); + assert_eq!( + slices.booleanity_chunks, + vec![ + witness.instruction_ra_booleanity[0].as_slice(), + witness.bytecode_ra_booleanity[0].as_slice(), + witness.ram_ra_booleanity[0].as_slice(), + ] + ); + assert_eq!( + slices.booleanity_index_chunks, + vec![ + witness.instruction_ra_indices[0].as_slice(), + witness.bytecode_ra_indices[0].as_slice(), + witness.ram_ra_indices[0].as_slice(), + ] + ); + assert_eq!( + slices.bytecode_ra_read_raf_chunks, + vec![witness.bytecode_ra_read_raf[0].as_slice()] + ); + assert_eq!(slices.bytecode_ra_read_raf_chunk_lens, vec![1]); + assert_eq!( + slices.instruction_ra_index_chunks, + vec![witness.instruction_ra_indices[0].as_slice()] + ); + assert_eq!( + slices.bytecode_ra_index_chunks, + vec![witness.bytecode_ra_indices[0].as_slice()] + ); + assert_eq!( + slices.ram_ra_index_chunks, + vec![witness.ram_ra_indices[0].as_slice()] + ); + } + + #[test] + fn msb_chunk_bit_widths_puts_partial_high_chunk_first() { + assert_eq!(msb_chunk_bit_widths(10, 4, 3), vec![2, 4, 4]); + assert_eq!(msb_chunk_bit_widths(12, 4, 3), vec![4, 4, 4]); + } + + #[test] + fn msb_point_chunks_left_pads_partial_high_chunk() { + let point = [Fr::from_u64(1), Fr::from_u64(2), Fr::from_u64(3)]; + let chunks = msb_point_chunks(&point, 2); + + assert_eq!( + chunks, + vec![ + vec![Fr::from_u64(0), Fr::from_u64(1)], + vec![Fr::from_u64(2), Fr::from_u64(3)] + ] + ); + } + + #[test] + fn one_hot_chunk_indices_are_msb_first_and_padded() { + let values = [Some(0xABu128), Some(0x12), None]; + let output = one_hot_chunk_indices(&values, 0, 2, 4, 4, Some(0)); + + assert_eq!(output, vec![Some(0xA), Some(0x1), Some(0), Some(0)]); + } + + #[test] + fn one_hot_chunk_indices_preserve_skipped_entries() { + let values = [Some(3u128), None]; + let output = one_hot_chunk_indices(&values, 0, 1, 2, 3, None); + + assert_eq!(output, vec![Some(3), None, None]); + } + + #[test] + fn one_hot_none_padding_skips_entries() { + let values = [Some(3u128), None]; + let output = one_hot_chunk_address_major::(&values, 0, 1, 2, 3, None); + + assert_eq!(output[3 * 3], Fr::from_u64(1)); + assert!(output + .iter() + .enumerate() + .all(|(index, value)| index == 9 || *value == Fr::from_u64(0))); + } +} diff --git a/examples/fibonacci/guest/src/lib.rs b/examples/fibonacci/guest/src/lib.rs index 9a13350d54..5c4f51e4ec 100644 --- a/examples/fibonacci/guest/src/lib.rs +++ b/examples/fibonacci/guest/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(feature = "guest", no_std)] use jolt::{end_cycle_tracking, start_cycle_tracking}; -#[jolt::provable(heap_size = 32768, max_trace_length = 65536)] +#[jolt::provable(backend = "modular", heap_size = 32768, max_trace_length = 65536)] fn fib(n: u32) -> u128 { let mut a: u128 = 0; let mut b: u128 = 1; diff --git a/examples/fibonacci/src/main.rs b/examples/fibonacci/src/main.rs index 78c0b967d0..919013ea2b 100644 --- a/examples/fibonacci/src/main.rs +++ b/examples/fibonacci/src/main.rs @@ -1,55 +1,30 @@ -use jolt_sdk::serialize_and_print_size; use std::time::Instant; + +use guest::{compile_fib, prove_fib, verify_fib}; use tracing::info; pub fn main() { tracing_subscriber::fmt::init(); - let save_to_disk = std::env::args().any(|arg| arg == "--save"); - - let target_dir = "/tmp/jolt-guest-targets"; - let mut program = guest::compile_fib(target_dir); - - let shared_preprocessing = guest::preprocess_shared_fib(&mut program).unwrap(); - - let prover_preprocessing = guest::preprocess_prover_fib(shared_preprocessing.clone()); - let verifier_setup = prover_preprocessing.generators.to_verifier_setup(); - let verifier_preprocessing = - guest::preprocess_verifier_fib(shared_preprocessing, verifier_setup, None); + let mut program = compile_fib(); - if save_to_disk { - serialize_and_print_size( - "Verifier Preprocessing", - "/tmp/jolt_verifier_preprocessing.dat", - &verifier_preprocessing, - ) - .expect("Could not serialize preprocessing."); - } - - let prove_fib = guest::build_prover_fib(program, prover_preprocessing); - let verify_fib = guest::build_verifier_fib(verifier_preprocessing); + let prove_start = Instant::now(); + let (output, bundle) = prove_fib(&mut program, 50).expect("modular prove succeeds on fib"); + let prove_secs = prove_start.elapsed().as_secs_f64(); - let program_summary = guest::analyze_fib(10); - program_summary - .write_to_file("fib_10.txt".into()) - .expect("should write"); + let verify_start = Instant::now(); + let verify_result = verify_fib(&bundle, &mut program); + let verify_secs = verify_start.elapsed().as_secs_f64(); + let valid = verify_result.is_ok(); - let trace_file = "/tmp/fib_trace.bin"; - guest::trace_fib_to_file(trace_file, 50); - info!("Trace file written to: {trace_file}."); + info!("=== fibonacci (modular Bolt backend) ==="); + info!("prove time : {prove_secs:.3} s"); + info!("verify time: {verify_secs:.3} s"); + info!("output : {output}"); + info!("valid : {valid}"); - let now = Instant::now(); - let (output, proof, io_device) = prove_fib(50); - info!("Prover runtime: {} s", now.elapsed().as_secs_f64()); - - if save_to_disk { - serialize_and_print_size("Proof", "/tmp/fib_proof.bin", &proof) - .expect("Could not serialize proof."); - serialize_and_print_size("io_device", "/tmp/fib_io_device.bin", &io_device) - .expect("Could not serialize io_device."); + if let Err(err) = verify_result { + info!("verify error: {err:?}"); + std::process::exit(1); } - - let is_valid = verify_fib(50, output, io_device.panic, proof); - info!("output: {output}"); - info!("valid: {is_valid}"); } diff --git a/examples/muldiv/guest/src/lib.rs b/examples/muldiv/guest/src/lib.rs index f116e65b20..2c485e4edb 100644 --- a/examples/muldiv/guest/src/lib.rs +++ b/examples/muldiv/guest/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(feature = "guest", no_std)] use core::hint::black_box; -#[jolt::provable(heap_size = 32768, max_trace_length = 65536)] +#[jolt::provable(backend = "modular", heap_size = 32768, max_trace_length = 65536)] fn muldiv(a: u32, b: u32, c: u32) -> u32 { use jolt::{end_cycle_tracking, start_cycle_tracking}; diff --git a/examples/muldiv/src/main.rs b/examples/muldiv/src/main.rs index 4dc1c6f720..8c684e9954 100644 --- a/examples/muldiv/src/main.rs +++ b/examples/muldiv/src/main.rs @@ -1,28 +1,31 @@ use std::time::Instant; + +use guest::{compile_muldiv, prove_muldiv, verify_muldiv}; use tracing::info; pub fn main() { tracing_subscriber::fmt::init(); - let target_dir = "/tmp/jolt-guest-targets"; - let mut program = guest::compile_muldiv(target_dir); + let mut program = compile_muldiv(); - let shared_preprocessing = guest::preprocess_shared_muldiv(&mut program).unwrap(); - let prover_preprocessing = guest::preprocess_prover_muldiv(shared_preprocessing.clone()); - let verifier_preprocessing = guest::preprocess_verifier_muldiv( - shared_preprocessing, - prover_preprocessing.generators.to_verifier_setup(), - None, - ); + let prove_start = Instant::now(); + let (output, bundle) = prove_muldiv(&mut program, 12031293, 17, 92) + .expect("modular prove succeeds on muldiv"); + let prove_secs = prove_start.elapsed().as_secs_f64(); - let prove = guest::build_prover_muldiv(program, prover_preprocessing); - let verify = guest::build_verifier_muldiv(verifier_preprocessing); + let verify_start = Instant::now(); + let verify_result = verify_muldiv(&bundle, &mut program); + let verify_secs = verify_start.elapsed().as_secs_f64(); + let valid = verify_result.is_ok(); - let now = Instant::now(); - let (output, proof, program_io) = prove(12031293, 17, 92); - info!("Prover runtime: {} s", now.elapsed().as_secs_f64()); - let is_valid = verify(12031293, 17, 92, output, program_io.panic, proof); + info!("=== muldiv (modular Bolt backend) ==="); + info!("prove time : {prove_secs:.3} s"); + info!("verify time: {verify_secs:.3} s"); + info!("output : {output}"); + info!("valid : {valid}"); - info!("output: {output}"); - info!("valid: {is_valid}"); + if let Err(err) = verify_result { + info!("verify error: {err:?}"); + std::process::exit(1); + } } diff --git a/jolt-sdk/Cargo.toml b/jolt-sdk/Cargo.toml index 8095c8167e..3096d398b1 100644 --- a/jolt-sdk/Cargo.toml +++ b/jolt-sdk/Cargo.toml @@ -23,6 +23,8 @@ host = [ "jolt-core/default", "jolt-platform/std", "dep:common", + "dep:jolt-host", + "dep:jolt-trace", "postcard/use-std", "serde/std", ] @@ -80,6 +82,8 @@ jolt-sdk-macros.workspace = true # Host-only dependencies common = { workspace = true, optional = true } jolt-core = { workspace = true, default-features = false, optional = true } +jolt-host = { workspace = true, optional = true } +jolt-trace = { workspace = true, optional = true } zeroos = { workspace = true, optional = true } cfg-if = { workspace = true } diff --git a/jolt-sdk/macros/src/lib.rs b/jolt-sdk/macros/src/lib.rs index 4d52816043..a1376203c3 100644 --- a/jolt-sdk/macros/src/lib.rs +++ b/jolt-sdk/macros/src/lib.rs @@ -3,7 +3,7 @@ extern crate proc_macro; use core::panic; use common::{ - attributes::parse_attributes, + attributes::{parse_attributes, Backend}, jolt_device::{MemoryConfig, MemoryLayout}, }; use proc_macro::TokenStream; @@ -70,20 +70,8 @@ impl MacroBuilder { } fn build(&mut self) -> TokenStream { - let memory_config_fn = self.make_memory_config_fn(); - let build_prover_fn = self.make_build_prover_fn(); - let build_verifier_fn = self.make_build_verifier_fn(); - let analyze_fn = self.make_analyze_function(); - let trace_to_file_fn = self.make_trace_to_file_func(); - let compile_fn = self.make_compile_func(); - let preprocess_shared_fn = self.make_preprocess_shared_func(); - let preprocess_prover_fn = self.make_preprocess_prover_func(); - let preprocess_verifier_fn = self.make_preprocess_verifier_func(); - let verifier_preprocess_from_prover_fn = self.make_preprocess_from_prover_func(); - let commit_trusted_advice_fn = self.make_commit_trusted_advice_func(); - let prove_fn = self.make_prove_func(); - let attributes = parse_attributes(&self.attr); + let mut execute_fn = quote! {}; if !attributes.guest_only { execute_fn = self.make_execute_function(); @@ -100,22 +88,54 @@ impl MacroBuilder { }; let require_zk = self.make_require_zk_check(); + let memory_config_fn = self.make_memory_config_fn(); + + let backend_specific = match attributes.backend { + Backend::Legacy => { + let build_prover_fn = self.make_build_prover_fn(); + let build_verifier_fn = self.make_build_verifier_fn(); + let analyze_fn = self.make_analyze_function(); + let trace_to_file_fn = self.make_trace_to_file_func(); + let compile_fn = self.make_compile_func(); + let preprocess_shared_fn = self.make_preprocess_shared_func(); + let preprocess_prover_fn = self.make_preprocess_prover_func(); + let preprocess_verifier_fn = self.make_preprocess_verifier_func(); + let verifier_preprocess_from_prover_fn = self.make_preprocess_from_prover_func(); + let commit_trusted_advice_fn = self.make_commit_trusted_advice_func(); + let prove_fn = self.make_prove_func(); + quote! { + #build_prover_fn + #build_verifier_fn + #analyze_fn + #trace_to_file_fn + #compile_fn + #preprocess_shared_fn + #preprocess_prover_fn + #preprocess_verifier_fn + #verifier_preprocess_from_prover_fn + #commit_trusted_advice_fn + #prove_fn + } + } + Backend::Modular => { + let modular_guard = self.make_modular_guard(); + let modular_compile_fn = self.make_modular_compile_func(); + let modular_prove_fn = self.make_modular_prove_func(); + let modular_verify_fn = self.make_modular_verify_func(); + quote! { + #modular_guard + #modular_compile_fn + #modular_prove_fn + #modular_verify_fn + } + } + }; quote! { #require_zk #memory_config_fn - #build_prover_fn - #build_verifier_fn #execute_fn - #analyze_fn - #trace_to_file_fn - #compile_fn - #preprocess_shared_fn - #preprocess_prover_fn - #preprocess_verifier_fn - #verifier_preprocess_from_prover_fn - #commit_trusted_advice_fn - #prove_fn + #backend_specific #main_fn } .into() @@ -951,6 +971,187 @@ impl MacroBuilder { } } + /// Emit a `compile_error!` if the user enables a feature the modular + /// backend does not yet support (ZK proving, WASM target, trusted advice, + /// or `max_trace_length` larger than the current goldens-baked fixture). + fn make_modular_guard(&self) -> TokenStream2 { + let has_trusted_advice = !self.trusted_func_args.is_empty(); + let trusted_advice_err = if has_trusted_advice { + quote! { + compile_error!( + "#[jolt::provable(backend = \"modular\")] does not yet support \ + trusted advice arguments (TrustedAdvice). Remove the trusted \ + advice parameter or use the default legacy backend." + ); + } + } else { + quote! {} + }; + + let wasm_err = if self.has_wasm_attr() { + quote! { + compile_error!( + "#[jolt::provable(backend = \"modular\")] is not compatible \ + with the `wasm` attribute." + ); + } + } else { + quote! {} + }; + + // The modular backend currently runs against a single goldens shape + // (log_t = 18). Reject `max_trace_length` above 2^18 at expansion + // time so users get a build error instead of an `UnsupportedShape` + // surprise from `prove_program`. Kept in sync with + // `jolt_host::FIXTURE_LOG_T`. + let attributes = parse_attributes(&self.attr); + const FIXTURE_LOG_T: u32 = 18; + const MAX_MODULAR_TRACE_LENGTH: u64 = 1 << FIXTURE_LOG_T; + let trace_len_err = if attributes.max_trace_length > MAX_MODULAR_TRACE_LENGTH { + let msg = format!( + "#[jolt::provable(backend = \"modular\")] only supports \ + max_trace_length <= 2^{FIXTURE_LOG_T} ({MAX_MODULAR_TRACE_LENGTH}), \ + got {}. Regenerating goldens at a larger shape is required \ + to lift this cap.", + attributes.max_trace_length + ); + quote! { compile_error!(#msg); } + } else { + quote! {} + }; + + // Note: the `zk` feature is rejected indirectly — `jolt-sdk` only + // re-exports `jolt_host`/`jolt_trace` when `feature = "zk"` is OFF, + // so enabling zk with this backend produces a compile-time + // "cannot find `jolt_host` in crate `jolt`" error. + quote! { + #trusted_advice_err + #wasm_err + #trace_len_err + } + } + + /// Modular `compile_` — builds the guest ELF via `jolt_trace::Program` + /// (matching the host-side path that `jolt_host::prove_program` consumes). + fn make_modular_compile_func(&self) -> TokenStream2 { + let guest_name = self.get_guest_name(); + let fn_name = self.get_func_name(); + let fn_name_str = fn_name.to_string(); + let compile_fn_name = Ident::new(&format!("compile_{fn_name}"), fn_name.span()); + let attributes = parse_attributes(&self.attr); + let stack_size = proc_macro2::Literal::u64_unsuffixed(attributes.stack_size); + let heap_size = proc_macro2::Literal::u64_unsuffixed(attributes.heap_size); + let max_input_size = proc_macro2::Literal::u64_unsuffixed(attributes.max_input_size); + let max_output_size = proc_macro2::Literal::u64_unsuffixed(attributes.max_output_size); + let max_untrusted_advice_size = + proc_macro2::Literal::u64_unsuffixed(attributes.max_untrusted_advice_size); + // Guests that depend on std (via the `guest-std` feature on jolt-sdk) + // must build for `riscv64imac-zero-linux-musl` rather than the default + // `*-unknown-none-elf` target. `self.std` reflects the macro's + // `guest-std` cfg detection (see MacroBuilder::new). + let set_std = self.make_set_std(); + let set_backtrace = self.make_set_backtrace(); + let set_profile = self.make_set_profile(); + + quote! { + #[cfg(all(not(target_arch = "wasm32"), not(feature = "guest")))] + pub fn #compile_fn_name() -> jolt::jolt_trace::Program { + let mut program = jolt::jolt_trace::Program::new(#guest_name); + program + .set_func(#fn_name_str) + .set_stack_size(#stack_size) + .set_heap_size(#heap_size) + .set_max_input_size(#max_input_size) + .set_max_output_size(#max_output_size) + .set_max_untrusted_advice_size(#max_untrusted_advice_size); + #set_std + #set_profile + #set_backtrace + program + } + } + } + + /// Modular `prove_(program, args...) -> (T, ProofBundle)`. + /// Postcard-encodes public + untrusted-advice arguments, delegates to + /// `jolt_host::prove_program`, then decodes the guest's typed return + /// value `T` from `io_device.outputs` so callers don't need to touch + /// raw bytes. + fn make_modular_prove_func(&self) -> TokenStream2 { + let fn_name = self.get_func_name(); + let prove_fn_name = Ident::new(&format!("prove_{fn_name}"), fn_name.span()); + + let pub_arg_names: Vec<_> = self.pub_func_args.iter().map(|(n, _)| n).collect(); + let pub_arg_types: Vec<_> = self.pub_func_args.iter().map(|(_, t)| t).collect(); + let untrusted_arg_names: Vec<_> = + self.untrusted_func_args.iter().map(|(n, _)| n).collect(); + let untrusted_arg_types: Vec<_> = + self.untrusted_func_args.iter().map(|(_, t)| t).collect(); + + let (ret_ty, decode_ret) = match &self.func.sig.output { + ReturnType::Default => ( + quote! { () }, + quote! { let ret_val: () = (); }, + ), + ReturnType::Type(_, ty) => ( + quote! { #ty }, + quote! { + let max_output = output.io_device.memory_layout.max_output_size as usize; + let mut output_bytes = output.io_device.outputs.clone(); + output_bytes.resize(max_output, 0); + let ret_val: #ty = jolt::postcard::from_bytes(&output_bytes) + .expect("decode guest return value via postcard"); + }, + ), + }; + + quote! { + #[cfg(all(not(target_arch = "wasm32"), not(feature = "guest")))] + pub fn #prove_fn_name( + program: &mut jolt::jolt_trace::Program, + #(#pub_arg_names: #pub_arg_types,)* + #(#untrusted_arg_names: #untrusted_arg_types,)* + ) -> Result<(#ret_ty, jolt::jolt_host::ProofBundle), jolt::jolt_host::ProveProgramError> + { + let mut input_bytes: Vec = Vec::new(); + #( + input_bytes.append(&mut jolt::postcard::to_stdvec(&#pub_arg_names).unwrap()); + )* + let mut untrusted_advice_bytes: Vec = Vec::new(); + #( + untrusted_advice_bytes.append( + &mut jolt::postcard::to_stdvec(&#untrusted_arg_names).unwrap() + ); + )* + let output = jolt::jolt_host::prove_program( + program, + &input_bytes, + &untrusted_advice_bytes, + &[], + )?; + #decode_ret + Ok((ret_val, output)) + } + } + } + + /// Modular `verify_(program, &output) -> Result<(), VerifyProgramError>`. + /// Thin wrapper around `jolt_host::verify_proof`. + fn make_modular_verify_func(&self) -> TokenStream2 { + let fn_name = self.get_func_name(); + let verify_fn_name = Ident::new(&format!("verify_{fn_name}"), fn_name.span()); + + quote! { + #[cfg(all(not(target_arch = "wasm32"), not(feature = "guest")))] + pub fn #verify_fn_name( + output: &jolt::jolt_host::ProofBundle, + program: &mut jolt::jolt_trace::Program, + ) -> Result<(), jolt::jolt_host::VerifyProgramError> { + jolt::jolt_host::verify_proof(output, program) + } + } + } + fn make_wasm_utilities(&self) -> TokenStream2 { quote! { #[cfg(target_arch = "wasm32")] diff --git a/jolt-sdk/src/host_utils.rs b/jolt-sdk/src/host_utils.rs index f7d09ba2ca..54fcbf232b 100644 --- a/jolt-sdk/src/host_utils.rs +++ b/jolt-sdk/src/host_utils.rs @@ -25,3 +25,12 @@ pub use jolt_core::poly::commitment::dory::{DoryContext, DoryGlobals}; pub use jolt_core::poly::multilinear_polynomial::MultilinearPolynomial; pub use jolt_core::zkvm::ram::populate_memory_states; pub use jolt_core::zkvm::verifier::BlindfoldSetup; + +// Modular backend re-exports (used by `#[jolt::provable(backend = "modular")]`). +// Gated on `not(feature = "zk")` so enabling the zk feature with the modular +// backend produces a clean "cannot find `jolt_host` in crate `jolt`" build +// error instead of a misleading runtime failure. +#[cfg(all(feature = "host", not(feature = "zk")))] +pub use jolt_host; +#[cfg(all(feature = "host", not(feature = "zk")))] +pub use jolt_trace; diff --git a/scripts/setup-bolt-dev.sh b/scripts/setup-bolt-dev.sh new file mode 100755 index 0000000000..7f23a687f3 --- /dev/null +++ b/scripts/setup-bolt-dev.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +env_file="${repo_root}/.bolt-dev-env" + +usage() { + cat <<'USAGE' +Usage: scripts/setup-bolt-dev.sh + +Installs/configures the local dependencies needed by Bolt MLIR/codegen and +Jolt-on-Bolt perf oracle checks on macOS: + + - Homebrew llvm + - rustup components: rust-src, rustfmt, clippy + - cargo-nextest + - .bolt-dev-env with MLIR_SYS_220_PREFIX, PATH, SDKROOT, and bindgen flags + +After it finishes, run: + + source .bolt-dev-env +USAGE +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +if [[ "$(uname -s)" != "Darwin" ]]; then + cat >&2 <<'EOF' +scripts/setup-bolt-dev.sh currently supports macOS/Homebrew. + +Bolt's MLIR path needs LLVM/MLIR 22 visible through llvm-config. On non-macOS +hosts, install an LLVM 22 toolchain and export: + + MLIR_SYS_220_PREFIX= + PATH=/bin:$PATH + +EOF + exit 1 +fi + +if ! command -v brew >/dev/null 2>&1; then + cat >&2 <<'EOF' +Homebrew is required to install the LLVM toolchain for this helper. +Install Homebrew first: https://brew.sh +EOF + exit 1 +fi + +if ! command -v xcrun >/dev/null 2>&1; then + cat >&2 <<'EOF' +xcrun is missing. Install the Xcode command line tools first: + + xcode-select --install +EOF + exit 1 +fi + +if ! brew list llvm >/dev/null 2>&1; then + brew install llvm +fi + +if command -v rustup >/dev/null 2>&1; then + rustup component add rust-src rustfmt clippy +else + cat >&2 <<'EOF' +rustup is not installed. Install Rust from https://rustup.rs, then rerun this +script so it can add rust-src, rustfmt, and clippy. +EOF + exit 1 +fi + +if ! cargo nextest --version >/dev/null 2>&1; then + cargo install cargo-nextest --locked +fi + +llvm_prefix="$(brew --prefix llvm)" +sdkroot="$(xcrun --show-sdk-path)" + +cat >"${env_file}" </dev/null + +cat <