diff --git a/.gitignore b/.gitignore index 1e94de3ce5..3cc4697d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ ### monad ignore build +build-zkvm perf.data* scratch diff --git a/CMakeLists.txt b/CMakeLists.txt index d5e7a64b4c..03f3e62063 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,69 +62,24 @@ include(cmake/test.cmake) set(THIRD_PARTY_DIR "${PROJECT_SOURCE_DIR}/third_party") -function(monad_compile_options target) - set_property(TARGET ${target} PROPERTY C_STANDARD 23) - set_property(TARGET ${target} PROPERTY C_STANDARD_REQUIRED ON) - set_property(TARGET ${target} PROPERTY CXX_STANDARD 23) - set_property(TARGET ${target} PROPERTY CXX_STANDARD_REQUIRED ON) - - target_compile_options(${target} PRIVATE -Wall -Wextra -Wconversion -Werror) - target_compile_definitions(${target} PUBLIC "_GNU_SOURCE") - - target_compile_options( - ${target} PRIVATE $<$:-Wno-missing-field-initializers>) - - target_compile_options(${target} PRIVATE $<$:-Og>) - - target_compile_definitions(${target} PUBLIC QUILL_ROOT_LOGGER_ONLY) - - if(MONAD_COMPILER_TESTING) - target_compile_definitions(${target} PUBLIC "MONAD_COMPILER_TESTING=1") - target_compile_definitions(${target} PUBLIC "MONAD_CORE_FORCE_DEBUG_ASSERT=1") - endif() - - if(MONAD_COMPILER_STATS) - target_compile_definitions(${target} PUBLIC "MONAD_COMPILER_STATS=1") - endif() - - if(MONAD_COMPILER_HOT_PATH_STATS) - target_compile_definitions(${target} PUBLIC "MONAD_COMPILER_HOT_PATH_STATS=1") - endif() - - target_compile_options( - ${target} - PUBLIC $<$:-Wno-attributes=clang::no_sanitize>) - - # this is needed to turn off ranges support in nlohmann_json, because the - # ranges standard header triggers a clang bug which is fixed in trunk but not - # currently available to us - # https://gcc.gnu.org/bugzilla//show_bug.cgi?id=109647 - target_compile_definitions(${target} PUBLIC "JSON_HAS_RANGES=0") -endfunction() +include(cmake/compile_options.cmake) find_package(Boost REQUIRED COMPONENTS context filesystem json CONFIG) find_package(PkgConfig REQUIRED) pkg_check_modules(brotli REQUIRED IMPORTED_TARGET libbrotlienc libbrotlidec) -pkg_check_modules(crypto++ REQUIRED IMPORTED_TARGET libcrypto++) pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd) # ankerl add_library(ankerl_hash INTERFACE) target_include_directories(ankerl_hash INTERFACE "third_party/ankerl") -# asmjit -set(ASMJIT_STATIC ON) -add_subdirectory(third_party/asmjit) - # BLAKE3 add_subdirectory(third_party/BLAKE3/c) -# blst +# Precompile deps: blst, silkpre, c-kzg-4844, cryptopp, immer, +# nlohmann_json, unordered_dense, asmjit set(DOWNLOAD_BLST OFF) -include(cmake/blst.cmake) - -# c-kzg-4844 -add_subdirectory("third_party/c-kzg-4844-builder") +include(cmake/precompile_deps.cmake) # cli11 find_package(CLI11 REQUIRED) @@ -139,11 +94,20 @@ add_subdirectory("third_party/cthash") set(ETHASH_TESTING NO) add_subdirectory("third_party/ethash") -# fiber (boost) -# Boost.Fiber's CMakeLists.txt expects granular Boost targets from the -# superproject build. The system-installed Boost only provides Boost::headers -# for header-only libs. Create shim targets so fiber can link them. -foreach(_boost_hdr_lib assert config core intrusive predef smart_ptr algorithm format) +# fiber (boost) Boost.Fiber's CMakeLists.txt expects granular Boost targets from +# the superproject build. The system-installed Boost only provides +# Boost::headers for header-only libs. Create shim targets so fiber can link +# them. +foreach( + _boost_hdr_lib + assert + config + core + intrusive + predef + smart_ptr + algorithm + format) if(NOT TARGET Boost::${_boost_hdr_lib}) add_library(boost_${_boost_hdr_lib} INTERFACE) target_link_libraries(boost_${_boost_hdr_lib} INTERFACE Boost::headers) @@ -152,13 +116,8 @@ foreach(_boost_hdr_lib assert config core intrusive predef smart_ptr algorithm f endforeach() add_subdirectory("third_party/fiber" SYSTEM) # Boost.Fiber triggers warnings under C++23 strict flags. -target_compile_options(boost_fiber PRIVATE -Wno-deprecated-declarations -Wno-conversion) - -# immer -option(immer_BUILD_TESTS OFF) -option(immer_BUILD_EXAMPLES OFF) -option(immer_BUILD_EXTRAS OFF) -add_subdirectory("third_party/immer" SYSTEM) +target_compile_options(boost_fiber PRIVATE -Wno-deprecated-declarations + -Wno-conversion) # intx add_subdirectory("third_party/intx") @@ -168,7 +127,7 @@ set(HUNTER_ENABLED OFF) add_subdirectory("third_party/evmc") # evmone -if (MONAD_COMPILER_TESTING OR MONAD_COMPILER_BENCHMARKS) +if(MONAD_COMPILER_TESTING OR MONAD_COMPILER_BENCHMARKS) include(cmake/evmone.cmake) endif() @@ -182,9 +141,6 @@ add_subdirectory("third_party/magic_enum") # nanobench add_subdirectory("third_party/nanobench") -# nlohmann_json -add_subdirectory("third_party/nlohmann_json" SYSTEM) - # quill add_subdirectory("third_party/quill") # TODO @@ -195,22 +151,9 @@ target_compile_options( target_compile_options( quill PUBLIC $<$:-Wno-tautological-compare>) -# silkpre -set(OPTIONAL_BUILD_TESTS OFF) -set(OLD_CMAKE_POLICY_VERSION_MINIMUM "${CMAKE_POLICY_VERSION_MINIMUM}") -set(CMAKE_POLICY_VERSION_MINIMUM "3.5") -add_subdirectory(third_party/silkpre) -set(CMAKE_POLICY_VERSION_MINIMUM "${OLD_CMAKE_POLICY_VERSION_MINIMUM}") -# undo the injection of ccache silkpre/ff does -set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE) -set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK) - # tbb find_package(TBB REQUIRED) -# unordered_dense -add_subdirectory("third_party/unordered_dense") - # ############################################################################## # unit tests # ############################################################################## @@ -240,8 +183,11 @@ function(monad_add_test target) target_link_libraries(${target} monad_execution GTest::GTest GTest::Main) gtest_discover_tests( ${target} DISCOVERY_MODE PRE_TEST - PROPERTIES ENVIRONMENT ASAN_OPTIONS=abort_on_error=1 ENVIRONMENT - UBSAN_OPTIONS=halt_on_error=1,print_stacktrace=1 ENVIRONMENT + PROPERTIES ENVIRONMENT + ASAN_OPTIONS=abort_on_error=1 + ENVIRONMENT + UBSAN_OPTIONS=halt_on_error=1,print_stacktrace=1 + ENVIRONMENT TSAN_OPTIONS=external_symbolizer_path=/usr/bin/llvm-symbolizer ${AT_PROPERTIES}) endfunction() @@ -307,6 +253,7 @@ target_link_libraries( monad_trie monad_execution_ethereum monad_execution_native + monad_precompiles monad_rpc monad_statesync monad-vm diff --git a/README.md b/README.md index aab6b3a46d..6585aef55e 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,23 @@ You can also run the full test suite in parallel with: CTEST_PARALLEL_LEVEL=$(nproc) ctest ``` +## Compiling zkVM binary + +To compile monad as a guest program for various zkVMs, such as ZisK or SP1, we need to use a riscv64 cross-compiler. The cmake build extracts only the needed libc objects (setjmp/longjmp) from the unmodified newlib; malloc and syscalls are weakly linked by the zkVM frameworks. + +```shell +cmake -B build-zkvm -S category/zkvm \ + -DCMAKE_TOOLCHAIN_FILE=category/core/toolchains/riscv64-elf-toolchain.cmake \ + -DRISCV_TOOLCHAIN_DIR=PATH-TO-RISCV-COMPILER \ + -DCMAKE_BUILD_TYPE=Release -GNinja +``` + +We can then build the static library: + +```shell +cmake --build build-zkvm --target monad-zkvm +``` + ## A tour of execution To understand how the source code is organized, you should start by reading diff --git a/category/core/runtime/non_temporal_memory.hpp b/category/core/runtime/non_temporal_memory.hpp index 6064127aa6..de0584c37d 100644 --- a/category/core/runtime/non_temporal_memory.hpp +++ b/category/core/runtime/non_temporal_memory.hpp @@ -17,15 +17,22 @@ #include -#include - #include #include +#ifdef MONAD_ZKVM + #include +#else + #include +#endif + namespace monad::vm::runtime { inline void non_temporal_bzero(void *dest, size_t n) { +#ifdef MONAD_ZKVM + std::memset(dest, 0, n); +#else MONAD_ASSERT((reinterpret_cast(dest) & 31) == 0); MONAD_ASSERT((n & 31) == 0); auto *d = static_cast(dest); @@ -35,10 +42,14 @@ namespace monad::vm::runtime _mm256_stream_si256(reinterpret_cast<__m256i *>(d), zero); d += 32; } +#endif } inline void non_temporal_memcpy(void *dest, void *src, size_t n) { +#ifdef MONAD_ZKVM + std::memcpy(dest, src, n); +#else MONAD_ASSERT((reinterpret_cast(dest) & 31) == 0); MONAD_ASSERT((reinterpret_cast(src) & 31) == 0); MONAD_ASSERT((n & 31) == 0); @@ -52,5 +63,6 @@ namespace monad::vm::runtime d += 32; s += 32; } +#endif } } diff --git a/category/core/toolchains/riscv64-elf-toolchain.cmake b/category/core/toolchains/riscv64-elf-toolchain.cmake new file mode 100755 index 0000000000..64ac6f633e --- /dev/null +++ b/category/core/toolchains/riscv64-elf-toolchain.cmake @@ -0,0 +1,45 @@ +# Copyright (C) 2025 Category Labs, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR riscv64) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +# Configurable toolchain directory — pass -DRISCV_TOOLCHAIN_DIR= to cmake. +set(RISCV_TOOLCHAIN_DIR "/opt/riscv" CACHE PATH "RISC-V toolchain directory") +list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES RISCV_TOOLCHAIN_DIR) + +# Auto-detect target prefix (riscv64-none-elf- for nix, riscv64-unknown-elf- for ZisK). +if(EXISTS "${RISCV_TOOLCHAIN_DIR}/bin/riscv64-none-elf-gcc") + set(RISCV_PREFIX "riscv64-none-elf-") +elseif(EXISTS "${RISCV_TOOLCHAIN_DIR}/bin/riscv64-unknown-elf-gcc") + set(RISCV_PREFIX "riscv64-unknown-elf-") +else() + message(FATAL_ERROR "No riscv64 gcc found in ${RISCV_TOOLCHAIN_DIR}/bin/") +endif() + +set(CMAKE_C_COMPILER "${RISCV_TOOLCHAIN_DIR}/bin/${RISCV_PREFIX}gcc") +set(CMAKE_CXX_COMPILER "${RISCV_TOOLCHAIN_DIR}/bin/${RISCV_PREFIX}g++") +set(CMAKE_AR "${RISCV_TOOLCHAIN_DIR}/bin/${RISCV_PREFIX}ar") + +set(CMAKE_C_FLAGS_INIT + "-march=rv64ima -mabi=lp64 -mcmodel=medany -nostartfiles -nostdlib -ffunction-sections -fdata-sections") +set(CMAKE_CXX_FLAGS_INIT + "-march=rv64ima -mabi=lp64 -mcmodel=medany -nostartfiles -nostdlib++ -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections") +set(CMAKE_ASM_FLAGS_INIT "-march=rv64ima -mabi=lp64") + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/category/execution/CMakeLists.txt b/category/execution/CMakeLists.txt index 15fd6b52c1..8fd19f41bd 100644 --- a/category/execution/CMakeLists.txt +++ b/category/execution/CMakeLists.txt @@ -142,12 +142,6 @@ add_library( "ethereum/fmt/event_trace_fmt.hpp" "ethereum/process_requests.cpp" "ethereum/process_requests.hpp" - "ethereum/precompiles.cpp" - "ethereum/precompiles.hpp" - "ethereum/precompiles_bls12.cpp" - "ethereum/precompiles_bls12.hpp" - "ethereum/precompiles_impl.cpp" - "ethereum/precompiles_gas_cost_impl.cpp" "ethereum/reserve_balance.cpp" "ethereum/reserve_balance.hpp" "ethereum/trace/call_frame.cpp" @@ -276,6 +270,21 @@ add_library( # monad/state2 "monad/state2/proposal_state.hpp") +# ############################################################################## +# Precompiles — extracted into a standalone OBJECT library so that the zkvm +# build can reuse it without pulling in the full execution dependency tree. +# ############################################################################## + +add_library( + monad_precompiles OBJECT + "ethereum/precompiles.cpp" + "ethereum/precompiles.hpp" + "ethereum/precompiles_gas_cost_impl.cpp" + "ethereum/precompiles_impl.cpp" + "ethereum/precompiles_bls12.cpp" + "ethereum/precompiles_bls12.hpp" +) + target_include_directories( monad_execution_ethereum PUBLIC ${CATEGORY_MAIN_DIR} @@ -298,6 +307,27 @@ target_compile_definitions( monad_execution_native PUBLIC MONAD_CXX_CTYPES_USE_EVMC_HPP MONAD_CXX_CTYPES_USE_INTX) +target_include_directories( + monad_precompiles + PUBLIC ${CATEGORY_MAIN_DIR} + PUBLIC ${silkpre_SOURCE_DIR}/lib) + +target_link_libraries( + monad_precompiles + PUBLIC monad-vm + PUBLIC monad_core + PUBLIC blst::blst + PUBLIC c-kzg-4844 + PRIVATE PkgConfig::crypto++ + PUBLIC ethash::keccak + PUBLIC evmc + PUBLIC immer + PUBLIC intx::intx + PUBLIC nlohmann_json::nlohmann_json + PUBLIC silkpre + PUBLIC unordered_dense) +monad_compile_options(monad_precompiles) + target_link_libraries( monad_execution_ethereum PUBLIC monad-vm @@ -306,9 +336,7 @@ target_link_libraries( PUBLIC blst::blst PRIVATE Boost::fiber PRIVATE Boost::json - PUBLIC c-kzg-4844 PRIVATE PkgConfig::brotli - PRIVATE PkgConfig::crypto++ PUBLIC ethash::keccak PUBLIC evmc PUBLIC immer diff --git a/category/execution/ethereum/core/contract/big_endian.hpp b/category/execution/ethereum/core/contract/big_endian.hpp index 9f606015de..978a8fae59 100644 --- a/category/execution/ethereum/core/contract/big_endian.hpp +++ b/category/execution/ethereum/core/contract/big_endian.hpp @@ -59,6 +59,14 @@ struct BigEndian unaligned_store(bytes, be); return *this; } + + [[gnu::always_inline]] static inline BigEndian + unsafe_from(uint8_t const *src) noexcept + { + BigEndian be; + std::memcpy(&be.bytes, src, sizeof(T)); + return be; + } }; using u8_be = BigEndian; diff --git a/category/execution/ethereum/precompiles.cpp b/category/execution/ethereum/precompiles.cpp index b4acb76f14..047b2d5755 100644 --- a/category/execution/ethereum/precompiles.cpp +++ b/category/execution/ethereum/precompiles.cpp @@ -19,14 +19,14 @@ #include #include #include -#include #include -#include #include #include #include +#include + #include #include #include @@ -190,4 +190,242 @@ check_call_precompile(State &, CallTracerBase &, evmc_message const &msg) EXPLICIT_EVM_TRAITS(check_call_precompile); +static void +right_pad(std::basic_string &str, size_t const min_size) noexcept +{ + if (str.length() < min_size) { + str.resize(min_size, '\0'); + } +} + +PrecompileResult from_impl_result(PrecompileImplResult result) +{ + auto const [data, size] = result; + if (data == nullptr) { + MONAD_DEBUG_ASSERT(size == 0); + return PrecompileResult::failure(); + } + return {EVMC_SUCCESS, data, size}; +} + +PrecompileResult ecrecover_execute(byte_string_view const input) +{ + using namespace intx; + + static constexpr auto kSecp256k1n = + 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141_u256; + + uint8_t *out{static_cast(std::malloc(32))}; + + std::basic_string d(input.data(), input.size()); + right_pad(d, 128); + auto const v{intx::be::unsafe::load(&d[32])}; + auto const r{intx::be::unsafe::load(&d[64])}; + auto const s{intx::be::unsafe::load(&d[96])}; + + if (!r || !s || r >= kSecp256k1n || s >= kSecp256k1n) { + return {EVMC_SUCCESS, out, 0}; + } + + if (v != 27 && v != 28) { + return {EVMC_SUCCESS, out, 0}; + } + + return from_impl_result(ecrecover_impl( + std::span{&d[0], 32}, + std::span{&d[64], 64}, + v != 27, + std::span{out, 32})); +} + +PrecompileResult sha256_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(32)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + sha256_impl(input, std::span{out, 32})); +} + +PrecompileResult ripemd160_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(32)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + ripemd160_impl(input, std::span{out, 32})); +} + +static void right_pad_copy( + uint8_t *dst, size_t dst_size, uint8_t const *src, size_t src_size, + size_t offset) noexcept +{ + std::memset(dst, 0, dst_size); + if (offset < src_size) { + auto const avail = src_size - offset; + auto const to_copy = std::min(avail, dst_size); + std::memcpy(dst, src + offset, to_copy); + } +} + +PrecompileResult expmod_execute(byte_string_view const input) +{ + uint8_t lengths[96]; + right_pad_copy(lengths, 96, input.data(), input.size(), 0); + + uint64_t const mod_len = intx::be::unsafe::load(&lengths[88]); + + if (mod_len == 0) { + return {EVMC_SUCCESS, nullptr, 0}; + } + + auto *const out = static_cast(std::malloc(mod_len)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + expmod_impl(input, std::span{out, mod_len})); +} + +PrecompileResult ecadd_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(64)); + MONAD_ASSERT(out != nullptr); + auto const clamped_input = input.substr(0, 128); + auto const result = + ecadd_impl(clamped_input, std::span{out, 64}); + if (result.data == nullptr) { + std::free(out); + } + return from_impl_result(result); +} + +PrecompileResult ecmul_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(64)); + MONAD_ASSERT(out != nullptr); + auto const clamped_input = input.substr(0, 96); + auto const result = + ecmul_impl(clamped_input, std::span{out, 64}); + if (result.data == nullptr) { + std::free(out); + } + return from_impl_result(result); +} + +PrecompileResult snarkv_execute(byte_string_view const input) +{ + if (input.size() % 192 != 0) { + return PrecompileResult::failure(); + } + + auto *const out = static_cast(std::malloc(32)); + MONAD_ASSERT(out != nullptr); + auto const result = snarkv_impl(input, std::span{out, 32}); + if (result.data == nullptr) { + std::free(out); + } + return from_impl_result(result); +} + +PrecompileResult blake2bf_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(64)); + MONAD_ASSERT(out != nullptr); + auto const result = blake2bf_impl(input, std::span{out, 64}); + if (result.data == nullptr) { + std::free(out); + } + return from_impl_result(result); +} + +PrecompileResult point_evaluation_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(64)); + MONAD_ASSERT(out != nullptr); + auto const result = + point_evaluation_impl(input, std::span{out, 64}); + if (result.data == nullptr) { + std::free(out); + } + return from_impl_result(result); +} + +PrecompileResult bls12_g1_add_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(128)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + bls12_g1_add_impl(input, std::span{out, 128})); +} + +PrecompileResult bls12_g1_msm_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(128)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + bls12_g1_msm_impl(input, std::span{out, 128})); +} + +PrecompileResult bls12_g2_add_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(256)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + bls12_g2_add_impl(input, std::span{out, 256})); +} + +PrecompileResult bls12_g2_msm_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(256)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + bls12_g2_msm_impl(input, std::span{out, 256})); +} + +PrecompileResult bls12_pairing_check_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(32)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + bls12_pairing_check_impl(input, std::span{out, 32})); +} + +PrecompileResult bls12_map_fp_to_g1_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(128)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + bls12_map_fp_to_g1_impl(input, std::span{out, 128})); +} + +PrecompileResult bls12_map_fp2_to_g2_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(256)); + MONAD_ASSERT(out != nullptr); + return from_impl_result( + bls12_map_fp2_to_g2_impl(input, std::span{out, 256})); +} + +PrecompileResult p256_verify_execute(byte_string_view const input) +{ + auto *const out = static_cast(std::malloc(32)); + MONAD_ASSERT(out != nullptr); + auto const result = + p256_verify_impl(input, std::span{out, 32}); + if (result.data == nullptr) { + std::free(out); + return {EVMC_SUCCESS, nullptr, 0}; + } + return {EVMC_SUCCESS, result.data, result.size}; +} + +PrecompileResult identity_execute(byte_string_view const input) +{ + if (input.empty()) { + return {EVMC_SUCCESS, nullptr, 0}; + } + auto *const out = static_cast(std::malloc(input.size())); + MONAD_ASSERT(out != nullptr); + auto const result = + identity_impl(input, std::span{out, input.size()}); + return {EVMC_SUCCESS, result.data, result.size}; +} + MONAD_NAMESPACE_END diff --git a/category/execution/ethereum/precompiles.hpp b/category/execution/ethereum/precompiles.hpp index 7fa64933d9..2558df61b5 100644 --- a/category/execution/ethereum/precompiles.hpp +++ b/category/execution/ethereum/precompiles.hpp @@ -18,8 +18,6 @@ #include #include #include -#include -#include #include #include @@ -28,9 +26,13 @@ #include #include #include +#include MONAD_NAMESPACE_BEGIN +class State; +struct CallTracerBase; + bool init_trusted_setup(); inline constexpr Address ripemd_address{3}; @@ -160,4 +162,64 @@ PrecompileResult bls12_map_fp2_to_g2_execute(byte_string_view); // Rollup precompiles PrecompileResult p256_verify_execute(byte_string_view); +struct PrecompileImplResult +{ + uint8_t *data; + size_t size; +}; + +PrecompileImplResult ecrecover_impl( + std::span msg, std::span sig, + uint8_t recid, std::span const out); + +PrecompileImplResult +sha256_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +ripemd160_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +expmod_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +ecadd_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +ecmul_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +snarkv_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +blake2bf_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +point_evaluation_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +bls12_g1_add_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +bls12_g1_msm_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +bls12_g2_add_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +bls12_g2_msm_impl(byte_string_view input, std::span const out); + +PrecompileImplResult bls12_pairing_check_impl( + byte_string_view input, std::span const out); + +PrecompileImplResult bls12_map_fp_to_g1_impl( + byte_string_view input, std::span const out); + +PrecompileImplResult bls12_map_fp2_to_g2_impl( + byte_string_view input, std::span const out); + +PrecompileImplResult +p256_verify_impl(byte_string_view input, std::span const out); + +PrecompileImplResult +identity_impl(byte_string_view input, std::span const out); MONAD_NAMESPACE_END diff --git a/category/execution/ethereum/precompiles_bls12.cpp b/category/execution/ethereum/precompiles_bls12.cpp index 098b9f6d0a..8f7834f2a6 100644 --- a/category/execution/ethereum/precompiles_bls12.cpp +++ b/category/execution/ethereum/precompiles_bls12.cpp @@ -175,20 +175,22 @@ namespace bls12 } template - PrecompileResult add(byte_string_view const input) + PrecompileImplResult + add(byte_string_view const input, + std::span const out) { if (MONAD_UNLIKELY(input.size() != 2 * Group::encoded_size)) { - return PrecompileResult::failure(); + return {nullptr, 0}; } auto const a = Group::read(input.data()); if (MONAD_UNLIKELY(!a.has_value())) { - return PrecompileResult::failure(); + return {nullptr, 0}; } auto const b = Group::read(input.data() + Group::encoded_size); if (MONAD_UNLIKELY(!b.has_value())) { - return PrecompileResult::failure(); + return {nullptr, 0}; } typename Group::Point a_non_affine; @@ -200,53 +202,53 @@ namespace bls12 typename Group::AffinePoint result; Group::to_affine(&result, &result_non_affine); - auto *const output_buf = - static_cast(std::malloc(Group::encoded_size)); - MONAD_ASSERT(output_buf != nullptr); + Group::write(result, out.data()); - Group::write(result, output_buf); - - return { - .status_code = EVMC_SUCCESS, - .obuf = output_buf, - .output_size = Group::encoded_size, - }; + return {out.data(), Group::encoded_size}; } - template PrecompileResult add(byte_string_view); - template PrecompileResult add(byte_string_view); + template PrecompileImplResult + add(byte_string_view, std::span); + template PrecompileImplResult + add(byte_string_view, std::span); template - PrecompileResult msm(byte_string_view const input) + PrecompileImplResult + msm(byte_string_view const input, + std::span const out) { static constexpr auto pair_size = Group::encoded_size + 32; if (MONAD_UNLIKELY(input.size() % pair_size != 0)) { - return PrecompileResult::failure(); + return {nullptr, 0}; } auto const k = input.size() / pair_size; if (MONAD_UNLIKELY(k == 0)) { - return PrecompileResult::failure(); + return {nullptr, 0}; } else if (k == 1) { - return mul(input); + return mul(input, out); } else { - return msm_pippenger(input, k); + return msm_pippenger(input, k, out); } } - template PrecompileResult msm(byte_string_view); - template PrecompileResult msm(byte_string_view); + template PrecompileImplResult + msm(byte_string_view, std::span); + template PrecompileImplResult + msm(byte_string_view, std::span); template - PrecompileResult mul(byte_string_view const input) + PrecompileImplResult + mul(byte_string_view const input, + std::span const out) { auto const affine_point = Group::read(input.data()); if (MONAD_UNLIKELY(!affine_point.has_value())) { - return PrecompileResult::failure(); + return {nullptr, 0}; } auto const scalar = read_scalar(input.data() + Group::encoded_size); @@ -255,7 +257,7 @@ namespace bls12 Group::from_affine(&point, &*affine_point); if (MONAD_UNLIKELY(!Group::point_in_group(&point))) { - return PrecompileResult::failure(); + return {nullptr, 0}; } typename Group::Point result; @@ -264,25 +266,20 @@ namespace bls12 typename Group::AffinePoint affine_result; Group::to_affine(&affine_result, &result); - auto *const output_buf = - static_cast(std::malloc(Group::encoded_size)); - MONAD_ASSERT(output_buf != nullptr); - - Group::write(affine_result, output_buf); + Group::write(affine_result, out.data()); - return { - .status_code = EVMC_SUCCESS, - .obuf = output_buf, - .output_size = Group::encoded_size, - }; + return {out.data(), Group::encoded_size}; } - template PrecompileResult mul(byte_string_view); - template PrecompileResult mul(byte_string_view); + template PrecompileImplResult + mul(byte_string_view, std::span); + template PrecompileImplResult + mul(byte_string_view, std::span); template - PrecompileResult - msm_pippenger(byte_string_view const input, uint64_t const k) + PrecompileImplResult msm_pippenger( + byte_string_view const input, uint64_t const k, + std::span const out) { auto affine_points = std::vector{}; affine_points.reserve(k); @@ -303,11 +300,11 @@ namespace bls12 for (auto const *ptr = input.data(); ptr != end_ptr; ptr += pair_size) { auto const affine_point = Group::read(ptr); if (MONAD_UNLIKELY(!affine_point.has_value())) { - return PrecompileResult::failure(); + return {nullptr, 0}; } if (MONAD_UNLIKELY(!Group::affine_point_in_group(&*affine_point))) { - return PrecompileResult::failure(); + return {nullptr, 0}; } if (Group::affine_point_is_inf(&*affine_point)) { @@ -323,12 +320,8 @@ namespace bls12 scalar_ptrs.emplace_back(s.b); } - auto *const output_buf = - static_cast(std::malloc(Group::encoded_size)); - MONAD_ASSERT(output_buf != nullptr); - if (affine_point_ptrs.empty()) { - std::memset(output_buf, 0, Group::encoded_size); + std::memset(out.data(), 0, Group::encoded_size); } else { auto const n_points = affine_point_ptrs.size(); @@ -349,31 +342,30 @@ namespace bls12 typename Group::AffinePoint affine_result; Group::to_affine(&affine_result, &result); - Group::write(affine_result, output_buf); + Group::write(affine_result, out.data()); } - return { - .status_code = EVMC_SUCCESS, - .obuf = output_buf, - .output_size = Group::encoded_size, - }; + return {out.data(), Group::encoded_size}; } - template PrecompileResult msm_pippenger(byte_string_view, uint64_t); - template PrecompileResult msm_pippenger(byte_string_view, uint64_t); + template PrecompileImplResult msm_pippenger( + byte_string_view, uint64_t, std::span); + template PrecompileImplResult msm_pippenger( + byte_string_view, uint64_t, std::span); - PrecompileResult pairing_check(byte_string_view const input) + PrecompileImplResult pairing_check( + byte_string_view const input, std::span const out) { static constexpr auto pair_size = G1::encoded_size + G2::encoded_size; if (MONAD_UNLIKELY(input.size() % pair_size != 0)) { - return PrecompileResult::failure(); + return {nullptr, 0}; } auto const k = input.size() / pair_size; if (MONAD_UNLIKELY(k == 0)) { - return PrecompileResult::failure(); + return {nullptr, 0}; } auto result = *blst_fp12_one(); @@ -382,20 +374,20 @@ namespace bls12 for (auto const *ptr = input.data(); ptr != end_ptr; ptr += pair_size) { auto const maybe_g1 = G1::read(ptr); if (MONAD_UNLIKELY(!maybe_g1.has_value())) { - return PrecompileResult::failure(); + return {nullptr, 0}; } auto const maybe_g2 = G2::read(ptr + G1::encoded_size); if (MONAD_UNLIKELY(!maybe_g2.has_value())) { - return PrecompileResult::failure(); + return {nullptr, 0}; } if (MONAD_UNLIKELY(!G1::affine_point_in_group(&*maybe_g1))) { - return PrecompileResult::failure(); + return {nullptr, 0}; } if (MONAD_UNLIKELY(!G2::affine_point_in_group(&*maybe_g2))) { - return PrecompileResult::failure(); + return {nullptr, 0}; } if (G1::affine_point_is_inf(&*maybe_g1)) { @@ -413,34 +405,27 @@ namespace bls12 blst_final_exp(&result, &result); - static constexpr auto bool_encoded_size = 32; - - auto *const output_buf = - static_cast(std::malloc(bool_encoded_size)); - MONAD_ASSERT(output_buf != nullptr); - std::memset(output_buf, 0, bool_encoded_size); + std::memset(out.data(), 0, 32); if (blst_fp12_is_one(&result)) { - output_buf[bool_encoded_size - 1] = 1; + out.data()[31] = 1; } - return { - .status_code = EVMC_SUCCESS, - .obuf = output_buf, - .output_size = bool_encoded_size, - }; + return {out.data(), 32}; } template - PrecompileResult map_fp_to_g(byte_string_view const input) + PrecompileImplResult map_fp_to_g( + byte_string_view const input, + std::span const out) { if (MONAD_UNLIKELY(input.size() != Group::element_encoded_size)) { - return PrecompileResult::failure(); + return {nullptr, 0}; } auto const maybe_fp = Group::read_element(input.data()); if (MONAD_UNLIKELY(!maybe_fp.has_value())) { - return PrecompileResult::failure(); + return {nullptr, 0}; } typename Group::Point point; @@ -449,21 +434,15 @@ namespace bls12 typename Group::AffinePoint result; Group::to_affine(&result, &point); - auto *const output_buf = - static_cast(std::malloc(Group::encoded_size)); - MONAD_ASSERT(output_buf != nullptr); - - Group::write(result, output_buf); + Group::write(result, out.data()); - return { - .status_code = EVMC_SUCCESS, - .obuf = output_buf, - .output_size = Group::encoded_size, - }; + return {out.data(), Group::encoded_size}; } - template PrecompileResult map_fp_to_g(byte_string_view); - template PrecompileResult map_fp_to_g(byte_string_view); + template PrecompileImplResult + map_fp_to_g(byte_string_view, std::span); + template PrecompileImplResult + map_fp_to_g(byte_string_view, std::span); } // namespace bls12 MONAD_NAMESPACE_END diff --git a/category/execution/ethereum/precompiles_bls12.hpp b/category/execution/ethereum/precompiles_bls12.hpp index ed24fced18..68a87c011a 100644 --- a/category/execution/ethereum/precompiles_bls12.hpp +++ b/category/execution/ethereum/precompiles_bls12.hpp @@ -19,7 +19,9 @@ #include #include -#include +#ifndef MONAD_ZKVM + #include +#endif #include #include @@ -37,6 +39,7 @@ namespace bls12 template uint16_t msm_discount(uint64_t); +#ifndef MONAD_ZKVM blst_scalar read_scalar(uint8_t const *); std::optional read_fp(uint8_t const *); std::optional read_fp2(uint8_t const *); @@ -49,22 +52,28 @@ namespace bls12 void write_g2(blst_p2_affine const &, uint8_t *); template - PrecompileResult add(byte_string_view); + PrecompileImplResult + add(byte_string_view, std::span); template - PrecompileResult msm(byte_string_view); + PrecompileImplResult + msm(byte_string_view, std::span); template - PrecompileResult mul(byte_string_view); + PrecompileImplResult + mul(byte_string_view, std::span); template - PrecompileResult msm_pippenger(byte_string_view, uint64_t); + PrecompileImplResult msm_pippenger( + byte_string_view, uint64_t, std::span); - PrecompileResult pairing_check(byte_string_view); + PrecompileImplResult + pairing_check(byte_string_view, std::span); template - PrecompileResult map_fp_to_g(byte_string_view); - + PrecompileImplResult + map_fp_to_g(byte_string_view, std::span); +#endif // The BLST library is implemented as an internal C static library with // language-specific bindings applied on top. The implementation and // bindings for C are not actually coupled: both the bindings and @@ -91,13 +100,13 @@ namespace bls12 struct G1 { + static constexpr auto element_encoded_size = 64; + static constexpr auto encoded_size = 2 * element_encoded_size; +#ifndef MONAD_ZKVM using FieldElement = blst_fp; using Point = blst_p1; using AffinePoint = blst_p1_affine; - static constexpr auto element_encoded_size = 64; - static constexpr auto encoded_size = 2 * element_encoded_size; - DECLARE_GROUP_FN(read, read_g1); DECLARE_GROUP_FN(read_element, read_fp); DECLARE_GROUP_FN(write, write_g1); @@ -112,18 +121,20 @@ namespace bls12 DECLARE_GROUP_FN(msm, blst_p1s_mult_pippenger); DECLARE_GROUP_FN(to_affine, blst_p1_to_affine); DECLARE_GROUP_FN(from_affine, blst_p1_from_affine); +#endif }; struct G2 { - using FieldElement = blst_fp2; - using Point = blst_p2; - using AffinePoint = blst_p2_affine; - static constexpr auto element_encoded_size = 2 * G1::element_encoded_size; static constexpr auto encoded_size = 2 * element_encoded_size; +#ifndef MONAD_ZKVM + using FieldElement = blst_fp2; + using Point = blst_p2; + using AffinePoint = blst_p2_affine; + DECLARE_GROUP_FN(read, read_g2); DECLARE_GROUP_FN(read_element, read_fp2); DECLARE_GROUP_FN(write, write_g2); @@ -138,6 +149,7 @@ namespace bls12 DECLARE_GROUP_FN(msm, blst_p2s_mult_pippenger); DECLARE_GROUP_FN(to_affine, blst_p2_to_affine); DECLARE_GROUP_FN(from_affine, blst_p2_from_affine); +#endif }; #undef DECLARE_GROUP_FN diff --git a/category/execution/ethereum/precompiles_gas_cost_impl.cpp b/category/execution/ethereum/precompiles_gas_cost_impl.cpp index 924444c2f1..f80cef42e2 100644 --- a/category/execution/ethereum/precompiles_gas_cost_impl.cpp +++ b/category/execution/ethereum/precompiles_gas_cost_impl.cpp @@ -14,6 +14,7 @@ // along with this program. If not, see . #include +#include #include #include #include @@ -24,6 +25,8 @@ #include #include +using uint256_t = ::intx::uint256; + namespace { constexpr size_t num_words(size_t const length) diff --git a/category/execution/ethereum/precompiles_impl.cpp b/category/execution/ethereum/precompiles_impl.cpp index 2a2097e5b9..30990ab1dc 100644 --- a/category/execution/ethereum/precompiles_impl.cpp +++ b/category/execution/ethereum/precompiles_impl.cpp @@ -39,7 +39,9 @@ #include #include +#include #include +#include #include #include @@ -101,78 +103,132 @@ static inline PrecompileResult silkpre_execute(byte_string_view const input) auto const [output, output_size] = Func(input.data(), input.size()); if (output == nullptr) { MONAD_ASSERT(output_size == 0); - return {EVMC_PRECOMPILE_FAILURE, nullptr, 0}; + return PrecompileResult::failure(); } return {EVMC_SUCCESS, output, output_size}; } -PrecompileResult ecrecover_execute(byte_string_view const input) +PrecompileImplResult ecrecover_impl( + std::span msg, std::span sig, + uint8_t recid, std::span const out) { - auto const clamped_input = input.substr(0, 128); - return silkpre_execute(clamped_input); + std::memset(out.data(), 0, 12); + thread_local secp256k1_context *context{ + secp256k1_context_create(SILKPRE_SECP256K1_CONTEXT_FLAGS)}; + if (!silkpre_recover_address( + &out[12], msg.data(), sig.data(), recid, context)) { + return {out.data(), 0}; + } + return {out.data(), 32}; } -PrecompileResult sha256_execute(byte_string_view const input) +PrecompileImplResult +sha256_impl(byte_string_view input, std::span const out) { if (MONAD_UNLIKELY(input.data() == nullptr)) { // Passing a null pointer to the Silkpre sha256 implementation invokes // undefined behaviour. We sidestep the UB here by passing a pointer to // the empty string instead. - byte_string_view const nonnull{ - reinterpret_cast(""), 0UL}; - return silkpre_execute(nonnull); + input = + byte_string_view{reinterpret_cast(""), 0UL}; } - return silkpre_execute(input); + silkpre_sha256( + out.data(), input.data(), input.size(), true /* use_cpu_extensions */); + return {out.data(), 32}; } -PrecompileResult ripemd160_execute(byte_string_view const input) +PrecompileImplResult +ripemd160_impl(byte_string_view const input, std::span const out) { - return silkpre_execute(input); + std::memset(out.data(), 0, 12); + silkpre_rmd160(&out[12], input.data(), input.size()); + return {out.data(), 32}; } -PrecompileResult ecadd_execute(byte_string_view const input) +PrecompileImplResult +ecadd_impl(byte_string_view const input, std::span const out) { - auto const clamped_input = input.substr(0, 128); - return silkpre_execute(clamped_input); + auto const [output, output_size] = + silkpre_bn_add_run(input.data(), input.size()); + if (output == nullptr) { + MONAD_ASSERT(output_size == 0); + return {nullptr, 0}; + } + std::memcpy(out.data(), output, output_size); + std::free(output); + return {out.data(), 64}; } -PrecompileResult ecmul_execute(byte_string_view const input) +PrecompileImplResult +ecmul_impl(byte_string_view const input, std::span const out) { - auto const clamped_input = input.substr(0, 96); - return silkpre_execute(clamped_input); + auto const [output, output_size] = + silkpre_bn_mul_run(input.data(), input.size()); + if (output == nullptr) { + MONAD_ASSERT(output_size == 0); + return {nullptr, 0}; + } + std::memcpy(out.data(), output, output_size); + std::free(output); + return {out.data(), 64}; } -PrecompileResult identity_execute(byte_string_view const input) +PrecompileImplResult +identity_impl(byte_string_view const input, std::span const out) { - if (input.empty()) { - return {EVMC_SUCCESS, nullptr, 0}; - } + MONAD_ASSERT(!input.empty()); - auto *const output = static_cast(malloc(input.size())); - MONAD_ASSERT(output != nullptr); - memcpy(output, input.data(), input.size()); - return {EVMC_SUCCESS, output, input.size()}; + std::memcpy(out.data(), input.data(), input.size()); + return {out.data(), input.size()}; } -PrecompileResult expmod_execute(byte_string_view const input) +PrecompileImplResult +expmod_impl(byte_string_view const input, std::span const out) { - return silkpre_execute(input); + auto const [output, output_size] = + silkpre_expmod_run(input.data(), input.size()); + if (output == nullptr) { + MONAD_ASSERT(output_size == 0); + return {out.data(), 0}; + } + std::memcpy(out.data(), output, output_size); + std::free(output); + return {out.data(), out.size()}; } -PrecompileResult snarkv_execute(byte_string_view const input) +PrecompileImplResult +snarkv_impl(byte_string_view const input, std::span const out) { - return silkpre_execute(input); + auto const [output, output_size] = + silkpre_snarkv_run(input.data(), input.size()); + if (output == nullptr) { + MONAD_ASSERT(output_size == 0); + return {nullptr, 0}; + } + std::memcpy(out.data(), output, output_size); + std::free(output); + return {out.data(), 32}; } -PrecompileResult blake2bf_execute(byte_string_view const input) +PrecompileImplResult +blake2bf_impl(byte_string_view const input, std::span const out) { - return silkpre_execute(input); + auto const [output, output_size] = + silkpre_blake2_f_run(input.data(), input.size()); + if (output == nullptr) { + MONAD_ASSERT(output_size == 0); + return {nullptr, 0}; + } + std::memcpy(out.data(), output, output_size); + std::free(output); + return {out.data(), 64}; } -PrecompileResult point_evaluation_execute(byte_string_view const input) +PrecompileImplResult point_evaluation_impl( + byte_string_view const input, std::span const out) { if (input.size() != 192) { - return PrecompileResult::failure(); + return {nullptr, 0}; } bytes32_t versioned_hash; @@ -189,74 +245,71 @@ PrecompileResult point_evaluation_execute(byte_string_view const input) KZGCommitment commitment{*commitment_data}; if (versioned_hash != kzg_to_version_hashed(commitment)) { - return PrecompileResult::failure(); + return {nullptr, 0}; } bool ok{false}; verify_kzg_proof(&ok, &commitment, z, y, proof, &g_trustedSetup.value()); if (!ok) { - return PrecompileResult::failure(); + return {nullptr, 0}; } - auto *const output = static_cast(std::malloc(sizeof(bytes64_t))); - MONAD_ASSERT(output != nullptr); std::memcpy( - output, blob_precompile_return_value().bytes, sizeof(bytes64_t)); - - return { - .status_code = EVMC_SUCCESS, - .obuf = output, - .output_size = sizeof(bytes64_t), - }; + out.data(), blob_precompile_return_value().bytes, sizeof(bytes64_t)); + return {out.data(), 64}; } -PrecompileResult bls12_g1_add_execute(byte_string_view const input) +PrecompileImplResult bls12_g1_add_impl( + byte_string_view const input, std::span const out) { - return bls12::add(input); + return bls12::add(input, out); } -PrecompileResult bls12_g1_msm_execute(byte_string_view const input) +PrecompileImplResult bls12_g1_msm_impl( + byte_string_view const input, std::span const out) { - return bls12::msm(input); + return bls12::msm(input, out); } -PrecompileResult bls12_g2_add_execute(byte_string_view const input) +PrecompileImplResult bls12_g2_add_impl( + byte_string_view const input, std::span const out) { - return bls12::add(input); + return bls12::add(input, out); } -PrecompileResult bls12_g2_msm_execute(byte_string_view const input) +PrecompileImplResult bls12_g2_msm_impl( + byte_string_view const input, std::span const out) { - return bls12::msm(input); + return bls12::msm(input, out); } -PrecompileResult bls12_pairing_check_execute(byte_string_view const input) +PrecompileImplResult bls12_pairing_check_impl( + byte_string_view const input, std::span const out) { - return bls12::pairing_check(input); + return bls12::pairing_check(input, out); } -PrecompileResult bls12_map_fp_to_g1_execute(byte_string_view const input) +PrecompileImplResult bls12_map_fp_to_g1_impl( + byte_string_view const input, std::span const out) { - return bls12::map_fp_to_g(input); + return bls12::map_fp_to_g(input, out); } -PrecompileResult bls12_map_fp2_to_g2_execute(byte_string_view const input) +PrecompileImplResult bls12_map_fp2_to_g2_impl( + byte_string_view const input, std::span const out) { - return bls12::map_fp_to_g(input); + return bls12::map_fp_to_g(input, out); } // Rollup precompiles // EIP-7951 -PrecompileResult p256_verify_execute(byte_string_view const input) +PrecompileImplResult +p256_verify_impl(byte_string_view const input, std::span const out) { using namespace CryptoPP; - auto const empty_result = PrecompileResult{ - .status_code = EVMC_SUCCESS, - .obuf = nullptr, - .output_size = 0, - }; + static constexpr PrecompileImplResult empty_result{nullptr, 0}; if (input.size() != 160) { return empty_result; @@ -324,17 +377,9 @@ PrecompileResult p256_verify_execute(byte_string_view const input) } // Return 0x000...1 - auto *const output_buf = static_cast(std::malloc(32)); - MONAD_ASSERT(output_buf != nullptr); - std::memset(output_buf, 0, 32); - - output_buf[31] = 1; - - return { - .status_code = EVMC_SUCCESS, - .obuf = output_buf, - .output_size = 32, - }; + std::memset(out.data(), 0, 32); + out.data()[31] = 1; + return {out.data(), 32}; } MONAD_NAMESPACE_END diff --git a/category/execution/ethereum/precompiles_test.cpp b/category/execution/ethereum/precompiles_test.cpp index 72d4a55156..2719863d50 100644 --- a/category/execution/ethereum/precompiles_test.cpp +++ b/category/execution/ethereum/precompiles_test.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/category/vm/evm/delegation.hpp b/category/vm/evm/delegation.hpp index ba42ab1f04..786cb9fab3 100644 --- a/category/vm/evm/delegation.hpp +++ b/category/vm/evm/delegation.hpp @@ -20,6 +20,7 @@ #include #include +#include namespace monad::vm::evm { diff --git a/category/vm/interpreter/CMakeLists.txt b/category/vm/interpreter/CMakeLists.txt index a7f2e7ec71..d617d70f6c 100644 --- a/category/vm/interpreter/CMakeLists.txt +++ b/category/vm/interpreter/CMakeLists.txt @@ -13,7 +13,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -enable_language(ASM) +if(NOT MONAD_ZKVM) + enable_language(ASM) +endif() option(MONAD_VM_INTERPRETER_DEBUG "Trace instructions executed by interpreter" OFF) @@ -28,7 +30,6 @@ add_library(monad-vm-interpreter OBJECT) target_sources(monad-vm-interpreter PRIVATE "call_runtime.hpp" "debug.hpp" - "entry.S" "execute.cpp" "execute.hpp" "instruction_stats.cpp" @@ -42,6 +43,10 @@ target_sources(monad-vm-interpreter PRIVATE "types.hpp" ) +if(NOT MONAD_ZKVM) + target_sources(monad-vm-interpreter PRIVATE "entry.S") +endif() + monad_compile_options(monad-vm-interpreter) # This is a workaround for a change made to LLVM between versions 18 and 19: @@ -77,7 +82,10 @@ target_link_libraries(monad-vm-interpreter PUBLIC evmc::evmc PUBLIC monad-vm::monad-vm-evm PUBLIC monad-vm::monad-vm-runtime - PUBLIC monad-vm::monad-vm-utils ) +if(NOT MONAD_ZKVM) + target_link_libraries(monad-vm-interpreter PUBLIC monad-vm::monad-vm-utils) +endif() + add_library(monad-vm::monad-vm-interpreter ALIAS monad-vm-interpreter) diff --git a/category/vm/interpreter/debug.hpp b/category/vm/interpreter/debug.hpp index 029a54c318..760485493e 100644 --- a/category/vm/interpreter/debug.hpp +++ b/category/vm/interpreter/debug.hpp @@ -22,8 +22,10 @@ #include -#include -#include +#ifndef MONAD_ZKVM + #include + #include +#endif namespace monad::vm::interpreter { @@ -38,13 +40,16 @@ namespace monad::vm::interpreter */ [[gnu::always_inline]] inline void trace( - Intercode const &analysis, int64_t const gas_remaining, - uint8_t const *const instr_ptr) + [[maybe_unused]] Intercode const &analysis, + [[maybe_unused]] int64_t const gas_remaining, + [[maybe_unused]] uint8_t const *const instr_ptr) { +#ifndef MONAD_ZKVM std::cerr << std::format( "offset: 0x{:02x} opcode: 0x{:x} gas_left: {}\n", instr_ptr - analysis.code(), *instr_ptr, gas_remaining); +#endif } } diff --git a/category/vm/interpreter/execute.cpp b/category/vm/interpreter/execute.cpp index 59671e9146..3dc2426186 100644 --- a/category/vm/interpreter/execute.cpp +++ b/category/vm/interpreter/execute.cpp @@ -23,6 +23,9 @@ #include #include +#ifdef MONAD_ZKVM + #include +#endif /** * Assembly trampoline into the interpreter's core loop (see entry.S). This @@ -51,6 +54,14 @@ namespace monad::vm::interpreter "Interpreter core loop and trampoline signatures must be " "identical"); +#ifdef MONAD_ZKVM + jmp_buf exit_jmp; + if (setjmp(exit_jmp)) { + return; + } + ctx->exit_stack_ptr = &exit_jmp; +#endif + auto *const stack_top = stack_ptr - 1; auto const *const stack_bottom = stack_top; auto const *const instr_ptr = analysis->code(); @@ -73,12 +84,21 @@ namespace monad::vm::interpreter void execute( runtime::Context &ctx, Intercode const &analysis, uint8_t *stack_ptr) { +#ifdef MONAD_ZKVM + core_loop( + nullptr, + &ctx, + &analysis, + reinterpret_cast(stack_ptr), + nullptr); +#else monad_vm_interpreter_trampoline( static_cast(&ctx.exit_stack_ptr), &ctx, &analysis, reinterpret_cast(stack_ptr), reinterpret_cast(core_loop)); +#endif } EXPLICIT_TRAITS(execute); diff --git a/category/vm/interpreter/instruction_table.hpp b/category/vm/interpreter/instruction_table.hpp index 4e32c3e032..9c32c62770 100644 --- a/category/vm/interpreter/instruction_table.hpp +++ b/category/vm/interpreter/instruction_table.hpp @@ -436,7 +436,7 @@ namespace monad::vm::interpreter int64_t gas_remaining, uint8_t const *instr_ptr) { checked_runtime_call( - monad_vm_runtime_mul, + runtime::mul, ctx, analysis, stack_bottom, @@ -1489,9 +1489,13 @@ namespace monad::vm::interpreter check_requirements( ctx, analysis, stack_bottom, stack_top, gas_remaining); +#ifdef MONAD_ZKVM + std::swap(*stack_top, *(stack_top - N)); +#else auto const top = stack_top->to_avx(); *stack_top = *(stack_top - N); *(stack_top - N) = runtime::uint256_t{top}; +#endif MONAD_VM_NEXT(SWAP1 + (N - 1)); } diff --git a/category/vm/interpreter/push.hpp b/category/vm/interpreter/push.hpp index a66e4abbe0..901f7baa77 100644 --- a/category/vm/interpreter/push.hpp +++ b/category/vm/interpreter/push.hpp @@ -26,7 +26,9 @@ #include -#include +#ifdef __AVX2__ + #include +#endif #include #include diff --git a/category/vm/runtime/CMakeLists.txt b/category/vm/runtime/CMakeLists.txt index 5e71070381..c4735b3142 100644 --- a/category/vm/runtime/CMakeLists.txt +++ b/category/vm/runtime/CMakeLists.txt @@ -13,7 +13,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -enable_language(ASM) +if(NOT MONAD_ZKVM) + enable_language(ASM) +endif() add_library(monad-vm-runtime OBJECT) @@ -24,7 +26,6 @@ target_sources(monad-vm-runtime PRIVATE "bin.hpp" "call.hpp" "call.cpp" - "context.S" "context.cpp" "create.hpp" "create.cpp" @@ -33,12 +34,10 @@ target_sources(monad-vm-runtime PRIVATE "detail.hpp" "environment.hpp" "environment.cpp" - "exit.S" "exit.cpp" "keccak.hpp" "log.hpp" "log.cpp" - "math.S" "math.hpp" "memory.hpp" "runtime.hpp" @@ -47,11 +46,19 @@ target_sources(monad-vm-runtime PRIVATE "storage.cpp" "storage.hpp" "storage_costs.hpp" - "transmute.S" "transmute.hpp" "types.hpp" ) +if(NOT MONAD_ZKVM) + target_sources(monad-vm-runtime PRIVATE + "context.S" + "exit.S" + "math.S" + "transmute.S" + ) +endif() + monad_compile_options(monad-vm-runtime) target_include_directories(monad-vm-runtime diff --git a/category/vm/runtime/allocator.cpp b/category/vm/runtime/allocator.cpp index 9a15277fac..28b9597069 100644 --- a/category/vm/runtime/allocator.cpp +++ b/category/vm/runtime/allocator.cpp @@ -18,5 +18,9 @@ namespace monad::vm::runtime { +#ifdef MONAD_ZKVM + CachedAllocatorList EvmStackAllocatorMeta::cache_list; +#else thread_local CachedAllocatorList EvmStackAllocatorMeta::cache_list; +#endif } diff --git a/category/vm/runtime/allocator.hpp b/category/vm/runtime/allocator.hpp index a2c030ef39..6d770b9ebb 100644 --- a/category/vm/runtime/allocator.hpp +++ b/category/vm/runtime/allocator.hpp @@ -24,7 +24,11 @@ namespace monad::vm::runtime using base_type = uint256_t; static constexpr size_t size = 1024; static constexpr size_t alignment = 32; +#ifdef MONAD_ZKVM + static CachedAllocatorList cache_list; +#else static thread_local CachedAllocatorList cache_list; +#endif }; using EvmStackAllocator = CachedAllocator; diff --git a/category/vm/runtime/cached_allocator.hpp b/category/vm/runtime/cached_allocator.hpp index fbffc30b36..f2b22812a2 100644 --- a/category/vm/runtime/cached_allocator.hpp +++ b/category/vm/runtime/cached_allocator.hpp @@ -19,6 +19,10 @@ #include #include +#ifdef MONAD_ZKVM + #include +#endif + #include #include #include @@ -70,6 +74,11 @@ namespace monad::vm::runtime return ptr; } +#ifdef MONAD_ZKVM + // Trivial destructor avoids __cxa_atexit for the global cache_list + // (no meaningful program exit in bare-metal zkVM). + ~CachedAllocatorList() = default; +#else ~CachedAllocatorList() { auto *e = elements; @@ -79,6 +88,7 @@ namespace monad::vm::runtime e = next; } } +#endif }; template @@ -116,7 +126,11 @@ namespace monad::vm::runtime { if (T::cache_list.empty()) { auto *const p = reinterpret_cast( +#ifdef MONAD_ZKVM + zkvm_aligned_alloc(T::alignment, alloc_size)); +#else std::aligned_alloc(T::alignment, alloc_size)); +#endif return p; } else { diff --git a/category/vm/runtime/context.cpp b/category/vm/runtime/context.cpp index ca4fe8cc33..a60413847a 100644 --- a/category/vm/runtime/context.cpp +++ b/category/vm/runtime/context.cpp @@ -25,6 +25,9 @@ #include #include #include +#ifdef MONAD_ZKVM + #include +#endif #include #include @@ -61,8 +64,13 @@ extern "C" void monad_vm_runtime_increase_capacity( MONAD_DEBUG_ASSERT((*new_total_capacity & 31) == 0); +#ifdef MONAD_ZKVM + auto *const new_handle = + static_cast(zkvm_aligned_alloc(32, *new_total_capacity)); +#else auto *const new_handle = static_cast(std::aligned_alloc(32, *new_total_capacity)); +#endif MONAD_ASSERT(new_handle); non_temporal_memcpy(new_handle, ctx->memory.data_handle, *old_total_size); diff --git a/category/vm/runtime/exit.cpp b/category/vm/runtime/exit.cpp index ba6ffe51f8..098b929107 100644 --- a/category/vm/runtime/exit.cpp +++ b/category/vm/runtime/exit.cpp @@ -15,13 +15,21 @@ #include +#ifdef MONAD_ZKVM + #include +#else extern "C" void monad_vm_runtime_exit [[noreturn]] (void *); +#endif extern "C" void monad_vm_runtime_context_out_of_gas_exit [[noreturn]] (monad::vm::runtime::Context *const ctx) { ctx->result.status = monad::vm::runtime::StatusCode::OutOfGas; +#ifdef MONAD_ZKVM + std::longjmp(*ctx->exit_stack_ptr, 1); +#else monad_vm_runtime_exit(ctx->exit_stack_ptr); +#endif } namespace monad::vm::runtime @@ -30,12 +38,20 @@ namespace monad::vm::runtime { is_stack_unwinding_active = true; result.status = StatusCode::Error; +#ifdef MONAD_ZKVM + std::longjmp(*exit_stack_ptr, 1); +#else monad_vm_runtime_exit(exit_stack_ptr); +#endif } void Context::exit [[noreturn]] (StatusCode const code) noexcept { result.status = code; +#ifdef MONAD_ZKVM + std::longjmp(*exit_stack_ptr, 1); +#else monad_vm_runtime_exit(exit_stack_ptr); +#endif } } diff --git a/category/vm/runtime/math.hpp b/category/vm/runtime/math.hpp index fdf04c4c60..194ff4a563 100644 --- a/category/vm/runtime/math.hpp +++ b/category/vm/runtime/math.hpp @@ -22,6 +22,7 @@ #include +#ifndef MONAD_ZKVM // It is assumed that if the `result` pointer overlaps with `left` and/or // `right`, then `result` pointer is equal to `left` and/or `right`. extern "C" void monad_vm_runtime_mul( @@ -35,12 +36,22 @@ extern "C" void monad_vm_runtime_mul_192( monad::vm::runtime::uint256_t *result, monad::vm::runtime::uint256_t const *left, monad::vm::runtime::uint256_t const *right) noexcept; +#endif namespace monad::vm::runtime { +#ifdef MONAD_ZKVM + inline void + mul(uint256_t *result_ptr, uint256_t const *a_ptr, + uint256_t const *b_ptr) noexcept + { + *result_ptr = *a_ptr * *b_ptr; + } +#else constexpr void (*mul)( uint256_t *, uint256_t const *, uint256_t const *) noexcept = monad_vm_runtime_mul; +#endif constexpr void udiv( uint256_t *const result_ptr, uint256_t const *const a_ptr, diff --git a/category/vm/runtime/transmute.hpp b/category/vm/runtime/transmute.hpp index 5201a1682f..375caa9b20 100644 --- a/category/vm/runtime/transmute.hpp +++ b/category/vm/runtime/transmute.hpp @@ -23,7 +23,8 @@ #include #include -#include +#ifndef MONAD_ZKVM + #include // Load `load_size` bytes from `src_buffer` and clear the remaining upper bytes // of the result. It is required that `load_size <= 32`. If `load_size <= 0` @@ -36,6 +37,7 @@ monad_vm_runtime_load_bounded_le(uint8_t const *src_buffer, int64_t load_size); // monad_vm_runtime_load_bounded_le function for a version // using standard calling convention. extern "C" __m256i monad_vm_runtime_load_bounded_le_raw(); +#endif namespace monad::vm::runtime { @@ -50,7 +52,13 @@ namespace monad::vm::runtime if (MONAD_LIKELY(max_len >= 32)) { return uint256_t::load_le_unsafe(bytes); } +#ifdef MONAD_ZKVM + uint256_t v{0}; + std::memcpy(v.as_bytes(), bytes, max_len); + return v; +#else return uint256_t{monad_vm_runtime_load_bounded_le(bytes, max_len)}; +#endif } [[gnu::always_inline]] diff --git a/category/vm/runtime/types.hpp b/category/vm/runtime/types.hpp index ce3eaf39eb..0a7742346a 100644 --- a/category/vm/runtime/types.hpp +++ b/category/vm/runtime/types.hpp @@ -27,6 +27,9 @@ #include #include +#ifdef MONAD_ZKVM + #include +#endif #include #include #include @@ -244,7 +247,11 @@ namespace monad::vm::runtime Memory memory; +#ifdef MONAD_ZKVM + std::jmp_buf *exit_stack_ptr = nullptr; +#else void *exit_stack_ptr = nullptr; +#endif bool is_stack_unwinding_active = false; [[gnu::always_inline]] diff --git a/category/zkvm/CMakeLists.txt b/category/zkvm/CMakeLists.txt new file mode 100644 index 0000000000..76207bb838 --- /dev/null +++ b/category/zkvm/CMakeLists.txt @@ -0,0 +1,256 @@ +# Copyright (C) 2025-26 Category Labs, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Standalone CMake project that builds the Monad VM components without +# requiring the full monad dependency tree (no TBB, Boost, asmjit, quill, +# etc.). +# +# Usage (cross-compiled for zkVM): +# cmake -B build-zkvm -S category/zkvm \ +# -DCMAKE_TOOLCHAIN_FILE=../../category/core/toolchains/riscv64-elf-toolchain.cmake \ +# -DRISCV_TOOLCHAIN_DIR=/path/to/riscv +# cmake --build build-zkvm +# +# Usage (native x86 build): +# cmake -B build-native -S category/zkvm \ +# -DMONAD_ZKVM_BACKEND=native -DCMAKE_BUILD_TYPE=Release +# cmake --build build-native + +cmake_minimum_required(VERSION 3.20) +project(monad-zkvm LANGUAGES C CXX ASM) + +# Backend selection — determines build mode and linked implementations. +# "native" builds for the host platform without any zkVM shimming. +set(MONAD_ZKVM_BACKEND "zisk" CACHE STRING "zkVM backend (zisk, sp1, or native)") +set_property(CACHE MONAD_ZKVM_BACKEND PROPERTY STRINGS zisk sp1 native) + +string(TOUPPER "${MONAD_ZKVM_BACKEND}" _ZKVM_BACKEND_UPPER) + +if(NOT MONAD_ZKVM_BACKEND STREQUAL "native") + set(MONAD_ZKVM ON) +endif() + +# Testing features are incompatible with bare-metal zkVM builds. +if(MONAD_COMPILER_TESTING AND MONAD_ZKVM) + message(FATAL_ERROR "MONAD_COMPILER_TESTING is not supported in zkVM builds") +endif() + +# Point to the monad repository root +set(MONAD_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../..") +set(CATEGORY_MAIN_DIR "${MONAD_ROOT}") +set(THIRD_PARTY_DIR "${MONAD_ROOT}/third_party") + +# Reuse the shared compile options +include("${MONAD_ROOT}/cmake/compile_options.cmake") + +# ------------------------------------------------------------------- +# Third-party dependencies (minimal for bare-metal) +# ------------------------------------------------------------------- + +# intx (header-only uint256 implementation) +add_subdirectory("${THIRD_PARTY_DIR}/intx" intx) + +# evmc — only need the header-only interface + instructions library. +# Skip loader (needs dlfcn.h), mocked_host, and tooling. +set(EVMC_INCLUDE_DIR "${THIRD_PARTY_DIR}/evmc/include") + +add_library(evmc INTERFACE) +add_library(evmc::evmc ALIAS evmc) +target_compile_features(evmc INTERFACE c_std_99) +target_include_directories(evmc INTERFACE ${EVMC_INCLUDE_DIR}) + +add_library(evmc_cpp INTERFACE) +add_library(evmc::evmc_cpp ALIAS evmc_cpp) +target_compile_features(evmc_cpp INTERFACE cxx_std_17) +target_include_directories(evmc_cpp INTERFACE ${EVMC_INCLUDE_DIR}) +target_link_libraries(evmc_cpp INTERFACE evmc::evmc) + +add_subdirectory("${THIRD_PARTY_DIR}/evmc/lib/instructions" evmc_instructions) + +# ethash — only keccak, no ethash or global_context (needs std::mutex) +set(ETHASH_BUILD_ETHASH OFF CACHE BOOL "" FORCE) +set(ETHASH_TESTING OFF CACHE BOOL "" FORCE) +set(ETHASH_INSTALL_CMAKE_CONFIG OFF CACHE BOOL "" FORCE) +add_subdirectory("${THIRD_PARTY_DIR}/ethash" ethash) + +# Stub monad_core — the full target lives in category/core/CMakeLists.txt and +# pulls in TBB, Boost, quill, etc. The zkvm build only needs the public +# include paths and the unordered_dense dependency that the VM runtime +# transitively requires through monad_core. +if(NOT TARGET monad_core) + add_library(monad_core INTERFACE) + target_include_directories(monad_core INTERFACE + "${MONAD_ROOT}" + "${THIRD_PARTY_DIR}/unordered_dense/include" + ) + target_link_libraries(monad_core INTERFACE ethash::keccak evmc intx::intx) +endif() + +# ------------------------------------------------------------------- +# VM component libraries +# ------------------------------------------------------------------- + +add_subdirectory("${MONAD_ROOT}/category/vm/evm" evm) +add_subdirectory("${MONAD_ROOT}/category/vm/runtime" runtime) +add_subdirectory("${MONAD_ROOT}/category/vm/interpreter" interpreter) + +if(NOT MONAD_ZKVM) + # Native builds need the utils library (linked by the interpreter). + add_subdirectory("${MONAD_ROOT}/category/vm/utils" utils) +endif() + +# The interpreter includes a few header-only utils (debug.hpp, traits.hpp, +# scope_exit.hpp) but we do not build the full utils library for zkVM. +# Provide the include path so the headers resolve. +target_include_directories(monad-vm-interpreter PUBLIC "${MONAD_ROOT}") + +# For zkVM backends: shadow upstream uint256.hpp with the intx-backed version. +# The BEFORE keyword ensures this is searched before the upstream includes. +# For native: skip shadowing so the upstream x86-optimised uint256 is used. +set(ZKVM_INCLUDE_DIR "${MONAD_ROOT}/category/zkvm/include") + +foreach(tgt monad-vm-evm monad-vm-runtime monad-vm-interpreter) + if(MONAD_ZKVM) + target_include_directories(${tgt} BEFORE PUBLIC "${ZKVM_INCLUDE_DIR}") + target_compile_definitions(${tgt} PUBLIC "MONAD_ZKVM_${_ZKVM_BACKEND_UPPER}") + endif() +endforeach() + +# ------------------------------------------------------------------- +# Helper: apply common compile options to a target +# ------------------------------------------------------------------- + +function(monad_zkvm_compile_options target) + monad_compile_options(${target}) + if(MONAD_ZKVM) + target_include_directories(${target} BEFORE PUBLIC "${ZKVM_INCLUDE_DIR}") + target_compile_definitions(${target} PUBLIC "MONAD_ZKVM_${_ZKVM_BACKEND_UPPER}") + endif() + target_include_directories(${target} PUBLIC "${MONAD_ROOT}") +endfunction() + +# ------------------------------------------------------------------- +# Assemble all objects into a single static library +# ------------------------------------------------------------------- + +if(MONAD_ZKVM) + # Extract setjmp/longjmp from the compiler's newlib libc.a. + # libc.a exists at configure time (it ships with the compiler), so we + # can extract the objects here for inclusion in the final archive. + execute_process( + COMMAND ${CMAKE_C_COMPILER} -print-file-name=libc.a + OUTPUT_VARIABLE _libc_a + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + set(_newlib_dir "${CMAKE_CURRENT_BINARY_DIR}/_libc_objs") + file(MAKE_DIRECTORY "${_newlib_dir}") + +# ------------------------------------------------------------------- +# Precompile execution layer (resolution + gas costs + zkvm shim) +# ------------------------------------------------------------------- + +set(MONAD_EXEC_DIR "${MONAD_ROOT}/category/execution/ethereum") + +add_library(monad-zkvm-precompiles OBJECT + "${MONAD_EXEC_DIR}/precompiles.cpp" + "${MONAD_EXEC_DIR}/precompiles_gas_cost_impl.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/precompiles_impl.cpp" +) +monad_zkvm_compile_options(monad-zkvm-precompiles) + +target_include_directories(monad-zkvm-precompiles PRIVATE + "${THIRD_PARTY_DIR}/unordered_dense/include" +) + +target_link_libraries(monad-zkvm-precompiles + PUBLIC evmc::evmc_cpp + PUBLIC intx::intx + PUBLIC ethash::keccak +) + + if(EXISTS "${_libc_a}") + foreach(_obj libc_a-setjmp.o) + execute_process( + COMMAND ${CMAKE_AR} x "${_libc_a}" "${_obj}" + WORKING_DIRECTORY "${_newlib_dir}" + ) + endforeach() + file(GLOB _libc_objs "${_newlib_dir}/*.o") + if(NOT _libc_objs) + message(FATAL_ERROR "Failed to extract setjmp/longjmp objects from ${_libc_a}") + endif() + else() + message(FATAL_ERROR "libc.a not found via compiler query; setjmp/longjmp will be missing") + endif() + + # Bare-metal C/C++ runtime stubs + add_library(monad-zkvm-libc OBJECT + "${CMAKE_CURRENT_SOURCE_DIR}/src/libc.cpp" + ) + monad_zkvm_compile_options(monad-zkvm-libc) + + add_library(monad-zkvm-libstdcxx OBJECT + "${CMAKE_CURRENT_SOURCE_DIR}/src/libstdcxx.cpp" + ) + monad_zkvm_compile_options(monad-zkvm-libstdcxx) + + add_library(monad-zkvm STATIC + $ + $ + $ + $ + $ + $ + ${_libc_objs} + ) +else() + # Native build — no bare-metal stubs needed, system libc/libstdc++ used. + + # Third-party libraries for precompile implementations + include("${MONAD_ROOT}/cmake/precompile_deps.cmake") + + # Precompile library (dispatch + gas costs + implementations) + set(MONAD_EXEC_DIR "${MONAD_ROOT}/category/execution/ethereum") + set(MONAD_EXEC_MONAD_DIR "${MONAD_ROOT}/category/execution/monad") + add_library(monad-precompiles OBJECT + "${MONAD_EXEC_DIR}/precompiles.cpp" + "${MONAD_EXEC_DIR}/precompiles_gas_cost_impl.cpp" + "${MONAD_EXEC_MONAD_DIR}/monad_precompiles_gas_cost_impl.cpp" + "${MONAD_EXEC_DIR}/precompiles_impl.cpp" + "${MONAD_EXEC_DIR}/precompiles_bls12.cpp" + ) + monad_zkvm_compile_options(monad-precompiles) + target_link_libraries(monad-precompiles PRIVATE + evmc::evmc_cpp + ethash::keccak + blst::blst + c-kzg-4844 + silkpre + PkgConfig::crypto++ + immer + unordered_dense + nlohmann_json::nlohmann_json + asmjit + ) + + add_library(monad-zkvm STATIC + $ + $ + $ + $ + $ + ) +endif() diff --git a/category/zkvm/include/category/core/runtime/uint256.hpp b/category/zkvm/include/category/core/runtime/uint256.hpp new file mode 100644 index 0000000000..684c68db02 --- /dev/null +++ b/category/zkvm/include/category/core/runtime/uint256.hpp @@ -0,0 +1,344 @@ +// Copyright (C) 2025-26 Category Labs, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// zkVM drop-in replacement for the upstream x86-specific uint256.hpp. +// Wraps intx::uint256 and exposes the same interface that the rest of +// the Monad VM code expects (member functions, free functions, types). +#ifdef MONAD_ZKVM + + #pragma once + + #include + + #include + + #include + #include + #include + #include + #include + #include + #include + +namespace monad::vm::runtime +{ + // --------------------------------------------------------------- + // uint256_t — inherits from intx::uint256, adds upstream API + // --------------------------------------------------------------- + + struct uint256_t : intx::uint256 + { + using word_type = uint64_t; + static constexpr auto word_num_bits = sizeof(word_type) * 8; + static constexpr auto num_bits = 256; + static constexpr auto num_bytes = num_bits / 8; + static constexpr auto num_words = num_bits / word_num_bits; + + // Inherit all intx constructors + using intx::uint256::uint256; + + // Implicit conversion from intx::uint256 + [[gnu::always_inline]] + constexpr uint256_t(intx::uint256 const &x) noexcept + : intx::uint256{x} + { + } + + // ------ Word / byte access ------ + + [[gnu::always_inline]] + inline uint8_t *as_bytes() noexcept + { + return reinterpret_cast(this); + } + + [[gnu::always_inline]] + inline uint8_t const *as_bytes() const noexcept + { + return reinterpret_cast(this); + } + + // ------ Endian conversion / serialisation ------ + + [[gnu::always_inline]] + inline constexpr uint256_t to_be() const noexcept + { + return intx::to_big_endian( + static_cast(*this)); + } + + [[gnu::always_inline]] + static inline constexpr uint256_t + load_be(uint8_t const (&bytes)[num_bytes]) noexcept + { + return load_le_unsafe(bytes).to_be(); + } + + [[gnu::always_inline]] + static inline constexpr uint256_t + load_be_unsafe(uint8_t const *bytes) noexcept + { + return load_le_unsafe(bytes).to_be(); + } + + [[gnu::always_inline]] + static inline constexpr uint256_t + load_le_unsafe(uint8_t const *bytes) noexcept + { + return intx::le::unsafe::load(bytes); + } + + template + [[gnu::always_inline]] + inline DstT store_be() const noexcept + { + DstT result; + static_assert(sizeof(result.bytes) == num_bytes); + store_be(result.bytes); + return result; + } + + [[gnu::always_inline]] + inline void store_be(uint8_t *dest) const noexcept + { + uint256_t const be = to_be(); + std::memcpy(dest, &be, num_bytes); + } + + // ------ Right-shift type enum (upstream compatibility) ------ + + enum class RightShiftType + { + Arithmetic, + Logical + }; + }; + + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(uint256_t) == 32); + + template + [[gnu::always_inline]] + inline constexpr uint256_t + shift_right(uint256_t const &x, uint256_t shift0) noexcept + { + if constexpr (type == uint256_t::RightShiftType::Logical) { + return x >> shift0; + } + else { + // Arithmetic right shift + int64_t const sign_bit = static_cast(x[3]) & + std::numeric_limits::min(); + uint64_t const fill = static_cast(sign_bit >> 63); + + if (shift0[3] | shift0[2] | shift0[1] | (shift0[0] >= 256)) { + return uint256_t{fill, fill, fill, fill}; + } + + auto const shift = static_cast(shift0[0]); + uint256_t result = x >> uint256_t{shift}; + if (fill && shift > 0) { + uint256_t mask = ~uint256_t{0}; + mask = mask << uint256_t{256 - shift}; + result = static_cast(result) | + static_cast(mask); + } + return result; + } + } + + // --------------------------------------------------------------- + // Byte/bit utilities + // --------------------------------------------------------------- + + [[gnu::always_inline]] + inline constexpr size_t countl_zero(uint256_t const &x) + { + return intx::clz(static_cast(x)); + } + + // --------------------------------------------------------------- + // Misc free functions expected by upstream VM code + // --------------------------------------------------------------- + + inline uint256_t + signextend(uint256_t const &byte_index_256, uint256_t const &x) + { + if (byte_index_256 >= 31) { + return x; + } + uint64_t const byte_index = byte_index_256[0]; + uint64_t const word_index = byte_index >> 3; + uint64_t const word = x[word_index]; + int64_t const signed_word = static_cast(word); + uint64_t const bit_index = (byte_index & 7) * 8; + // NOLINTNEXTLINE(bugprone-signed-char-misuse) + int64_t const signed_byte = static_cast(word >> bit_index); + uint64_t const upper = static_cast(signed_byte) << bit_index; + int64_t const signed_lower = + signed_word & + ~(std::numeric_limits::min() >> (63 - bit_index)); + uint64_t const lower = static_cast(signed_lower); + uint64_t const sign_bits = static_cast(signed_byte >> 63); + uint256_t ret; + for (uint64_t j = 0; j < word_index; ++j) { + ret[j] = x[j]; + } + ret[word_index] = upper | lower; + for (uint64_t j = word_index + 1; j < 4; ++j) { + ret[j] = sign_bits; + } + return ret; + } + + [[gnu::always_inline]] + inline uint256_t sar(uint256_t const &shift, uint256_t const &x) + { + return shift_right(x, shift); + } + + inline uint256_t countr_zero(uint256_t const &x) + { + int total_count = 0; + for (size_t i = 0; i < 4; i++) { + int const count = std::countr_zero(x[i]); + total_count += count; + if (count < 64) { + return uint256_t{total_count}; + } + } + return uint256_t{total_count}; + } + + [[gnu::always_inline]] + inline constexpr bool slt(uint256_t const &x, uint256_t const &y) noexcept + { + auto const x_neg = x[uint256_t::num_words - 1] >> 63; + auto const y_neg = y[uint256_t::num_words - 1] >> 63; + auto const diff = x_neg ^ y_neg; + return (~diff & (x < y)) | (x_neg & ~y_neg); + } + + [[gnu::always_inline]] + inline uint256_t byte(uint256_t const &byte_index_256, uint256_t const &x) + { + if (byte_index_256 >= 32) { + return uint256_t{0}; + } + uint64_t const byte_index = 31 - byte_index_256[0]; + uint64_t const word_index = byte_index >> 3; + uint64_t const word = x[word_index]; + uint64_t const bit_index = (byte_index & 7) << 3; + uint64_t const b = static_cast(word >> bit_index); + return uint256_t{b}; + } + + inline uint256_t + from_bytes(std::size_t n, std::size_t remaining, uint8_t const *src) + { + if (n == 0) { + return 0; + } + uint8_t dst[32] = {}; + std::memcpy(&dst[32 - n], src, std::min(n, remaining)); + return uint256_t::load_be(dst); + } + + inline uint256_t from_bytes(std::size_t const n, uint8_t const *src) + { + return from_bytes(n, n, src); + } + +} + +namespace std +{ + template <> + struct numeric_limits + { + using type = monad::vm::runtime::uint256_t; + + static constexpr bool is_specialized = true; + static constexpr bool is_integer = true; + static constexpr bool is_signed = false; + static constexpr bool is_exact = true; + static constexpr bool has_infinity = false; + static constexpr bool has_quiet_NaN = false; + static constexpr bool has_signaling_NaN = false; + static constexpr float_denorm_style has_denorm = denorm_absent; + static constexpr bool has_denorm_loss = false; + static constexpr float_round_style round_style = round_toward_zero; + static constexpr bool is_iec559 = false; + static constexpr bool is_bounded = true; + static constexpr bool is_modulo = true; + static constexpr int digits = CHAR_BIT * sizeof(type); + static constexpr int digits10 = int(0.3010299956639812 * digits); + static constexpr int max_digits10 = 0; + static constexpr int radix = 2; + static constexpr int min_exponent = 0; + static constexpr int min_exponent10 = 0; + static constexpr int max_exponent = 0; + static constexpr int max_exponent10 = 0; + static constexpr bool traps = std::numeric_limits::traps; + static constexpr bool tinyness_before = false; + + static constexpr type min() noexcept + { + return type{0}; + } + + static constexpr type lowest() noexcept + { + return min(); + } + + static constexpr type max() noexcept + { + return ~type{0}; + } + + static constexpr type epsilon() noexcept + { + return type{0}; + } + + static constexpr type round_error() noexcept + { + return type{0}; + } + + static constexpr type infinity() noexcept + { + return type{0}; + } + + static constexpr type quiet_NaN() noexcept + { + return type{0}; + } + + static constexpr type signaling_NaN() noexcept + { + return type{0}; + } + + static constexpr type denorm_min() noexcept + { + return type{0}; + } + }; +} + +#endif diff --git a/category/zkvm/include/category/zkvm/zkvm_accelerators.h b/category/zkvm/include/category/zkvm/zkvm_accelerators.h new file mode 100644 index 0000000000..658a5700ea --- /dev/null +++ b/category/zkvm/include/category/zkvm/zkvm_accelerators.h @@ -0,0 +1,500 @@ +// Copyright (C) 2025-26 Category Labs, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// TODO(dhil): The above header is placed here to satisfy the license check +// requirement. This file should probably be exempt from the check or be pulled +// from the third party folder? + +/** + * zkVM Cryptographic Accelerators C Interface + * + * This header defines the standard C interface for guest programs to access + * accelerators in zkVMs. + * + * Design Notes: + * - All struct types are sized as multiples of 8 bytes (64-bit word alignment) + * for efficient memory operations, as allocating word-aligned data is cheaper + * in most zkVM implementations. + * - Some types (e.g., RIPEMD-160) are zero-padded to achieve this alignment. + * Since the EVM also attempts to make all inputs aligned to 256-bits, one + * does may not see a difference between the sizes needed for the EVM and the + * sizes needed here. + * + * Usage Notes: + * - Caller MUST ensure all pointers are valid. If a function is called + * with a NULL pointer, the function SHOULD panic. + * - The caller SHOULD allocate and free the input and output memory. + */ + +#ifndef ZKVM_ACCELERATORS_H +#define ZKVM_ACCELERATORS_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* ============================================================================ + * Return codes + * ============================================================================ + */ + +/** + * Status codes returned by zkVM accelerator functions + * + * - 0 indicates success + * - Non-zero indicates failure + */ +typedef enum +{ + ZKVM_EOK = 0, /* Success */ + ZKVM_EFAIL = -1 /* Failure */ +} zkvm_status; + +/* ============================================================================ + * Type definitions + * ============================================================================ + */ + +/* Common byte array types */ +typedef struct +{ + uint8_t data[16]; +} zkvm_bytes_16; + +typedef struct +{ + uint8_t data[32]; +} zkvm_bytes_32; + +typedef struct +{ + uint8_t data[48]; +} zkvm_bytes_48; + +typedef struct +{ + uint8_t data[64]; +} zkvm_bytes_64; + +typedef struct +{ + uint8_t data[96]; +} zkvm_bytes_96; + +typedef struct +{ + uint8_t data[128]; +} zkvm_bytes_128; + +typedef struct +{ + uint8_t data[192]; +} zkvm_bytes_192; + +/* Hash types */ +typedef zkvm_bytes_32 zkvm_keccak256_hash; +typedef zkvm_bytes_32 zkvm_sha256_hash; +typedef zkvm_bytes_32 zkvm_ripemd160_hash; /* 20-byte hash padded to 32 bytes, + last 12 bytes are zero */ + +/* secp256k1 types */ +typedef zkvm_bytes_32 zkvm_secp256k1_hash; +typedef zkvm_bytes_64 zkvm_secp256k1_signature; +typedef zkvm_bytes_64 zkvm_secp256k1_pubkey; + +/* secp256r1 (P-256) types */ +typedef zkvm_bytes_32 zkvm_secp256r1_hash; +typedef zkvm_bytes_64 zkvm_secp256r1_signature; +typedef zkvm_bytes_64 zkvm_secp256r1_pubkey; + +/* BN254 types */ +typedef zkvm_bytes_64 zkvm_bn254_g1_point; +typedef zkvm_bytes_128 zkvm_bn254_g2_point; +typedef zkvm_bytes_32 zkvm_bn254_scalar; + +typedef struct +{ + zkvm_bn254_g1_point g1; + zkvm_bn254_g2_point g2; +} zkvm_bn254_pairing_pair; + +/* BLS12-381 types */ +typedef zkvm_bytes_96 zkvm_bls12_381_g1_point; +typedef zkvm_bytes_192 zkvm_bls12_381_g2_point; +typedef zkvm_bytes_32 zkvm_bls12_381_scalar; + +typedef zkvm_bytes_48 zkvm_bls12_381_fp; +typedef zkvm_bytes_96 zkvm_bls12_381_fp2; + +typedef struct +{ + zkvm_bls12_381_g1_point point; + zkvm_bls12_381_scalar scalar; +} zkvm_bls12_381_g1_msm_pair; + +typedef struct +{ + zkvm_bls12_381_g2_point point; + zkvm_bls12_381_scalar scalar; +} zkvm_bls12_381_g2_msm_pair; + +typedef struct +{ + zkvm_bls12_381_g1_point g1; + zkvm_bls12_381_g2_point g2; +} zkvm_bls12_381_pairing_pair; + +/* BLAKE2f types */ +typedef zkvm_bytes_64 zkvm_blake2f_state; +typedef zkvm_bytes_128 zkvm_blake2f_message; +typedef zkvm_bytes_16 zkvm_blake2f_offset; + +/* KZG types */ +typedef zkvm_bytes_48 zkvm_kzg_commitment; +typedef zkvm_bytes_48 zkvm_kzg_proof; +typedef zkvm_bytes_32 zkvm_kzg_field_element; + +/* ============================================================================ + * Non-Precompile Functions + * ============================================================================ + */ + +/** + * Compute Keccak-256 hash + * + * @param data Pointer to input data + * @param len Length of input data in bytes + * @param[out] output Pointer to output hash + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status +zkvm_keccak256(uint8_t const *data, size_t len, zkvm_keccak256_hash *output); + +/** + * secp256k1 signature verification + * + * Verifies an ECDSA signature on the secp256k1 curve. + * + * @param msg Pointer to message hash + * @param sig Pointer to signature (r || s) + * @param pubkey Pointer to uncompressed public key (x || y) + * @param[out] verified Pointer to bool indicating if signature is valid + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_secp256k1_verify( + zkvm_secp256k1_hash const *msg, zkvm_secp256k1_signature const *sig, + zkvm_secp256k1_pubkey const *pubkey, bool *verified); + +/* ============================================================================ + * Ethereum Precompiles + * + * Note: These methods may not have the same API as the EVM precompiles because + * in most cases, we care about the raw underlying cryptographic primitive. + * ============================================================================ + */ + +/** + * ECRECOVER - Recover public key from signature + * + * Precompile: 0x01 + * + * Implements ecrecover precompile for secp256k1 signature recovery. + * Note: The function as defined on the Ethereum layer returns an address. + * We return a public key and the user will need to call Keccak manually. + * + * + * @param msg Pointer to message hash + * @param sig Pointer to signature (r || s) + * @param recid Recovery ID + * @param[out] output Pointer to output buffer (public key) + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_secp256k1_ecrecover( + zkvm_secp256k1_hash const *msg, zkvm_secp256k1_signature const *sig, + uint8_t recid, zkvm_secp256k1_pubkey *output); + +/** + * Compute SHA-256 hash + * + * Precompile: 0x02 + * + * @param data Pointer to input data + * @param len Length of input data in bytes + * @param[out] output Pointer to output hash + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status +zkvm_sha256(uint8_t const *data, size_t len, zkvm_sha256_hash *output); + +/** + * Compute RIPEMD-160 hash + * + * Precompile: 0x03 + * + * @param data Pointer to input data + * @param len Length of input data in bytes + * @param[out] output Pointer to output hash (20 bytes of hash, last 12 bytes + * zero-padded) + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status +zkvm_ripemd160(uint8_t const *data, size_t len, zkvm_ripemd160_hash *output); + +/** + * The Identity/datacopy function is not provided as it can be implemented + * in the guest program efficiently. + * + * Precompile: 0x04 + */ + +/** + * Modular exponentiation + * + * Precompile: 0x05 + * + * Computes (base^exp) % modulus for arbitrary precision integers. + * + * @param base Pointer to base value bytes + * @param base_len Length of base in bytes + * @param exp Pointer to exponent bytes + * @param exp_len Length of exponent in bytes + * @param modulus Pointer to modulus bytes + * @param mod_len Length of modulus in bytes + * @param[out] output Pointer to output buffer (must be exactly mod_len bytes) + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_modexp( + uint8_t const *base, size_t base_len, uint8_t const *exp, size_t exp_len, + uint8_t const *modulus, size_t mod_len, uint8_t *output); + +/** + * BN254 G1 point addition + * + * Precompile: 0x06 + * EIP-196 + * + * @param p1 Pointer to first point (x || y) + * @param p2 Pointer to second point (x || y) + * @param[out] result Pointer to output point (x || y) + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bn254_g1_add( + zkvm_bn254_g1_point const *p1, zkvm_bn254_g1_point const *p2, + zkvm_bn254_g1_point *result); + +/** + * BN254 G1 scalar multiplication + * + * Precompile: 0x07 + * EIP-196 + * + * @param point Pointer to input point (x || y) + * @param scalar Pointer to scalar + * @param[out] result Pointer to output point (x || y) + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bn254_g1_mul( + zkvm_bn254_g1_point const *point, zkvm_bn254_scalar const *scalar, + zkvm_bn254_g1_point *result); + +/** + * BN254 pairing check + * + * Precompile: 0x08 + * EIP-197 + * + * Checks if the pairing equation holds for the given points. + * + * @param pairs Array of G1-G2 point pairs + * @param num_pairs Number of point pairs + * @param[out] verified Pointer to bool indicating if pairing check passes + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bn254_pairing( + zkvm_bn254_pairing_pair const *pairs, size_t num_pairs, bool *verified); + +/** + * BLAKE2f compression function + * + * Precompile: 0x09 + * EIP-152 + * + * Implements the BLAKE2 compression function F. + * + * BLAKE2f is highly performance-sensitive and often used in tight loops for + * hashing. The in-place update design minimizes memory allocations and copies. + * + * @param rounds Number of rounds (uint32, big-endian) + * @param[in,out] h Pointer to state vector (8 × uint64 little-endian). + * Input: initial state. Output: updated state after + * compression. + * @param m Pointer to message block (16 × uint64 little-endian) + * @param t Pointer to offset counters (2 × uint64 little-endian) + * @param f Final block indicator (1 byte: 0x00 or 0x01) + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + * + * @remark The use of big-endian encoding for the rounds parameter matches the + * specification in EIP-152. + */ +zkvm_status zkvm_blake2f( + uint32_t rounds, zkvm_blake2f_state *h, zkvm_blake2f_message const *m, + zkvm_blake2f_offset const *t, uint8_t f); + +/** + * Point evaluation precompile + * + * Precompile: 0x0a + * EIP-4844 + * + * Verifies a KZG proof for point evaluation. + * + * @param commitment Pointer to KZG commitment + * @param z Pointer to evaluation point + * @param y Pointer to claimed evaluation + * @param proof Pointer to KZG proof + * @param[out] verified Pointer to bool indicating if proof is valid + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_kzg_point_eval( + zkvm_kzg_commitment const *commitment, zkvm_kzg_field_element const *z, + zkvm_kzg_field_element const *y, zkvm_kzg_proof const *proof, + bool *verified); + +/** + * BLS12-381 G1 point addition + * + * Precompile: 0x0b + * EIP-2537 + * + * @param p1 Pointer to first G1 point (Fp x, Fp y) + * @param p2 Pointer to second G1 point (Fp x, Fp y) + * @param[out] result Pointer to output G1 point + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bls12_g1_add( + zkvm_bls12_381_g1_point const *p1, zkvm_bls12_381_g1_point const *p2, + zkvm_bls12_381_g1_point *result); + +/** + * BLS12-381 G1 multi-scalar multiplication + * + * Precompile: 0x0c + * EIP-2537 + * + * @param pairs Pointer to array of point-scalar pairs + * @param num_pairs Number of point-scalar pairs + * @param[out] result Pointer to output G1 point + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bls12_g1_msm( + zkvm_bls12_381_g1_msm_pair const *pairs, size_t num_pairs, + zkvm_bls12_381_g1_point *result); + +/** + * BLS12-381 G2 point addition + * + * Precompile: 0x0d + * EIP-2537 + * + * @param p1 Pointer to first G2 point (Fp2 x, Fp2 y) + * @param p2 Pointer to second G2 point (Fp2 x, Fp2 y) + * @param[out] result Pointer to output G2 point + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bls12_g2_add( + zkvm_bls12_381_g2_point const *p1, zkvm_bls12_381_g2_point const *p2, + zkvm_bls12_381_g2_point *result); + +/** + * BLS12-381 G2 multi-scalar multiplication + * + * Precompile: 0x0e + * EIP-2537 + * + * @param pairs Pointer to array of point-scalar pairs + * @param num_pairs Number of point-scalar pairs + * @param[out] result Pointer to output G2 point + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bls12_g2_msm( + zkvm_bls12_381_g2_msm_pair const *pairs, size_t num_pairs, + zkvm_bls12_381_g2_point *result); + +/** + * BLS12-381 pairing check + * + * Precompile: 0x0f + * EIP-2537 + * + * @param pairs Array of G1-G2 point pairs + * @param num_pairs Number of point pairs + * @param[out] verified Pointer to bool indicating if pairing check passes + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bls12_pairing( + zkvm_bls12_381_pairing_pair const *pairs, size_t num_pairs, bool *verified); + +/** + * BLS12-381 map Fp to G1 + * + * Precompile: 0x10 + * EIP-2537 + * + * @param field_element Pointer to Fp element + * @param[out] result Pointer to output G1 point + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bls12_map_fp_to_g1( + zkvm_bls12_381_fp const *field_element, zkvm_bls12_381_g1_point *result); + +/** + * BLS12-381 map Fp2 to G2 + * + * Precompile: 0x11 + * EIP-2537 + * + * @param field_element Pointer to Fp2 element + * @param[out] result Pointer to output G2 point + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_bls12_map_fp2_to_g2( + zkvm_bls12_381_fp2 const *field_element, zkvm_bls12_381_g2_point *result); + +/** + * secp256r1 (P-256) signature verification + * + * Precompile: 0x100 + * EIP-7212 + * + * @param msg Pointer to message hash + * @param sig Pointer to signature (r || s) + * @param pubkey Pointer to uncompressed public key (x || y) + * @param[out] verified Pointer to bool indicating if signature is valid + * @return ZKVM_EOK on success, ZKVM_EFAIL on failure + */ +zkvm_status zkvm_secp256r1_verify( + zkvm_secp256r1_hash const *msg, zkvm_secp256r1_signature const *sig, + zkvm_secp256r1_pubkey const *pubkey, bool *verified); + +#ifdef __cplusplus +} +#endif + +#endif /* ZKVM_ACCELERATORS_H */ diff --git a/category/zkvm/include/category/zkvm/zkvm_allocator.h b/category/zkvm/include/category/zkvm/zkvm_allocator.h new file mode 100644 index 0000000000..e759ef7fbf --- /dev/null +++ b/category/zkvm/include/category/zkvm/zkvm_allocator.h @@ -0,0 +1,53 @@ +// Copyright (C) 2025-26 Category Labs, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef ZKVM_ALLOCATOR_H +#define ZKVM_ALLOCATOR_H + +#ifdef MONAD_ZKVM + + #include + + #ifdef __cplusplus +extern "C" +{ + #endif + + #if defined(MONAD_ZKVM_ZISK) +void *sys_alloc_aligned(size_t bytes, size_t align); + #elif defined(MONAD_ZKVM_SP1) +void *sp1_alloc_aligned(size_t bytes, size_t align); + #endif + +// Matches std::aligned_alloc(alignment, size) argument order. +inline void *zkvm_aligned_alloc(size_t alignment, size_t size) +{ + #if defined(MONAD_ZKVM_ZISK) + return sys_alloc_aligned(size, alignment); + #elif defined(MONAD_ZKVM_SP1) + return sp1_alloc_aligned(size, alignment); + #else + #error \ + "No zkVM aligned_alloc backend defined (expected MONAD_ZKVM_ZISK or MONAD_ZKVM_SP1)" + #endif +} + + #ifdef __cplusplus +} + #endif + +#endif // MONAD_ZKVM + +#endif // ZKVM_ALLOCATOR_H diff --git a/category/zkvm/include/category/zkvm/zkvm_exit.h b/category/zkvm/include/category/zkvm/zkvm_exit.h new file mode 100644 index 0000000000..0db8bf505b --- /dev/null +++ b/category/zkvm/include/category/zkvm/zkvm_exit.h @@ -0,0 +1,52 @@ +// Copyright (C) 2025-26 Category Labs, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef ZKVM_EXIT_H +#define ZKVM_EXIT_H + +#ifdef MONAD_ZKVM + + #if defined(MONAD_ZKVM_SP1) +void syscall_halt(unsigned char exit_code) __attribute__((noreturn)); + #endif + + #ifdef __cplusplus +extern "C" +{ + #endif + +__attribute__((noreturn)) inline void zkvm_exit(int status) +{ + #if defined(MONAD_ZKVM_ZISK) + // ZisK: ecall with syscall number 93 + register int a0 __asm__("a0") = status; + register int a7 __asm__("a7") = 93; + __asm__ volatile("ecall" : : "r"(a0), "r"(a7)); + __builtin_unreachable(); + #elif defined(MONAD_ZKVM_SP1) + syscall_halt((unsigned char)status); + #else + #error \ + "No zkVM exit backend defined (expected MONAD_ZKVM_ZISK or MONAD_ZKVM_SP1)" + #endif +} + + #ifdef __cplusplus +} + #endif + +#endif // MONAD_ZKVM + +#endif // ZKVM_EXIT_H diff --git a/category/zkvm/src/libc.cpp b/category/zkvm/src/libc.cpp new file mode 100644 index 0000000000..ea6e7b6a4c --- /dev/null +++ b/category/zkvm/src/libc.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2025-26 Category Labs, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Minimal C library functions for bare-metal zkVM. +// The zkVM environment uses a bump allocator — free is a no-op. +#ifdef MONAD_ZKVM + + #include + #include + + #include + #include + #include + +extern "C" +{ + +void *malloc(std::size_t size) +{ + if (size == 0) { + return nullptr; + } + return zkvm_aligned_alloc(16, size); +} + +void free(void *ptr) +{ + // Bump allocator — no deallocation. + (void)ptr; +} + +void *calloc(std::size_t num, std::size_t size) +{ + std::size_t total; + if (__builtin_mul_overflow(num, size, &total)) { + return nullptr; + } + void *ptr = malloc(total); + if (ptr) { + std::memset(ptr, 0, total); + } + return ptr; +} + +void monad_assertion_failed( + char const *, char const *, char const *, long, char const *) +{ + __builtin_trap(); +} + +} // extern "C" + +#endif diff --git a/category/zkvm/src/libstdcxx.cpp b/category/zkvm/src/libstdcxx.cpp new file mode 100644 index 0000000000..cb66e69d93 --- /dev/null +++ b/category/zkvm/src/libstdcxx.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2025-26 Category Labs, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Minimal C++ runtime stubs for bare-metal zkVM. +#ifdef MONAD_ZKVM + + #include + #include + + #include + +// operator new / delete +[[gnu::always_inline]] static inline void *alloc_or_exit(std::size_t size) +{ + if (size == 0) { + zkvm_exit(1); + } + void *ptr = zkvm_aligned_alloc(16, size); + if (!ptr) { + zkvm_exit(1); + } + return ptr; +} + +void *operator new(std::size_t size) +{ + return alloc_or_exit(size); +} + +void *operator new[](std::size_t size) +{ + return alloc_or_exit(size); +} + +void operator delete(void *) noexcept {} + +void operator delete[](void *) noexcept {} + +void operator delete(void *, std::size_t) noexcept {} + +void operator delete[](void *, std::size_t) noexcept {} + +namespace std +{ + [[noreturn]] void terminate() noexcept + { + zkvm_exit(1); + } +} + +#endif diff --git a/category/zkvm/src/precompiles_impl.cpp b/category/zkvm/src/precompiles_impl.cpp new file mode 100644 index 0000000000..ccdde2632b --- /dev/null +++ b/category/zkvm/src/precompiles_impl.cpp @@ -0,0 +1,558 @@ +// Copyright (C) 2025-26 Category Labs, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// zkVM precompiles shim: implements the EVM precompile execute functions +// by calling through the zkvm_accelerators.h C interface to Rust FFI. + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace +{ + // Read from input with zero-padding for short inputs. + void safe_copy( + uint8_t *dst, size_t dst_size, uint8_t const *src, size_t src_size, + size_t offset) + { + std::memset(dst, 0, dst_size); + if (offset < src_size) { + auto const avail = src_size - offset; + auto const to_copy = std::min(avail, dst_size); + std::memcpy(dst, src + offset, to_copy); + } + } + + evmc::bytes32 kzg_to_version_hashed(uint8_t const *commitment) + { + constexpr uint8_t VERSION_HASH_VERSION_KZG = 1; + evmc::bytes32 h; + zkvm_sha256( + commitment, + sizeof(zkvm_kzg_commitment), + reinterpret_cast(&h)); + h.bytes[0] = VERSION_HASH_VERSION_KZG; + return h; + } + + struct bytes64_t + { + uint8_t bytes[64]; + }; + + constexpr bytes64_t blob_precompile_return_value() + { + constexpr std::string_view v{ + "0x0000000000000000000000000000000000000000000000000000000000001000" + "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"}; + constexpr auto r = evmc::from_hex(v); + static_assert(r.has_value()); + return r.value(); + } + + // Strip 16-byte zero padding from EVM BLS12 Fp (64B) to raw Fp (48B). + bool evm_fp_to_raw(uint8_t const *evm64, uint8_t *raw48) + { + for (int i = 0; i < 16; ++i) { + if (evm64[i] != 0) { + return false; + } + } + std::memcpy(raw48, evm64 + 16, 48); + return true; + } + + void raw_fp_to_evm(uint8_t const *raw48, uint8_t *evm64) + { + std::memset(evm64, 0, 16); + std::memcpy(evm64 + 16, raw48, 48); + } + + bool evm_g1_to_zkvm(uint8_t const *evm128, uint8_t *zkvm96) + { + return evm_fp_to_raw(evm128, zkvm96) && + evm_fp_to_raw(evm128 + 64, zkvm96 + 48); + } + + void zkvm_g1_to_evm(uint8_t const *zkvm96, uint8_t *evm128) + { + raw_fp_to_evm(zkvm96, evm128); + raw_fp_to_evm(zkvm96 + 48, evm128 + 64); + } + + bool evm_g2_to_zkvm(uint8_t const *evm256, uint8_t *zkvm192) + { + return evm_fp_to_raw(evm256, zkvm192) && + evm_fp_to_raw(evm256 + 64, zkvm192 + 48) && + evm_fp_to_raw(evm256 + 128, zkvm192 + 96) && + evm_fp_to_raw(evm256 + 192, zkvm192 + 144); + } + + void zkvm_g2_to_evm(uint8_t const *zkvm192, uint8_t *evm256) + { + raw_fp_to_evm(zkvm192, evm256); + raw_fp_to_evm(zkvm192 + 48, evm256 + 64); + raw_fp_to_evm(zkvm192 + 96, evm256 + 128); + raw_fp_to_evm(zkvm192 + 144, evm256 + 192); + } +} + +MONAD_NAMESPACE_BEGIN + +bool init_trusted_setup() +{ + return true; +} + +PrecompileImplResult ecrecover_impl( + std::span const msg, + std::span const sig, uint8_t recid, + std::span const out) +{ + auto const *msg_hash = reinterpret_cast(msg.data()); + // TODO(dhil): Check `sig` is well-formed; the patch + // before had subscript 64 on a `uint8_t[64]`, which I think was copy-pasted + // from the previous implementation, but it used an array of length 128. + auto const *signature = + reinterpret_cast(sig.data()); + + zkvm_secp256k1_pubkey pubkey; + + if (zkvm_secp256k1_ecrecover(msg_hash, signature, recid, &pubkey) != + ZKVM_EOK) { + return {out.data(), 0}; + } + + zkvm_bytes_32 key_hash; + if (zkvm_keccak256(pubkey.data, 64, &key_hash) != ZKVM_EOK) { + return {out.data(), 0}; + } + + std::memcpy(out.data() + 12, key_hash.data + 12, 20); + return {out.data(), 32}; +} + +PrecompileImplResult +sha256_impl(byte_string_view const input, std::span const out) +{ + std::memset(out.data(), 0, 32); + if (zkvm_sha256( + input.data(), + input.size(), + reinterpret_cast(out.data())) != ZKVM_EOK) { + return {out.data(), 0}; + } + return {out.data(), 32}; +} + +PrecompileImplResult +ripemd160_impl(byte_string_view const input, std::span const out) +{ + std::memset(out.data(), 0, 32); + zkvm_ripemd160( + input.data(), + input.size(), + reinterpret_cast(out.data())); + return {out.data(), 32}; +} + +PrecompileImplResult +identity_impl(byte_string_view const input, std::span const out) +{ + MONAD_ASSERT(!input.empty()); + + std::memcpy(out.data(), input.data(), input.size()); + return {out.data(), input.size()}; +} + +PrecompileImplResult +expmod_impl(byte_string_view const input, std::span const out) +{ + // TODO(dhil): This duplicates some of the work of _execute. + uint8_t lengths[96]; + safe_copy(lengths, 96, input.data(), input.size(), 0); + + uint64_t const base_len = u64_be::unsafe_from(&lengths[24]).native(); + uint64_t const exp_len = u64_be::unsafe_from(&lengths[56]).native(); + uint64_t const mod_len = u64_be::unsafe_from(&lengths[88]).native(); + + if (mod_len == 0) { + return {out.data(), 0}; + } + + auto const data = input.data() + 96; + std::memset(out.data(), 0, out.size()); + zkvm_modexp( + data, + base_len, + data + base_len, + exp_len, + data + base_len + exp_len, + mod_len, + out.data()); + return {out.data(), out.size()}; +} + +PrecompileImplResult +ecadd_impl(byte_string_view const input, std::span const out) +{ + uint8_t d[128]; + safe_copy(d, 128, input.data(), input.size(), 0); + auto const *p1 = reinterpret_cast(&d[0]); + auto const *p2 = reinterpret_cast(&d[64]); + + if (zkvm_bn254_g1_add( + p1, p2, reinterpret_cast(out.data())) != + ZKVM_EOK) { + return {nullptr, 0}; + } + return {out.data(), 64}; +} + +PrecompileImplResult +ecmul_impl(byte_string_view const input, std::span const out) +{ + uint8_t d[96]; + safe_copy(d, 96, input.data(), input.size(), 0); + + auto const *point = reinterpret_cast(&d[0]); + auto const *scalar = reinterpret_cast(&d[64]); + + if (zkvm_bn254_g1_mul( + point, + scalar, + reinterpret_cast(out.data())) != ZKVM_EOK) { + return {nullptr, 0}; + } + return {out.data(), 64}; +} + +PrecompileImplResult +snarkv_impl(byte_string_view const input, std::span const out) +{ + auto const k = input.size() / 192; + + std::memset(out.data(), 0, 32); + if (k == 0) { + out.data()[31] = 1; + return {out.data(), 32}; + } + + auto const *pairs = + reinterpret_cast(input.data()); + bool verified = false; + if (zkvm_bn254_pairing(pairs, k, &verified) != ZKVM_EOK) { + return {nullptr, 0}; + } + + out.data()[31] = verified ? 1 : 0; + return {out.data(), 32}; +} + +PrecompileImplResult +blake2bf_impl(byte_string_view const input, std::span const out) +{ + if (input.size() != 213) { + return {nullptr, 0}; + } + + uint8_t const f = input[212]; + if (f != 0 && f != 1) { + return {nullptr, 0}; + } + + uint32_t const rounds = u32_be::unsafe_from(input.data()).native(); + + std::memcpy(out.data(), input.data() + 4, 64); + + auto *h = reinterpret_cast(out.data()); + auto const *m = + reinterpret_cast(input.data() + 68); + auto const *t = + reinterpret_cast(input.data() + 196); + + if (zkvm_blake2f(rounds, h, m, t, f) != ZKVM_EOK) { + return {nullptr, 0}; + } + + return {out.data(), 64}; +} + +PrecompileImplResult +point_evaluation_impl(byte_string_view input, std::span const out) +{ + if (input.size() != 192) { + return {nullptr, 0}; + } + + evmc::bytes32 versioned_hash; + std::memcpy(versioned_hash.bytes, input.data(), sizeof(evmc::bytes32)); + + auto const *const z = reinterpret_cast( + input.substr(32).data()); + auto const *const y = reinterpret_cast( + input.substr(64).data()); + auto const *const commitment_data = input.substr(96).data(); + auto const *const commitment = + reinterpret_cast(commitment_data); + auto const *const proof = + reinterpret_cast(input.substr(144).data()); + + if (versioned_hash != kzg_to_version_hashed(commitment_data)) { + return {nullptr, 0}; + } + + bool ok{false}; + zkvm_kzg_point_eval(commitment, z, y, proof, &ok); + if (!ok) { + return {nullptr, 0}; + } + + std::memcpy( + out.data(), + blob_precompile_return_value().bytes, + sizeof(zkvm_bytes_64)); + return {out.data(), 64}; +} + +PrecompileImplResult bls12_g1_add_impl( + byte_string_view const input, std::span const out) +{ + if (input.size() != 256) { + return {nullptr, 0}; + } + + zkvm_bls12_381_g1_point p1, p2; + if (!evm_g1_to_zkvm(input.data(), p1.data) || + !evm_g1_to_zkvm(input.data() + 128, p2.data)) { + return {nullptr, 0}; + } + + zkvm_bls12_381_g1_point result_point; + if (zkvm_bls12_g1_add(&p1, &p2, &result_point) != ZKVM_EOK) { + return {nullptr, 0}; + } + + zkvm_g1_to_evm(result_point.data, out.data()); + return {out.data(), 128}; +} + +PrecompileImplResult bls12_g1_msm_impl( + byte_string_view const input, std::span const out) +{ + auto const k = input.size() / 160; + if (k == 0 || input.size() % 160 != 0) { + return {nullptr, 0}; + } + + auto *pairs = static_cast( + std::malloc(k * sizeof(zkvm_bls12_381_g1_msm_pair))); + MONAD_ASSERT(pairs != nullptr); + for (size_t i = 0; i < k; ++i) { + auto const *entry = input.data() + i * 160; + if (!evm_g1_to_zkvm(entry, pairs[i].point.data)) { + std::free(pairs); + return {nullptr, 0}; + } + std::memcpy(pairs[i].scalar.data, entry + 128, 32); + } + + zkvm_bls12_381_g1_point result_point; + auto const status = zkvm_bls12_g1_msm(pairs, k, &result_point); + std::free(pairs); + + if (status != ZKVM_EOK) { + return {nullptr, 0}; + } + + zkvm_g1_to_evm(result_point.data, out.data()); + return {out.data(), 128}; +} + +PrecompileImplResult bls12_g2_add_impl( + byte_string_view const input, std::span const out) +{ + if (input.size() != 512) { + return {nullptr, 0}; + } + + zkvm_bls12_381_g2_point p1, p2; + if (!evm_g2_to_zkvm(input.data(), p1.data) || + !evm_g2_to_zkvm(input.data() + 256, p2.data)) { + return {nullptr, 0}; + } + + zkvm_bls12_381_g2_point result_point; + if (zkvm_bls12_g2_add(&p1, &p2, &result_point) != ZKVM_EOK) { + return {nullptr, 0}; + } + + zkvm_g2_to_evm(result_point.data, out.data()); + return {out.data(), 256}; +} + +PrecompileImplResult bls12_g2_msm_impl( + byte_string_view const input, std::span const out) +{ + auto const k = input.size() / 288; + if (k == 0 || input.size() % 288 != 0) { + return {nullptr, 0}; + } + + auto *pairs = static_cast( + std::malloc(k * sizeof(zkvm_bls12_381_g2_msm_pair))); + + for (size_t i = 0; i < k; ++i) { + auto const *entry = input.data() + i * 288; + if (!evm_g2_to_zkvm(entry, pairs[i].point.data)) { + std::free(pairs); + return {nullptr, 0}; + } + std::memcpy(pairs[i].scalar.data, entry + 256, 32); + } + + zkvm_bls12_381_g2_point result_point; + auto const status = zkvm_bls12_g2_msm(pairs, k, &result_point); + std::free(pairs); + + if (status != ZKVM_EOK) { + return {nullptr, 0}; + } + + zkvm_g2_to_evm(result_point.data, out.data()); + return {out.data(), 256}; +} + +PrecompileImplResult bls12_pairing_check_impl( + byte_string_view const input, std::span const out) +{ + auto const k = input.size() / 384; + if (input.size() % 384 != 0) { + return {nullptr, 0}; + } + + if (k == 0) { + std::memset(out.data(), 0, 32); + out.data()[31] = 1; + return {out.data(), 32}; + } + + auto *pairs = static_cast( + std::malloc(k * sizeof(zkvm_bls12_381_pairing_pair))); + + for (size_t i = 0; i < k; ++i) { + auto const *entry = input.data() + i * 384; + if (!evm_g1_to_zkvm(entry, pairs[i].g1.data) || + !evm_g2_to_zkvm(entry + 128, pairs[i].g2.data)) { + std::free(pairs); + return {nullptr, 0}; + } + } + + bool verified = false; + auto const status = zkvm_bls12_pairing(pairs, k, &verified); + std::free(pairs); + + if (status != ZKVM_EOK) { + return {nullptr, 0}; + } + + std::memset(out.data(), 0, 32); + out.data()[31] = verified ? 1 : 0; + return {out.data(), 32}; +} + +PrecompileImplResult bls12_map_fp_to_g1_impl( + byte_string_view const input, std::span const out) +{ + if (input.size() != 64) { + return {nullptr, 0}; + } + + zkvm_bls12_381_fp fp; + if (!evm_fp_to_raw(input.data(), fp.data)) { + return {nullptr, 0}; + } + + zkvm_bls12_381_g1_point result_point; + if (zkvm_bls12_map_fp_to_g1(&fp, &result_point) != ZKVM_EOK) { + return {nullptr, 0}; + } + + zkvm_g1_to_evm(result_point.data, out.data()); + return {out.data(), 128}; +} + +PrecompileImplResult bls12_map_fp2_to_g2_impl( + byte_string_view const input, std::span const out) +{ + if (input.size() != 128) { + return {nullptr, 0}; + } + + zkvm_bls12_381_fp2 fp2; + if (!evm_fp_to_raw(input.data(), fp2.data) || + !evm_fp_to_raw(input.data() + 64, fp2.data + 48)) { + return {nullptr, 0}; + } + + zkvm_bls12_381_g2_point result_point; + if (zkvm_bls12_map_fp2_to_g2(&fp2, &result_point) != ZKVM_EOK) { + return {nullptr, 0}; + } + + zkvm_g2_to_evm(result_point.data, out.data()); + return {out.data(), 256}; +} + +PrecompileImplResult +p256_verify_impl(byte_string_view const input, std::span const out) +{ + if (input.size() != 160) { + return {nullptr, 0}; + } + + auto const *msg = + reinterpret_cast(input.data()); + auto const *sig = + reinterpret_cast(input.data() + 32); + auto const *pubkey = + reinterpret_cast(input.data() + 96); + + bool verified = false; + if (zkvm_secp256r1_verify(msg, sig, pubkey, &verified) != ZKVM_EOK) { + return {nullptr, 0}; + } + + if (!verified) { + return {nullptr, 0}; + } + + std::memset(out.data(), 0, 32); + out.data()[31] = 1; + return {out.data(), 32}; +} + +MONAD_NAMESPACE_END diff --git a/cmake/blst.cmake b/cmake/blst.cmake index 1bc28c8993..dbcab66866 100644 --- a/cmake/blst.cmake +++ b/cmake/blst.cmake @@ -1,4 +1,4 @@ -set(BLST_SOURCE_DIR "${PROJECT_SOURCE_DIR}/third_party/blst") +set(BLST_SOURCE_DIR "${THIRD_PARTY_DIR}/blst") enable_language(ASM) diff --git a/cmake/compile_options.cmake b/cmake/compile_options.cmake new file mode 100644 index 0000000000..142d6b5f3c --- /dev/null +++ b/cmake/compile_options.cmake @@ -0,0 +1,61 @@ +function(monad_compile_options target) + set_property(TARGET ${target} PROPERTY C_STANDARD 23) + set_property(TARGET ${target} PROPERTY C_STANDARD_REQUIRED ON) + set_property(TARGET ${target} PROPERTY CXX_STANDARD 23) + set_property(TARGET ${target} PROPERTY CXX_STANDARD_REQUIRED ON) + + target_compile_options(${target} PRIVATE -Wall -Wextra -Wconversion -Werror) + target_compile_definitions(${target} PUBLIC "_GNU_SOURCE") + + target_compile_options( + ${target} PRIVATE $<$:-Wno-missing-field-initializers>) + + if(MONAD_ZKVM) + # GCC 15+ checks uninstantiated template bodies for errors (-Wtemplate-body). + # This fires on constexpr-guarded AVX2 code paths that are never instantiated + # on non-x86 targets. + target_compile_options( + ${target} PRIVATE $<$:-Wno-template-body>) + + # GCC 15+ warns when reference parameters are forwarded through musttail + # calls. The interpreter's threaded dispatch passes references to objects + # that live in earlier stack frames, not the current one, so this is safe. + target_compile_options( + ${target} PRIVATE $<$:-Wno-maybe-musttail-local-addr>) + endif() + + target_compile_options(${target} PRIVATE $<$:-Og>) + + target_compile_definitions(${target} PUBLIC QUILL_ROOT_LOGGER_ONLY) + + if(MONAD_COMPILER_TESTING) + target_compile_definitions(${target} PUBLIC "MONAD_COMPILER_TESTING=1") + target_compile_definitions(${target} + PUBLIC "MONAD_CORE_FORCE_DEBUG_ASSERT=1") + endif() + + if(MONAD_COMPILER_STATS) + target_compile_definitions(${target} PUBLIC "MONAD_COMPILER_STATS=1") + endif() + + if(MONAD_COMPILER_HOT_PATH_STATS) + target_compile_definitions(${target} + PUBLIC "MONAD_COMPILER_HOT_PATH_STATS=1") + endif() + + target_compile_options( + ${target} + PUBLIC $<$:-Wno-attributes=clang::no_sanitize>) + + # this is needed to turn off ranges support in nlohmann_json, because the + # ranges standard header triggers a clang bug which is fixed in trunk but not + # currently available to us + # https://gcc.gnu.org/bugzilla//show_bug.cgi?id=109647 + target_compile_definitions(${target} PUBLIC "JSON_HAS_RANGES=0") + + if(MONAD_ZKVM) + # NDEBUG is required because the bare-metal zkVM environment does not link + # the C standard library, so __assert_func (used by assert()) is unavailable. + target_compile_definitions(${target} PUBLIC MONAD_ZKVM NDEBUG) + endif() +endfunction() diff --git a/cmake/precompile_deps.cmake b/cmake/precompile_deps.cmake new file mode 100644 index 0000000000..ca76a45a3a --- /dev/null +++ b/cmake/precompile_deps.cmake @@ -0,0 +1,56 @@ +# Copyright (C) 2025-26 Category Labs, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Shared setup for third-party libraries needed by the precompile +# implementations. Included by both the root CMakeLists.txt (full build) +# and category/zkvm/CMakeLists.txt (standalone zkvm build). +# +# Expects THIRD_PARTY_DIR to be set by the caller. + +# blst (BLS12-381) +include("${CMAKE_CURRENT_LIST_DIR}/blst.cmake") + +# silkpre (brings in secp256k1, libff, gmp) +set(OPTIONAL_BUILD_TESTS OFF) +set(OLD_CMAKE_POLICY_VERSION_MINIMUM "${CMAKE_POLICY_VERSION_MINIMUM}") +set(CMAKE_POLICY_VERSION_MINIMUM "3.5") +add_subdirectory("${THIRD_PARTY_DIR}/silkpre" "${CMAKE_CURRENT_BINARY_DIR}/_silkpre") +set(CMAKE_POLICY_VERSION_MINIMUM "${OLD_CMAKE_POLICY_VERSION_MINIMUM}") +# Undo the ccache injection that silkpre/libff does +set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE) +set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK) + +# c-kzg-4844 +add_subdirectory("${THIRD_PARTY_DIR}/c-kzg-4844-builder" "${CMAKE_CURRENT_BINARY_DIR}/_c-kzg-4844") + +# cryptopp (system library) +find_package(PkgConfig REQUIRED) +pkg_check_modules(crypto++ REQUIRED IMPORTED_TARGET libcrypto++) + +# immer +option(immer_BUILD_TESTS OFF) +option(immer_BUILD_EXAMPLES OFF) +option(immer_BUILD_EXTRAS OFF) +add_subdirectory("${THIRD_PARTY_DIR}/immer" "${CMAKE_CURRENT_BINARY_DIR}/_immer" SYSTEM) + +# nlohmann_json +add_subdirectory("${THIRD_PARTY_DIR}/nlohmann_json" "${CMAKE_CURRENT_BINARY_DIR}/_nlohmann_json" SYSTEM) + +# unordered_dense +add_subdirectory("${THIRD_PARTY_DIR}/unordered_dense" "${CMAKE_CURRENT_BINARY_DIR}/_unordered_dense") + +# asmjit +set(ASMJIT_STATIC ON) +add_subdirectory("${THIRD_PARTY_DIR}/asmjit" "${CMAKE_CURRENT_BINARY_DIR}/_asmjit")