Skip to content

Commit

Permalink
fix: payment address parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
fpelliccioni committed Nov 20, 2024
1 parent 416ed9b commit d82765c
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 36 deletions.
50 changes: 50 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ option(WITH_ICU "Compile with International Components for Unicode." OFF)
option(WITH_PNG "Compile with Libpng support." OFF)
option(WITH_QRENCODE "Compile with QREncode." OFF)
option(JUST_KTH_SOURCES "Just Knuth source code to be linted." OFF)
option(WITH_CONSOLE "Compile console application." OFF)

option(GLOBAL_BUILD "" OFF)

Expand Down Expand Up @@ -461,6 +462,55 @@ endif()

_group_sources(${PROJECT_NAME} "${CMAKE_CURRENT_LIST_DIR}")

# Console
# ------------------------------------------------------------------------------
message(STATUS "Knuth: WITH_CONSOLE ${WITH_CONSOLE}")
if (WITH_CONSOLE)
set(_test_console_sources
console/main.cpp
)

add_executable(kth_domain_console ${_test_console_sources})

target_link_libraries(kth_domain_console ${PROJECT_NAME})

set_target_properties(
kth_domain_console PROPERTIES
FOLDER "domain"
OUTPUT_NAME kth_domain_console)


# Enable sanitizers and other debug options only in debug mode
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Activating sanitizers and debug options")
message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
message(STATUS "APPLE: ${APPLE}")

if (APPLE)
# In macOS, use only AddressSanitizer and UndefinedBehaviorSanitizer
target_compile_options(kth_domain_console PRIVATE
-fsanitize=address,undefined
-fno-omit-frame-pointer
-g
)
target_link_options(kth_domain_console PRIVATE
-fsanitize=address,undefined
)
else()
# In other operating systems (like Linux), add LeakSanitizer also
target_compile_options(kth_domain_console PRIVATE
-fsanitize=address,undefined,leak
-fno-omit-frame-pointer
-g
)
target_link_options(kth_domain_console PRIVATE
-fsanitize=address,undefined,leak
)
endif()

endif()
endif()

# Tests
# ------------------------------------------------------------------------------
if (WITH_TESTS)
Expand Down
16 changes: 16 additions & 0 deletions console/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <iostream>
#include <kth/domain/wallet/payment_address.hpp>

