Skip to content

Commit

Permalink
Added eth address class and conversion from eckey to addr
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenghaven committed Jun 27, 2024
1 parent d4b0c56 commit c9a566e
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 1 deletion.
134 changes: 134 additions & 0 deletions include/EclipseMonitor/Eth/Address.hpp
Original file line number Diff line number Diff line change
@@ -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 <string>

#include <SimpleObjects/Codec/Hex.hpp>

#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<uint8_t, 20> should generate a hex string of length 40
std::string hexLower =
Internal::Obj::Codec::Hex::Encode<std::string>(m_addr, "");
// std::array<uint8_t, 20> should generate a hex string of length 40
std::string hexUpper =
Internal::Obj::Codec::HEX::Encode<std::string>(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

38 changes: 38 additions & 0 deletions include/EclipseMonitor/Eth/Transaction/Ecdsa.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -931,6 +932,43 @@ inline void SignTransaction(
}


template<typename _PkeyTraits>
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<uint8_t> bytesXY;
bytesXY.reserve(sk_curveByteSize * 2);
auto appendBytesXY = [&bytesXY](std::vector<uint8_t>&& bytes)
{
bytesXY.insert(bytesXY.end(), bytes.begin(), bytes.end());
};
appendBytesXY(pubKey.BorrowPubPointX().template Bytes</*little endian=*/false>());
appendBytesXY(pubKey.BorrowPubPointY().template Bytes</*little endian=*/false>());

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
Expand Down
2 changes: 1 addition & 1 deletion test/src/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
112 changes: 112 additions & 0 deletions test/src/TestEthAddress.cpp
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>

#include <EclipseMonitor/Eth/Address.hpp>

#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"
);
}

67 changes: 67 additions & 0 deletions test/src/TestEthTxnEcdsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <gtest/gtest.h>

#include <EclipseMonitor/Eth/Transaction/Ecdsa.hpp>

#include <mbedTLScpp/DefaultRbg.hpp>
#include <mbedTLScpp/EcKey.hpp>

#include "Common.hpp"
Expand Down Expand Up @@ -540,3 +542,68 @@ GTEST_TEST(TestEthTxnEcdsa, EcdsaRawSign)
}
}


GTEST_TEST(TestEthTxnEcdsa, AddressFromPublicKey)
{
std::unique_ptr<mbedTLScpp::RbgInterface> rand =
mbedTLScpp::Internal::make_unique<mbedTLScpp::DefaultRbg>();

{
auto keyPair = mbedTLScpp::EcKeyPair<mbedTLScpp::EcType::SECP256K1>::
FromSecretNum(
mbedTLScpp::BigNum(
"4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318",
/*radix=*/16
),
*rand
);

Address addr = Transaction::AddressFromPublicKey(keyPair);
EXPECT_EQ(addr.ToString(), "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23");
}

{
auto keyPair = mbedTLScpp::EcKeyPair<mbedTLScpp::EcType::SECP256R1>::
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::vector<uint8_t> >(
std::string(
"3056301006072a8648ce3d020106052b8104000a034200040630aac5785f14f4"
"cf4713a9ef9b4f32e3e7ae4793de26bdc28c4ea1dd80f8b01bfe05ebb211e441"
"0b2ed29e36c7d9b8ae75f4514d7dd435cb86fc3e5cdf267f"
)
);
auto pubKey = mbedTLScpp::EcPublicKey<mbedTLScpp::EcType::SECP256K1>::
FromDER(mbedTLScpp::CtnFullR(pubKeyDer));
Address addr = Transaction::AddressFromPublicKey(pubKey);
EXPECT_EQ(addr.ToString(), "0x010EEE07C4020148D96F80CEd0EE4D129a267D20");
}

{
auto pubKeyDer = SimpleObjects::Codec::Hex::Decode<std::vector<uint8_t> >(
std::string(
"3056301006072a8648ce3d020106052b8104000a034200049d3dea6bb79267e1"
"1135464ecbf99061b50c8ce852db578616a230d37ac3c0bcbe76bb3fa280b582"
"542a474d16e754e4f83b04cc9448c95c02b16945e4e16063"
)
);
auto pubKey = mbedTLScpp::EcPublicKey<mbedTLScpp::EcType::SECP256K1>::
FromDER(mbedTLScpp::CtnFullR(pubKeyDer));
Address addr = Transaction::AddressFromPublicKey(pubKey);
EXPECT_EQ(addr.ToString(), "0x453272C49Dd5b2343Fef13EAdb746E083fB36411");
}
}

0 comments on commit c9a566e

Please sign in to comment.