Skip to content

Commit

Permalink
precompiles: Implement multi scalar multiplication for P1 and P2
Browse files Browse the repository at this point in the history
  • Loading branch information
rodiazet committed Sep 17, 2024
1 parent 01eca77 commit d2a43ca
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 10 deletions.
2 changes: 2 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ jobs:
prague/eip2537_bls_12_381_precompiles/bls12_g1mul
prague/eip2537_bls_12_381_precompiles/bls12_g2add
prague/eip2537_bls_12_381_precompiles/bls12_g2mul
prague/eip2537_bls_12_381_precompiles/bls12_g1msm
prague/eip2537_bls_12_381_precompiles/bls12_g2msm
- run:
name: "Execution spec tests (develop, blockchain_tests)"
# Tests for in-development EVM revision currently passing.
Expand Down
130 changes: 130 additions & 0 deletions lib/evmone_precompiles/bls.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "bls.hpp"
#include <blst.h>
#include <optional>
#include <vector>

namespace evmone::crypto::bls
{
Expand Down Expand Up @@ -183,4 +184,133 @@ void store(uint8_t _rx[128], const blst_fp2& _x) noexcept
return true;
}

[[nodiscard]] bool g1_msm(
uint8_t _rx[64], uint8_t _ry[64], const uint8_t* _xycs, size_t size) noexcept
{
constexpr auto SINGLE_ENTRY_SIZE = (64 * 2 + 32);
assert(size % SINGLE_ENTRY_SIZE == 0);
auto npoints = size / SINGLE_ENTRY_SIZE;

std::vector<blst_p1_affine> p1_affines;
std::vector<const blst_p1_affine*> p1_affine_ptrs;
p1_affines.reserve(npoints);
p1_affine_ptrs.reserve(npoints);

std::vector<blst_scalar> scalars;
std::vector<const uint8_t*> scalars_ptrs;
scalars.reserve(npoints);
scalars_ptrs.reserve(npoints);

auto ptr = _xycs;

for (size_t i = 0; i < npoints; ++i)
{
const auto p_affine = validate_p1(ptr, &ptr[64]);
if (!p_affine.has_value())
return false;

if (!blst_p1_affine_in_g1(&*p_affine))
return false;

if (blst_p1_affine_is_inf(&*p_affine))
continue;

p1_affines.emplace_back(*p_affine);
p1_affine_ptrs.emplace_back(&p1_affines.back());

blst_scalar scalar;
blst_scalar_from_bendian(&scalar, &ptr[128]);
scalars.emplace_back(scalar);
scalars_ptrs.emplace_back(scalars.back().b);

ptr += SINGLE_ENTRY_SIZE;
}

npoints = p1_affine_ptrs.size();

if (npoints == 0)
{
memset(_rx, 0, 64);
memset(_ry, 0, 64);
return true;
}

const auto sz = blst_p1s_mult_pippenger_scratch_sizeof(npoints) / 8;
blst_p1 out;
blst_p1s_mult_pippenger(&out, p1_affine_ptrs.data(), npoints, scalars_ptrs.data(), 256,
std::vector<limb_t>(sz).data());

blst_p1_affine result;
blst_p1_to_affine(&result, &out);
store(_rx, result.x);
store(_ry, result.y);

return true;
}