int main() {
using kth::domain::wallet::payment_address;

std::cout << "start" << std::endl;
payment_address const address("bitcoincash:pvstqkm54dtvnpyqxt5m5n7sjsn4enrlxc526xyxlnjkaycdzfeu69reyzmqx");
std::cout << "address created" << std::endl;
std::cout << "address is valid: " << bool(address) << std::endl;
std::cout << "address encoded cashaddr: " << address.encoded_cashaddr(false) << std::endl;
std::cout << "address encoded token address: " << address.encoded_cashaddr(true) << std::endl;
std::cout << "address encoded legacy: " << address.encoded_legacy() << std::endl;

return 0;
}
25 changes: 18 additions & 7 deletions include/kth/domain/wallet/payment_address.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,22 @@ class KD_API payment_address {

/// Constructors.
payment_address() = default;
payment_address(payment_address const& x) = default;
// payment_address(payment_address const& x) = default;
// payment_address& operator=(payment_address const& x) = default;
// payment_address(payment_address&& x) noexcept;
// payment_address(payment_address&& x) noexcept = default;
// payment_address(payment_address&&) = delete;
// payment_address& operator=(payment_address&&) = delete;


payment_address(payment const& decoded);
payment_address(ec_private const& secret);
payment_address(std::string const& address);
payment_address(short_hash const& hash, uint8_t version = mainnet_p2kh);
payment_address(hash_digest const& hash, uint8_t version = mainnet_p2kh);
payment_address(ec_public const& point, uint8_t version = mainnet_p2kh);
payment_address(chain::script const& script, uint8_t version = mainnet_p2sh);

payment_address& operator=(payment_address const& x) = default;

/// Operators.
bool operator==(payment_address const& x) const;
Expand Down Expand Up @@ -93,9 +98,8 @@ class KD_API payment_address {
[[nodiscard]]
uint8_t version() const;

//TODO(fernando): re-enable this
// [[nodiscard]]
// byte_span hash() const;
[[nodiscard]]
byte_span hash() const;

[[nodiscard]]
short_hash hash20() const;
Expand Down Expand Up @@ -157,6 +161,12 @@ struct KD_API wrapped_data {
uint32_t checksum;
};


// static_assert(std::is_copy_constructible_v<payment_address>, "payment_address should be copy constructible.");
// static_assert(std::is_copy_assignable_v<payment_address>, "payment_address should be copy assignable.");
// static_assert( ! std::is_move_constructible_v<payment_address>, "payment_address should not be move constructible.");
// static_assert( ! std::is_move_assignable_v<payment_address>, "payment_address should not be move assignable.");

} // namespace kth::domain::wallet

// Allow payment_address to be in indexed in std::*map classes.
Expand All @@ -165,8 +175,9 @@ template <>
struct hash<kth::domain::wallet::payment_address> {
size_t operator()(kth::domain::wallet::payment_address const& address) const {
//TODO(fernando): re-enable this
// return std::hash<kth::byte_span>()(address.hash());
return std::hash<kth::short_hash>()(address.hash20());
// return std::hash<kth::byte_span>()(address.hash32());
// return std::hash<kth::short_hash>()(address.hash20());
return std::hash<kth::hash_digest>()(address.hash32());
}
};
} // namespace std
Expand Down
135 changes: 107 additions & 28 deletions src/wallet/payment_address.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,71 @@ using namespace kth::infrastructure::wallet;
namespace kth::domain::wallet {

payment_address::payment_address(payment const& decoded)
: payment_address(from_payment(decoded))
{}
: payment_address(payment_address{from_payment(decoded)})
{
std::cout << "payment_address(payment const& decoded)" << std::endl;
}

payment_address::payment_address(std::string const& address)
: payment_address(from_string(address))
{}
: payment_address(payment_address{from_string(address)})
{
std::cout << "payment_address(std::string const& address)" << std::endl;

printf("hash_data_.data(): %p\n", hash_data_.data());

std::cout << "hash().size(): " << this->hash().size() << std::endl;
std::cout << "hash_span_.size(): " << hash_span_.size() << std::endl;

for (auto byte : this->hash()) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
}
std::cout << std::endl;
std::cout << "----------------------------------------" << std::endl;
}

payment_address::payment_address(ec_private const& secret)
: payment_address(from_private(secret))
{}
: payment_address(payment_address{from_private(secret)})
{
std::cout << "payment_address(ec_private const& secret)" << std::endl;
}

payment_address::payment_address(ec_public const& point, uint8_t version)
: payment_address(from_public(point, version))
{}
: payment_address(payment_address{from_public(point, version)})
{
std::cout << "payment_address(ec_public const& point, uint8_t version)" << std::endl;
}

payment_address::payment_address(chain::script const& script, uint8_t version)
: payment_address(from_script(script, version))
{}
: payment_address(payment_address{from_script(script, version)})
{
std::cout << "payment_address(chain::script const& script, uint8_t version)" << std::endl;
}

payment_address::payment_address(short_hash const& hash, uint8_t version)
: valid_(true)
, version_(version)
, hash_span_{hash_data_.begin(), hash.size()}
{
std::cout << "payment_address(short_hash const& hash, uint8_t version)" << std::endl;
std::copy_n(hash.begin(), hash.size(), hash_data_.begin());
}

payment_address::payment_address(hash_digest const& hash, uint8_t version)
: valid_(true), version_(version), hash_span_{hash_data_.begin(), hash.size()}
{
std::cout << "payment_address(hash_digest const& hash, uint8_t version)" << std::endl;
printf("hash_data_.data(): %p\n", hash_data_.data());
std::copy_n(hash.begin(), hash.size(), hash_data_.begin());
printf("hash_data_.data(): %p\n", hash_data_.data());

std::cout << "hash().size(): " << this->hash().size() << std::endl;
std::cout << "hash_span_.size(): " << hash_span_.size() << std::endl;
// print the hash in hex format (each byte as 2 chars)
for (auto byte : this->hash()) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
}
std::cout << std::endl;
std::cout << "----------------------------------------" << std::endl;
}

