From 11020823a60d552421be3fa78540ed08af5bf7ab Mon Sep 17 00:00:00 2001 From: Han Date: Tue, 21 Oct 2025 11:21:26 +0800 Subject: [PATCH] Integrate Airbender (#175) --- .github/workflows/test-zkvm-airbender.yml | 18 + .github/workflows/test-zkvm.yml | 16 +- Cargo.lock | 570 +++++++++++++++++- Cargo.toml | 5 + crates/dockerized/compiler/Cargo.toml | 2 + crates/dockerized/compiler/src/main.rs | 6 +- crates/dockerized/dockerized/build.rs | 3 + crates/dockerized/dockerized/src/lib.rs | 30 +- crates/dockerized/server/Cargo.toml | 2 + crates/dockerized/server/src/main.rs | 6 +- crates/zkvm/airbender/Cargo.toml | 28 + crates/zkvm/airbender/build.rs | 5 + crates/zkvm/airbender/src/client.rs | 244 ++++++++ crates/zkvm/airbender/src/compiler.rs | 5 + .../airbender/src/compiler/rust_rv32ima.rs | 97 +++ .../src/compiler/rust_rv32ima/link.x | 193 ++++++ .../src/compiler/rust_rv32ima/memory.x | 15 + crates/zkvm/airbender/src/error.rs | 99 +++ crates/zkvm/airbender/src/lib.rs | 153 +++++ docker/airbender/Dockerfile.base | 35 ++ docker/airbender/Dockerfile.compiler | 34 ++ docker/airbender/Dockerfile.server | 37 ++ docker/nexus/Dockerfile.base | 2 +- docker/pico/Dockerfile.base | 2 +- .../sdk_installers/install_airbender_sdk.sh | 51 ++ tests/airbender/basic/Cargo.toml | 10 + tests/airbender/basic/src/airbender_rt.rs | 60 ++ tests/airbender/basic/src/asm_reduced.S | 160 +++++ tests/airbender/basic/src/main.rs | 42 ++ 29 files changed, 1901 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/test-zkvm-airbender.yml create mode 100644 crates/zkvm/airbender/Cargo.toml create mode 100644 crates/zkvm/airbender/build.rs create mode 100644 crates/zkvm/airbender/src/client.rs create mode 100644 crates/zkvm/airbender/src/compiler.rs create mode 100644 crates/zkvm/airbender/src/compiler/rust_rv32ima.rs create mode 100644 crates/zkvm/airbender/src/compiler/rust_rv32ima/link.x create mode 100644 crates/zkvm/airbender/src/compiler/rust_rv32ima/memory.x create mode 100644 crates/zkvm/airbender/src/error.rs create mode 100644 crates/zkvm/airbender/src/lib.rs create mode 100644 docker/airbender/Dockerfile.base create mode 100644 docker/airbender/Dockerfile.compiler create mode 100644 docker/airbender/Dockerfile.server create mode 100755 scripts/sdk_installers/install_airbender_sdk.sh create mode 100644 tests/airbender/basic/Cargo.toml create mode 100644 tests/airbender/basic/src/airbender_rt.rs create mode 100644 tests/airbender/basic/src/asm_reduced.S create mode 100644 tests/airbender/basic/src/main.rs diff --git a/.github/workflows/test-zkvm-airbender.yml b/.github/workflows/test-zkvm-airbender.yml new file mode 100644 index 0000000..94c05ca --- /dev/null +++ b/.github/workflows/test-zkvm-airbender.yml @@ -0,0 +1,18 @@ +name: Test and clippy Airbender + +on: + push: + branches: + - master + pull_request: + +jobs: + test: + uses: ./.github/workflows/test-zkvm.yml + permissions: + contents: read + packages: write + with: + zkvm: airbender + toolchain: nightly + skip_prove_test: true diff --git a/.github/workflows/test-zkvm.yml b/.github/workflows/test-zkvm.yml index 345dc54..1269fb0 100644 --- a/.github/workflows/test-zkvm.yml +++ b/.github/workflows/test-zkvm.yml @@ -173,7 +173,8 @@ jobs: --rm \ --interactive \ --volume ${{ github.workspace }}:/ere \ - --volume $HOME/.cargo:/root/.cargo \ + --volume $HOME/.cargo/registry:/usr/local/cargo/registry \ + --volume $HOME/.cargo/git:/usr/local/cargo/git \ --workdir /ere \ ${{ needs.build_image.outputs.base_zkvm_image }} \ /bin/bash" @@ -186,8 +187,9 @@ jobs: cargo clippy --package ere-compiler --features ${{ inputs.zkvm }} \$OPTIONS cargo clippy --package ere-server --features ${{ inputs.zkvm }} \$OPTIONS - chown -R $(id -u):$(id -g) ~/.cargo - chown -R $(id -u):$(id -g) target + time chown -R $(id -u):$(id -g) /usr/local/cargo/registry + time chown -R $(id -u):$(id -g) /usr/local/cargo/git + time chown -R $(id -u):$(id -g) target EOF test_via_docker: @@ -221,7 +223,8 @@ jobs: --rm \ --interactive \ --volume ${{ github.workspace }}:/ere \ - --volume $HOME/.cargo:/root/.cargo \ + --volume $HOME/.cargo/registry:/usr/local/cargo/registry \ + --volume $HOME/.cargo/git:/usr/local/cargo/git \ --workdir /ere \ ${{ needs.build_image.outputs.base_zkvm_image }} \ /bin/bash" @@ -232,8 +235,9 @@ jobs: cargo test --release --package ere-${{ inputs.zkvm }} \ -- ${{ inputs.skip_prove_test && '--skip prove' || '' }} - chown -R $(id -u):$(id -g) ~/.cargo - chown -R $(id -u):$(id -g) target + time chown -R $(id -u):$(id -g) /usr/local/cargo/registry + time chown -R $(id -u):$(id -g) /usr/local/cargo/git + time chown -R $(id -u):$(id -g) target EOF test_ere_dockerized: diff --git a/Cargo.lock b/Cargo.lock index f3dc3b6..eade040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "cpp_demangle", + "fallible-iterator", + "gimli 0.29.0", + "memmap2", + "object 0.35.0", + "rustc-demangle", + "smallvec", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -31,9 +46,9 @@ checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "cpp_demangle", "fallible-iterator", - "gimli", + "gimli 0.31.1", "memmap2", - "object", + "object 0.36.7", "rustc-demangle", "smallvec", "typed-arena", @@ -63,6 +78,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -1851,11 +1868,11 @@ version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ - "addr2line", + "addr2line 0.24.2", "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.36.7", "rustc-demangle", "serde", "windows-targets 0.52.6", @@ -1928,6 +1945,17 @@ dependencies = [ "serde", ] +[[package]] +name = "bigint_with_control" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "prover", + "serde", + "serde_json", + "verifier_generator", +] + [[package]] name = "bincode" version = "1.3.3" @@ -2103,6 +2131,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake2_with_compression" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "prover", + "serde", + "serde_json", + "verifier_generator", +] + [[package]] name = "blake2b_simd" version = "1.0.3" @@ -2114,6 +2153,14 @@ dependencies = [ "constant_time_eq 0.3.1", ] +[[package]] +name = "blake2s_u32" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "unroll", +] + [[package]] name = "blake3" version = "1.8.2" @@ -2443,6 +2490,16 @@ dependencies = [ "inout", ] +[[package]] +name = "circuit_common" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "fft", + "field", + "worker", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -2612,6 +2669,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + [[package]] name = "const_format" version = "0.2.34" @@ -2859,6 +2936,31 @@ dependencies = [ "typenum", ] +[[package]] +name = "cs" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "arrayvec", + "bincode 1.3.3", + "blake2s_u32", + "derivative", + "divrem", + "field", + "itertools 0.14.0", + "poseidon2", + "proc-macro2", + "quote", + "rayon", + "seq-macro", + "serde", + "serde_json", + "smallvec", + "super-seq-macro", + "syn 2.0.101", + "type-map", +] + [[package]] name = "csv" version = "1.3.1" @@ -3347,6 +3449,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" +[[package]] +name = "divrem" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82" + [[package]] name = "docker-generate" version = "0.1.3" @@ -3619,6 +3727,21 @@ dependencies = [ "typeid", ] +[[package]] +name = "ere-airbender" +version = "0.0.14" +dependencies = [ + "bincode 2.0.1", + "ere-build-utils", + "ere-compile-utils", + "ere-test-utils", + "ere-zkvm-interface", + "execution_utils", + "serde_json", + "tempfile", + "thiserror 2.0.12", +] + [[package]] name = "ere-build-utils" version = "0.0.14" @@ -3644,6 +3767,7 @@ dependencies = [ "anyhow", "bincode 2.0.1", "clap", + "ere-airbender", "ere-jolt", "ere-miden", "ere-nexus", @@ -3796,6 +3920,7 @@ dependencies = [ "anyhow", "bincode 2.0.1", "clap", + "ere-airbender", "ere-jolt", "ere-miden", "ere-nexus", @@ -4227,6 +4352,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "execution_utils" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "clap", + "risc_v_simulator", + "serde", + "serde_json", + "trace_and_split", + "verifier_common", +] + [[package]] name = "eyre" version = "0.6.12" @@ -4310,12 +4448,48 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "fft" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "field", + "itertools 0.14.0", + "rayon", + "seq-macro", + "trace_holder", + "unroll", + "worker", +] + [[package]] name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "field" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "rand 0.9.2", + "seq-macro", + "serde", +] + +[[package]] +name = "final_reduced_risc_v_machine" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "circuit_common", + "prover", + "serde", + "serde_json", + "verifier_generator", +] + [[package]] name = "findshlibs" version = "0.10.2" @@ -4692,6 +4866,16 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +dependencies = [ + "fallible-iterator", + "stable_deref_trait", +] + [[package]] name = "gimli" version = "0.31.1" @@ -5549,6 +5733,28 @@ dependencies = [ "web-time", ] +[[package]] +name = "inferno" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96d2465363ed2d81857759fc864cf6bb7997f79327aec028d65bd7989393685" +dependencies = [ + "ahash", + "clap", + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "env_logger", + "indexmap 2.10.0", + "itoa", + "log", + "num-format", + "once_cell", + "quick-xml", + "rgb", + "str_stack", +] + [[package]] name = "inout" version = "0.1.4" @@ -5969,6 +6175,20 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lib-rv32-asm" +version = "0.2.0" +source = "git+https://github.com/shamatar/lib-rv32.git#869f82795f82314f17014a04fb6f2c31808e6b42" +dependencies = [ + "lib-rv32-common", + "log", +] + +[[package]] +name = "lib-rv32-common" +version = "0.2.0" +source = "git+https://github.com/shamatar/lib-rv32.git#869f82795f82314f17014a04fb6f2c31808e6b42" + [[package]] name = "libc" version = "0.2.175" @@ -6128,6 +6348,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "machine_without_signed_mul_div" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "circuit_common", + "prover", + "serde", + "serde_json", + "verifier_generator", +] + [[package]] name = "macro-string" version = "0.1.4" @@ -6868,6 +7100,11 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "non_determinism_source" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" + [[package]] name = "ntapi" version = "0.4.1" @@ -6967,6 +7204,16 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -7161,6 +7408,17 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "object" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +dependencies = [ + "flate2", + "memchr", + "ruzstd 0.6.0", +] + [[package]] name = "object" version = "0.36.7" @@ -7172,7 +7430,7 @@ dependencies = [ "hashbrown 0.15.3", "indexmap 2.10.0", "memchr", - "ruzstd", + "ruzstd 0.7.3", ] [[package]] @@ -7180,6 +7438,9 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] [[package]] name = "open-fastrlp" @@ -9919,6 +10180,17 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "poseidon2" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "field", + "non_determinism_source", + "rand 0.9.2", + "unroll", +] + [[package]] name = "postcard" version = "1.1.1" @@ -10282,6 +10554,34 @@ dependencies = [ "prost 0.13.5", ] +[[package]] +name = "prover" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "bit-set 0.8.0", + "blake2s_u32", + "cs", + "fft", + "field", + "itertools 0.14.0", + "lib-rv32-asm", + "non_determinism_source", + "poseidon2", + "proc-macro2", + "quote", + "rayon", + "risc_v_simulator", + "seq-macro", + "serde", + "serde_json", + "syn 2.0.101", + "trace_holder", + "transcript", + "unroll", + "worker", +] + [[package]] name = "puffin" version = "0.19.1" @@ -10317,6 +10617,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.8" @@ -10621,6 +10930,30 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "reduced_risc_v_log_23_machine" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "circuit_common", + "prover", + "serde", + "serde_json", + "verifier_generator", +] + +[[package]] +name = "reduced_risc_v_machine" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "circuit_common", + "prover", + "serde", + "serde_json", + "verifier_generator", +] + [[package]] name = "regex" version = "1.11.1" @@ -10798,6 +11131,43 @@ dependencies = [ "subtle", ] +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rhai" +version = "1.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527390cc333a8d2cd8237890e15c36518c26f8b54c903d86fc59f42f08d25594" +dependencies = [ + "ahash", + "bitflags 2.9.0", + "instant", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_codegen" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4322a2a4e8cf30771dd9f27f7f37ca9ac8fe812dddd811096a98483080dabe6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "ring" version = "0.16.20" @@ -11120,7 +11490,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fcce11648a9ff60b8e7af2f0ce7fbf8d25275ab6d414cc91b9da69ee75bc978" dependencies = [ - "addr2line", + "addr2line 0.24.2", "anyhow", "bincode 1.3.3", "borsh", @@ -11131,13 +11501,13 @@ dependencies = [ "enum-map", "gdbstub", "gdbstub_arch", - "gimli", + "gimli 0.31.1", "hex", "keccak", "lazy-regex", "num-bigint 0.4.6", "num-traits", - "object", + "object 0.36.7", "prost 0.13.5", "rand 0.9.2", "rayon", @@ -11179,6 +11549,37 @@ dependencies = [ "stability", ] +[[package]] +name = "risc_v_cycles" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "circuit_common", + "prover", + "serde", + "serde_json", + "verifier_generator", +] + +[[package]] +name = "risc_v_simulator" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "addr2line 0.22.0", + "blake2s_u32", + "cs", + "field", + "inferno", + "memmap2", + "object 0.36.7", + "poseidon2", + "rand 0.9.2", + "ringbuffer", + "ruint", + "serde", +] + [[package]] name = "rlp" version = "0.5.2" @@ -11517,6 +11918,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ruzstd" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" +dependencies = [ + "byteorder", + "derive_more 0.99.20", + "twox-hash", +] + [[package]] name = "ruzstd" version = "0.7.3" @@ -11786,6 +12198,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + [[package]] name = "serde" version = "1.0.221" @@ -11960,6 +12378,27 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "setups" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "bigint_with_control", + "blake2_with_compression", + "final_reduced_risc_v_machine", + "machine_without_signed_mul_div", + "proc-macro2", + "prover", + "quote", + "reduced_risc_v_log_23_machine", + "reduced_risc_v_machine", + "risc_v_cycles", + "serde", + "serde_json", + "syn 2.0.101", + "verifier_common", +] + [[package]] name = "sha1" version = "0.10.6" @@ -12090,6 +12529,17 @@ dependencies = [ "serde", ] +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + [[package]] name = "smawk" version = "0.3.2" @@ -12688,6 +13138,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str_stack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" + [[package]] name = "strength_reduce" version = "0.2.4" @@ -12803,6 +13259,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "super-seq-macro" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a7568b180699c2138a4bfaadda9a33a7419251489e58760d3b4e74d6588da3d" +dependencies = [ + "proc-macro2", + "rhai", + "syn 2.0.101", +] + [[package]] name = "supports-color" version = "3.0.2" @@ -13087,6 +13554,12 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + [[package]] name = "thiserror" version = "1.0.69" @@ -13592,6 +14065,27 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "trace_and_split" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "prover", + "serde", + "serde_json", + "setups", + "worker", +] + +[[package]] +name = "trace_holder" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "field", + "worker", +] + [[package]] name = "tracer" version = "0.2.0" @@ -13600,7 +14094,7 @@ dependencies = [ "clap", "common", "fnv", - "object", + "object 0.36.7", "tracing", "tracing-subscriber 0.3.19", ] @@ -13743,6 +14237,16 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "transcript" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "blake2s_u32", + "unroll", + "worker", +] + [[package]] name = "transpose" version = "0.2.3" @@ -13863,6 +14367,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.1", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -14107,6 +14620,36 @@ dependencies = [ "time", ] +[[package]] +name = "verifier_common" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "blake2s_u32", + "cs", + "field", + "non_determinism_source", + "poseidon2", + "prover", + "serde", + "transcript", + "unroll", +] + +[[package]] +name = "verifier_generator" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "prover", + "quote", + "serde", + "serde_json", + "syn 2.0.101", +] + [[package]] name = "version_check" version = "0.9.5" @@ -14859,6 +15402,15 @@ dependencies = [ "bitflags 2.9.0", ] +[[package]] +name = "worker" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-airbender?tag=v0.5.0#a3c74040d2f994608f451550232392333400bbf1" +dependencies = [ + "num_cpus", + "rayon", +] + [[package]] name = "writeable" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index 408de69..7e11d19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ # zkVM interface "crates/zkvm-interface", # zkVMs + "crates/zkvm/airbender", "crates/zkvm/jolt", "crates/zkvm/miden", "crates/zkvm/nexus", @@ -62,6 +63,9 @@ tracing-subscriber = "0.3.19" twirp = "0.9.1" twirp-build = "0.9.0" +# Airbender dependencies +airbender_execution_utils = { git = "https://github.com/matter-labs/zksync-airbender", package = "execution_utils", tag = "v0.5.0" } + # Jolt dependencies ark-serialize = "0.5.0" common = { git = "https://github.com/a16z/jolt.git", rev = "55b9830a3944dde55d33a55c42522b81dd49f87a" } @@ -106,6 +110,7 @@ zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren.git", tag = "v1.1.4" } # Local dependencies ere-zkvm-interface = { path = "crates/zkvm-interface" } +ere-airbender = { path = "crates/zkvm/airbender", default-features = false } ere-jolt = { path = "crates/zkvm/jolt", default-features = false } ere-miden = { path = "crates/zkvm/miden", default-features = false } ere-nexus = { path = "crates/zkvm/nexus", default-features = false } diff --git a/crates/dockerized/compiler/Cargo.toml b/crates/dockerized/compiler/Cargo.toml index 7acec52..c693200 100644 --- a/crates/dockerized/compiler/Cargo.toml +++ b/crates/dockerized/compiler/Cargo.toml @@ -13,6 +13,7 @@ serde.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } # Local dependencies +ere-airbender = { workspace = true, optional = true } ere-jolt = { workspace = true, optional = true } ere-miden = { workspace = true, optional = true } ere-nexus = { workspace = true, optional = true } @@ -30,6 +31,7 @@ ere-zkvm-interface.workspace = true default = [] # zkVM +airbender = ["dep:ere-airbender"] jolt = ["dep:ere-jolt"] miden = ["dep:ere-miden"] nexus = ["dep:ere-nexus"] diff --git a/crates/dockerized/compiler/src/main.rs b/crates/dockerized/compiler/src/main.rs index ee8ddde..0963812 100644 --- a/crates/dockerized/compiler/src/main.rs +++ b/crates/dockerized/compiler/src/main.rs @@ -8,7 +8,8 @@ use tracing_subscriber::EnvFilter; // Compile-time check to ensure exactly one zkVM feature is enabled for `ere-compiler` const _: () = { assert!( - (cfg!(feature = "jolt") as u8 + (cfg!(feature = "airbender") as u8 + + cfg!(feature = "jolt") as u8 + cfg!(feature = "miden") as u8 + cfg!(feature = "nexus") as u8 + cfg!(feature = "openvm") as u8 @@ -50,6 +51,9 @@ fn main() -> Result<(), Error> { } fn compile(guest_path: PathBuf) -> Result { + #[cfg(feature = "airbender")] + let result = ere_airbender::compiler::RustRv32ima.compile(&guest_path); + #[cfg(feature = "jolt")] let result = if use_stock_rust() { ere_jolt::compiler::RustRv32ima.compile(&guest_path) diff --git a/crates/dockerized/dockerized/build.rs b/crates/dockerized/dockerized/build.rs index 7ea6a98..3dd3d6c 100644 --- a/crates/dockerized/dockerized/build.rs +++ b/crates/dockerized/dockerized/build.rs @@ -20,6 +20,7 @@ fn generate_crate_version() { fn generate_zkvm_sdk_version_impl() { let [ + airbender_version, jolt_version, miden_version, nexus_version, @@ -29,6 +30,7 @@ fn generate_zkvm_sdk_version_impl() { sp1_version, ziren_version, ] = [ + "execution_utils", "jolt-sdk", "miden-core", "nexus-sdk", @@ -51,6 +53,7 @@ fn generate_zkvm_sdk_version_impl() { r#"impl crate::ErezkVM {{ pub fn sdk_version(&self) -> &'static str {{ match self {{ + Self::Airbender => "{airbender_version}", Self::Jolt => "{jolt_version}", Self::Miden => "{miden_version}", Self::Nexus => "{nexus_version}", diff --git a/crates/dockerized/dockerized/src/lib.rs b/crates/dockerized/dockerized/src/lib.rs index 16d180e..6c8e96f 100644 --- a/crates/dockerized/dockerized/src/lib.rs +++ b/crates/dockerized/dockerized/src/lib.rs @@ -95,6 +95,7 @@ const ERE_SERVER_PORT_OFFSET: u16 = 4174; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ErezkVM { + Airbender, Jolt, Miden, Nexus, @@ -109,6 +110,7 @@ pub enum ErezkVM { impl ErezkVM { pub fn as_str(&self) -> &'static str { match self { + Self::Airbender => "airbender", Self::Jolt => "jolt", Self::Miden => "miden", Self::Nexus => "nexus", @@ -126,7 +128,7 @@ impl ErezkVM { let suffix = match (gpu, self) { // Only the following zkVMs requires CUDA setup in the base image // when GPU support is required. - (true, Self::OpenVM | Self::Risc0 | Self::Zisk) => "-cuda", + (true, Self::Airbender | Self::OpenVM | Self::Risc0 | Self::Zisk) => "-cuda", _ => "", }; format!("{version}{suffix}") @@ -195,7 +197,7 @@ impl ErezkVM { let cuda_arch = cuda_arch(); match self { - ErezkVM::OpenVM | ErezkVM::Risc0 | ErezkVM::Zisk => { + Self::Airbender | Self::OpenVM | Self::Risc0 | Self::Zisk => { if let Some(cuda_arch) = cuda_arch { cmd = cmd.build_arg("CUDA_ARCH", cuda_arch) } @@ -259,12 +261,12 @@ impl ErezkVM { // zkVM specific options cmd = match self { - ErezkVM::Risc0 => cmd + Self::Risc0 => cmd .inherit_env("RISC0_SEGMENT_PO2") .inherit_env("RISC0_KECCAK_PO2"), // ZisK uses shared memory to exchange data between processes, it // requires at least 8G shared memory, here we set 16G for safety. - ErezkVM::Zisk => cmd + Self::Zisk => cmd .option("shm-size", "16G") .option("ulimit", "memlock=-1:-1") .inherit_env("ZISK_PORT") @@ -282,13 +284,14 @@ impl ErezkVM { // zkVM specific options when using GPU if gpu { cmd = match self { - ErezkVM::OpenVM => cmd.gpus("all"), + Self::Airbender => cmd.gpus("all"), + Self::OpenVM => cmd.gpus("all"), // SP1 runs docker command to spin up the server to do GPU // proving, to give the client access to the prover service, we // need to use the host networking driver. - ErezkVM::SP1 => cmd.mount_docker_socket().network("host"), - ErezkVM::Risc0 => cmd.gpus("all").inherit_env("RISC0_DEFAULT_PROVER_NUM_GPUS"), - ErezkVM::Zisk => cmd.gpus("all"), + Self::SP1 => cmd.mount_docker_socket().network("host"), + Self::Risc0 => cmd.gpus("all").inherit_env("RISC0_DEFAULT_PROVER_NUM_GPUS"), + Self::Zisk => cmd.gpus("all"), _ => cmd, } } @@ -303,11 +306,11 @@ impl ErezkVM { // create a temporary directory and mount it on the top level, so // the volume could be shared, and override `TMPDIR` so we don't // need to mount the whole `/tmp`. - ErezkVM::Risc0 => cmd + Self::Risc0 => cmd .mount_docker_socket() .env("TMPDIR", tempdir.path().to_string_lossy()) .volume(tempdir.path(), tempdir.path()), - ErezkVM::SP1 => { + Self::SP1 => { let groth16_circuit_path = home_dir().join(".sp1").join("circuits").join("groth16"); cmd.mount_docker_socket() .env( @@ -336,6 +339,7 @@ impl FromStr for ErezkVM { fn from_str(s: &str) -> Result { Ok(match s { + "airbender" => Self::Airbender, "jolt" => Self::Jolt, "miden" => Self::Miden, "nexus" => Self::Nexus, @@ -610,6 +614,12 @@ mod test { }; } + mod airbender { + test_compile!(Airbender, "basic"); + test_execute!(Airbender, BasicProgramInput::valid().into_output_sha256()); + test_prove!(Airbender, BasicProgramInput::valid().into_output_sha256()); + } + mod jolt { test_compile!(Jolt, "basic"); } diff --git a/crates/dockerized/server/Cargo.toml b/crates/dockerized/server/Cargo.toml index 4f5a9ff..83ebf65 100644 --- a/crates/dockerized/server/Cargo.toml +++ b/crates/dockerized/server/Cargo.toml @@ -20,6 +20,7 @@ tracing = { workspace = true, optional = true } tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true } # Local dependencies +ere-airbender = { workspace = true, optional = true } ere-jolt = { workspace = true, optional = true } ere-miden = { workspace = true, optional = true } ere-nexus = { workspace = true, optional = true } @@ -45,6 +46,7 @@ default = [] server = ["dep:clap", "dep:tower-http", "dep:tracing", "dep:tracing-subscriber", "tokio/macros", "tokio/rt-multi-thread", "tokio/signal"] # zkVM +airbender = ["dep:ere-airbender", "server"] jolt = ["dep:ere-jolt", "server"] miden = ["dep:ere-miden", "server"] nexus = ["dep:ere-nexus", "server"] diff --git a/crates/dockerized/server/src/main.rs b/crates/dockerized/server/src/main.rs index c2e88a0..28f3487 100644 --- a/crates/dockerized/server/src/main.rs +++ b/crates/dockerized/server/src/main.rs @@ -21,7 +21,8 @@ use twirp::{ const _: () = { if cfg!(feature = "server") { assert!( - (cfg!(feature = "jolt") as u8 + (cfg!(feature = "airbender") as u8 + + cfg!(feature = "jolt") as u8 + cfg!(feature = "miden") as u8 + cfg!(feature = "nexus") as u8 + cfg!(feature = "openvm") as u8 @@ -111,6 +112,9 @@ fn construct_zkvm(program: Vec, resource: ProverResourceType) -> Result(ere_airbender::EreAirbender::new(program, resource)); + #[cfg(feature = "jolt")] let zkvm = ere_jolt::EreJolt::new(program, resource); diff --git a/crates/zkvm/airbender/Cargo.toml b/crates/zkvm/airbender/Cargo.toml new file mode 100644 index 0000000..8383bc5 --- /dev/null +++ b/crates/zkvm/airbender/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ere-airbender" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[dependencies] +bincode = { workspace = true, features = ["alloc", "serde"] } +serde_json.workspace = true +tempfile.workspace = true +thiserror.workspace = true + +# Airbender dependencies +airbender_execution_utils.workspace = true + +# Local dependencies +ere-compile-utils.workspace = true +ere-zkvm-interface.workspace = true + +[dev-dependencies] +ere-test-utils = { workspace = true, features = ["host"] } + +[build-dependencies] +ere-build-utils.workspace = true + +[lints] +workspace = true diff --git a/crates/zkvm/airbender/build.rs b/crates/zkvm/airbender/build.rs new file mode 100644 index 0000000..c570e89 --- /dev/null +++ b/crates/zkvm/airbender/build.rs @@ -0,0 +1,5 @@ +use ere_build_utils::detect_and_generate_name_and_sdk_version; + +fn main() { + detect_and_generate_name_and_sdk_version("airbender", "execution_utils"); +} diff --git a/crates/zkvm/airbender/src/client.rs b/crates/zkvm/airbender/src/client.rs new file mode 100644 index 0000000..12391d5 --- /dev/null +++ b/crates/zkvm/airbender/src/client.rs @@ -0,0 +1,244 @@ +use crate::error::AirbenderError; +use airbender_execution_utils::{ + Machine, ProgramProof, compute_chain_encoding, generate_params_for_binary, + universal_circuit_verifier_vk, verify_recursion_log_23_layer, +}; +use ere_zkvm_interface::PublicValues; +use std::{array, fs, io::BufRead, iter, process::Command}; +use tempfile::tempdir; + +/// Verification key hash chain. +/// +/// For recursive verifier program, it exposes the chaining hash of verification +/// keys of programs that it verifies, which is computed as +/// `blake(blake(blake(0 || base_vk)|| verifier_0_vk) || verifier_1_vk)...`. +/// +/// For a base program, the VK is computed as `blake(PC || setup_caps)`, where +/// `PC` is the program counter value at the end of execution, and `setup_caps` +/// is the merkle tree caps derived from the program. +pub type VkHashChain = [u32; 8]; + +pub struct AirbenderSdk { + bin: Vec, + vk_hash_chain: VkHashChain, + gpu: bool, +} + +impl AirbenderSdk { + pub fn new(bin: &[u8], gpu: bool) -> Self { + let vk_hash_chain = { + // Compute base VK as `blake(PC || setup_caps)`. + let base_vk = generate_params_for_binary(bin, Machine::Standard); + // The 1st recursion layer VK + let verifier_vk = universal_circuit_verifier_vk().params; + // Compute hash chain as `blake(blake(0 || guest_vk) || verifier_vk)`, + // that is expected to be exposed by second layer recursion program. + compute_chain_encoding(vec![[0; 8], base_vk, verifier_vk]) + }; + Self { + bin: bin.to_vec(), + vk_hash_chain, + gpu, + } + } + + pub fn execute(&self, input: &[u8]) -> Result<(PublicValues, u64), AirbenderError> { + let tempdir = tempdir().map_err(AirbenderError::TempDir)?; + + let bin_path = tempdir.path().join("guest.bin"); + fs::write(&bin_path, &self.bin) + .map_err(|err| AirbenderError::write_file(err, "guest.bin", &bin_path))?; + + let input_path = tempdir.path().join("input.hex"); + fs::write(&input_path, encode_input(input)) + .map_err(|err| AirbenderError::write_file(err, "input.hex", &input_path))?; + + let output = Command::new("airbender-cli") + .arg("run") + .arg("--bin") + .arg(&bin_path) + .arg("--input-file") + .arg(&input_path) + .args(["--cycles", &u64::MAX.to_string()]) + .output() + .map_err(AirbenderError::AirbenderRun)?; + + if !output.status.success() { + return Err(AirbenderError::AirbenderRunFailed { + status: output.status, + }); + } + + // Parse public values 8 u32 words (32 bytes) from stdout in format of: + // `Result: {v0}, {v1}, {v2}, {v3}, {v4}, {v5}, {v6}, {v7}` + let public_values = output + .stdout + .lines() + .find_map(|line| { + let line = line.ok()?; + let line = line.split_once("Result:")?.1; + let mut words = line.split(','); + let mut bytes = Vec::with_capacity(32); + for _ in 0..8 { + bytes.extend(words.next()?.trim().parse::().ok()?.to_le_bytes()) + } + Some(bytes) + }) + .ok_or_else(|| { + AirbenderError::ParsePublicValue( + String::from_utf8_lossy(&output.stdout).to_string(), + ) + })?; + + // Parse cycles from stdout in format of: + // `Took {cycles} cycles to finish` + let cycles = output + .stdout + .lines() + .find_map(|line| { + let line = line.ok()?; + let line = line.split_once("Took ")?.1; + let cycle = line.split_once(" cycles")?.0; + cycle.parse().ok() + }) + .ok_or_else(|| { + AirbenderError::ParseCycles(String::from_utf8_lossy(&output.stdout).to_string()) + })?; + + Ok((public_values, cycles)) + } + + pub fn prove(&self, input: &[u8]) -> Result<(PublicValues, ProgramProof), AirbenderError> { + let tempdir = tempdir().map_err(AirbenderError::TempDir)?; + + let bin_path = tempdir.path().join("guest.bin"); + fs::write(&bin_path, &self.bin) + .map_err(|err| AirbenderError::write_file(err, "guest.bin", &bin_path))?; + + let input_path = tempdir.path().join("input.hex"); + fs::write(&input_path, encode_input(input)) + .map_err(|err| AirbenderError::write_file(err, "input.hex", &input_path))?; + + let output_dir = tempdir.path().join("output"); + fs::create_dir_all(&output_dir) + .map_err(|err| AirbenderError::create_dir(err, "output", &output_dir))?; + + // Prove guest program + 1st recursion layer (tree of recursive proofs until root). + let output = Command::new("airbender-cli") + .arg("prove") + .arg("--bin") + .arg(&bin_path) + .arg("--output-dir") + .arg(&output_dir) + .arg("--input-file") + .arg(&input_path) + .args(["--until", "final-recursion"]) + .args(["--cycles", &u64::MAX.to_string()]) + .args(self.gpu.then_some("--gpu")) + .output() + .map_err(AirbenderError::AirbenderProve)?; + + if !output.status.success() { + return Err(AirbenderError::AirbenderProveFailed { + status: output.status, + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + + let proof_path = output_dir.join("recursion_program_proof.json"); + if !proof_path.exists() { + return Err(AirbenderError::RecursionProofNotFound { path: proof_path }); + } + + // Prove 2nd recursion layer (wrapping root of 1st recursion layer) + let output = Command::new("airbender-cli") + .arg("prove-final") + .arg("--input-file") + .arg(&proof_path) + .arg("--output-dir") + .arg(&output_dir) + .args(self.gpu.then_some("--gpu")) + .output() + .map_err(AirbenderError::AirbenderProveFinal)?; + + if !output.status.success() { + return Err(AirbenderError::AirbenderProveFinalFailed { + status: output.status, + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + + let proof_path = output_dir.join("final_program_proof.json"); + if !proof_path.exists() { + return Err(AirbenderError::FinalProofNotFound { path: proof_path }); + } + + let proof_bytes = fs::read(&proof_path).map_err(|err| { + AirbenderError::read_file(err, "final_program_proof.json", &proof_path) + })?; + + let proof: ProgramProof = + serde_json::from_slice(&proof_bytes).map_err(AirbenderError::JsonDeserialize)?; + + let (public_values, vk_hash_chain) = extract_public_values_and_vk_hash_chain(&proof)?; + + if self.vk_hash_chain != vk_hash_chain { + return Err(AirbenderError::UnexpectedVkHashChain { + preprocessed: self.vk_hash_chain, + proved: vk_hash_chain, + }); + } + + Ok((public_values, proof)) + } + + pub fn verify(&self, proof: &ProgramProof) -> Result { + let is_valid = verify_recursion_log_23_layer(proof); + if !is_valid { + return Err(AirbenderError::ProofVerificationFailed); + } + + let (public_values, vk_hash_chain) = extract_public_values_and_vk_hash_chain(proof)?; + + if self.vk_hash_chain != vk_hash_chain { + return Err(AirbenderError::UnexpectedVkHashChain { + preprocessed: self.vk_hash_chain, + proved: vk_hash_chain, + }); + } + + Ok(public_values) + } +} + +/// Encode input with length prefixed to hex string for `airbender-cli`. +fn encode_input(input: &[u8]) -> String { + iter::once((input.len() as u32).to_le_bytes().as_slice()) + .chain(input.chunks(4)) + .map(|chunk| { + let mut bytes = [0u8; 4]; + bytes[..chunk.len()].copy_from_slice(chunk); + format!("{:08x}", u32::from_le_bytes(bytes)) + }) + .collect() +} + +// Extract public values and VK hash chain from register values. +fn extract_public_values_and_vk_hash_chain( + proof: &ProgramProof, +) -> Result<(PublicValues, VkHashChain), AirbenderError> { + if proof.register_final_values.len() != 32 { + return Err(AirbenderError::InvalidRegisterCount( + proof.register_final_values.len(), + )); + } + + let public_values = proof.register_final_values[10..18] + .iter() + .flat_map(|value| value.value.to_le_bytes()) + .collect(); + + let vk_chain_hash = array::from_fn(|i| proof.register_final_values[18 + i].value); + + Ok((public_values, vk_chain_hash)) +} diff --git a/crates/zkvm/airbender/src/compiler.rs b/crates/zkvm/airbender/src/compiler.rs new file mode 100644 index 0000000..b79589a --- /dev/null +++ b/crates/zkvm/airbender/src/compiler.rs @@ -0,0 +1,5 @@ +mod rust_rv32ima; + +pub use rust_rv32ima::RustRv32ima; + +pub type AirbenderProgram = Vec; diff --git a/crates/zkvm/airbender/src/compiler/rust_rv32ima.rs b/crates/zkvm/airbender/src/compiler/rust_rv32ima.rs new file mode 100644 index 0000000..0c3d7ac --- /dev/null +++ b/crates/zkvm/airbender/src/compiler/rust_rv32ima.rs @@ -0,0 +1,97 @@ +use crate::{compiler::AirbenderProgram, error::AirbenderError}; +use ere_compile_utils::CargoBuildCmd; +use ere_zkvm_interface::Compiler; +use std::{ + env, + io::Write, + path::Path, + process::{Command, Stdio}, +}; + +const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf"; +// Rust flags according to https://github.com/matter-labs/zksync-airbender/blob/v0.5.0/examples/dynamic_fibonacci/.cargo/config.toml. +const RUSTFLAGS: &[&str] = &[ + // Replace atomic ops with nonatomic versions since the guest is single threaded. + "-C", + "passes=lower-atomic", + "-C", + "target-feature=-unaligned-scalar-mem,+relax", + "-C", + "link-arg=--save-temps", + "-C", + "force-frame-pointers", +]; +const CARGO_BUILD_OPTIONS: &[&str] = &[ + // For bare metal we have to build core and alloc + "-Zbuild-std=core,alloc", +]; + +const LINKER_SCRIPT: &str = concat!( + include_str!("rust_rv32ima/memory.x"), + include_str!("rust_rv32ima/link.x"), +); + +/// Compiler for Rust guest program to RV32IMA architecture. +pub struct RustRv32ima; + +impl Compiler for RustRv32ima { + type Error = AirbenderError; + + type Program = AirbenderProgram; + + fn compile(&self, guest_directory: &Path) -> Result { + let toolchain = env::var("ERE_RUST_TOOLCHAIN").unwrap_or_else(|_| "nightly".into()); + let elf = CargoBuildCmd::new() + .linker_script(Some(LINKER_SCRIPT)) + .toolchain(toolchain) + .build_options(CARGO_BUILD_OPTIONS) + .rustflags(RUSTFLAGS) + .exec(guest_directory, TARGET_TRIPLE)?; + let bin = objcopy_binary(&elf)?; + Ok(bin) + } +} + +fn objcopy_binary(elf: &[u8]) -> Result, AirbenderError> { + let mut child = Command::new("rust-objcopy") + .args(["-O", "binary", "-", "-"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(AirbenderError::RustObjcopy)?; + + child + .stdin + .as_mut() + .unwrap() + .write_all(elf) + .map_err(AirbenderError::RustObjcopyStdin)?; + + let output = child + .wait_with_output() + .map_err(AirbenderError::RustObjcopy)?; + + if !output.status.success() { + return Err(AirbenderError::RustObjcopyFailed { + status: output.status, + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + + Ok(output.stdout) +} + +#[cfg(test)] +mod tests { + use crate::compiler::RustRv32ima; + use ere_test_utils::host::testing_guest_directory; + use ere_zkvm_interface::Compiler; + + #[test] + fn test_compile() { + let guest_directory = testing_guest_directory("airbender", "basic"); + let bin = RustRv32ima.compile(&guest_directory).unwrap(); + assert!(!bin.is_empty(), "Binary should not be empty."); + } +} diff --git a/crates/zkvm/airbender/src/compiler/rust_rv32ima/link.x b/crates/zkvm/airbender/src/compiler/rust_rv32ima/link.x new file mode 100644 index 0000000..1001fe2 --- /dev/null +++ b/crates/zkvm/airbender/src/compiler/rust_rv32ima/link.x @@ -0,0 +1,193 @@ +/* Copied from https://github.com/matter-labs/zksync-airbender/blob/v0.5.0/examples/scripts/lds/link.x */ + +PROVIDE(_stext = ORIGIN(REGION_TEXT)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 64M); +PROVIDE(_heap_size = 768M); + +/* +PROVIDE(UserSoft = DefaultHandler); +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(UserTimer = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(UserExternal = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); +*/ + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +/* +PROVIDE(_setup_interrupts = default_setup_interrupts); +*/ + + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); +PROVIDE(_machine_start_trap = machine_default_start_trap); + +PHDRS +{ + text PT_LOAD; + data PT_LOAD; + bss PT_LOAD; +} + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > REGION_TEXT AT > REGION_TEXT :text + + .text _stext : ALIGN(4096) + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + + *(.text .text.*); + } > REGION_TEXT AT > REGION_TEXT :text + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following + section will have the correct alignment. */ + . = ALIGN(4); + } > REGION_RODATA AT > REGION_RODATA :text + + /* fictitious region that represents the memory available for the stack */ + .stack ORIGIN(REGION_STACK) (NOLOAD) : ALIGN(4096) + { + _estack = .; + . += (_max_hart_id + 1) * _hart_stack_size; + . = ALIGN(4); + _sstack = .; + } > REGION_STACK + + .data : ALIGN(4096) + { + _sidata = LOADADDR(.data); + _sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(4); + _edata = .; + } > REGION_DATA AT > REGION_DATAINIT :data + + .bss (NOLOAD) : ALIGN(4096) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(4); + _ebss = .; + } > REGION_BSS AT > REGION_BSS :bss + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : ALIGN(2097152) + { + _sheap = .; + . += _heap_size; + . = ALIGN(2097152); + _eheap = .; + } > REGION_HEAP + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_RODATA) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_DATAINIT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_DATAINIT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_DATA) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_DATA must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_STACK) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 4 == 0 && _edata % 4 == 0, " +BUG(riscv-rt): .data is not 4-byte aligned"); + +ASSERT(_sidata % 4 == 0, " +BUG(riscv-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(_sbss % 4 == 0 && _ebss % 4 == 0, " +BUG(riscv-rt): .bss is not 4-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT), " +ERROR(riscv-rt): The .text section must be placed inside the REGION_TEXT region. +Set _stext to an address smaller than 'ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT)'"); + +ASSERT(_sidata + SIZEOF(.data) < ORIGIN(REGION_DATAINIT) + LENGTH(REGION_DATAINIT), " +ERROR(riscv-rt): The init data for .data section must be placed inside the REGION_DATAINIT region. +Set _sidata to an address smaller than 'ORIGIN(REGION_DATAINIT) + LENGTH(REGION_DATAINIT)'"); + +ASSERT(SIZEOF(.stack) >= (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +ASSERT(SIZEOF(.data) == 0, " +.data section detected in the input files. Global variables with non-trivial +initialization are not supported yet. Variables with zero-initialization can be +linked to .bss section instead, as the platform guarantees zero-initialization +of all RAM space."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/crates/zkvm/airbender/src/compiler/rust_rv32ima/memory.x b/crates/zkvm/airbender/src/compiler/rust_rv32ima/memory.x new file mode 100644 index 0000000..37ee407 --- /dev/null +++ b/crates/zkvm/airbender/src/compiler/rust_rv32ima/memory.x @@ -0,0 +1,15 @@ +/* Copied from https://github.com/matter-labs/zksync-airbender/blob/v0.5.0/examples/scripts/lds/memory.x */ + +MEMORY +{ + ROM (rx): ORIGIN = 0, LENGTH = 2M + RAM (rwa!x) : ORIGIN = 2M, LENGTH = 1022M +} + +REGION_ALIAS("REGION_TEXT", ROM); +REGION_ALIAS("REGION_RODATA", ROM); +REGION_ALIAS("REGION_DATAINIT", ROM); +REGION_ALIAS("REGION_STACK", RAM); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); diff --git a/crates/zkvm/airbender/src/error.rs b/crates/zkvm/airbender/src/error.rs new file mode 100644 index 0000000..d29e1c9 --- /dev/null +++ b/crates/zkvm/airbender/src/error.rs @@ -0,0 +1,99 @@ +use crate::client::VkHashChain; +use ere_zkvm_interface::zkVMError; +use std::{ + io, + path::{Path, PathBuf}, + process::ExitStatus, +}; +use thiserror::Error; + +impl From for zkVMError { + fn from(value: AirbenderError) -> Self { + zkVMError::Other(Box::new(value)) + } +} + +#[derive(Debug, Error)] +pub enum AirbenderError { + // Compilation + #[error(transparent)] + CompileError(#[from] ere_compile_utils::CompileError), + #[error("Failed to execute `rust-objcopy`: {0}")] + RustObjcopy(#[source] io::Error), + #[error("`rust-objcopy` failed with status: {status}\nstderr: {stderr}")] + RustObjcopyFailed { status: ExitStatus, stderr: String }, + #[error("Failed to write ELF to `rust-objcopy` stdin: {0}")] + RustObjcopyStdin(#[source] io::Error), + + // IO and file system + #[error("IO failure: {0}")] + Io(#[from] io::Error), + #[error("IO failure in temporary directory: {0}")] + TempDir(io::Error), + + // Serialization + #[error("Bincode encode failed: {0}")] + BincodeEncode(#[from] bincode::error::EncodeError), + #[error("Bincode decode failed: {0}")] + BincodeDecode(#[from] bincode::error::DecodeError), + #[error("JSON deserialization failed: {0}")] + JsonDeserialize(#[from] serde_json::Error), + + // Execution + #[error("Failed to execute `airbender-cli run`: {0}")] + AirbenderRun(#[source] io::Error), + #[error("`airbender-cli run` failed with status: {status}")] + AirbenderRunFailed { status: ExitStatus }, + #[error("Failed to parse public value from stdout: {0}")] + ParsePublicValue(String), + #[error("Failed to parse cycles from stdout: {0}")] + ParseCycles(String), + + // Proving + #[error("Failed to execute `airbender-cli prove`: {0}")] + AirbenderProve(#[source] io::Error), + #[error("`airbender-cli prove` failed with status: {status}\nstderr: {stderr}")] + AirbenderProveFailed { status: ExitStatus, stderr: String }, + #[error("Failed to execute `airbender-cli prove-final`: {0}")] + AirbenderProveFinal(#[source] io::Error), + #[error("`airbender-cli prove-final` failed with status: {status}\nstderr: {stderr}")] + AirbenderProveFinalFailed { status: ExitStatus, stderr: String }, + #[error("Recursion proof not found at {path}")] + RecursionProofNotFound { path: PathBuf }, + #[error("Final proof not found at {path}")] + FinalProofNotFound { path: PathBuf }, + + // Verification + #[error("Proof verification failed")] + ProofVerificationFailed, + #[error("Invalid final register count, expected 32 but got {0}")] + InvalidRegisterCount(usize), + #[error( + "Unexpected verification key hash chain - preprocessed: {preprocessed:?}, proved: {proved:?}" + )] + UnexpectedVkHashChain { + preprocessed: VkHashChain, + proved: VkHashChain, + }, +} + +impl AirbenderError { + pub fn io(err: io::Error, context: impl Into) -> Self { + Self::Io(io::Error::other(format!("{}: {}", context.into(), err))) + } + + pub fn create_dir(err: io::Error, id: &str, path: impl AsRef) -> Self { + let ctx = format!("Failed to create dir {id} at {}", path.as_ref().display()); + Self::io(err, ctx) + } + + pub fn write_file(err: io::Error, id: &str, path: impl AsRef) -> Self { + let ctx = format!("Failed to write {id} to {}", path.as_ref().display()); + Self::io(err, ctx) + } + + pub fn read_file(err: io::Error, id: &str, path: impl AsRef) -> Self { + let ctx = format!("Failed to read {id} from {}", path.as_ref().display()); + Self::io(err, ctx) + } +} diff --git a/crates/zkvm/airbender/src/lib.rs b/crates/zkvm/airbender/src/lib.rs new file mode 100644 index 0000000..968a7b6 --- /dev/null +++ b/crates/zkvm/airbender/src/lib.rs @@ -0,0 +1,153 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use crate::{client::AirbenderSdk, compiler::AirbenderProgram, error::AirbenderError}; +use airbender_execution_utils::ProgramProof; +use ere_zkvm_interface::{ + ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, + PublicValues, zkVM, zkVMError, +}; +use std::time::Instant; + +mod client; +pub mod compiler; +pub mod error; + +include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); + +pub struct EreAirbender { + sdk: AirbenderSdk, +} + +impl EreAirbender { + pub fn new(bin: AirbenderProgram, resource: ProverResourceType) -> Self { + let gpu = matches!(resource, ProverResourceType::Gpu); + let sdk = AirbenderSdk::new(&bin, gpu); + Self { sdk } + } +} + +impl zkVM for EreAirbender { + fn execute(&self, input: &[u8]) -> Result<(PublicValues, ProgramExecutionReport), zkVMError> { + let start = Instant::now(); + let (public_values, cycles) = self.sdk.execute(input)?; + let execution_duration = start.elapsed(); + + Ok(( + public_values, + ProgramExecutionReport { + total_num_cycles: cycles, + execution_duration, + ..Default::default() + }, + )) + } + + fn prove( + &self, + input: &[u8], + proof_kind: ProofKind, + ) -> Result<(PublicValues, Proof, ProgramProvingReport), zkVMError> { + if proof_kind != ProofKind::Compressed { + panic!("Only Compressed proof kind is supported."); + } + + let start = Instant::now(); + let (public_values, proof) = self.sdk.prove(input)?; + let proving_time = start.elapsed(); + + let proof_bytes = bincode::serde::encode_to_vec(&proof, bincode::config::legacy()) + .map_err(AirbenderError::BincodeEncode)?; + + Ok(( + public_values, + Proof::Compressed(proof_bytes), + ProgramProvingReport::new(proving_time), + )) + } + + fn verify(&self, proof: &Proof) -> Result { + let Proof::Compressed(proof) = proof else { + return Err(zkVMError::other("Only Compressed proof kind is supported.")); + }; + + let (proof, _): (ProgramProof, _) = + bincode::serde::decode_from_slice(proof, bincode::config::legacy()) + .map_err(AirbenderError::BincodeDecode)?; + + let public_values = self.sdk.verify(&proof)?; + + Ok(public_values) + } + + fn name(&self) -> &'static str { + NAME + } + + fn sdk_version(&self) -> &'static str { + SDK_VERSION + } +} + +#[cfg(test)] +mod tests { + use crate::{ + EreAirbender, + compiler::{AirbenderProgram, RustRv32ima}, + }; + use ere_test_utils::{ + host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory}, + program::basic::BasicProgramInput, + }; + use ere_zkvm_interface::{Compiler, ProofKind, ProverResourceType, zkVM}; + use std::sync::OnceLock; + + fn basic_program() -> AirbenderProgram { + static PROGRAM: OnceLock = OnceLock::new(); + PROGRAM + .get_or_init(|| { + RustRv32ima + .compile(&testing_guest_directory("airbender", "basic")) + .unwrap() + }) + .clone() + } + + #[test] + fn test_execute() { + let program = basic_program(); + let zkvm = EreAirbender::new(program, ProverResourceType::Cpu); + + let test_case = BasicProgramInput::valid().into_output_sha256(); + run_zkvm_execute(&zkvm, &test_case); + } + + #[test] + fn test_execute_invalid_input() { + let program = basic_program(); + let zkvm = EreAirbender::new(program, ProverResourceType::Cpu); + + for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { + zkvm.execute(&input).unwrap_err(); + } + } + + #[test] + fn test_prove() { + let program = basic_program(); + let zkvm = EreAirbender::new(program, ProverResourceType::Cpu); + + let test_case = BasicProgramInput::valid().into_output_sha256(); + run_zkvm_execute(&zkvm, &test_case); + run_zkvm_prove(&zkvm, &test_case); + } + + #[test] + fn test_prove_invalid_input() { + let program = basic_program(); + let zkvm = EreAirbender::new(program, ProverResourceType::Cpu); + + for input in [Vec::new(), BasicProgramInput::invalid().serialized_input()] { + zkvm.prove(&input, ProofKind::default()).unwrap_err(); + } + } +} diff --git a/docker/airbender/Dockerfile.base b/docker/airbender/Dockerfile.base new file mode 100644 index 0000000..bfd1924 --- /dev/null +++ b/docker/airbender/Dockerfile.base @@ -0,0 +1,35 @@ +ARG BASE_IMAGE=ere-base:latest +ARG BASE_CUDA_IMAGE=ere-base:latest-cuda + +# Whether to enable CUDA feature or not. +ARG CUDA + +FROM $BASE_IMAGE AS base +FROM $BASE_CUDA_IMAGE AS base_cuda +FROM base${CUDA:+_cuda} + +# Set default toolchain to nightly +RUN rustup default nightly + +ARG CUDA + +# Default to build for RTX 50 series +ARG CUDA_ARCH=sm_120 + +# Env read by Airbender crate `gpu_prover`, only taking the numeric part +ARG CUDAARCHS=${CUDA_ARCH#sm_} + +# Copy the Airbender SDK installer script from the workspace context +COPY --chmod=755 scripts/sdk_installers/install_airbender_sdk.sh /tmp/install_airbender_sdk.sh + +# Run the Airbender SDK installation script. +# This script installs airbender-cli. +RUN /tmp/install_airbender_sdk.sh && rm /tmp/install_airbender_sdk.sh + +# Verify airbender-cli is accessible with the correct toolchain +RUN airbender-cli --version + +# Add `rust-src` component to enable std build for nightly rust. +RUN rustup component add rust-src + +CMD ["/bin/bash"] diff --git a/docker/airbender/Dockerfile.compiler b/docker/airbender/Dockerfile.compiler new file mode 100644 index 0000000..506e8d9 --- /dev/null +++ b/docker/airbender/Dockerfile.compiler @@ -0,0 +1,34 @@ +ARG BASE_ZKVM_IMAGE=ere-base-airbender:latest +ARG RUNTIME_IMAGE=ubuntu:24.04 + +FROM $BASE_ZKVM_IMAGE AS build_stage + +COPY . /ere + +WORKDIR /ere + +RUN cargo build --release --package ere-compiler --bin ere-compiler --features airbender \ + && mkdir bin && mv target/release/ere-compiler bin/ere-compiler \ + && cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache + +FROM $RUNTIME_IMAGE AS runtime_stage + +# Install common dependencies and build tools +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Copy Rust +COPY --from=build_stage /usr/local/cargo /usr/local/cargo +COPY --from=build_stage /usr/local/rustup /usr/local/rustup + +# Add Rust to path +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH + +# Copy ere-compiler +COPY --from=build_stage /ere/bin/ere-compiler /ere/bin/ere-compiler + +ENTRYPOINT ["/ere/bin/ere-compiler"] diff --git a/docker/airbender/Dockerfile.server b/docker/airbender/Dockerfile.server new file mode 100644 index 0000000..d0f08d0 --- /dev/null +++ b/docker/airbender/Dockerfile.server @@ -0,0 +1,37 @@ +ARG BASE_ZKVM_IMAGE=ere-base-airbender:latest +ARG BASE_ZKVM_CUDA_IMAGE=ere-base-airbender:latest-cuda +ARG RUNTIME_IMAGE=ubuntu:24.04 +ARG RUNTIME_CUDA_IMAGE=nvidia/cuda:12.9.1-runtime-ubuntu24.04 + +# Whether to enable CUDA feature or not. +ARG CUDA + +FROM $BASE_ZKVM_IMAGE AS base +FROM $BASE_ZKVM_CUDA_IMAGE AS base_cuda +FROM base${CUDA:+_cuda} AS build_stage + +COPY . /ere + +WORKDIR /ere + +ARG CUDA +ARG RUSTFLAGS="-Ctarget-cpu=native" + +RUN cargo build --release --package ere-server --bin ere-server --features airbender${CUDA:+,cuda} \ + && mkdir bin && mv target/release/ere-server bin/ere-server \ + && cargo clean && rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache + +FROM $RUNTIME_IMAGE AS runtime +FROM $RUNTIME_CUDA_IMAGE AS runtime_cuda +FROM runtime${CUDA:+_cuda} AS runtime_stage + +# Copy airbender-cli +COPY --from=build_stage /usr/local/cargo/bin/airbender-cli /usr/local/cargo/bin/airbender-cli + +# Add airbender-cli to path +ENV PATH=/usr/local/cargo/bin:$PATH + +# Copy ere-server +COPY --from=build_stage /ere/bin/ere-server /ere/bin/ere-server + +ENTRYPOINT ["/ere/bin/ere-server"] diff --git a/docker/nexus/Dockerfile.base b/docker/nexus/Dockerfile.base index 21deb13..d96217b 100644 --- a/docker/nexus/Dockerfile.base +++ b/docker/nexus/Dockerfile.base @@ -17,7 +17,7 @@ COPY --chmod=755 scripts/sdk_installers/install_nexus_sdk.sh /tmp/install_nexus_ # Run the Nexus SDK installation script. # This script installs the specific Rust toolchain (nightly-2025-04-06) # and installs cargo-nexus -# The CARGO_HOME from ere-base (e.g., /root/.cargo) will be used, and cargo-nexus will be in its bin. +# The CARGO_HOME from ere-base (e.g., /usr/local/cargo) will be used, and cargo-nexus will be in its bin. RUN /tmp/install_nexus_sdk.sh && rm /tmp/install_nexus_sdk.sh # Clean up the script # Verify Nexus installation diff --git a/docker/pico/Dockerfile.base b/docker/pico/Dockerfile.base index 5aed1d8..dce466c 100644 --- a/docker/pico/Dockerfile.base +++ b/docker/pico/Dockerfile.base @@ -17,7 +17,7 @@ COPY --chmod=755 scripts/sdk_installers/install_pico_sdk.sh /tmp/install_pico_sd # Run the Pico SDK installation script. # This script installs the specific Rust toolchain (nightly-2025-08-04) # and installs pico-cli (as cargo-pico). -# The CARGO_HOME from ere-base (e.g., /root/.cargo) will be used, and cargo-pico will be in its bin. +# The CARGO_HOME from ere-base (e.g., /usr/local/cargo) will be used, and cargo-pico will be in its bin. RUN /tmp/install_pico_sdk.sh && rm /tmp/install_pico_sdk.sh # Clean up the script # Verify Pico installation diff --git a/scripts/sdk_installers/install_airbender_sdk.sh b/scripts/sdk_installers/install_airbender_sdk.sh new file mode 100755 index 0000000..31d2000 --- /dev/null +++ b/scripts/sdk_installers/install_airbender_sdk.sh @@ -0,0 +1,51 @@ +#!/bin/bash +set -e + +# --- Utility functions (duplicated) --- +# Checks if a tool is installed and available in PATH. +is_tool_installed() { + command -v "$1" &> /dev/null +} + +# Ensures a tool is installed. Exits with an error if not. +ensure_tool_installed() { + local tool_name="$1" + local purpose_message="$2" + if ! is_tool_installed "${tool_name}"; then + echo "Error: Required tool '${tool_name}' could not be found." >&2 + if [ -n "${purpose_message}" ]; then + echo " It is needed ${purpose_message}." >&2 + fi + echo " Please install it first and ensure it is in your PATH." >&2 + exit 1 + fi +} +# --- End of Utility functions --- + +ensure_tool_installed "rustup" "to manage Rust toolchains" +ensure_tool_installed "git" "to install airbender-cli from a git repository" +ensure_tool_installed "cargo" "to build and install Rust packages" + +AIRBENDER_CLI_VERSION_TAG="v0.5.0" + +# Install airbender-cli using the specified toolchain and version tag +echo "Installing airbender-cli (version ${AIRBENDER_CLI_VERSION_TAG}) from GitHub repository (matter-labs/zksync-airbender)..." +cargo +nightly install --locked --git https://github.com/matter-labs/zksync-airbender.git --tag "${AIRBENDER_CLI_VERSION_TAG}" ${CUDA:+-F gpu} cli + +# Rename cli to airbender-cli +CARGO_HOME=${CARGO_HOME:-$HOME/.cargo} +mv $CARGO_HOME/bin/cli $CARGO_HOME/bin/airbender-cli + +# Verify airbender-cli installation +echo "Verifying airbender-cli installation..." +if airbender-cli --version; then + echo "airbender-cli installation verified successfully." +else + echo "Error: 'airbender-cli --version' failed. airbender-cli might not have installed correctly." >&2 + echo " Ensure $CARGO_HOME/bin is in your PATH for new shells." >&2 + exit 1 +fi + +# Install cargo-binutils to objcopy ELF to binary file +rustup +nightly component add llvm-tools +cargo install cargo-binutils diff --git a/tests/airbender/basic/Cargo.toml b/tests/airbender/basic/Cargo.toml new file mode 100644 index 0000000..1862105 --- /dev/null +++ b/tests/airbender/basic/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ere-test-airbender-guest" +version = "0.1.0" +edition = "2021" + +[dependencies] +riscv_common = { git = "https://github.com/matter-labs/zksync-airbender", features = ["custom_allocator"], tag = "v0.5.0" } +ere-test-utils = { path = "../../../crates/test-utils" } + +[workspace] diff --git a/tests/airbender/basic/src/airbender_rt.rs b/tests/airbender/basic/src/airbender_rt.rs new file mode 100644 index 0000000..54217db --- /dev/null +++ b/tests/airbender/basic/src/airbender_rt.rs @@ -0,0 +1,60 @@ +use core::alloc::{GlobalAlloc, Layout}; + +core::arch::global_asm!(include_str!("./asm_reduced.S")); + +#[link_section = ".init.rust"] +#[export_name = "_start_rust"] +unsafe extern "C" fn start_rust() -> ! { + crate::main(); + unsafe { core::hint::unreachable_unchecked() } +} + +/// A simple heap allocator. +/// +/// Allocates memory from left to right, without any deallocation. +struct SimpleAlloc; + +unsafe impl GlobalAlloc for SimpleAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { sys_alloc_aligned(layout.size(), layout.align()) } + } + + unsafe fn dealloc(&self, _: *mut u8, _: Layout) {} +} + +#[global_allocator] +static HEAP: SimpleAlloc = SimpleAlloc; + +static mut HEAP_POS: usize = 0; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 { + unsafe extern "C" { + // start/end of heap defined in `link.x`. + unsafe static _sheap: u8; + unsafe static _eheap: u8; + } + + // SAFETY: Single threaded, so nothing else can touch this while we're working. + let mut heap_pos = unsafe { HEAP_POS }; + + if heap_pos == 0 { + heap_pos = unsafe { (&_sheap) as *const u8 as usize }; + } + + let offset = heap_pos & (align - 1); + if offset != 0 { + heap_pos += align - offset; + } + + let ptr = heap_pos as *mut u8; + let (heap_pos, overflowed) = heap_pos.overflowing_add(bytes); + + let eheap = unsafe { (&_eheap) as *const u8 as usize }; + if overflowed || heap_pos > eheap { + panic!("Heap exhausted"); + } + + unsafe { HEAP_POS = heap_pos }; + ptr +} diff --git a/tests/airbender/basic/src/asm_reduced.S b/tests/airbender/basic/src/asm_reduced.S new file mode 100644 index 0000000..6a76482 --- /dev/null +++ b/tests/airbender/basic/src/asm_reduced.S @@ -0,0 +1,160 @@ +/* Copied from https://github.com/matter-labs/zksync-airbender/blob/v0.5.0/examples/scripts/asm/asm_reduced.S */ + +/* + Entry point of all programs (_start). + + It initializes DWARF call frame information, the stack pointer, the + frame pointer (needed for closures to work in start_rust) and the global + pointer. Then it calls _start_rust. +*/ + +.section .init, "ax" +.global _start + +_start: + /* Jump to the absolute address defined by the linker script. */ + // for 32bit + # lui ra, %hi(_abs_start) + # jr %lo(_abs_start)(ra) + + la ra, _abs_start + jr ra + +_abs_start: + .cfi_startproc + .cfi_undefined ra + + .option push + .option norelax + la gp, __global_pointer$ + .option pop + + // Assume single core, and put SP to the very top address of the stack region + la sp, _sstack + + // Set frame pointer + add s0, sp, zero + + jal zero, _start_rust + + .cfi_endproc + +/* + Machine trap entry point (_machine_start_trap) +*/ +.section .trap, "ax" +.global machine_default_start_trap +.align 4 +machine_default_start_trap: + // We assume that exception stack is always saved to MSCRATCH + + // so we swap it with x31 + csrrw x31, mscratch, x31 + + // write to exception stack + # sw x31, -4(sp) + sw x30, -8(x31) + sw x29, -12(x31) + sw x28, -16(x31) + sw x27, -20(x31) + sw x26, -24(x31) + sw x25, -28(x31) + sw x24, -32(x31) + sw x23, -36(x31) + sw x22, -40(x31) + sw x21, -44(x31) + sw x20, -48(x31) + sw x19, -52(x31) + sw x18, -56(x31) + sw x17, -60(x31) + sw x16, -64(x31) + sw x15, -68(x31) + sw x14, -72(x31) + sw x13, -76(x31) + sw x12, -80(x31) + sw x11, -84(x31) + sw x10, -88(x31) + sw x9, -92(x31) + sw x8, -96(x31) + sw x7, -100(x31) + sw x6, -104(x31) + sw x5, -108(x31) + sw x4, -112(x31) + sw x3, -116(x31) + sw x2, -120(x31) + sw x1, -124(x31) + + // we will not restore it, so we are ok to avoid write + # sw x0, -128(x31) + + // move valid sp into a0, + mv a0, x31 + csrrw x31, mscratch, x0 + sw x31, -4(a0) + // restore sp + mv sp, a0 + // sp is valid now + + addi sp, sp, -128 + // pass pointer as first argument + add a0, sp, zero + + jal ra, _machine_start_trap_rust + + // set return address into mepc + csrw mepc, a0 + + // save original SP to mscratch for now + lw a0, 8(sp) // it's original sp that we saved in the stack + csrw mscratch, a0 // save it for now + + // restore everything we saved + + // it's illegal instruction, so we skip. Anyway can not overwrite x0 + # lw x0, 0(sp) + + lw x1, 4(sp) + # lw x2, 8(sp) // do not overwrite SP yet + lw x3, 12(sp) + lw x4, 16(sp) + lw x5, 20(sp) + lw x6, 24(sp) + lw x7, 28(sp) + lw x8, 32(sp) + lw x9, 36(sp) + lw x10, 40(sp) + lw x11, 44(sp) + lw x12, 48(sp) + lw x13, 52(sp) + lw x14, 56(sp) + lw x15, 60(sp) + lw x16, 64(sp) + lw x17, 68(sp) + lw x18, 72(sp) + lw x19, 76(sp) + lw x20, 80(sp) + lw x21, 84(sp) + lw x22, 88(sp) + lw x23, 92(sp) + lw x24, 96(sp) + lw x25, 100(sp) + lw x26, 104(sp) + lw x27, 108(sp) + lw x28, 112(sp) + lw x29, 116(sp) + lw x30, 120(sp) + lw x31, 124(sp) + + addi sp, sp, 128 + // we popped everything from the stack + // now save current exception SP to mscratch, + // and put original SP back + csrrw sp, mscratch, sp + + mret + +/* Make sure there is an abort when linking */ +.section .text.abort +.globl abort +abort: + j abort diff --git a/tests/airbender/basic/src/main.rs b/tests/airbender/basic/src/main.rs new file mode 100644 index 0000000..0425e4b --- /dev/null +++ b/tests/airbender/basic/src/main.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] +#![no_builtins] +#![allow(incomplete_features)] +#![feature(allocator_api)] +#![feature(generic_const_exprs)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::{array, iter::repeat_with}; +use ere_test_utils::{ + guest::{Digest, Platform, Sha256}, + program::{basic::BasicProgram, Program}, +}; +use riscv_common::{csr_read_word, zksync_os_finish_success}; + +mod airbender_rt; + +struct AirbenderPlatform; + +impl Platform for AirbenderPlatform { + fn read_input() -> Vec { + let len = csr_read_word() as usize; + repeat_with(csr_read_word) + .take(len.div_ceil(4)) + .flat_map(|word| word.to_le_bytes()) + .take(len) + .collect() + } + + fn write_output(output: &[u8]) { + let digest = Sha256::digest(output); + let words = array::from_fn(|i| u32::from_le_bytes(array::from_fn(|j| digest[4 * i + j]))); + zksync_os_finish_success(&words); + } +} + +#[inline(never)] +fn main() { + BasicProgram::run::(); +}