diff --git a/libethashseal/genesis/metropolisTest.cpp b/libethashseal/genesis/metropolisTest.cpp index d0878e26054..7772dbdba42 100644 --- a/libethashseal/genesis/metropolisTest.cpp +++ b/libethashseal/genesis/metropolisTest.cpp @@ -56,6 +56,7 @@ R"E( "0000000000000000000000000000000000000002": { "wei": "1", "precompiled": { "name": "sha256", "linear": { "base": 60, "word": 12 } } }, "0000000000000000000000000000000000000003": { "wei": "1", "precompiled": { "name": "ripemd160", "linear": { "base": 600, "word": 120 } } }, "0000000000000000000000000000000000000004": { "wei": "1", "precompiled": { "name": "identity", "linear": { "base": 15, "word": 3 } } } + "0000000000000000000000000000000000000005": { "wei": "1", "precompiled": { "name": "modexp" } } } } )E"; diff --git a/libethcore/ChainOperationParams.cpp b/libethcore/ChainOperationParams.cpp index 9d051fa49d0..a93e9c2f1a7 100644 --- a/libethcore/ChainOperationParams.cpp +++ b/libethcore/ChainOperationParams.cpp @@ -32,9 +32,9 @@ PrecompiledContract::PrecompiledContract( PrecompiledExecutor const& _exec, u256 const& _startingBlock ): - PrecompiledContract([=](unsigned size) -> bigint + PrecompiledContract([=](bytesConstRef _in) -> bigint { - bigint s = size; + bigint s = _in.size(); bigint b = _base; bigint w = _word; return b + (s + 31) / 32 * w; diff --git a/libethcore/ChainOperationParams.h b/libethcore/ChainOperationParams.h index 234f7a7bce2..d3622d0a979 100644 --- a/libethcore/ChainOperationParams.h +++ b/libethcore/ChainOperationParams.h @@ -37,7 +37,7 @@ class PrecompiledContract public: PrecompiledContract() = default; PrecompiledContract( - std::function const& _cost, + PrecompiledPricer const& _cost, PrecompiledExecutor const& _exec, u256 const& _startingBlock = 0 ): @@ -52,13 +52,13 @@ class PrecompiledContract u256 const& _startingBlock = 0 ); - bigint cost(bytesConstRef _in) const { return m_cost(_in.size()); } + bigint cost(bytesConstRef _in) const { return m_cost(_in); } std::pair execute(bytesConstRef _in) const { return m_execute(_in); } u256 const& startingBlock() const { return m_startingBlock; } private: - std::function m_cost; + PrecompiledPricer m_cost; PrecompiledExecutor m_execute; u256 m_startingBlock = 0; }; diff --git a/libethcore/Precompiled.cpp b/libethcore/Precompiled.cpp index 8aef8783085..c9a1c5735e7 100644 --- a/libethcore/Precompiled.cpp +++ b/libethcore/Precompiled.cpp @@ -38,6 +38,13 @@ PrecompiledExecutor const& PrecompiledRegistrar::executor(std::string const& _na return get()->m_execs[_name]; } +PrecompiledPricer const& PrecompiledRegistrar::pricer(std::string const& _name) +{ + if (!get()->m_pricers.count(_name)) + BOOST_THROW_EXCEPTION(PricerNotFound()); + return get()->m_pricers[_name]; +} + namespace { @@ -90,4 +97,50 @@ ETH_REGISTER_PRECOMPILED(identity)(bytesConstRef _in) return {true, _in.toBytes()}; } +// Parse _count bytes of _in starting with _begin offset as big endian int. +// If there's not enough bytes in _in, consider it infinitely right-padded with zeroes. +bigint parseBigEndianRightPadded(bytesConstRef _in, size_t _begin, size_t _count) +{ + if (_begin > _in.count()) + return 0; + + // crop _in, not going beyond its size + bytesConstRef cropped = _in.cropped(_begin, min(_count, _in.count() - _begin)); + + bigint ret = fromBigEndian(cropped); + // shift as if we had right-padding zeroes + ret <<= 8 * (_count - cropped.count()); + + return ret; +} + +ETH_REGISTER_PRECOMPILED(modexp)(bytesConstRef _in) +{ + size_t const baseLength(parseBigEndianRightPadded(_in, 0, 32)); + size_t const expLength(parseBigEndianRightPadded(_in, 32, 32)); + size_t const modLength(parseBigEndianRightPadded(_in, 64, 32)); + + bigint const base(parseBigEndianRightPadded(_in, 96, baseLength)); + bigint const exp(parseBigEndianRightPadded(_in, 96 + baseLength, expLength)); + bigint const mod(parseBigEndianRightPadded(_in, 96 + baseLength + expLength, modLength)); + + bigint const result = mod != 0 ? boost::multiprecision::powm(base, exp, mod) : bigint{0}; + + bytes ret(modLength); + toBigEndian(result, ret); + + return {true, ret}; +} + +ETH_REGISTER_PRECOMPILED_PRICER(modexp)(bytesConstRef _in) +{ + bigint const baseLength(parseBigEndianRightPadded(_in, 0, 32)); + bigint const expLength(parseBigEndianRightPadded(_in, 32, 32)); + bigint const modLength(parseBigEndianRightPadded(_in, 64, 32)); + + bigint const maxLength = max(modLength, baseLength); + + return maxLength * maxLength * max(expLength, 1) / 20; +} + } diff --git a/libethcore/Precompiled.h b/libethcore/Precompiled.h index e14ef931084..df2e34239b0 100644 --- a/libethcore/Precompiled.h +++ b/libethcore/Precompiled.h @@ -32,8 +32,10 @@ namespace eth { using PrecompiledExecutor = std::function(bytesConstRef _in)>; +using PrecompiledPricer = std::function; DEV_SIMPLE_EXCEPTION(ExecutorNotFound); +DEV_SIMPLE_EXCEPTION(PricerNotFound); class PrecompiledRegistrar { @@ -41,20 +43,29 @@ class PrecompiledRegistrar /// Get the executor object for @a _name function or @throw ExecutorNotFound if not found. static PrecompiledExecutor const& executor(std::string const& _name); + /// Get the price calculator object for @a _name function or @throw PricerNotFound if not found. + static PrecompiledPricer const& pricer(std::string const& _name); + /// Register an executor. In general just use ETH_REGISTER_PRECOMPILED. - static PrecompiledExecutor registerPrecompiled(std::string const& _name, PrecompiledExecutor const& _exec) { return (get()->m_execs[_name] = _exec); } + static PrecompiledExecutor registerExecutor(std::string const& _name, PrecompiledExecutor const& _exec) { return (get()->m_execs[_name] = _exec); } /// Unregister an executor. Shouldn't generally be necessary. - static void unregisterPrecompiled(std::string const& _name) { get()->m_execs.erase(_name); } + static void unregisterExecutor(std::string const& _name) { get()->m_execs.erase(_name); } + + /// Register a pricer. In general just use ETH_REGISTER_PRECOMPILED_PRICER. + static PrecompiledPricer registerPricer(std::string const& _name, PrecompiledPricer const& _exec) { return (get()->m_pricers[_name] = _exec); } + /// Unregister a pricer. Shouldn't generally be necessary. + static void unregisterPricer(std::string const& _name) { get()->m_pricers.erase(_name); } private: static PrecompiledRegistrar* get() { if (!s_this) s_this = new PrecompiledRegistrar; return s_this; } std::unordered_map m_execs; + std::unordered_map m_pricers; static PrecompiledRegistrar* s_this; }; // TODO: unregister on unload with a static object. -#define ETH_REGISTER_PRECOMPILED(Name) static std::pair __eth_registerPrecompiledFunction ## Name(bytesConstRef _in); static PrecompiledExecutor __eth_registerPrecompiledFactory ## Name = ::dev::eth::PrecompiledRegistrar::registerPrecompiled(#Name, &__eth_registerPrecompiledFunction ## Name); static std::pair __eth_registerPrecompiledFunction ## Name - +#define ETH_REGISTER_PRECOMPILED(Name) static std::pair __eth_registerPrecompiledFunction ## Name(bytesConstRef _in); static PrecompiledExecutor __eth_registerPrecompiledFactory ## Name = ::dev::eth::PrecompiledRegistrar::registerExecutor(#Name, &__eth_registerPrecompiledFunction ## Name); static std::pair __eth_registerPrecompiledFunction ## Name +#define ETH_REGISTER_PRECOMPILED_PRICER(Name) static bigint __eth_registerPricerFunction ## Name(bytesConstRef _in); static PrecompiledPricer __eth_registerPricerFactory ## Name = ::dev::eth::PrecompiledRegistrar::registerPricer(#Name, &__eth_registerPricerFunction ## Name); static bigint __eth_registerPricerFunction ## Name } } diff --git a/libethereum/Account.cpp b/libethereum/Account.cpp index d168865e75e..9a96e8a9c00 100644 --- a/libethereum/Account.cpp +++ b/libethereum/Account.cpp @@ -37,6 +37,9 @@ void Account::setNewCode(bytes&& _code) namespace js = json_spirit; +namespace +{ + uint64_t toUnsigned(js::mValue const& _v) { switch (_v.type()) @@ -47,6 +50,37 @@ uint64_t toUnsigned(js::mValue const& _v) } } +PrecompiledContract createPrecompiledContract(js::mObject& _precompiled) +{ + auto n = _precompiled["name"].get_str(); + try + { + u256 startingBlock = 0; + if (_precompiled.count("startingBlock")) + startingBlock = u256(_precompiled["startingBlock"].get_str()); + + if (!_precompiled.count("linear")) + return PrecompiledContract(PrecompiledRegistrar::pricer(n), PrecompiledRegistrar::executor(n), startingBlock); + + auto l = _precompiled["linear"].get_obj(); + unsigned base = toUnsigned(l["base"]); + unsigned word = toUnsigned(l["word"]); + return PrecompiledContract(base, word, PrecompiledRegistrar::executor(n), startingBlock); + } + catch (PricerNotFound const&) + { + cwarn << "Couldn't create a precompiled contract account. Missing a pricer called:" << n; + throw; + } + catch (ExecutorNotFound const&) + { + // Oh dear - missing a plugin? + cwarn << "Couldn't create a precompiled contract account. Missing an executor called:" << n; + throw; + } +} + +} AccountMap dev::eth::jsonToAccountMap(std::string const& _json, u256 const& _defaultNonce, AccountMaskMap* o_mask, PrecompiledContractMap* o_precompiled) { auto u256Safe = [](std::string const& s) -> u256 { @@ -115,28 +149,7 @@ AccountMap dev::eth::jsonToAccountMap(std::string const& _json, u256 const& _def if (o_precompiled && o.count("precompiled")) { js::mObject p = o["precompiled"].get_obj(); - auto n = p["name"].get_str(); - if (!p.count("linear")) - { - cwarn << "No gas cost given for precompiled contract " << n; - throw; - } - try - { - auto l = p["linear"].get_obj(); - u256 startingBlock = 0; - if (p.count("startingBlock")) - startingBlock = u256(p["startingBlock"].get_str()); - unsigned base = toUnsigned(l["base"]); - unsigned word = toUnsigned(l["word"]); - o_precompiled->insert(make_pair(a, PrecompiledContract(base, word, PrecompiledRegistrar::executor(n), startingBlock))); - } - catch (ExecutorNotFound) - { - // Oh dear - missing a plugin? - cwarn << "Couldn't create a precompiled contract account. Missing an executor called:" << n; - throw; - } + o_precompiled->insert(make_pair(a, createPrecompiledContract(p))); } } diff --git a/test/unittests/libethcore/PrecompiledTest.cpp b/test/unittests/libethcore/PrecompiledTest.cpp new file mode 100644 index 00000000000..81b474282b7 --- /dev/null +++ b/test/unittests/libethcore/PrecompiledTest.cpp @@ -0,0 +1,220 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** @file PrecompiledTest.cpp + * Preompiled contract implemetations testing. + */ + +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; +using namespace dev::test; + +BOOST_FIXTURE_TEST_SUITE(PrecompiledTests, TestOutputHelper) + +BOOST_AUTO_TEST_CASE(modexpFermatTheorem) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "03" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + bytes expected = fromHex("0000000000000000000000000000000000000000000000000000000000000001"); + BOOST_REQUIRE_EQUAL_COLLECTIONS(res.second.begin(), res.second.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(modexpZeroBase) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + bytes expected = fromHex("0000000000000000000000000000000000000000000000000000000000000000"); + BOOST_REQUIRE_EQUAL_COLLECTIONS(res.second.begin(), res.second.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(modexpExtraByteIgnored) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000020" + "03" + "ffff" + "8000000000000000000000000000000000000000000000000000000000000000" + "07"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + bytes expected = fromHex("3b01b01ac41f2d6e917c6d6a221ce793802469026d9ab7578fa2e79e4da6aaab"); + BOOST_REQUIRE_EQUAL_COLLECTIONS(res.second.begin(), res.second.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(modexpRightPadding) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000020" + "03" + "ffff" + "80"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + bytes expected = fromHex("3b01b01ac41f2d6e917c6d6a221ce793802469026d9ab7578fa2e79e4da6aaab"); + BOOST_REQUIRE_EQUAL_COLLECTIONS(res.second.begin(), res.second.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(modexpMissingValues) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000020" + "03"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + bytes expected = fromHex("0000000000000000000000000000000000000000000000000000000000000000"); + BOOST_REQUIRE_EQUAL_COLLECTIONS(res.second.begin(), res.second.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(modexpEmptyValue) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000020" + "03" + "8000000000000000000000000000000000000000000000000000000000000000"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + bytes expected = fromHex("0000000000000000000000000000000000000000000000000000000000000001"); + BOOST_REQUIRE_EQUAL_COLLECTIONS(res.second.begin(), res.second.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(modexpZeroPowerZero) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000020" + "00" + "00" + "80"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + bytes expected = fromHex("0000000000000000000000000000000000000000000000000000000000000001"); + BOOST_REQUIRE_EQUAL_COLLECTIONS(res.second.begin(), res.second.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(modexpZeroPowerZeroModZero) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000020" + "00" + "00" + "00"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + bytes expected = fromHex("0000000000000000000000000000000000000000000000000000000000000000"); + BOOST_REQUIRE_EQUAL_COLLECTIONS(res.second.begin(), res.second.end(), expected.begin(), expected.end()); +} + +BOOST_AUTO_TEST_CASE(modexpModLengthZero) +{ + PrecompiledExecutor exec = PrecompiledRegistrar::executor("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000000" + "01" + "01"); + auto res = exec(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res.first); + BOOST_REQUIRE(res.second.empty()); +} + +BOOST_AUTO_TEST_CASE(modexpCostFermatTheorem) +{ + PrecompiledPricer cost = PrecompiledRegistrar::pricer("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "03" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + auto res = cost(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE_EQUAL(static_cast(res), 1638); +} + +BOOST_AUTO_TEST_CASE(modexpCostTooLarge) +{ + PrecompiledPricer cost = PrecompiledRegistrar::pricer("modexp"); + + bytes in = fromHex( + "0000000000000000000000000000000000000000000000000000000000000000" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "0000000000000000000000000000000000000000000000000000000000000020" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"); + auto res = cost(bytesConstRef(in.data(), in.size())); + + BOOST_REQUIRE(res == bigint{"5928554968950589205686834432444820882087423214880796878820228301205152237564672"}); +} + +BOOST_AUTO_TEST_SUITE_END()