[[nodiscard]] bool g2_msm(
uint8_t _rx[128], uint8_t _ry[128], const uint8_t* _xycs, size_t size) noexcept
{
constexpr auto SINGLE_ENTRY_SIZE = (128 * 2 + 32);
assert(size % SINGLE_ENTRY_SIZE == 0);
auto npoints = size / SINGLE_ENTRY_SIZE;

std::vector<blst_p2_affine> p2_affines;
std::vector<const blst_p2_affine*> p2_affine_ptrs;
p2_affines.reserve(npoints);
p2_affine_ptrs.reserve(npoints);

std::vector<blst_scalar> scalars;
std::vector<const uint8_t*> scalars_ptrs;
scalars.reserve(npoints);
scalars_ptrs.reserve(npoints);

auto ptr = _xycs;

for (size_t i = 0; i < npoints; ++i)
{
const auto p_affine = validate_p2(ptr, &ptr[128]);
if (!p_affine.has_value())
return false;

if (!blst_p2_affine_in_g2(&*p_affine))
return false;

blst_scalar scalar;
blst_scalar_from_bendian(&scalar, &ptr[256]);

if (blst_p2_affine_is_inf(&*p_affine))
continue;

p2_affines.emplace_back(*p_affine);
p2_affine_ptrs.emplace_back(&p2_affines.back());

scalars.emplace_back(scalar);
scalars_ptrs.emplace_back(scalars.back().b);

ptr += SINGLE_ENTRY_SIZE;
}

npoints = p2_affine_ptrs.size();

if (npoints == 0)
{
memset(_rx, 0, 128);
memset(_ry, 0, 128);
return true;
}

const auto sz = blst_p2s_mult_pippenger_scratch_sizeof(npoints) / 8;
blst_p2 out;
blst_p2s_mult_pippenger(&out, p2_affine_ptrs.data(), npoints, scalars_ptrs.data(), 256,
std::vector<limb_t>(sz).data());

blst_p2_affine result;
blst_p2_to_affine(&result, &out);
store(_rx, result.x);
store(_ry, result.y);

return true;
}

} // namespace evmone::crypto::bls
16 changes: 16 additions & 0 deletions lib/evmone_precompiles/bls.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,20 @@ inline constexpr auto BLS_FIELD_MODULUS =
[[nodiscard]] bool g2_mul(uint8_t _rx[128], uint8_t _ry[128], const uint8_t _x[128],
const uint8_t _y[128], const uint8_t _c[32]) noexcept;

/// Multi scalar multiplication in BLS12-381 curve G1 subgroup.
///
/// Computes ∑ⁿₖ₌₁cₖPₖ for points in affine coordinate on the BLS12-381 curve, performs
/// subgroup check according to spec
/// https://eips.ethereum.org/EIPS/eip-2537#abi-for-g1-msm
[[nodiscard]] bool g1_msm(
uint8_t _rx[64], uint8_t _ry[64], const uint8_t* _xycs, size_t size) noexcept;

/// Multi scalar multiplication in BLS12-381 curve G2 subgroup.
///
/// Computes ∑ⁿₖ₌₁cₖPₖ for points in affine coordinate on the BLS12-381 curve over G2 extension
/// field, performs subgroup check according to spec
/// https://eips.ethereum.org/EIPS/eip-2537#abi-for-g2-msm
[[nodiscard]] bool g2_msm(
uint8_t _rx[128], uint8_t _ry[128], const uint8_t* _xycs, size_t size) noexcept;

} // namespace evmone::crypto::bls
64 changes: 54 additions & 10 deletions test/state/precompiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ inline constexpr int64_t cost_per_input_word(size_t input_size) noexcept
{
return BaseCost + WordCost * num_words(input_size);
}

int64_t bls_msm_cost(size_t k, int64_t multiplication_cost) noexcept
{
assert(k > 0);

constexpr auto MAX_DISCOUNT = 174;
constexpr int64_t MULTIPLIER = 1000;
constexpr int16_t discount[128] = {1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394,
379, 364, 349, 334, 330, 326, 322, 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277,
273, 269, 268, 266, 265, 263, 262, 260, 259, 257, 256, 254, 253, 251, 250, 248, 247, 245,
244, 242, 241, 239, 238, 236, 235, 233, 232, 231, 229, 228, 226, 225, 223, 222, 221, 220,
219, 219, 218, 217, 216, 216, 215, 214, 213, 213, 212, 211, 211, 210, 209, 208, 208, 207,
206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199, 198, 197, 196, 196, 195, 194, 193,
193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185, 184, 183, 182, 182, 181, 180,
179, 179, 178, 177, 176, 176, 175, 174};

const auto d = k <= 128 ? discount[k - 1] : MAX_DISCOUNT;
return (static_cast<int64_t>(k) * multiplication_cost * d) / MULTIPLIER;
}

} // namespace

