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 21, 2024
1 parent 416ed9b commit 4e3f194
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 83 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;
}
37 changes: 26 additions & 11 deletions include/kth/domain/wallet/payment_address.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,28 @@ class KD_API payment_address {

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

explicit
payment_address(payment const& decoded);

explicit
payment_address(ec_private const& secret);

explicit
payment_address(std::string const& address);

explicit
payment_address(short_hash const& hash, uint8_t version = mainnet_p2kh);

explicit
payment_address(hash_digest const& hash, uint8_t version = mainnet_p2kh);

explicit
payment_address(ec_public const& point, uint8_t version = mainnet_p2kh);

explicit
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;
bool operator!=(payment_address const& x) const;
Expand Down Expand Up @@ -93,9 +103,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_span() const;

[[nodiscard]]
short_hash hash20() const;
Expand Down Expand Up @@ -145,9 +154,8 @@ class KD_API payment_address {

bool valid_ = false;
uint8_t version_ = 0;
// short_hash hash_ = null_short_hash;
hash_digest hash_data_ = null_hash;
byte_span hash_span_ = {hash_data_.begin(), size_t(0)};
size_t hash_size_ = 0;
};

/// The pre-encoded structure of a payment address or other similar data.
Expand All @@ -157,6 +165,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 +179,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
2 changes: 1 addition & 1 deletion src/wallet/bitcoin_uri.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ std::string bitcoin_uri::r() const {
}

payment_address bitcoin_uri::payment() const {
return {address_};
return payment_address{address_};
}

stealth_address bitcoin_uri::stealth() const {
Expand Down
110 changes: 73 additions & 37 deletions src/wallet/payment_address.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,31 +33,40 @@ 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)})
{}

payment_address::payment_address(std::string const& address)
: payment_address(from_string(address))
: payment_address(payment_address{from_string(address)})
{}

payment_address::payment_address(ec_private const& secret)
: payment_address(from_private(secret))
: payment_address(payment_address{from_private(secret)})
{}

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)})
{}

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)})
{}

payment_address::payment_address(short_hash const& hash, uint8_t version)
: valid_(true), version_(version), hash_span_{hash_data_.begin(), hash.size()}
: valid_(true)
, version_(version)
, hash_size_(hash.size())
{
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_data_(hash)
, hash_size_(hash.size())
{}

// Validators.
// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -160,13 +169,34 @@ 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) {
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 payment_address{hash, type == PUBKEY_TYPE ? payment_address::mainnet_p2kh : payment_address::mainnet_p2sh};
}
return payment_address{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) {
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 payment_address{hash, type == PUBKEY_TYPE ? payment_address::mainnet_p2kh : payment_address::mainnet_p2sh};
}
return payment_address{hash, type == PUBKEY_TYPE ? payment_address::testnet_p2kh : payment_address::testnet_p2sh};
}

// Invalid address.
return {};
}

#endif //KTH_CURRENCY_BCH
Expand All @@ -181,7 +211,7 @@ payment_address payment_address::from_string(std::string const& address) {
#endif //KTH_CURRENCY_BCH
}

return {decoded};
return payment_address{decoded};
}

payment_address payment_address::from_payment(payment const& decoded) {
Expand All @@ -190,34 +220,34 @@ payment_address payment_address::from_payment(payment const& decoded) {
}

auto const hash = slice<1, short_hash_size + 1>(decoded);
return {hash, decoded.front()};
return payment_address{hash, decoded.front()};
}

payment_address payment_address::from_private(ec_private const& secret) {
if ( ! secret) {
return {};
return payment_address{};
}

return {secret.to_public(), secret.payment_version()};
return payment_address{secret.to_public(), secret.payment_version()};
}

payment_address payment_address::from_public(ec_public const& point, uint8_t version) {
if ( ! point) {
return {};
return payment_address{};
}

data_chunk data;
if ( ! point.to_data(data)) {
return {};
return payment_address{};
}

return {bitcoin_short_hash(data), version};
return payment_address{bitcoin_short_hash(data), version};
}

payment_address payment_address::from_script(chain::script const& script, uint8_t version) {
// Working around VC++ CTP compiler break here.
auto const data = script.to_data(false);
return {bitcoin_short_hash(data), version};
return payment_address{bitcoin_short_hash(data), version};
}

// Cast operators.
Expand Down Expand Up @@ -292,25 +322,26 @@ 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_span(), addr.version() == payment_address::mainnet_p2kh ? TOKEN_PUBKEY_TYPE : TOKEN_SCRIPT_TYPE));
}
return cashaddr::encode(payment_address::cashaddr_prefix_mainnet,
pack_addr_data_(wallet.hash20(), wallet.version() == payment_address::mainnet_p2kh ? PUBKEY_TYPE : SCRIPT_TYPE));
return cashaddr::encode(
payment_address::cashaddr_prefix_mainnet,
pack_addr_data_(addr.hash_span(), 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_span(), 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_span(), addr.version() == payment_address::testnet_p2kh ? PUBKEY_TYPE : SCRIPT_TYPE));
}
return "";
}
Expand All @@ -330,10 +361,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_span() const {
return {hash_data_.begin(), hash_size_};
}

short_hash payment_address::hash20() const {
short_hash hash;
Expand Down Expand Up @@ -405,11 +435,14 @@ payment_address::list payment_address::extract_input(chain::script const& script
// A server can differentiate by extracting from the previous output.
case script_pattern::sign_key_hash: {
return {
{ec_public{script[1].data()}, p2kh_version},
{bitcoin_short_hash(script.back().data()), p2sh_version}};
payment_address{ec_public{script[1].data()}, p2kh_version},
payment_address{bitcoin_short_hash(script.back().data()), p2sh_version}
};
}
case script_pattern::sign_script_hash: {
return {{bitcoin_short_hash(script.back().data()), p2sh_version}};
return {
payment_address{bitcoin_short_hash(script.back().data()), p2sh_version}
};
}

// There is no address in sign_public_key script (signature only)
Expand Down Expand Up @@ -439,16 +472,19 @@ payment_address::list payment_address::extract_output(chain::script const& scrip
switch (pattern) {
case script_pattern::pay_key_hash: {
return {
{to_array<short_hash_size>(script[2].data()), p2kh_version}};
payment_address{to_array<short_hash_size>(script[2].data()), p2kh_version}
};
}
case script_pattern::pay_script_hash: {
return {
{to_array<short_hash_size>(script[1].data()), p2sh_version}};
payment_address{to_array<short_hash_size>(script[1].data()), p2sh_version}
};
}
case script_pattern::pay_public_key: {
return {
// pay_public_key is not p2kh but we conflate for tracking.
{ec_public{script[0].data()}, p2kh_version}};
payment_address{ec_public{script[0].data()}, p2kh_version}
};
}

// Bare multisig and null data do not associate a payment address.
Expand Down
Loading

0 comments on commit 4e3f194

Please sign in to comment.