Skip to content

Commit

Permalink
Merge pull request #754 from eosnetworkfoundation/arhag/GH-747-mod-ex…
Browse files Browse the repository at this point in the history
…p-limits-for-main

[3.1 -> main] Use more flexible subjective limits on mod_exp
  • Loading branch information
arhag authored Aug 5, 2022
2 parents 67d8252 + 23ae97a commit 461a055
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 18 deletions.
26 changes: 23 additions & 3 deletions libraries/chain/webassembly/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
#include <fc/crypto/sha3.hpp>
#include <fc/crypto/k1_recover.hpp>

namespace {
uint32_t ceil_log2(uint32_t n)
{
if (n <= 1) {
return 0;
}
return 32 - __builtin_clz(n - 1);
};
}

namespace eosio { namespace chain { namespace webassembly {

void interface::assert_recover_key( legacy_ptr<const fc::sha256> digest,
Expand Down Expand Up @@ -159,10 +169,20 @@ namespace eosio { namespace chain { namespace webassembly {
span<const char> modulus,
span<char> out) const {
if (context.control.is_producing_block()) {
static constexpr int byte_size_limit = 256; // Allow up to 2048-bit values for base, exp, and modulus.
unsigned int base_modulus_size = std::max(base.size(), modulus.size());

if (base_modulus_size < exp.size()) {
EOS_THROW(subjective_block_production_exception,
"mod_exp restriction: exponent bit size cannot exceed bit size of either base or modulus");
}

static constexpr uint64_t bit_calc_limit = 106;

uint64_t bit_calc = 5 * ceil_log2(exp.size()) + 8 * ceil_log2(base_modulus_size);

if (std::max(std::max(base.size(), exp.size()), modulus.size()) > byte_size_limit) {
EOS_THROW(subjective_block_production_exception, "Bit size too large for values passed into mod_exp");
if (bit_calc_limit < bit_calc) {
EOS_THROW(subjective_block_production_exception,
"mod_exp restriction: bit size too large for input arguments");
}
}

Expand Down
2 changes: 1 addition & 1 deletion libraries/fc
63 changes: 49 additions & 14 deletions unittests/crypto_primitives_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ BOOST_AUTO_TEST_CASE( modexp_test ) { try {
{
{
"01",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e",
"ff",
"",
},
return_code::failure,
Expand Down Expand Up @@ -415,9 +415,12 @@ BOOST_AUTO_TEST_CASE( modexp_subjective_limit_test ) { try {
// Given the need to respect the deadline timer and the current limitation that the deadline timer is not plumbed into the
// inner loops of the implementation of mod_exp (which currently exists in the gmp shared library), only a small enough duration for
// mod_exp can be tolerated to avoid going over the deadline timer by too much. A good threshold for small may be less than 5 ms.
// Then based on benchmarks within the test_modular_arithmetic test within fc, it seems safe to limit the bit size to 2048 bits.
// Based on benchmarks within the test_modular_arithmetic test within fc, the following constraints are subjectively enforced on the
// base, exp, and modulus input arguments of the mod_exp host function:
// 1. exp.size() <= std::max(base.size(), modulus.size())
// 2. 5 * ceil(log2(exp.size())) + 8 * ceil(log2(std::max(base.size(), modulus.size()))) <= 101

// This test case verifies that the subjective bit size limit for mod_exp is properly enforced within libchain.
// This test case verifies that the above constraints on mod_exp are subjectively enforced properly within libchain.

// To allow mod_exp to be more useful, the limits on bit size need to be removed and the deadline timer plumbing into the implementation
// needs to occur. When that happens, this test case can be removed.
Expand All @@ -439,26 +442,58 @@ BOOST_AUTO_TEST_CASE( modexp_subjective_limit_test ) { try {
c.set_abi( tester1_account, contracts::crypto_primitives_test_abi().data() );
c.produce_block();

bytes exponent(256); // 2048 bits of all zeros is fine
auto exponent = h2bin("010001");

BOOST_CHECK_EXCEPTION(c.push_action(tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object()
("base", h2bin("01"))
("exp", exponent)
("modulo", h2bin("0F"))
("expected_error", static_cast<int32_t>(return_code::success))
("expected_result", h2bin("01"))),
eosio::chain::subjective_block_production_exception,
fc_exception_message_is("mod_exp restriction: exponent bit size cannot exceed bit size of either base or modulus")
);

std::vector<char> modulus(4096 - 1);
std::vector<char> expected_result(modulus.size());
modulus.push_back(0x0F);
expected_result.push_back(0x01);

auto ceil_log2 = [](uint32_t n) -> uint32_t {
if (n <= 1) {
return 0;
}
return 32 - __builtin_clz(n - 1);
};

BOOST_CHECK(5 * ceil_log2(exponent.size()) + 8 * ceil_log2(modulus.size()) == 106);

c.push_action( tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object()
("base", h2bin("01"))
("exp", exponent)
("modulo", h2bin("0F"))
("modulo", modulus)
("expected_error", static_cast<int32_t>(return_code::success))
("expected_result", h2bin("01"))
("expected_result", expected_result)
);

exponent.push_back(0); // But 2056 bits of all zeros crosses the subjective limit (even if the value is still technically only zero).
modulus.pop_back();
expected_result.pop_back();

modulus.resize(4096);
expected_result.resize(modulus.size());
modulus.push_back(0x0F);
expected_result.push_back(0x01);

BOOST_CHECK(5 * ceil_log2(exponent.size()) + 8 * ceil_log2(modulus.size()) == 114);

BOOST_CHECK_EXCEPTION(c.push_action( tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object()
("base", h2bin("01"))
("exp", exponent)
("modulo", h2bin("0F"))
("expected_error", static_cast<int32_t>(return_code::success))
("expected_result", h2bin("01"))),
BOOST_CHECK_EXCEPTION(c.push_action(tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object()
("base", h2bin("01"))
("exp", exponent)
("modulo", modulus)
("expected_error", static_cast<int32_t>(return_code::success))
("expected_result", expected_result)),
eosio::chain::subjective_block_production_exception,
fc_exception_message_is("Bit size too large for values passed into mod_exp")
fc_exception_message_is("mod_exp restriction: bit size too large for input arguments")
);

} FC_LOG_AND_RETHROW() }
Expand Down

0 comments on commit 461a055

Please sign in to comment.