PrecompileAnalysis ecrecover_analyze(bytes_view /*input*/, evmc_revision /*rev*/) noexcept
Expand Down Expand Up @@ -166,10 +186,13 @@ PrecompileAnalysis bls12_g1mul_analyze(bytes_view, evmc_revision) noexcept
return {BLS12_G1MUL_PRECOMPILE_GAS, 128};
}

PrecompileAnalysis bls12_g1msm_analyze(bytes_view, evmc_revision) noexcept
PrecompileAnalysis bls12_g1msm_analyze(bytes_view input, evmc_revision) noexcept
{
// TODO: Implement
return {GasCostMax, 0};
if (input.empty() || input.size() % 160 != 0)
return {GasCostMax, 0};

static constexpr auto BLS12_G1MUL_PRECOMPILE_GAS = 12000;
return {bls_msm_cost(input.size() / 160, BLS12_G1MUL_PRECOMPILE_GAS), 128};
}

PrecompileAnalysis bls12_g2add_analyze(bytes_view, evmc_revision) noexcept
Expand All @@ -184,10 +207,13 @@ PrecompileAnalysis bls12_g2mul_analyze(bytes_view, evmc_revision) noexcept
return {BLS12_G2MUL_PRECOMPILE_GAS, 256};
}

PrecompileAnalysis bls12_g2msm_analyze(bytes_view, evmc_revision) noexcept
PrecompileAnalysis bls12_g2msm_analyze(bytes_view input, evmc_revision) noexcept
{
// TODO: Implement
return {GasCostMax, 0};
if (input.empty() || input.size() % 288 != 0)
return {GasCostMax, 0};

static constexpr auto BLS12_G2MUL_PRECOMPILE_GAS = 45000;
return {bls_msm_cost(input.size() / 288, BLS12_G2MUL_PRECOMPILE_GAS), 256};
}

PrecompileAnalysis bls12_pairing_check_analyze(bytes_view, evmc_revision) noexcept
Expand Down Expand Up @@ -375,9 +401,18 @@ ExecutionResult bls12_g1mul_execute(const uint8_t* input, size_t input_size, uin
return {EVMC_SUCCESS, 128};
}

ExecutionResult bls12_g1msm_execute(const uint8_t*, size_t, uint8_t*, size_t) noexcept
ExecutionResult bls12_g1msm_execute(const uint8_t* input, size_t input_size, uint8_t* output,
[[maybe_unused]] size_t output_size) noexcept
{
return {EVMC_PRECOMPILE_FAILURE, 0};
if (input_size % 160 != 0)
return {EVMC_PRECOMPILE_FAILURE, 0};

Check warning on line 408 in test/state/precompiles.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/precompiles.cpp#L408

Added line #L408 was not covered by tests

assert(output_size == 128);

if (!crypto::bls::g1_msm(output, &output[64], input, input_size))
return {EVMC_PRECOMPILE_FAILURE, 0};

return {EVMC_SUCCESS, 128};
}

ExecutionResult bls12_g2add_execute(const uint8_t* input, size_t input_size, uint8_t* output,
Expand Down Expand Up @@ -408,9 +443,18 @@ ExecutionResult bls12_g2mul_execute(const uint8_t* input, size_t input_size, uin
return {EVMC_SUCCESS, 256};
}

ExecutionResult bls12_g2msm_execute(const uint8_t*, size_t, uint8_t*, size_t) noexcept
ExecutionResult bls12_g2msm_execute(const uint8_t* input, size_t input_size, uint8_t* output,
[[maybe_unused]] size_t output_size) noexcept
{
return {EVMC_PRECOMPILE_FAILURE, 0};
if (input_size % 288 != 0)
return {EVMC_PRECOMPILE_FAILURE, 0};

Check warning on line 450 in test/state/precompiles.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/precompiles.cpp#L450

Added line #L450 was not covered by tests

assert(output_size == 256);

if (!crypto::bls::g2_msm(output, &output[128], input, input_size))
return {EVMC_PRECOMPILE_FAILURE, 0};

return {EVMC_SUCCESS, 256};
}

ExecutionResult bls12_pairing_check_execute(const uint8_t*, size_t, uint8_t*, size_t) noexcept
Expand Down
117 changes: 117 additions & 0 deletions test/unittests/precompiles_bls_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,120 @@ TEST(bls, g2_mul)
EXPECT_EQ(evmc::bytes_view(rx, sizeof rx), expected_x);
EXPECT_EQ(evmc::bytes_view(ry, sizeof ry), expected_y);
}