// Validators.
Expand Down Expand Up @@ -160,13 +202,36 @@ payment_address payment_address::from_string_cashaddr(std::string const& address
return {};
}

short_hash hash;
std::copy(std::begin(data) + 1, std::end(data), std::begin(hash));
if (data.size() == short_hash_size + 1) {
std::cout << "***** 20 bytes hash" << std::endl;
short_hash hash;
if ((data.size() - 1) != hash.size()) {
return {};
}
std::copy(std::begin(data) + 1, std::end(data), std::begin(hash));

if (prefix == payment_address::cashaddr_prefix_mainnet) {
return {hash, type == PUBKEY_TYPE ? payment_address::mainnet_p2kh : payment_address::mainnet_p2sh};
if (prefix == payment_address::cashaddr_prefix_mainnet) {
return {hash, type == PUBKEY_TYPE ? payment_address::mainnet_p2kh : payment_address::mainnet_p2sh};
}
return {hash, type == PUBKEY_TYPE ? payment_address::testnet_p2kh : payment_address::testnet_p2sh};
}
return {hash, type == PUBKEY_TYPE ? payment_address::testnet_p2kh : payment_address::testnet_p2sh};

if (data.size() == hash_size + 1) {
std::cout << "***** 32 bytes hash" << std::endl;
hash_digest hash;
if ((data.size() - 1) != hash.size()) {
return {};
}
std::copy(std::begin(data) + 1, std::end(data), std::begin(hash));

if (prefix == payment_address::cashaddr_prefix_mainnet) {
return {hash, type == PUBKEY_TYPE ? payment_address::mainnet_p2kh : payment_address::mainnet_p2sh};
}
return {hash, type == PUBKEY_TYPE ? payment_address::testnet_p2kh : payment_address::testnet_p2sh};
}

// Invalid address.
return {};
}

#endif //KTH_CURRENCY_BCH
Expand All @@ -175,7 +240,15 @@ payment_address payment_address::from_string(std::string const& address) {
payment decoded;
if ( ! decode_base58(decoded, address) || ! is_address(decoded)) {
#if defined(KTH_CURRENCY_BCH)
return from_string_cashaddr(address);
auto result = from_string_cashaddr(address);
std::cout << "payment_address::from_string(std::string const& address)" << std::endl;
printf("result.hash().data(): %p\n", result.hash().data());
std::cout << "result.hash().size(): " << result.hash().size() << std::endl;
for (auto byte : result.hash()) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
}
std::cout << std::endl;
return result;
#else
return {};
#endif //KTH_CURRENCY_BCH
Expand Down Expand Up @@ -292,25 +365,32 @@ data_chunk pack_addr_data_(T const& id, uint8_t type) {
return converted;
}

std::string encode_cashaddr_(payment_address const& wallet, bool token_aware) {
std::string encode_cashaddr_(payment_address const& addr, bool token_aware) {
// Mainnet
if (wallet.version() == payment_address::mainnet_p2kh || wallet.version() == payment_address::mainnet_p2sh) {
if (addr.version() == payment_address::mainnet_p2kh || addr.version() == payment_address::mainnet_p2sh) {
if (token_aware) {
return cashaddr::encode(payment_address::cashaddr_prefix_mainnet,
pack_addr_data_(wallet.hash20(), wallet.version() == payment_address::mainnet_p2kh ? TOKEN_PUBKEY_TYPE : TOKEN_SCRIPT_TYPE));
pack_addr_data_(addr.hash(), addr.version() == payment_address::mainnet_p2kh ? TOKEN_PUBKEY_TYPE : TOKEN_SCRIPT_TYPE));
}
std::cout << "addr.hash().size(): " << addr.hash().size() << std::endl;
// print the hash in hex format (each byte as 2 chars)
for (auto byte : addr.hash()) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
}
return cashaddr::encode(payment_address::cashaddr_prefix_mainnet,
pack_addr_data_(wallet.hash20(), wallet.version() == payment_address::mainnet_p2kh ? PUBKEY_TYPE : SCRIPT_TYPE));
std::cout << std::endl;
return cashaddr::encode(
payment_address::cashaddr_prefix_mainnet,
pack_addr_data_(addr.hash(), addr.version() == payment_address::mainnet_p2kh ? PUBKEY_TYPE : SCRIPT_TYPE));
}

// Testnet
if (wallet.version() == payment_address::testnet_p2kh || wallet.version() == payment_address::testnet_p2sh) {
if (addr.version() == payment_address::testnet_p2kh || addr.version() == payment_address::testnet_p2sh) {
if (token_aware) {
return cashaddr::encode(payment_address::cashaddr_prefix_testnet,
pack_addr_data_(wallet.hash20(), wallet.version() == payment_address::testnet_p2kh ? TOKEN_PUBKEY_TYPE : TOKEN_SCRIPT_TYPE));
pack_addr_data_(addr.hash(), addr.version() == payment_address::testnet_p2kh ? TOKEN_PUBKEY_TYPE : TOKEN_SCRIPT_TYPE));
}
return cashaddr::encode(payment_address::cashaddr_prefix_testnet,
pack_addr_data_(wallet.hash20(), wallet.version() == payment_address::testnet_p2kh ? PUBKEY_TYPE : SCRIPT_TYPE));
pack_addr_data_(addr.hash(), addr.version() == payment_address::testnet_p2kh ? PUBKEY_TYPE : SCRIPT_TYPE));
}
return "";
}
Expand All @@ -330,10 +410,9 @@ uint8_t payment_address::version() const {
return version_;
}

