Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: payment address parsing in domain #39

Merged
merged 1 commit into from
Nov 21, 2024
Merged
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
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;
}
30 changes: 18 additions & 12 deletions include/kth/domain/wallet/payment_address.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,27 @@ 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);
payment_address(chain::script const& script, uint8_t version = mainnet_p2sh);

payment_address& operator=(payment_address const& x) = default;
explicit
payment_address(chain::script const& script, uint8_t version = mainnet_p2sh);

/// Operators.
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 @@ -164,9 +172,7 @@ namespace std {
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.hash_span());
}
};
} // 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
Loading