TEST(bls, g1_msm)
{
using namespace evmc::literals;
auto input =
"0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f17"
"1bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e"
"30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000"
"00000000000000000000000000000000000000000000000002"_hex;
uint8_t rx[64];
uint8_t ry[64];

EXPECT_TRUE(evmone::crypto::bls::g1_msm(rx, ry, input.data(), input.size()));

const auto expected_x =
"000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e"_hex;
const auto expected_y =
"00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28"_hex;

EXPECT_EQ(evmc::bytes_view(rx, sizeof rx), expected_x);
EXPECT_EQ(evmc::bytes_view(ry, sizeof ry), expected_y);
}

TEST(bls, g1_msm_inf_0)
{
using namespace evmc::literals;
auto input =
"0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000"_hex;
uint8_t rx[64];
uint8_t ry[64];

EXPECT_TRUE(evmone::crypto::bls::g1_msm(rx, ry, input.data(), input.size()));

const auto expected_x =
"000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e"_hex;
const auto expected_y =
"00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28"_hex;

EXPECT_EQ(evmc::bytes_view(rx, sizeof rx), expected_x);
EXPECT_EQ(evmc::bytes_view(ry, sizeof ry), expected_y);
}

TEST(bls, g1_msm_inf_2)
{
using namespace evmc::literals;
auto input =
"0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"_hex;
uint8_t rx[64];
uint8_t ry[64];

EXPECT_TRUE(evmone::crypto::bls::g1_msm(rx, ry, input.data(), input.size()));

const auto expected_x =
"000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e"_hex;
const auto expected_y =
"00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28"_hex;

EXPECT_EQ(evmc::bytes_view(rx, sizeof rx), expected_x);
EXPECT_EQ(evmc::bytes_view(ry, sizeof ry), expected_y);
}

TEST(bls, g2_msm)
{
using namespace evmc::literals;
auto input =
"00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002"_hex;
uint8_t rx[128];
uint8_t ry[128];

EXPECT_TRUE(evmone::crypto::bls::g2_msm(rx, ry, input.data(), input.size()));

const auto expected_x =
"000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577"_hex;
const auto expected_y =
"000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3"_hex;

EXPECT_EQ(evmc::bytes_view(rx, sizeof rx), expected_x);
EXPECT_EQ(evmc::bytes_view(ry, sizeof ry), expected_y);
}

TEST(bls, g2_msm_inf_0)
{
using namespace evmc::literals;
auto input =
"00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000"_hex;
uint8_t rx[128];
uint8_t ry[128];

EXPECT_TRUE(evmone::crypto::bls::g2_msm(rx, ry, input.data(), input.size()));

const auto expected_x =
"000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577"_hex;
const auto expected_y =
"000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3"_hex;

EXPECT_EQ(evmc::bytes_view(rx, sizeof rx), expected_x);
EXPECT_EQ(evmc::bytes_view(ry, sizeof ry), expected_y);
}

TEST(bls, g2_msm_inf_2)
{
using namespace evmc::literals;
auto input =
"00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"_hex;
uint8_t rx[128];
uint8_t ry[128];

EXPECT_TRUE(evmone::crypto::bls::g2_msm(rx, ry, input.data(), input.size()));

const auto expected_x =
"000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577"_hex;
const auto expected_y =
"000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3"_hex;

EXPECT_EQ(evmc::bytes_view(rx, sizeof rx), expected_x);
EXPECT_EQ(evmc::bytes_view(ry, sizeof ry), expected_y);
}

0 comments on commit d2a43ca

Please sign in to comment.