diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b2102f4b..99d0cfbc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) diff --git a/console/main.cpp b/console/main.cpp new file mode 100644 index 000000000..50b634e5d --- /dev/null +++ b/console/main.cpp @@ -0,0 +1,16 @@ +#include +#include + +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; +} diff --git a/include/kth/domain/wallet/payment_address.hpp b/include/kth/domain/wallet/payment_address.hpp index 2aaad0ec0..3b7639ab5 100644 --- a/include/kth/domain/wallet/payment_address.hpp +++ b/include/kth/domain/wallet/payment_address.hpp @@ -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; @@ -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; @@ -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. @@ -164,9 +172,7 @@ namespace std { template <> struct hash { size_t operator()(kth::domain::wallet::payment_address const& address) const { - //TODO(fernando): re-enable this - // return std::hash()(address.hash()); - return std::hash()(address.hash20()); + return std::hash()(address.hash_span()); } }; } // namespace std diff --git a/src/wallet/bitcoin_uri.cpp b/src/wallet/bitcoin_uri.cpp index c23ae5a58..29788b83d 100644 --- a/src/wallet/bitcoin_uri.cpp +++ b/src/wallet/bitcoin_uri.cpp @@ -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 { diff --git a/src/wallet/payment_address.cpp b/src/wallet/payment_address.cpp index 1b0eb5529..404a6c6ef 100644 --- a/src/wallet/payment_address.cpp +++ b/src/wallet/payment_address.cpp @@ -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. // ---------------------------------------------------------------------------- @@ -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 @@ -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) { @@ -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. @@ -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 ""; } @@ -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; @@ -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) @@ -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(script[2].data()), p2kh_version}}; + payment_address{to_array(script[2].data()), p2kh_version} + }; } case script_pattern::pay_script_hash: { return { - {to_array(script[1].data()), p2sh_version}}; + payment_address{to_array(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. diff --git a/test/wallet/payment_address.cpp b/test/wallet/payment_address.cpp index 565921c27..b0fe66111 100644 --- a/test/wallet/payment_address.cpp +++ b/test/wallet/payment_address.cpp @@ -10,7 +10,7 @@ using namespace kd; using namespace kth::domain::chain; using namespace kth::domain::wallet; -// Start Test Suite: payment address tests +// Start Test Suite: payment_address tests // $ bx base16-encode "Satoshi" | bx sha256 #define SECRET "002688cc350a5333a87fa622eacec626c3d1c0ebf9f3793de3885fa254d7e393" @@ -57,13 +57,13 @@ using namespace kth::domain::wallet; // negative tests: -TEST_CASE("payment address construct default invalid", "[payment address]") { +TEST_CASE("payment_address construct default invalid", "[payment_address]") { payment_address const address; REQUIRE( ! address); REQUIRE(address.encoded_legacy() == UNINITIALIZED_ADDRESS); } -TEST_CASE("payment address construct string invalid invalid", "[payment address]") { +TEST_CASE("payment_address construct string invalid invalid", "[payment_address]") { payment_address const address("bogus"); REQUIRE( ! address); REQUIRE(address.encoded_legacy() == UNINITIALIZED_ADDRESS); @@ -71,89 +71,89 @@ TEST_CASE("payment address construct string invalid invalid", "[payment address] // construct secret: -TEST_CASE("payment address construct secret valid expected", "[payment address]") { +TEST_CASE("payment_address construct secret valid expected", "[payment_address]") { ec_secret secret; REQUIRE(decode_base16(secret, SECRET)); - payment_address const address(secret); + payment_address const address(ec_private{secret}); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_COMPRESSED); } -TEST_CASE("payment address construct secret testnet valid expected", "[payment address]") { +TEST_CASE("payment_address construct secret testnet valid expected", "[payment_address]") { ec_secret secret; REQUIRE(decode_base16(secret, SECRET)); // 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); } -TEST_CASE("payment address construct secret mainnet uncompressed valid expected", "[payment address]") { +TEST_CASE("payment_address construct secret mainnet uncompressed valid expected", "[payment_address]") { ec_secret secret; REQUIRE(decode_base16(secret, SECRET)); - payment_address const address({secret, payment_address::mainnet_p2kh, false}); + payment_address const address(ec_private{secret, payment_address::mainnet_p2kh, false}); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_UNCOMPRESSED); } -TEST_CASE("payment address construct secret testnet uncompressed valid expected", "[payment address]") { +TEST_CASE("payment_address construct secret testnet uncompressed valid expected", "[payment_address]") { ec_secret secret; REQUIRE(decode_base16(secret, SECRET)); // 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, false}); + payment_address const address(ec_private{secret, 0x806f, false}); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_UNCOMPRESSED_TESTNET); } // construct public: -TEST_CASE("payment address construct public valid expected", "[payment address]") { +TEST_CASE("payment_address construct public valid expected", "[payment_address]") { payment_address const address(ec_public(COMPRESSED)); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_COMPRESSED); } -TEST_CASE("payment address construct public testnet valid expected", "[payment address]") { +TEST_CASE("payment_address construct public testnet valid expected", "[payment_address]") { payment_address const address(ec_public(COMPRESSED), 0x6f); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_COMPRESSED_TESTNET); } -TEST_CASE("payment address construct public uncompressed valid expected", "[payment address]") { +TEST_CASE("payment_address construct public uncompressed valid expected", "[payment_address]") { payment_address const address(ec_public(UNCOMPRESSED)); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_UNCOMPRESSED); } -TEST_CASE("payment address construct public testnet uncompressed valid expected", "[payment address]") { +TEST_CASE("payment_address construct public testnet uncompressed valid expected", "[payment_address]") { payment_address const address(ec_public(UNCOMPRESSED), 0x6f); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_UNCOMPRESSED_TESTNET); } -TEST_CASE("payment address construct public compressed from uncompressed testnet valid expected", "[payment address]") { +TEST_CASE("payment_address construct public compressed from uncompressed testnet valid expected", "[payment_address]") { ec_uncompressed point; REQUIRE(decode_base16(point, UNCOMPRESSED)); - payment_address const address({point, true}, 0x6f); + payment_address const address(ec_public{point, true}, 0x6f); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_COMPRESSED_TESTNET); } -TEST_CASE("payment address construct public uncompressed from compressed testnet valid expected", "[payment address]") { +TEST_CASE("payment_address construct public uncompressed from compressed testnet valid expected", "[payment_address]") { ec_compressed point; REQUIRE(decode_base16(point, COMPRESSED)); - payment_address const address({point, false}, 0x6f); + payment_address const address(ec_public{point, false}, 0x6f); REQUIRE(address); REQUIRE(address.encoded_legacy() == ADDRESS_UNCOMPRESSED_TESTNET); } // construct hash: -TEST_CASE("payment address construct hash valid expected", "[payment address]") { +TEST_CASE("payment_address construct hash valid expected", "[payment_address]") { short_hash hash; REQUIRE(decode_base16(hash, COMPRESSED_HASH)); payment_address const address(hash); @@ -161,7 +161,7 @@ TEST_CASE("payment address construct hash valid expected", "[payment address]") REQUIRE(address.encoded_legacy() == ADDRESS_COMPRESSED); } -TEST_CASE("payment address construct uncompressed hash testnet valid expected", "[payment address]") { +TEST_CASE("payment_address construct uncompressed hash testnet valid expected", "[payment_address]") { short_hash hash; REQUIRE(decode_base16(hash, UNCOMPRESSED_HASH)); payment_address const address(hash, 0x6f); @@ -171,7 +171,7 @@ TEST_CASE("payment address construct uncompressed hash testnet valid expected", // construct script: -TEST_CASE("payment address construct script valid expected", "[payment address]") { +TEST_CASE("payment_address construct script valid expected", "[payment_address]") { script ops; REQUIRE(ops.from_string(SCRIPT)); payment_address const address(ops); @@ -179,7 +179,7 @@ TEST_CASE("payment address construct script valid expected", "[payment address] REQUIRE(address.encoded_legacy() == ADDRESS_SCRIPT); } -TEST_CASE("payment address construct script testnet valid expected", "[payment address]") { +TEST_CASE("payment_address construct script testnet valid expected", "[payment_address]") { script ops; REQUIRE(ops.from_string(SCRIPT)); payment_address const address(ops, 0xc4); @@ -189,7 +189,7 @@ TEST_CASE("payment address construct script testnet valid expected", "[payment // construct payment: -TEST_CASE("payment address construct payment valid expected", "[payment address]") { +TEST_CASE("payment_address construct payment valid expected", "[payment_address]") { payment pay; REQUIRE(decode_base16(pay, PAYMENT)); payment_address const address(pay); @@ -197,7 +197,7 @@ TEST_CASE("payment address construct payment valid expected", "[payment address REQUIRE(address.encoded_legacy() == ADDRESS_SCRIPT); } -TEST_CASE("payment address construct payment testnet valid expected", "[payment address]") { +TEST_CASE("payment_address construct payment testnet valid expected", "[payment_address]") { payment pay; REQUIRE(decode_base16(pay, PAYMENT_TESTNET)); payment_address const address(pay); @@ -207,7 +207,7 @@ TEST_CASE("payment address construct payment testnet valid expected", "[payment // construct copy: -TEST_CASE("payment address construct copy valid expected", "[payment address]") { +TEST_CASE("payment_address construct copy valid expected", "[payment_address]") { payment pay; REQUIRE(decode_base16(pay, PAYMENT)); payment_address const address(pay); @@ -218,19 +218,19 @@ TEST_CASE("payment address construct copy valid expected", "[payment address]") // version property: -TEST_CASE("payment address version default mainnet", "[payment address]") { +TEST_CASE("payment_address version default mainnet", "[payment_address]") { payment_address const address(ec_public(COMPRESSED)); REQUIRE(address.version() == payment_address::mainnet_p2kh); } -TEST_CASE("payment address version testnet testnet", "[payment address]") { +TEST_CASE("payment_address version testnet testnet", "[payment_address]") { uint8_t const testnet = 0x6f; payment_address const address(ec_public(COMPRESSED), testnet); REQUIRE(address); REQUIRE(address.version() == testnet); } -TEST_CASE("payment address version script valid mainnet p2sh", "[payment address]") { +TEST_CASE("payment_address version script valid mainnet p2sh", "[payment_address]") { script ops; REQUIRE(ops.from_string(SCRIPT)); payment_address const address(ops); @@ -240,7 +240,7 @@ TEST_CASE("payment address version script valid mainnet p2sh", "[payment address // hash property: -TEST_CASE("payment address hash compressed point expected", "[payment address]") { +TEST_CASE("payment_address hash compressed point expected", "[payment_address]") { payment_address const address(ec_public(COMPRESSED)); REQUIRE(address); REQUIRE(encode_base16(address.hash20()) == COMPRESSED_HASH); @@ -248,34 +248,42 @@ TEST_CASE("payment address hash compressed point expected", "[payment address]") #if defined(KTH_CURRENCY_BCH) //cashAddr payment_address -TEST_CASE("payment address cashAddr mainnet encode", "[payment address]") { +TEST_CASE("payment_address cashAddr mainnet encode", "[payment_address]") { payment_address const address(ec_public("04278f7bfee4ef625f85279c3a01d57c22e2877a902128b2df85071f9d6c95b290f094f5bd1bff5880d09cc231c774d71ac22d3ab9bdd9dda2e75017b52d893367"), kth::domain::wallet::payment_address::mainnet_p2kh); REQUIRE(address); REQUIRE(address.encoded_cashaddr(false) == "bitcoincash:qpzz8n7jp6847yyx8t33matrgcsdx6c0cvmtevrfgz"); } -TEST_CASE("payment address cashAddr testnet encode", "[payment address]") { +TEST_CASE("payment_address cashAddr testnet encode", "[payment_address]") { payment_address const address(ec_public("04278f7bfee4ef625f85279c3a01d57c22e2877a902128b2df85071f9d6c95b290f094f5bd1bff5880d09cc231c774d71ac22d3ab9bdd9dda2e75017b52d893367"), kth::domain::wallet::payment_address::testnet_p2kh); REQUIRE(address); REQUIRE(address.encoded_cashaddr(false) == "bchtest:qpzz8n7jp6847yyx8t33matrgcsdx6c0cvleatp707"); } -TEST_CASE("payment address cashAddr mainnet from string", "[payment address]") { +TEST_CASE("payment_address cashAddr mainnet from string", "[payment_address]") { payment_address const address("bitcoincash:qpzz8n7jp6847yyx8t33matrgcsdx6c0cvmtevrfgz"); REQUIRE(address); REQUIRE(address.encoded_cashaddr(false) == "bitcoincash:qpzz8n7jp6847yyx8t33matrgcsdx6c0cvmtevrfgz"); REQUIRE(address.encoded_legacy() == "17DHrHvtmMRs9ciersFCPNhvJtryd5NWbT"); } -TEST_CASE("payment address cashAddr testnet from string", "[payment address]") { +TEST_CASE("payment_address cashAddr testnet from string", "[payment_address]") { set_cashaddr_prefix("bchtest"); payment_address const address("bchtest:qpzz8n7jp6847yyx8t33matrgcsdx6c0cvleatp707"); REQUIRE(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); + REQUIRE(address.encoded_cashaddr(false) == "bitcoincash:pvstqkm54dtvnpyqxt5m5n7sjsn4enrlxc526xyxlnjkaycdzfeu69reyzmqx"); + REQUIRE(address.encoded_cashaddr(true) == "bitcoincash:rvstqkm54dtvnpyqxt5m5n7sjsn4enrlxc526xyxlnjkaycdzfeu6hs99m6ed"); + REQUIRE(address.encoded_legacy() == "34frpCV2v6wtzig9xx4Z9XJ6s4jU3zqwR7"); // In fact a 32-byte address is not representable in legacy encoding. +} #endif // End Test Suite