diff --git a/category/crypto/CMakeLists.txt b/category/crypto/CMakeLists.txt index 9af25c0c9f..84b3aae86f 100644 --- a/category/crypto/CMakeLists.txt +++ b/category/crypto/CMakeLists.txt @@ -37,13 +37,16 @@ endif() target_include_directories(monad_crypto PRIVATE "${THIRD_PARTY_DIR}/openssl") target_sources(monad_crypto PRIVATE "keccak_impl.S") -# Precompiles vendored from silkpre: SHA-256, RIPEMD-160, BLAKE2F, ECRECOVER. +# Precompiles vendored from silkpre: SHA-256, RIPEMD-160, BLAKE2F, ECRECOVER, +# and bn128 bn_add, bn_mul, snarkv. # The sources stay under third_party/ with their LICENSE and NOTICE set(SILKPRE_VENDOR_DIR "${THIRD_PARTY_DIR}/silkpre_vendor/src") target_sources(monad_crypto PRIVATE "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/blake2b.c" "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/blake2b.h" + "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/bn128.cpp" + "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/bn128.hpp" "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/ecdsa.c" "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/ecdsa.h" "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/rmd160.c" @@ -53,8 +56,8 @@ target_sources(monad_crypto PRIVATE target_include_directories(monad_crypto PUBLIC "${SILKPRE_VENDOR_DIR}") -# ecdsa needs libsecp256k1 -target_link_libraries(monad_crypto PUBLIC secp256k1) +# ecdsa needs libsecp256k1 and bn128 needs libff +target_link_libraries(monad_crypto PUBLIC secp256k1 PUBLIC ff) # Silence the warnings the vendored sources legitimately trip on; the upstream # silkpre build doesn't enforce them. @@ -63,5 +66,6 @@ set_source_files_properties( "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/ecdsa.c" "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/rmd160.c" "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/sha256.c" + "${SILKPRE_VENDOR_DIR}/category/crypto/silkpre_vendor/bn128.cpp" TARGET_DIRECTORY monad_crypto - PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-sign-conversion") \ No newline at end of file + PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-sign-conversion") diff --git a/category/execution/ethereum/precompiles_impl.hpp b/category/execution/ethereum/precompiles_impl.hpp index 6e4358f4b7..8cc42144f0 100644 --- a/category/execution/ethereum/precompiles_impl.hpp +++ b/category/execution/ethereum/precompiles_impl.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -48,8 +49,6 @@ #include #include -#include - #include #include #include @@ -111,18 +110,6 @@ bool init_trusted_setup() return g_trustedSetup.has_value(); } -// TODO: remove silkpre -template -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 PrecompileResult::failure(); - } - return {EVMC_SUCCESS, output, output_size}; -} - [[gnu::always_inline]] inline PrecompileImplResult ecrecover_impl( std::span msg, std::span sig, uint8_t recid, std::span const out) @@ -162,29 +149,19 @@ ripemd160_impl(byte_string_view const input, std::span const out) [[gnu::always_inline]] inline PrecompileImplResult ecadd_impl(byte_string_view const input, std::span const out) { - auto const [output, output_size] = - silkpre_bn_add_run(input.data(), input.size()); - if (output == nullptr) { - MONAD_ASSERT(output_size == 0); + if (!monad_bn_add(out.data(), input.data(), input.size())) { return PrecompileImplResult::failure(); } - std::memcpy(out.data(), output, output_size); - std::free(output); - return {out.data(), output_size}; + return {out.data(), out.size()}; } [[gnu::always_inline]] inline PrecompileImplResult ecmul_impl(byte_string_view const input, std::span const out) { - auto const [output, output_size] = - silkpre_bn_mul_run(input.data(), input.size()); - if (output == nullptr) { - MONAD_ASSERT(output_size == 0); + if (!monad_bn_mul(out.data(), input.data(), input.size())) { return PrecompileImplResult::failure(); } - std::memcpy(out.data(), output, output_size); - std::free(output); - return {out.data(), output_size}; + return {out.data(), out.size()}; } [[gnu::always_inline]] inline PrecompileImplResult @@ -245,15 +222,10 @@ identity_impl(byte_string_view const input, std::span const out) [[gnu::always_inline]] inline PrecompileImplResult snarkv_impl(byte_string_view const input, std::span const out) { - auto const [output, output_size] = - silkpre_snarkv_run(input.data(), input.size()); - if (output == nullptr) { - MONAD_ASSERT(output_size == 0); + if (!monad_snarkv(out.data(), input.data(), input.size())) { return PrecompileImplResult::failure(); } - std::memcpy(out.data(), output, output_size); - std::free(output); - return {out.data(), output_size}; + return {out.data(), out.size()}; } [[gnu::always_inline]] inline PrecompileImplResult diff --git a/third_party/silkpre_vendor/NOTICE b/third_party/silkpre_vendor/NOTICE index 2442753705..d724968a6c 100644 --- a/third_party/silkpre_vendor/NOTICE +++ b/third_party/silkpre_vendor/NOTICE @@ -19,6 +19,9 @@ preserved in the file headers. ecdsa.c / ecdsa.h are derived from the Silkpre project implementation under the Silkpre Apache-2.0 copyright. +bn128.cpp / bn128.hpp are derived from the Silkpre project implementation +in precompile.cpp, under the Silkpre Apache-2.0 copyright. + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.cpp b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.cpp new file mode 100644 index 0000000000..8c4709eee8 --- /dev/null +++ b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.cpp @@ -0,0 +1,239 @@ +/* + Copyright 2022 The Silkpre Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Modified 2026 by Category Labs: +// - alt_bn128 (EIP-196 / EIP-197) precompiles from silkpre/precompile.cpp +// - Rename to use monad prefixes +// - Return the result in a caller-provided buffer instead of SilkpreOutput + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +// Utility functions for zkSNARK related precompiled contracts. +// See Yellow Paper, Appendix E "Precompiled Contracts", as well as +// https://eips.ethereum.org/EIPS/eip-196 +// https://eips.ethereum.org/EIPS/eip-197 +using Scalar = libff::bigint; + +// Must be called prior to invoking any other method. +// May be called many times from multiple threads. +static void init_libff() noexcept { + // magic static + [[maybe_unused]] static bool initialized = []() noexcept { + libff::inhibit_profiling_info = true; + libff::inhibit_profiling_counters = true; + libff::alt_bn128_pp::init_public_params(); + return true; + }(); +} + +static Scalar to_scalar(const uint8_t bytes_be[32]) noexcept { + mpz_t m; + mpz_init(m); + mpz_import(m, 32, /*order=*/1, /*size=*/1, /*endian=*/0, /*nails=*/0, + bytes_be); + Scalar out{m}; + mpz_clear(m); + return out; +} + +// Notation warning: Yellow Paper's p is the same libff's q. +// Returns x < p (YP notation). +static bool valid_element_of_fp(const Scalar &x) noexcept { + return mpn_cmp(x.data, libff::alt_bn128_modulus_q.data, + libff::alt_bn128_q_limbs) < 0; +} + +static std::optional +decode_g1_element(const uint8_t bytes_be[64]) noexcept { + Scalar x{to_scalar(bytes_be)}; + if (!valid_element_of_fp(x)) { + return {}; + } + + Scalar y{to_scalar(bytes_be + 32)}; + if (!valid_element_of_fp(y)) { + return {}; + } + + if (x.is_zero() && y.is_zero()) { + return libff::alt_bn128_G1::zero(); + } + + libff::alt_bn128_G1 point{x, y, libff::alt_bn128_Fq::one()}; + if (!point.is_well_formed()) { + return {}; + } + return point; +} + +static std::optional +decode_fp2_element(const uint8_t bytes_be[64]) noexcept { + // big-endian encoding + Scalar c0{to_scalar(bytes_be + 32)}; + Scalar c1{to_scalar(bytes_be)}; + + if (!valid_element_of_fp(c0) || !valid_element_of_fp(c1)) { + return {}; + } + + return libff::alt_bn128_Fq2{c0, c1}; +} + +static std::optional +decode_g2_element(const uint8_t bytes_be[128]) noexcept { + std::optional x{decode_fp2_element(bytes_be)}; + if (!x) { + return {}; + } + + std::optional y{decode_fp2_element(bytes_be + 64)}; + if (!y) { + return {}; + } + + if (x->is_zero() && y->is_zero()) { + return libff::alt_bn128_G2::zero(); + } + + libff::alt_bn128_G2 point{*x, *y, libff::alt_bn128_Fq2::one()}; + if (!point.is_well_formed()) { + return {}; + } + + if (!(libff::alt_bn128_G2::order() * point).is_zero()) { + // wrong order, doesn't belong to the subgroup G2 + return {}; + } + + return point; +} + +static void encode_g1_element(libff::alt_bn128_G1 p, + uint8_t out[64]) noexcept { + std::memset(out, 0, 64); + if (p.is_zero()) { + return; + } + + p.to_affine_coordinates(); + + auto x{p.X.as_bigint()}; + auto y{p.Y.as_bigint()}; + + // Here we convert little-endian data to big-endian output + static_assert(sizeof(x.data) == 32); + + std::memcpy(&out[0], y.data, 32); + std::memcpy(&out[32], x.data, 32); + + std::reverse(out, out + 64); +} + +bool monad_bn_add(uint8_t out[64], const uint8_t *ptr, size_t len) { + std::basic_string input(128, '\0'); + if (len != 0) { + std::memcpy(input.data(), ptr, std::min(len, 128uz)); + } + + init_libff(); + + std::optional x{decode_g1_element(input.data())}; + if (!x) { + return false; + } + + std::optional y{decode_g1_element(&input[64])}; + if (!y) { + return false; + } + + libff::alt_bn128_G1 sum{*x + *y}; + encode_g1_element(sum, out); + return true; +} + +bool monad_bn_mul(uint8_t out[64], const uint8_t *ptr, size_t len) { + std::basic_string input(96, '\0'); + if (len != 0) { + std::memcpy(input.data(), ptr, std::min(len, 96uz)); + } + + init_libff(); + + std::optional x{decode_g1_element(input.data())}; + if (!x) { + return false; + } + + Scalar n{to_scalar(&input[64])}; + + libff::alt_bn128_G1 product{n * *x}; + encode_g1_element(product, out); + return true; +} + +static constexpr size_t kSnarkvStride{192}; + +bool monad_snarkv(uint8_t out[32], const uint8_t *input, size_t len) { + if (len % kSnarkvStride != 0) { + return false; + } + size_t k{len / kSnarkvStride}; + + init_libff(); + using namespace libff; + + static const auto one{alt_bn128_Fq12::one()}; + auto accumulator{one}; + + for (size_t i{0}; i < k; ++i) { + std::optional a{ + decode_g1_element(&input[i * kSnarkvStride])}; + if (!a) { + return false; + } + std::optional b{ + decode_g2_element(&input[i * kSnarkvStride + 64])}; + if (!b) { + return false; + } + + if (a->is_zero() || b->is_zero()) { + continue; + } + + accumulator = accumulator * alt_bn128_miller_loop( + alt_bn128_precompute_G1(*a), + alt_bn128_precompute_G2(*b)); + } + + std::memset(out, 0, 32); + if (alt_bn128_final_exponentiation(accumulator) == one) { + out[31] = 1; + } + return true; +} diff --git a/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.hpp b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.hpp new file mode 100644 index 0000000000..a41a22928b --- /dev/null +++ b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.hpp @@ -0,0 +1,34 @@ +/* + Copyright 2022 The Silkpre Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Modified 2026 by Category Labs: +// - alt_bn128 (EIP-196 / EIP-197) precompiles from silkpre/precompile.cpp +// - Rename to use monad prefixes +// - Return the result in a caller-provided buffer instead of SilkpreOutput + +#pragma once + +#include +#include + +// EIP-196: Precompiled contract for addition on the elliptic curve alt_bn128 +bool monad_bn_add(uint8_t out[64], const uint8_t *input, size_t len); + +// EIP-196: Precompiled contract for multiplication on the elliptic curve alt_bn128 +bool monad_bn_mul(uint8_t out[64], const uint8_t *input, size_t len); + +// EIP-197: Precompiled contracts for optimal ate pairing check on the elliptic curve alt_bn128 +bool monad_snarkv(uint8_t out[32], const uint8_t *input, size_t len);