From b4a616af9f27b988049367f891b662312a63eeaf Mon Sep 17 00:00:00 2001 From: rkeleti Date: Thu, 27 Aug 2026 04:57:43 -0400 Subject: [PATCH 1/2] Copy bn128 functions from silkpre @ bbc3498e Verbatim copies of the bn128 functions from silkpre without modifications. Monad-specific changes follow. --- category/crypto/CMakeLists.txt | 12 +- third_party/silkpre_vendor/NOTICE | 3 + .../category/crypto/silkpre_vendor/bn128.cpp | 240 ++++++++++++++++++ .../category/crypto/silkpre_vendor/bn128.hpp | 37 +++ 4 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.cpp create mode 100644 third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.hpp diff --git a/category/crypto/CMakeLists.txt b/category/crypto/CMakeLists.txt index 9af25c0c9f..8afdc10649 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 PRIVATE 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/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..ca0f4e6b99 --- /dev/null +++ b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.cpp @@ -0,0 +1,240 @@ +/* + 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. +*/ + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +static void right_pad(std::basic_string &str, + const size_t min_size) noexcept { + if (str.length() < min_size) { + str.resize(min_size, '\0'); + } +} + +// 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 std::basic_string encode_g1_element(libff::alt_bn128_G1 p) noexcept { + std::basic_string out(64, '\0'); + if (p.is_zero()) { + return out; + } + + 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.begin(), out.end()); + return out; +} + +SilkpreOutput silkpre_bn_add_run(const uint8_t* ptr, size_t len) { + std::basic_string input(ptr, len); + right_pad(input, 128); + + init_libff(); + + std::optional x{decode_g1_element(input.data())}; + if (!x) { + return {nullptr, 0}; + } + + std::optional y{decode_g1_element(&input[64])}; + if (!y) { + return {nullptr, 0}; + } + + libff::alt_bn128_G1 sum{*x + *y}; + const std::basic_string res{encode_g1_element(sum)}; + + uint8_t* out{static_cast(std::malloc(res.length()))}; + std::memcpy(out, res.data(), res.length()); + return {out, res.length()}; +} + +SilkpreOutput silkpre_bn_mul_run(const uint8_t* ptr, size_t len) { + std::basic_string input(ptr, len); + right_pad(input, 96); + + init_libff(); + + std::optional x{decode_g1_element(input.data())}; + if (!x) { + return {nullptr, 0}; + } + + Scalar n{to_scalar(&input[64])}; + + libff::alt_bn128_G1 product{n * *x}; + const std::basic_string res{encode_g1_element(product)}; + + uint8_t* out{static_cast(std::malloc(res.length()))}; + std::memcpy(out, res.data(), res.length()); + return {out, res.length()}; +} + +static constexpr size_t kSnarkvStride{192}; + +SilkpreOutput silkpre_snarkv_run(const uint8_t* input, size_t len) { + if (len % kSnarkvStride != 0) { + return {nullptr, 0}; + } + 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 {nullptr, 0}; + } + std::optional b{decode_g2_element(&input[i * kSnarkvStride + 64])}; + if (!b) { + return {nullptr, 0}; + } + + if (a->is_zero() || b->is_zero()) { + continue; + } + + accumulator = accumulator * alt_bn128_miller_loop(alt_bn128_precompute_G1(*a), alt_bn128_precompute_G2(*b)); + } + + uint8_t* out{static_cast(std::malloc(32))}; + std::memset(out, 0, 32); + if (alt_bn128_final_exponentiation(accumulator) == one) { + out[31] = 1; + } + return {out, 32}; +} 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..50b7b7fc1f --- /dev/null +++ b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.hpp @@ -0,0 +1,37 @@ +/* + 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. +*/ + +#ifndef SILKPRE_BN128_HPP_ +#define SILKPRE_BN128_HPP_ + +#include +#include + +typedef struct SilkpreOutput { + uint8_t* data; // Has to be freed if not NULL!!! + size_t size; +} SilkpreOutput; + +// EIP-196: Precompiled contract for addition on the elliptic curve alt_bn128 +SilkpreOutput silkpre_bn_add_run(const uint8_t* input, size_t len); + +// EIP-196: Precompiled contract for multiplication on the elliptic curve alt_bn128 +SilkpreOutput silkpre_bn_mul_run(const uint8_t* input, size_t len); + +// EIP-197: Precompiled contracts for optimal ate pairing check on the elliptic curve alt_bn128 +SilkpreOutput silkpre_snarkv_run(const uint8_t* input, size_t len); + +#endif // SILKPRE_BN128_HPP_ From 99141aff3eda714393e67dbf88ab094bc80fc21d Mon Sep 17 00:00:00 2001 From: rkeleti Date: Thu, 27 Aug 2026 05:06:52 -0400 Subject: [PATCH 2/2] Wire in bn128 functions Vendored code changed from silkpre to monad prefixes. We no longer technically need the silkpre git submodule, but it provides libsecp256k1 and libff that the vendored precompiles rely on. Once it is suitable to make libsecp256k1 a system dependency, we can also add libff as a git submodule and remove silkpre. --- category/crypto/CMakeLists.txt | 2 +- .../execution/ethereum/precompiles_impl.hpp | 42 ++-------- .../category/crypto/silkpre_vendor/bn128.cpp | 79 +++++++++---------- .../category/crypto/silkpre_vendor/bn128.hpp | 21 +++-- 4 files changed, 56 insertions(+), 88 deletions(-) diff --git a/category/crypto/CMakeLists.txt b/category/crypto/CMakeLists.txt index 8afdc10649..84b3aae86f 100644 --- a/category/crypto/CMakeLists.txt +++ b/category/crypto/CMakeLists.txt @@ -57,7 +57,7 @@ target_sources(monad_crypto PRIVATE target_include_directories(monad_crypto PUBLIC "${SILKPRE_VENDOR_DIR}") # ecdsa needs libsecp256k1 and bn128 needs libff -target_link_libraries(monad_crypto PUBLIC secp256k1 PRIVATE ff) +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. 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/src/category/crypto/silkpre_vendor/bn128.cpp b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.cpp index ca0f4e6b99..8c4709eee8 100644 --- a/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.cpp +++ b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.cpp @@ -14,6 +14,11 @@ 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 @@ -27,13 +32,6 @@ #include #include -static void right_pad(std::basic_string &str, - const size_t min_size) noexcept { - if (str.length() < min_size) { - str.resize(min_size, '\0'); - } -} - // Utility functions for zkSNARK related precompiled contracts. // See Yellow Paper, Appendix E "Precompiled Contracts", as well as // https://eips.ethereum.org/EIPS/eip-196 @@ -134,10 +132,11 @@ decode_g2_element(const uint8_t bytes_be[128]) noexcept { return point; } -static std::basic_string encode_g1_element(libff::alt_bn128_G1 p) noexcept { - std::basic_string out(64, '\0'); +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 out; + return; } p.to_affine_coordinates(); @@ -151,60 +150,57 @@ static std::basic_string encode_g1_element(libff::alt_bn128_G1 p) noexc std::memcpy(&out[0], y.data, 32); std::memcpy(&out[32], x.data, 32); - std::reverse(out.begin(), out.end()); - return out; + std::reverse(out, out + 64); } -SilkpreOutput silkpre_bn_add_run(const uint8_t* ptr, size_t len) { - std::basic_string input(ptr, len); - right_pad(input, 128); +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 {nullptr, 0}; + return false; } std::optional y{decode_g1_element(&input[64])}; if (!y) { - return {nullptr, 0}; + return false; } libff::alt_bn128_G1 sum{*x + *y}; - const std::basic_string res{encode_g1_element(sum)}; - - uint8_t* out{static_cast(std::malloc(res.length()))}; - std::memcpy(out, res.data(), res.length()); - return {out, res.length()}; + encode_g1_element(sum, out); + return true; } -SilkpreOutput silkpre_bn_mul_run(const uint8_t* ptr, size_t len) { - std::basic_string input(ptr, len); - right_pad(input, 96); +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 {nullptr, 0}; + return false; } Scalar n{to_scalar(&input[64])}; libff::alt_bn128_G1 product{n * *x}; - const std::basic_string res{encode_g1_element(product)}; - - uint8_t* out{static_cast(std::malloc(res.length()))}; - std::memcpy(out, res.data(), res.length()); - return {out, res.length()}; + encode_g1_element(product, out); + return true; } static constexpr size_t kSnarkvStride{192}; -SilkpreOutput silkpre_snarkv_run(const uint8_t* input, size_t len) { +bool monad_snarkv(uint8_t out[32], const uint8_t *input, size_t len) { if (len % kSnarkvStride != 0) { - return {nullptr, 0}; + return false; } size_t k{len / kSnarkvStride}; @@ -215,26 +211,29 @@ SilkpreOutput silkpre_snarkv_run(const uint8_t* input, size_t len) { auto accumulator{one}; for (size_t i{0}; i < k; ++i) { - std::optional a{decode_g1_element(&input[i * kSnarkvStride])}; + std::optional a{ + decode_g1_element(&input[i * kSnarkvStride])}; if (!a) { - return {nullptr, 0}; + return false; } - std::optional b{decode_g2_element(&input[i * kSnarkvStride + 64])}; + std::optional b{ + decode_g2_element(&input[i * kSnarkvStride + 64])}; if (!b) { - return {nullptr, 0}; + 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)); + accumulator = accumulator * alt_bn128_miller_loop( + alt_bn128_precompute_G1(*a), + alt_bn128_precompute_G2(*b)); } - uint8_t* out{static_cast(std::malloc(32))}; std::memset(out, 0, 32); if (alt_bn128_final_exponentiation(accumulator) == one) { out[31] = 1; } - return {out, 32}; + 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 index 50b7b7fc1f..a41a22928b 100644 --- a/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.hpp +++ b/third_party/silkpre_vendor/src/category/crypto/silkpre_vendor/bn128.hpp @@ -14,24 +14,21 @@ limitations under the License. */ -#ifndef SILKPRE_BN128_HPP_ -#define SILKPRE_BN128_HPP_ +// 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 -typedef struct SilkpreOutput { - uint8_t* data; // Has to be freed if not NULL!!! - size_t size; -} SilkpreOutput; - // EIP-196: Precompiled contract for addition on the elliptic curve alt_bn128 -SilkpreOutput silkpre_bn_add_run(const uint8_t* input, size_t len); +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 -SilkpreOutput silkpre_bn_mul_run(const uint8_t* input, size_t len); +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 -SilkpreOutput silkpre_snarkv_run(const uint8_t* input, size_t len); - -#endif // SILKPRE_BN128_HPP_ +bool monad_snarkv(uint8_t out[32], const uint8_t *input, size_t len);