diff --git a/include/EclipseMonitor/Eth/Address.hpp b/include/EclipseMonitor/Eth/Address.hpp new file mode 100644 index 0000000..a9dba17 --- /dev/null +++ b/include/EclipseMonitor/Eth/Address.hpp @@ -0,0 +1,134 @@ +// Copyright (c) 2024 Haofan Zheng +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +#pragma once + + +#include + +#include + +#include "../Exceptions.hpp" +#include "../Internal/SimpleObj.hpp" +#include "DataTypes.hpp" +#include "Keccak256.hpp" + + +namespace EclipseMonitor +{ +namespace Eth +{ + + +class Address +{ +public: // static members: + + using value_type = ContractAddr; + + static constexpr size_t sk_sizeBytes = 20; + +public: + + Address(const value_type& addr) : + m_addr(addr) + {} + + explicit Address(const std::string& addr) : + m_addr() + { + size_t expLen = sk_sizeBytes * 2; + auto begin = addr.begin(); + + // check if the string begins with "0x" + if ( + (addr.size() >= 2) && // at least 2 characters + ((addr[0] == '0') && (addr[1] == 'x')) // starts with "0x" + ) + { + begin += 2; + expLen += 2; + } + + // check if the string is of the correct length + if (addr.size() != expLen) + { + throw Exception( + "The given ETH address hex string is of incorrect length" + ); + } + + Internal::Obj::Codec::Hex::Decode(m_addr.begin(), begin, addr.end()); + } + + Address(const Address& addr) : + m_addr(addr.m_addr) + {} + + Address(Address&& addr) : + m_addr(std::move(addr.m_addr)) + {} + + ~Address() = default; + + bool operator==(const Address& addr) const + { + return m_addr == addr.m_addr; + } + + bool operator!=(const Address& addr) const + { + return m_addr != addr.m_addr; + } + + std::string ToString(const std::string& prefix = "0x") const + { + // std::array should generate a hex string of length 40 + std::string hexLower = + Internal::Obj::Codec::Hex::Encode(m_addr, ""); + // std::array should generate a hex string of length 40 + std::string hexUpper = + Internal::Obj::Codec::HEX::Encode(m_addr, ""); + + // the checksummed address that is going to be generated + std::string checksummed = prefix; + + // The result of a 256-bit hash should have 32 bytes + auto addrHash = Keccak256(hexLower); + + for (size_t i = 0; i < hexLower.size(); ++i) + { + // if `i` is even, the nibble is the higher 4-bit of the byte + // e.g., (0 % 2) = 0, (2 % 2) = 0, (4 % 2) = 0, ... + // 1 - (i % 2) = 1 - 0 = 1 + // (1 - (i % 2)) * 4 = 1 * 4 = 4 + // if `i` is odd, the nibble is the lower 4-bit of the byte + // e.g., (1 % 2) = 1, (3 % 2) = 1, (5 % 2) = 1, ... + // 1 - (i % 2) = 1 - 1 = 0 + // (1 - (i % 2)) * 4 = 0 * 4 = 0 + uint8_t rightShift = (1 - (i % 2)) * 4; + uint8_t hashByte = addrHash[i / 2]; + uint8_t hashNibble = (hashByte >> rightShift) & 0x0FU; + + // if the nibble is greater than 7, the hex should be upper case + // otherwise, the hex should be lower case + char hexCh = hashNibble > 7 ? hexUpper[i] : hexLower[i]; + + checksummed.push_back(hexCh); + } + + return checksummed; + } + + +private: // members: + + value_type m_addr; +}; // class Address + + +} // namespace Eth +} // namespace EclipseMonitor + diff --git a/include/EclipseMonitor/Eth/Transaction/Ecdsa.hpp b/include/EclipseMonitor/Eth/Transaction/Ecdsa.hpp index 53c800e..beca30f 100644 --- a/include/EclipseMonitor/Eth/Transaction/Ecdsa.hpp +++ b/include/EclipseMonitor/Eth/Transaction/Ecdsa.hpp @@ -20,6 +20,7 @@ #include "../../Internal/SimpleObj.hpp" #include "../../Internal/Tls.hpp" #include "../../Exceptions.hpp" +#include "../Address.hpp" #include "../DataTypes.hpp" #include "../Keccak256.hpp" #include "DynamicFee.hpp" @@ -931,6 +932,43 @@ inline void SignTransaction( } +template +inline Address AddressFromPublicKey( + const Internal::Tls::EcPublicKeyBase<_PkeyTraits>& pubKey +) +{ + static constexpr Internal::Tls::EcType sk_ecType = + Internal::Tls::EcType::SECP256K1; + static constexpr size_t sk_curveByteSize = + Internal::Tls::GetCurveByteSize(sk_ecType); + + if (pubKey.GetEcType() != sk_ecType) + { + throw Exception("ETH key should be on curve secp256k1"); + } + + std::vector bytesXY; + bytesXY.reserve(sk_curveByteSize * 2); + auto appendBytesXY = [&bytesXY](std::vector&& bytes) + { + bytesXY.insert(bytesXY.end(), bytes.begin(), bytes.end()); + }; + appendBytesXY(pubKey.BorrowPubPointX().template Bytes()); + appendBytesXY(pubKey.BorrowPubPointY().template Bytes()); + + auto bytesHash = Keccak256(bytesXY); + size_t copyBegins = bytesHash.size() - 20; + ContractAddr bytesHash20; + std::copy_n( + bytesHash.begin() + copyBegins, + bytesHash20.size(), + bytesHash20.begin() + ); + + return Address(bytesHash20); +} + + } // namespace Transaction } // namespace Eth } // namespace EclipseMonitor diff --git a/test/src/Main.cpp b/test/src/Main.cpp index 3c111bf..3707cd0 100644 --- a/test/src/Main.cpp +++ b/test/src/Main.cpp @@ -12,7 +12,7 @@ namespace EclipseMonitor_Test int main(int argc, char** argv) { - constexpr size_t EXPECTED_NUM_OF_TEST_FILE = 26; + constexpr size_t EXPECTED_NUM_OF_TEST_FILE = 27; std::cout << "===== EclipseMonitor test program =====" << std::endl; std::cout << std::endl; diff --git a/test/src/TestEthAddress.cpp b/test/src/TestEthAddress.cpp new file mode 100644 index 0000000..aaf4007 --- /dev/null +++ b/test/src/TestEthAddress.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2024 Haofan Zheng +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + + +#include + +#include + +#include "Common.hpp" + + +namespace EclipseMonitor_Test +{ + extern size_t g_numOfTestFile; +} + +using namespace EclipseMonitor_Test; +using namespace EclipseMonitor::Eth; + + +GTEST_TEST(TestEthAddress, CountTestFile) +{ + static auto tmp = ++EclipseMonitor_Test::g_numOfTestFile; + (void)tmp; +} + + +/** + * @brief + * + * @param addrHexStr this string should avoid the prefix "0x" + * @param prefix + */ +static void TestAddressParseAndString( + const std::string& addrHexStr, + const std::string& prefix = "0x" +) +{ + // parse without the prefix + Address addr(addrHexStr); + auto generatedStr = addr.ToString(""); + EXPECT_EQ(generatedStr, addrHexStr); + + // parse again with the prefix + Address addr2(generatedStr); + auto generatedStr2 = addr2.ToString(prefix); + EXPECT_EQ(generatedStr2, prefix + addrHexStr); + + // two `Address` instances should be equal + EXPECT_EQ(addr, addr2); + EXPECT_FALSE(addr != addr2); + + // test the copy constructor + Address copied(addr); + EXPECT_EQ(copied, addr); + + // test the move constructor + Address moved(std::move(copied)); + EXPECT_EQ(moved, addr); +} + + +GTEST_TEST(TestEthAddress, ParseAndString) +{ + TestAddressParseAndString("010EEE07C4020148D96F80CEd0EE4D129a267D20"); + TestAddressParseAndString("453272C49Dd5b2343Fef13EAdb746E083fB36411"); + TestAddressParseAndString("653E2Bb1258edA29c2F348e88de7F936af8E32C3"); + TestAddressParseAndString("359E745B64498408F11e2811c7376c745084C80f"); + TestAddressParseAndString("9Baa87097A3C3Ff7Fb6428baa2930a031A1Ea4dF"); + TestAddressParseAndString("Dbc12BE0FB8059040b275fe35D6C0c44e420436E"); + TestAddressParseAndString("2bE4803127CD97Abb65F1bE319fA18b6A5567C77"); + TestAddressParseAndString("B39c2ecB0BC4Fa3e75e4Adcb3A59B8cb46AEc16c"); + TestAddressParseAndString("7Bc655F54f53c5ae0aac55d19CCe245368f518AB"); + TestAddressParseAndString("786d53fCc2ac73F3ac8aC21a1E03c0c1bDC70Ad3"); + + // string with incorrect length + EXPECT_THROW_MSG( + TestAddressParseAndString(""), + EclipseMonitor::Exception, + "The given ETH address hex string is of incorrect length" + ); + EXPECT_THROW_MSG( + TestAddressParseAndString("0"), + EclipseMonitor::Exception, + "The given ETH address hex string is of incorrect length" + ); + EXPECT_THROW_MSG( + TestAddressParseAndString("0x"), + EclipseMonitor::Exception, + "The given ETH address hex string is of incorrect length" + ); + EXPECT_THROW_MSG( + TestAddressParseAndString("786d53fCc2"), + EclipseMonitor::Exception, + "The given ETH address hex string is of incorrect length" + ); + EXPECT_THROW_MSG( + TestAddressParseAndString("786d53fCc2ac73F3ac8aC21a1E03c0c1bDC70Ad3786d53fCc2"), + EclipseMonitor::Exception, + "The given ETH address hex string is of incorrect length" + ); + + // string containing invalid characters + EXPECT_THROW_MSG( + TestAddressParseAndString("786d53fCc2ac73F3ac8HC21a1E03c0c1bDC70Ad3"), + std::invalid_argument, + "Invalid hex character" + ); +} + diff --git a/test/src/TestEthTxnEcdsa.cpp b/test/src/TestEthTxnEcdsa.cpp index dd34942..f584675 100644 --- a/test/src/TestEthTxnEcdsa.cpp +++ b/test/src/TestEthTxnEcdsa.cpp @@ -7,6 +7,8 @@ #include #include + +#include #include #include "Common.hpp" @@ -540,3 +542,68 @@ GTEST_TEST(TestEthTxnEcdsa, EcdsaRawSign) } } + +GTEST_TEST(TestEthTxnEcdsa, AddressFromPublicKey) +{ + std::unique_ptr rand = + mbedTLScpp::Internal::make_unique(); + + { + auto keyPair = mbedTLScpp::EcKeyPair:: + FromSecretNum( + mbedTLScpp::BigNum( + "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318", + /*radix=*/16 + ), + *rand + ); + + Address addr = Transaction::AddressFromPublicKey(keyPair); + EXPECT_EQ(addr.ToString(), "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23"); + } + + { + auto keyPair = mbedTLScpp::EcKeyPair:: + FromSecretNum( + mbedTLScpp::BigNum( + "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318", + /*radix=*/16 + ), + *rand + ); + EXPECT_THROW_MSG( + Transaction::AddressFromPublicKey(keyPair), + EclipseMonitor::Exception, + "ETH key should be on curve secp256k1" + ); + } + + { + auto pubKeyDer = SimpleObjects::Codec::Hex::Decode >( + std::string( + "3056301006072a8648ce3d020106052b8104000a034200040630aac5785f14f4" + "cf4713a9ef9b4f32e3e7ae4793de26bdc28c4ea1dd80f8b01bfe05ebb211e441" + "0b2ed29e36c7d9b8ae75f4514d7dd435cb86fc3e5cdf267f" + ) + ); + auto pubKey = mbedTLScpp::EcPublicKey:: + FromDER(mbedTLScpp::CtnFullR(pubKeyDer)); + Address addr = Transaction::AddressFromPublicKey(pubKey); + EXPECT_EQ(addr.ToString(), "0x010EEE07C4020148D96F80CEd0EE4D129a267D20"); + } + + { + auto pubKeyDer = SimpleObjects::Codec::Hex::Decode >( + std::string( + "3056301006072a8648ce3d020106052b8104000a034200049d3dea6bb79267e1" + "1135464ecbf99061b50c8ce852db578616a230d37ac3c0bcbe76bb3fa280b582" + "542a474d16e754e4f83b04cc9448c95c02b16945e4e16063" + ) + ); + auto pubKey = mbedTLScpp::EcPublicKey:: + FromDER(mbedTLScpp::CtnFullR(pubKeyDer)); + Address addr = Transaction::AddressFromPublicKey(pubKey); + EXPECT_EQ(addr.ToString(), "0x453272C49Dd5b2343Fef13EAdb746E083fB36411"); + } +} +