Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions test/unittests/statetest_loader_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,40 @@ TEST(statetest_loader, load_minimal_test)
EXPECT_EQ(st.input_labels.size(), 0);
}

TEST(statetest_loader, sender_from_secret_key)
{
// The first account of ethereum/tests and its secret key.
static constexpr auto KEY =
"0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8";
static constexpr auto KEY_OWNER = 0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b_address;

const auto load_sender = [](const std::string& sender_fields) {
std::istringstream s{R"({"test": {
"pre": {},
"transaction": {"gasPrice": "", "to": "", "data": null, "gasLimit": "0",
"value": null, "nonce": "0", )" +
sender_fields + R"(},
"post": {},
"env": {"currentNumber": "0", "currentTimestamp": "0", "currentGasLimit": "0",
"currentCoinbase": ""}
}})"};
return load_state_tests(s).at(0).multi_tx.sender;
};

EXPECT_EQ(load_sender(R"("secretKey": ")" + std::string{KEY} + '"'), KEY_OWNER);
// `sender` takes precedence, as in go-ethereum.
EXPECT_EQ(load_sender(R"("sender": "0xa0a1", "secretKey": ")" + std::string{KEY} + '"'),
0xa0a1_address);
EXPECT_THAT([&] { load_sender(R"("secretKey": "0x00")"); },
ThrowsMessage<std::invalid_argument>("invalid secretKey: 0x00"));
EXPECT_THAT(
[&] {
load_sender(
R"("secretKey": "0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")");
},
ThrowsMessage<std::invalid_argument>(HasSubstr("invalid secretKey")));
}

TEST(statetest_loader, validate_state_zero_storage_slot)
{
TestState state{{0xadd4_address, {.storage = {{0x01_bytes32, 0x00_bytes32}}}}};
Expand Down
45 changes: 45 additions & 0 deletions test/unittests/statetest_runner_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,46 @@ constexpr std::string_view FAILING_TX = R"({"failing_tx": {
}
}})";

/// The reproducer from #1490: a value transfer signed by `secretKey`, with no `sender` given.
/// The expected hash is go-ethereum's.
constexpr std::string_view SECRET_KEY_ONLY = R"({"secretkey_sender_recovery": {
"env": {
"currentCoinbase": "0x8888f1f195afa192cfee860698584c030f4c9db1",
"currentNumber": "0x01",
"currentTimestamp": "0x54c99069",
"currentGasLimit": "0x2fefd8",
"currentDifficulty": "0x0",
"currentRandom": "0x0000000000000000000000000000000000000000000000000000000000000001",
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"currentBaseFee": "0x1",
"currentExcessBlobGas": "0x0",
"withdrawals": []
},
"pre": {
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"code": "0x", "nonce": "0x00", "balance": "0xde0b6b3a7640000", "storage": {}
},
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87": {
"code": "0x", "nonce": "0x00", "balance": "0x00", "storage": {}
}
},
"transaction": {
"data": ["0x"],
"gasLimit": ["0x5208"],
"value": ["0x0"],
"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
"nonce": "0x0",
"chainId": "0x1",
"gasPrice": "0x1",
"secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
},
"post": {"Cancun": [{
"indexes": {"data": 0, "gas": 0, "value": 0},
"hash": "0x5a605f957d4aa4145411d63287ddf1bae2202c30cc6ec86ae003423bb6343778",
"logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
}]}
}})";

/// A case given as raw txbytes, which the runner decodes and recovers the sender of, in a fixture
/// that expects it to succeed. The `transaction` beside it is what the loader wants, not what runs.
std::string txbytes_case(std::string_view txbytes)
Expand Down Expand Up @@ -276,3 +316,8 @@ TEST(statetest_runner, txbytes_invalid_encoding)
ASSERT_EQ(failures.size(), 1u);
EXPECT_EQ(failures[0].detail, "unexpected invalid transaction: invalid transaction encoding");
}

TEST(statetest_runner, sender_from_secret_key)
{
EXPECT_THAT(run(SECRET_KEY_ONLY).failures, testing::IsEmpty());
}
20 changes: 20 additions & 0 deletions test/utils/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "stdx/utility.hpp"
#include "utils.hpp"
#include <evmone/delegation.hpp>
#include <evmone_precompiles/secp256k1.hpp>
#include <nlohmann/json.hpp>
#include <test/state/precompiles.hpp>

Expand Down Expand Up @@ -380,6 +381,21 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o)
}
}

/// Derives the address of the secret key a state test signs its transaction with.
/// The scalar multiplication is not constant-time, which is fine for public test keys.
static address secret_key_to_address(const json::json& j_secret_key)
{
using namespace crypto::secp256k1;
static constexpr AffinePoint G{
0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798_u256,
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8_u256};

const auto secret_key = from_json<intx::uint256>(j_secret_key);
if (secret_key == 0 || secret_key >= Curve::ORDER)
throw std::invalid_argument("invalid secretKey: " + j_secret_key.get<std::string>());
return to_address(crypto::ecc::to_affine(crypto::ecc::mul(G, secret_key)));
}

template <>
state::Transaction from_json<state::Transaction>(const json::json& j)
{
Expand Down Expand Up @@ -425,6 +441,10 @@ static void from_json(const json::json& j, TestMultiTransaction& o)
{
from_json_tx_common(j, o);

// Without `sender`, the sender is the owner of `secretKey`, as in go-ethereum.
if (const auto it = j.find("secretKey"); it != j.end() && !j.contains("sender"))
o.sender = secret_key_to_address(*it);

for (const auto& j_data : j.at("data"))
o.inputs.emplace_back(from_json<bytes>(j_data));

Expand Down