//TODO(fernando): re-enable this
// kth::byte_span payment_address::hash() const {
// return hash_span_;
// }
kth::byte_span payment_address::hash() const {
return hash_span_;
}

short_hash payment_address::hash20() const {
short_hash hash;
Expand Down
20 changes: 19 additions & 1 deletion test/wallet/payment_address.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ TEST_CASE("payment address construct secret testnet valid expected", "[payment a

// MSVC CTP loses the MSB (WIF prefix) byte of the literal version when
// using this initializer, but the MSB isn't used by payment_address.
payment_address const address({secret, 0x806f});
payment_address const address(ec_private{secret, 0x806f});
REQUIRE(address);
REQUIRE(address.encoded_legacy() == ADDRESS_COMPRESSED_TESTNET);
}
Expand Down Expand Up @@ -276,6 +276,24 @@ TEST_CASE("payment address cashAddr testnet from string", "[payment address]") {
REQUIRE(address.encoded_cashaddr(false) == "bchtest:qpzz8n7jp6847yyx8t33matrgcsdx6c0cvleatp707");
REQUIRE(address.encoded_legacy() == "mmjF9M1saNs7vjCGaSDaDHvFAtTgUNtfrJ");
}

TEST_CASE("payment address token address from string", "[payment address]") {
payment_address const address("bitcoincash:pvstqkm54dtvnpyqxt5m5n7sjsn4enrlxc526xyxlnjkaycdzfeu69reyzmqx");
REQUIRE(address);

std::cout << "----------------------------------------" << std::endl;
printf("address.hash().data(): %p\n", address.hash().data());
std::cout << "in the test: " << std::endl;
std::cout << "addr.hash().size(): " << address.hash().size() << std::endl;
for (auto byte : address.hash()) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
}
std::cout << std::endl;

REQUIRE(address.encoded_cashaddr(false) == "bitcoincash:pvstqkm54dtvnpyqxt5m5n7sjsn4enrlxc526xyxlnjkaycdzfeu69reyzmqx");
REQUIRE(address.encoded_cashaddr(true) == "bitcoincash:pvstqkm54dtvnpyqxt5m5n7sjsn4enrlxc526xyxlnjkaycdzfeu69reyzmqx");
REQUIRE(address.encoded_legacy() == "17DHrHvtmMRs9ciersFCPNhvJtryd5NWbT");
}
#endif

// End Test Suite

0 comments on commit d82765c

Please sign in to comment.