diff --git a/.gitmodules b/.gitmodules index 97a855bd..57896bbb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ [submodule "contrib/jwt-cpp"] path = contrib/jwt-cpp url = https://github.com/Thalhammer/jwt-cpp.git +[submodule "contrib/bitcoin-secp256k1"] + path = contrib/bitcoin-secp256k1 + url = https://github.com/bitcoin-core/secp256k1.git diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index abfc4885..c1a5535f 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -5,6 +5,14 @@ add_subdirectory(zlib) add_subdirectory(db) add_subdirectory(ethereum) +option(SECP256K1_BUILD_BENCHMARK "Build benchmarks." OFF) +option(SECP256K1_BUILD_TESTS "Build tests." OFF) +option(SECP256K1_BUILD_EXHAUSTIVE_TESTS "Build exhaustive tests." OFF) +option(SECP256K1_BUILD_CTIME_TESTS "Build constant-time tests." OFF) +option(SECP256K1_BUILD_EXAMPLES "Build examples." OFF) +set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) +add_subdirectory(bitcoin-secp256k1) + if( NOT DISABLE_TOR) add_subdirectory(tor-connect) endif() @@ -23,6 +31,9 @@ set_property(TARGET libminiupnpc-static PROPERTY FOLDER "contrib") set_property(TARGET zlibstatic PROPERTY FOLDER "contrib") set_property(TARGET mdbx PROPERTY FOLDER "contrib") set_property(TARGET lmdb PROPERTY FOLDER "contrib") +set_property(TARGET secp256k1 PROPERTY FOLDER "contrib") +set_property(TARGET secp256k1_precomputed PROPERTY FOLDER "contrib") + if( NOT DISABLE_TOR) set_property(TARGET tor-connect PROPERTY FOLDER "contrib") endif() diff --git a/contrib/bitcoin-secp256k1 b/contrib/bitcoin-secp256k1 new file mode 160000 index 00000000..a5269373 --- /dev/null +++ b/contrib/bitcoin-secp256k1 @@ -0,0 +1 @@ +Subproject commit a5269373fa13ff845f654d81b90629dd78495641 diff --git a/contrib/epee/include/misc_language.h b/contrib/epee/include/misc_language.h index 3ee7001e..c200a3d0 100644 --- a/contrib/epee/include/misc_language.h +++ b/contrib/epee/include/misc_language.h @@ -84,9 +84,9 @@ namespace misc_utils { template - void cast_assign_a_to_b(t_type_a& a, const t_type_b& b) + void cast_assign_a_to_b(const t_type_a& a, t_type_b& b) { - *static_cast(&a) = b; + *static_cast(&b) = a; } template +#include #include "misc_language.h" namespace epee { @@ -51,24 +53,73 @@ namespace epee } }; + template + struct is_std_optional : std::false_type {}; + + template + struct is_std_optional> : std::true_type {}; + + + template + struct is_std_optional> : std::true_type {}; + + + //basic helpers for pod-to-hex serialization + template + std::string transform_t_pod_to_str_internal(const t_pod_type& a) + { + return epee::string_tools::pod_to_hex(a); + } + + template + std::string transform_t_pod_to_str_internal(const std::optional& a) + { + if (a.has_value()) + return epee::string_tools::pod_to_hex(*a); + else + return ""; + } + + template + std::string transform_t_pod_to_str_internal(const boost::optional& a) + { + if (a.has_value()) + return epee::string_tools::pod_to_hex(*a); + else + return ""; + } //basic helpers for pod-to-hex serialization template std::string transform_t_pod_to_str(const t_pod_type & a) { - return epee::string_tools::pod_to_hex(a); + return transform_t_pod_to_str_internal(a); } - template + + + + template t_pod_type transform_str_to_t_pod(const std::string& a) { - t_pod_type res = AUTO_VAL_INIT(res); + t_pod_type res = AUTO_VAL_INIT(res); if (a.empty()) return res; + if constexpr (is_std_optional::value) + { + typename t_pod_type::value_type v = AUTO_VAL_INIT(v); + if (!epee::string_tools::hex_to_pod(a, v)) + throw std::runtime_error(std::string("Unable to transform \"") + a + "\" to pod type " + typeid(typename t_pod_type::value_type).name()); + return v; + } + + if (!epee::string_tools::hex_to_pod(a, res)) throw std::runtime_error(std::string("Unable to transform \"") + a + "\" to pod type " + typeid(t_pod_type).name()); return res; } + + //basic helpers for blob-to-hex serialization inline std::string transform_binbuf_to_hexstr(const std::string& a) diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index cb88897f..d0d7c408 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -1,3 +1,4 @@ +// Copyright (c) 2024, Zano Project // Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net // All rights reserved. // @@ -473,6 +474,29 @@ namespace epee return r; } //------------------------------------------------------------------------------------------------------------------- + //std::optional + template + bool kv_serialize(const std::optional& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + if(d.has_value()) + { + return kv_serialize(*d, stg, hparent_section, pname); + } + return true; + } + //------------------------------------------------------------------------------------------------------------------- + template + bool kv_unserialize(std::optional& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + d = t_type{}; + bool r = kv_unserialize(*d, stg, hparent_section, pname); + if (!r) + { + d = std::nullopt; + } + return r; + } + //------------------------------------------------------------------------------------------------------------------- //boost::shared_ptr template bool kv_serialize(const boost::shared_ptr& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) @@ -496,6 +520,30 @@ namespace epee } return r; } + //------------------------------------------------------------------------------------------------------------------- + //std::shared_ptr + template + bool kv_serialize(const std::shared_ptr& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + if (d.get()) + { + return kv_serialize(*d, stg, hparent_section, pname); + } + return true; + } + //------------------------------------------------------------------------------------------------------------------- + template + bool kv_unserialize(std::shared_ptr& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + d.reset(); + t_type* ptr = new t_type(); + bool r = kv_unserialize(*ptr, stg, hparent_section, pname); + if (!r) + { + d.reset(ptr); + } + return r; + } } } \ No newline at end of file diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 24dbffee..8cf56e5a 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -444,7 +444,7 @@ POP_GCC_WARNINGS inline bool string_to_num_fast(const std::string& buff, int& val) { val = atoi(buff.c_str()); - if(buff != "0" && val == 0) + if (val == 0 && buff.find_first_not_of('0') != std::string::npos) return false; return true; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 200096f3..563b4266 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -116,6 +116,8 @@ else() endif() add_library(crypto ${CRYPTO}) +add_dependencies(crypto secp256k1) +target_link_libraries(crypto secp256k1) add_library(currency_core ${CURRENCY_CORE}) add_dependencies(currency_core version ${PCH_LIB_NAME}) diff --git a/src/common/boost_serialization_maps.h b/src/common/boost_serialization_maps.h index 7f44e4d2..5fbecea0 100644 --- a/src/common/boost_serialization_maps.h +++ b/src/common/boost_serialization_maps.h @@ -4,7 +4,7 @@ #pragma once -#define BEGIN_BOOST_SERIALIZATION() template inline void serialize(t_archive &_arch, const unsigned int ver) { +#define BEGIN_BOOST_SERIALIZATION() template void serialize(t_archive &_arch, const unsigned int ver) { template struct TAssertEquality { static_assert(A == B, "Serialization map is not updated, sizeof() missmatch"); diff --git a/src/common/crypto_serialization.h b/src/common/crypto_serialization.h index dd988a68..481b3ff2 100644 --- a/src/common/crypto_serialization.h +++ b/src/common/crypto_serialization.h @@ -21,6 +21,7 @@ #include "crypto/clsag.h" #include "crypto/zarcanum.h" #include "crypto/one_out_of_many_proofs.h" +#include "crypto/eth_signature.h" #include "boost_serialization_maps.h" #include "serialization/keyvalue_enable_POD_serialize_as_string.h" // @@ -230,6 +231,8 @@ BLOB_SERIALIZER(crypto::key_image); BLOB_SERIALIZER(crypto::signature); BLOB_SERIALIZER(crypto::scalar_t); BLOB_SERIALIZER(crypto::point_t); +BLOB_SERIALIZER(crypto::eth_public_key); +BLOB_SERIALIZER(crypto::eth_signature); VARIANT_TAG(debug_archive, crypto::hash, "hash"); VARIANT_TAG(debug_archive, crypto::public_key, "public_key"); @@ -237,6 +240,8 @@ VARIANT_TAG(debug_archive, crypto::secret_key, "secret_key"); VARIANT_TAG(debug_archive, crypto::key_derivation, "key_derivation"); VARIANT_TAG(debug_archive, crypto::key_image, "key_image"); VARIANT_TAG(debug_archive, crypto::signature, "signature"); +VARIANT_TAG(debug_archive, crypto::eth_public_key, "eth_public_key"); +VARIANT_TAG(debug_archive, crypto::eth_signature, "eth_signature"); // @@ -245,6 +250,8 @@ VARIANT_TAG(debug_archive, crypto::signature, "signature"); KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::scalar_t); KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::hash); +KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::eth_public_key); +KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::eth_signature); // // Boost serialization @@ -296,5 +303,15 @@ namespace boost { a & reinterpret_cast(x); } + template + inline void serialize(Archive &a, crypto::eth_public_key &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast(x); + } + template + inline void serialize(Archive &a, crypto::eth_signature &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast(x); + } } // namespace serialization } // namespace boost diff --git a/src/common/pre_download.h b/src/common/pre_download.h index ec12df55..ebd883c2 100644 --- a/src/common/pre_download.h +++ b/src/common/pre_download.h @@ -21,8 +21,8 @@ namespace tools }; #ifndef TESTNET - static constexpr pre_download_entry c_pre_download_mdbx = { "http://95.217.42.247/pre-download/zano_mdbx_95_2500000.pak", "8ffa2cb4213f4f96f97033c65a9e52bc350f683237808597784e79b24d5bfee7", 3242348793, 5905489920 }; - static constexpr pre_download_entry c_pre_download_lmdb = { "http://95.217.42.247/pre-download/zano_lmdb_95_2500000.pak", "5509650e12c8f901e6731a2bfaf3abfd64409e3e1366d3d94cd11db8beddb0c3", 4239505801, 5893566464 }; + static constexpr pre_download_entry c_pre_download_mdbx = { "http://95.217.42.247/pre-download/zano_mdbx_95_2829200.pak", "ea874a67934f22d500658fe603f6a25e85f6a64c51669ae78bf9e1c5be6d6d87", 5824475897, 9663528960 }; + static constexpr pre_download_entry c_pre_download_lmdb = { "http://95.217.42.247/pre-download/zano_lmdb_95_2829200.pak", "d8d50c2aa832ed4ae8ca00ef9a0de0eb72a402791ebf8b3f64e546036879545e", 7204761982, 9414295552 }; #else static constexpr pre_download_entry c_pre_download_mdbx = { "", "", 0, 0 }; static constexpr pre_download_entry c_pre_download_lmdb = { "", "", 0, 0 }; diff --git a/src/common/util.cpp b/src/common/util.cpp index 1fdbe537..f87f0582 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -658,31 +658,68 @@ std::string get_nix_version_display_string() return static_cast(in.tellg()); } - bool check_remote_client_version(const std::string& client_ver) + bool parse_client_version(const std::string& str, int& major, int& minor, int& revision, int& build_number, std::string& commit_id, bool& dirty) { - std::string v = client_ver.substr(0, client_ver.find('[')); // remove commit id - v = v.substr(0, v.rfind('.')); // remove build number + // "10.101.999.28391" + // "10.101.999.28391[deadbeef31337]" + // "10.101.999.28391[deadbeef31337-dirty]" + // 0123456789012345678901234567890123456 - int v_major = 0, v_minor = 0, v_revision = 0; - - size_t dot_pos = v.find('.'); - if (dot_pos == std::string::npos || !epee::string_tools::string_to_num_fast(v.substr(0, dot_pos), v_major)) + if (str.size() == 0) return false; - v = v.substr(dot_pos + 1); - dot_pos = v.find('.'); - if (!epee::string_tools::string_to_num_fast(v.substr(0, dot_pos), v_minor)) - return false; - - if (dot_pos != std::string::npos) + auto bracket_pos = str.find('['); + if (bracket_pos != std::string::npos) { - // revision - v = v.substr(dot_pos + 1); - if (!epee::string_tools::string_to_num_fast(v, v_revision)) + if (str[str.size() - 1] != ']') return false; + + commit_id = str.substr(bracket_pos + 1, str.size() - bracket_pos - 2); + auto d_pos = commit_id.find("-dirty"); + if (d_pos != std::string::npos) + { + dirty = true; + commit_id.erase(d_pos); + } } - // got v_major, v_minor, v_revision + std::string ver_str = str.substr(0, bracket_pos); + std::vector versions; + boost::split(versions, ver_str, boost::is_any_of(".")); + if (versions.size() != 4) + return false; + + if (!epee::string_tools::string_to_num_fast(versions[0], major)) + return false; + + if (!epee::string_tools::string_to_num_fast(versions[1], minor)) + return false; + + if (!epee::string_tools::string_to_num_fast(versions[2], revision)) + return false; + + if (!epee::string_tools::string_to_num_fast(versions[3], build_number)) + return false; + + return true; + } + + bool parse_client_version_build_number(const std::string& str, int& build_number) + { + int major = -1, minor = -1, revision = -1; + std::string commit_id; + bool dirty = false; + return tools::parse_client_version(str, major, minor, revision, build_number, commit_id, dirty); + } + + bool check_remote_client_version(const std::string& client_ver) + { + int v_major = 0, v_minor = 0, v_revision = 0, v_build_number = 0; + std::string commit_id; + bool dirty_flag = false; + + if (!parse_client_version(client_ver, v_major, v_minor, v_revision, v_build_number, commit_id, dirty_flag)) + return false; // allow 2.x and greater if (v_major < 2) diff --git a/src/common/util.h b/src/common/util.h index 0c5b0bb1..7761775d 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -37,6 +37,9 @@ namespace tools std::string get_current_username(); std::string get_os_version_string(); bool copy_dir(boost::filesystem::path const & source, boost::filesystem::path const & destination); + + bool parse_client_version(const std::string& str, int& major, int& minor, int& revision, int& build_number, std::string& commit_id, bool& dirty); + bool parse_client_version_build_number(const std::string& str, int& build_number); bool check_remote_client_version(const std::string& client_ver); bool create_directories_if_necessary(const std::string& path); diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 846e4572..b74edc4f 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -8,6 +8,7 @@ #include #include #include "crypto.h" +#include "eth_signature.h" namespace crypto { @@ -1209,6 +1210,16 @@ namespace crypto m_elements.emplace_back(pk); } + void add_eth_pub_key(const crypto::eth_public_key& epk) + { + static_assert(sizeof(item_t) == 32, "unexpected size of hs_t::item_t"); + static_assert(sizeof epk.data == 33, "unexpected size of eth_public_key"); + m_elements.emplace_back(c_scalar_0); + m_elements.emplace_back(c_scalar_0); + char* p = m_elements[m_elements.size() - 2].c; // pointer to the first of the two added items + memcpy(p, &epk.data, sizeof epk.data); + } + void add_key_image(const crypto::key_image& ki) { m_elements.emplace_back(ki); diff --git a/src/crypto/eth_signature.cpp b/src/crypto/eth_signature.cpp new file mode 100644 index 00000000..ba5b1358 --- /dev/null +++ b/src/crypto/eth_signature.cpp @@ -0,0 +1,166 @@ +// Copyright (c) 2024 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "eth_signature.h" +#include "crypto.h" +#include "bitcoin-secp256k1/include/secp256k1.h" +#include "random.h" +#include "misc_language.h" +#include + + +namespace crypto +{ + bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept + { + try + { + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ + secp256k1_context_destroy(ctx); + ctx = nullptr; + }); + + uint8_t randomness[32]; + crypto::generate_random_bytes(sizeof randomness, randomness); + if (!secp256k1_context_randomize(ctx, randomness)) + return false; + + for(size_t i = 1024; i != 0; --i) + { + crypto::generate_random_bytes(sizeof sec_key, sec_key.data); + if (secp256k1_ec_seckey_verify(ctx, sec_key.data)) + break; + if (i == 1) + return false; + } + + secp256k1_pubkey uncompressed_pub_key{}; + if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data)) + return false; + + size_t output_len = sizeof pub_key; + if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED)) + return false; + + return true; + } + catch(...) + { + return false; + } + } + + bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept + { + try + { + // TODO: do we need this? consider using static context + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ + secp256k1_context_destroy(ctx); + ctx = nullptr; + }); + + secp256k1_pubkey uncompressed_pub_key{}; + if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data)) + return false; + + size_t output_len = sizeof pub_key; + if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED)) + return false; + + return true; + } + catch(...) + { + return false; + } + } + + // generates secp256k1 ECDSA signature + bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept + { + try + { + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ + secp256k1_context_destroy(ctx); + ctx = nullptr; + }); + + uint8_t randomness[32]; + crypto::generate_random_bytes(sizeof randomness, randomness); + if (!secp256k1_context_randomize(ctx, randomness)) + return false; + + secp256k1_ecdsa_signature secp256k1_ecdsa_sig{}; + if (!secp256k1_ecdsa_sign(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, sec_key.data, NULL, NULL)) + return false; + + if (!secp256k1_ecdsa_signature_serialize_compact(ctx, sig.data, &secp256k1_ecdsa_sig)) + return false; + + return true; + } + catch(...) + { + return false; + } + } + + // verifies secp256k1 ECDSA signature + bool verify_eth_signature(const hash& m, const eth_public_key& pub_key, const eth_signature& sig) noexcept + { + try + { + // TODO (performance) consider using secp256k1_context_static for verification -- sowle + + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ + secp256k1_context_destroy(ctx); + ctx = nullptr; + }); + + uint8_t randomness[32]; + crypto::generate_random_bytes(sizeof randomness, randomness); + if (!secp256k1_context_randomize(ctx, randomness)) + return false; + + secp256k1_ecdsa_signature secp256k1_ecdsa_sig{}; + secp256k1_pubkey uncompressed_pub_key{}; + + if (!secp256k1_ecdsa_signature_parse_compact(ctx, &secp256k1_ecdsa_sig, sig.data)) + return false; + + if (!secp256k1_ec_pubkey_parse(ctx, &uncompressed_pub_key, pub_key.data, sizeof pub_key)) + return false; + + // verify a signature + if (!secp256k1_ecdsa_verify(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, &uncompressed_pub_key)) + return false; + + return true; + } + catch(...) + { + return false; + } + } + + std::ostream& operator<<(std::ostream& o, const eth_secret_key& v) + { + return o << epee::string_tools::pod_to_hex(v); + } + + std::ostream& operator<<(std::ostream& o, const eth_public_key& v) + { + return o << epee::string_tools::pod_to_hex(v); + } + std::ostream& operator<<(std::ostream& o, const eth_signature& v) + { + return o << epee::string_tools::pod_to_hex(v); + } + + +} // namespace crypto diff --git a/src/crypto/eth_signature.h b/src/crypto/eth_signature.h new file mode 100644 index 00000000..b12d41fe --- /dev/null +++ b/src/crypto/eth_signature.h @@ -0,0 +1,67 @@ +// Copyright (c) 2024 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#pragma once +#include +#include +#include "hash.h" + +namespace crypto +{ + + // secp256k1 public key in serialized (compressed) form that is used in Etherium + struct eth_public_key + { + uint8_t data[33]; + }; + + // secp256k1 secret key + struct eth_secret_key + { + uint8_t data[32]; + }; + + // secp256k1 ECDSA signature is serialized (compressed) form that is used in Etherium + struct eth_signature + { + uint8_t data[64]; + }; + + // generates secp256k1 keypair + bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept; + + // converts eth_secret_key to eth_public_key + bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept; + + // generates secp256k1 ECDSA signature + bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept; + + // verifies secp256k1 ECDSA signature + bool verify_eth_signature(const hash& m, const eth_public_key& pub_key, const eth_signature& sig) noexcept; + + + inline bool operator==(const eth_public_key& lhs, const eth_public_key& rhs) + { + return memcmp(lhs.data, rhs.data, sizeof lhs.data) == 0; + } + + inline bool operator!=(const eth_public_key& lhs, const eth_public_key& rhs) + { + return !(lhs == rhs); + } + + inline bool operator==(const eth_secret_key& lhs, const eth_secret_key& rhs) + { + return memcmp(lhs.data, rhs.data, sizeof lhs.data) == 0; + } + + inline bool operator!=(const eth_secret_key& lhs, const eth_secret_key& rhs) + { + return !(lhs == rhs); + } + + std::ostream& operator<<(std::ostream& o, const eth_secret_key& v); + std::ostream& operator<<(std::ostream& o, const eth_public_key& v); + std::ostream& operator<<(std::ostream& o, const eth_signature& v); + +} // namespace crypto diff --git a/src/crypto/zarcanum.h b/src/crypto/zarcanum.h index 3fe4b5c1..55e6b202 100644 --- a/src/crypto/zarcanum.h +++ b/src/crypto/zarcanum.h @@ -123,6 +123,15 @@ namespace crypto return generate_schnorr_sig(m, point_t(A), scalar_t(secret_a), result); } + inline bool generate_schnorr_sig(const hash& m, const secret_key& secret_a, generic_schnorr_sig& result) + { + scalar_t secret_a_s(secret_a); + if (!secret_a_s.is_reduced()) + return false; + point_t A = secret_a_s * c_point_G; + return generate_schnorr_sig_custom_generator(m, A, secret_a_s, result, c_point_G); + } + template inline bool verify_schnorr_sig(const hash& m, const public_key& A, const generic_schnorr_sig& sig) noexcept; diff --git a/src/currency_core/account.cpp b/src/currency_core/account.cpp index 6a07b7c9..f5663826 100644 --- a/src/currency_core/account.cpp +++ b/src/currency_core/account.cpp @@ -60,7 +60,7 @@ namespace currency return m_keys; } //----------------------------------------------------------------- - void crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password) + void account_base::crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password) { crypto::chacha8_key key = AUTO_VAL_INIT(key); crypto::generate_chacha8_key(password, key); @@ -71,16 +71,23 @@ namespace currency crypto::chacha8(scr_data, src_length, key, iv, (char*)dst_data); } //----------------------------------------------------------------- - std::string account_base::get_seed_phrase(const std::string& password) const + std::string account_base::get_seed_phrase(const std::string& password) const { if (m_keys_seed_binary.empty()) return ""; + return get_seed_phrase(password, m_keys_seed_binary); + } + //----------------------------------------------------------------- + std::string account_base::get_seed_phrase(const std::string& password, const std::vector& keys_seed_binary) const + { + if (keys_seed_binary.empty()) + return ""; - std::vector processed_seed_binary = m_keys_seed_binary; + std::vector processed_seed_binary = keys_seed_binary; if (!password.empty()) { //encrypt seed phrase binary data - crypt_with_pass(&m_keys_seed_binary[0], m_keys_seed_binary.size(), &processed_seed_binary[0], password); + crypt_with_pass(&keys_seed_binary[0], keys_seed_binary.size(), &processed_seed_binary[0], password); } std::string keys_seed_text = tools::mnemonic_encoding::binary2text(processed_seed_binary); @@ -92,7 +99,7 @@ namespace currency CHECK_AND_ASSERT_THROW_MES(self_check_is_password_used == !password.empty(), "Account seed phrase internal error: password flag encoded wrong"); constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum - std::string binary_for_check_sum((const char*)&m_keys_seed_binary[0], m_keys_seed_binary.size()); + std::string binary_for_check_sum((const char*)&keys_seed_binary[0], keys_seed_binary.size()); binary_for_check_sum.append(password); crypto::hash h = crypto::cn_fast_hash(binary_for_check_sum.data(), binary_for_check_sum.size()); *reinterpret_cast(&h) = creation_timestamp_rounded; diff --git a/src/currency_core/account.h b/src/currency_core/account.h index 37a0c953..d732181d 100644 --- a/src/currency_core/account.h +++ b/src/currency_core/account.h @@ -56,6 +56,7 @@ namespace currency std::string get_public_address_str() const; std::string get_seed_phrase(const std::string& seed_password) const; + std::string get_seed_phrase(const std::string& password, const std::vector& keys_seed_binary) const; std::string get_tracking_seed() const; bool restore_from_seed_phrase(const std::string& seed_phrase, const std::string& seed_password); bool restore_from_tracking_seed(const std::string& tracking_seed); @@ -82,6 +83,8 @@ namespace currency static std::vector string_to_vector_of_chars(const std::string& v) { return std::vector(v.begin(), v.end()); } static bool is_seed_password_protected(const std::string& seed_phrase, bool& is_password_protected); static bool is_seed_tracking(const std::string& seed_phrase); + static void crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password); + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_keys) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 419cdd1c..6d4cbc1f 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -3880,11 +3880,9 @@ uint64_t blockchain_storage::get_assets(uint64_t offset, uint64_t count, std::li m_db_assets.enumerate_items([&](uint64_t i, const crypto::public_key& asset_id, const std::list& asset_descriptor_history) { if (i < offset) - { - return true; - } + return true; // continue - CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.size(), "asset_descriptor_history unexpectedly have 0 size"); + CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.size(), "asset_descriptor_history unexpectedly have 0 size, asset_id: " << asset_id); assets.push_back(asset_descriptor_with_id()); static_cast(assets.back()) = asset_descriptor_history.back().descriptor; assets.back().asset_id = asset_id; @@ -4010,7 +4008,8 @@ bool blockchain_storage::put_alias_info(const transaction & tx, extra_alias_entr //@@ remove get_tx_fee_median(); LOG_PRINT_MAGENTA("[ALIAS_REGISTERED]: " << ai.m_alias << ": " << get_account_address_as_str(ai.m_address) << ", fee median: " << get_tx_fee_median(), LOG_LEVEL_1); rise_core_event(CORE_EVENT_ADD_ALIAS, alias_info_to_rpc_alias_info(ai)); - }else + } + else { //update procedure CHECK_AND_ASSERT_MES(ai.m_sign.size() == 1, false, "alias " << ai.m_alias << " can't be update, wrong ai.m_sign.size() count: " << ai.m_sign.size()); @@ -4103,16 +4102,38 @@ bool blockchain_storage::pop_asset_info(const crypto::public_key& asset_id) return true; } //------------------------------------------------------------------ -bool validate_ado_ownership(asset_op_verification_context& avc) +bool blockchain_storage::validate_ado_ownership(asset_op_verification_context& avc) const { - asset_operation_ownership_proof aoop = AUTO_VAL_INIT(aoop); - bool r = get_type_in_variant_container(avc.tx.proofs, aoop); - CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed - missing signature (asset_operation_ownership_proof)"); + bool r = false; + CHECK_AND_ASSERT_MES(avc.asset_op_history->size() != 0, false, "asset with id " << avc.asset_id << " has empty history record"); + const asset_descriptor_operation& last_ado = avc.asset_op_history->back(); - CHECK_AND_ASSERT_MES(avc.asset_op_history->size() != 0, false, "asset with id " << avc.asset_id << " has invalid history size() == 0"); + if (is_hardfork_active(ZANO_HARDFORK_05)) // TODO: consider changing to height-specific check + { + if (last_ado.descriptor.owner_eth_pub_key.has_value()) + { + CHECK_AND_ASSERT_MES(last_ado.descriptor.owner == null_pkey, false, "owner_eth_pub_key is set but owner pubkey is nonzero"); + asset_operation_ownership_proof_eth aoop_eth{}; + r = get_type_in_variant_container(avc.tx.proofs, aoop_eth); + CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof_eth is missing"); + if (!crypto::verify_eth_signature(avc.tx_id, last_ado.descriptor.owner_eth_pub_key.value(), aoop_eth.eth_sig)) + { + LOG_ERROR("Failed to validate secp256k1 signature for hash: " << avc.tx_id << ", signature: " << aoop_eth.eth_sig); + return false; + } + else + { + return true; + } + } + // owner_eth_pub_key has no value -- fallback to default + } - crypto::public_key owner_key = avc.asset_op_history->back().descriptor.owner; - return crypto::verify_schnorr_sig(avc.tx_id, owner_key, aoop.gss); + asset_operation_ownership_proof aoop{}; + r = get_type_in_variant_container(avc.tx.proofs, aoop); + CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof is missing"); + + return crypto::verify_schnorr_sig(avc.tx_id, last_ado.descriptor.owner, aoop.gss); } //------------------------------------------------------------------ bool blockchain_storage::validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const @@ -4124,11 +4145,12 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state const asset_descriptor_operation& ado = avc.ado; + bool need_to_validate_ao_amount_commitment = true; + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) { CHECK_AND_ASSERT_MES(!avc.asset_op_history, false, "asset with id " << avc.asset_id << " has already been registered"); avc.amount_to_validate = ado.descriptor.current_supply; - CHECK_AND_ASSERT_MES(validate_asset_operation_amount_commitment(avc), false, "validate_asset_operation_amount_commitment failed!"); if(this->is_hardfork_active(ZANO_HARDFORK_05)) { CHECK_AND_ASSERT_MES(validate_ado_initial(ado.descriptor), false, "validate_ado_initial failed!"); @@ -4136,48 +4158,48 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state } else { - CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size(), false, "asset with id " << avc.asset_id << " has not been registered"); + CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size() > 0, false, "asset with id " << avc.asset_id << " has not been registered"); + const asset_descriptor_operation& last_ado = avc.asset_op_history->back(); // check ownership permission - if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE /*|| ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN*/) + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) { bool r = validate_ado_ownership(avc); - CHECK_AND_ASSERT_MES(r, false, "Faild to validate ownership of asset_descriptor_operation, rejecting"); + CHECK_AND_ASSERT_MES(r, false, "Failed to validate ownership of asset_descriptor_operation, rejecting"); } avc.amount_to_validate = 0; - bool need_to_validate_balance_proof = true; if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) { //check that total current_supply haven't changed - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply == avc.asset_op_history->back().descriptor.current_supply, false, "update operation attempted to change emission, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "update operation attempted to change fileds that shouldn't be modified, failed"); - need_to_validate_balance_proof = false; + CHECK_AND_ASSERT_MES(ado.descriptor.current_supply == last_ado.descriptor.current_supply, false, "update operation attempted to change emission, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "update operation modifies asset descriptor in a prohibited manner"); + need_to_validate_ao_amount_commitment = false; } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) { - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > avc.asset_op_history->back().descriptor.current_supply, false, "emit operation does not increase the current supply, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "emit operation is not allowed to update fields"); - CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == avc.asset_op_history->back().descriptor.meta_info, false, "emit operation is not allowed to update meta info"); - avc.amount_to_validate = ado.descriptor.current_supply - avc.asset_op_history->back().descriptor.current_supply; + CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_ado.descriptor.current_supply, false, "emit operation does not increase the current supply, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "emit operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_ado.descriptor.meta_info, false, "emit operation is not allowed to update meta info"); + avc.amount_to_validate = ado.descriptor.current_supply - last_ado.descriptor.current_supply; } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < avc.asset_op_history->back().descriptor.current_supply, false, "burn operation does not decrease the current supply, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "burn operation is not allowed to update fields"); - CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == avc.asset_op_history->back().descriptor.meta_info, false, "burn operation is not allowed to update meta info"); - avc.amount_to_validate = avc.asset_op_history->back().descriptor.current_supply - ado.descriptor.current_supply; + CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_ado.descriptor.current_supply, false, "burn operation does not decrease the current supply, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "burn operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_ado.descriptor.meta_info, false, "burn operation is not allowed to update meta info"); + avc.amount_to_validate = last_ado.descriptor.current_supply - ado.descriptor.current_supply; } else { LOG_ERROR("Unknown operation type: " << (int)ado.operation_type); return false; } + } - if (need_to_validate_balance_proof) - { - bool r = validate_asset_operation_amount_commitment(avc); - CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation"); - } + if (need_to_validate_ao_amount_commitment) + { + bool r = validate_asset_operation_amount_commitment(avc); + CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation"); } return true; @@ -5803,6 +5825,7 @@ bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transacti bool var_is_after_hardfork_2_zone = m_core_runtime_config.is_hardfork_active_for_height(2, block_height); bool var_is_after_hardfork_3_zone = m_core_runtime_config.is_hardfork_active_for_height(3, block_height); bool var_is_after_hardfork_4_zone = m_core_runtime_config.is_hardfork_active_for_height(4, block_height); + bool var_is_after_hardfork_5_zone = m_core_runtime_config.is_hardfork_active_for_height(5, block_height); auto is_allowed_before_hardfork1 = [&](const auto& el) -> bool { @@ -5932,8 +5955,22 @@ bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transacti LOG_ERROR("asset_descriptor_operation not allowed in tx with TX_FLAG_SIGNATURE_MODE_SEPARATE"); return false; } - } + + if (var_is_after_hardfork_5_zone) + { + // additional checks here + } + else + { + if (count_type_in_variant_container(tx.proofs) != 0) + { + LOG_ERROR("asset_operation_ownership_proof_eth is not allowed prior to HF5"); + return false; + } + } + + return true; } //------------------------------------------------------------------ diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 2c6e0bb5..9d7cba3a 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -380,6 +380,7 @@ namespace currency bool for_altchain, const alt_chain_type& alt_chain = alt_chain_type(), uint64_t split_height = 0)const; + bool validate_ado_ownership(asset_op_verification_context& avc) const; bool validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const; void set_core_runtime_config(const core_runtime_config& pc) const; diff --git a/src/currency_core/connection_context.h b/src/currency_core/connection_context.h index 6c242cc1..1320619c 100644 --- a/src/currency_core/connection_context.h +++ b/src/currency_core/connection_context.h @@ -51,6 +51,8 @@ namespace currency uint64_t m_last_response_height; int64_t m_time_delta; std::string m_remote_version; + int m_build_number = 0; + private: template friend class t_currency_protocol_handler; uncopybale_currency_context m_priv; diff --git a/src/currency_core/core_runtime_config.h b/src/currency_core/core_runtime_config.h index c1ba86af..799ba0f9 100644 --- a/src/currency_core/core_runtime_config.h +++ b/src/currency_core/core_runtime_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Boolberry developers // Distributed under the MIT/X11 software license, see the accompanying @@ -109,10 +109,16 @@ namespace currency wide_difficulty_type max_pos_difficulty; hard_forks_descriptor hard_forks; + std::array min_build_numbers_for_hard_forks; - bool is_hardfork_active_for_height(size_t hardfork_id, uint64_t height) const + bool is_hardfork_active_for_height(size_t hardfork_id, uint64_t upcoming_block_height) const { - return hard_forks.is_hardfork_active_for_height(hardfork_id, height); + return hard_forks.is_hardfork_active_for_height(hardfork_id, upcoming_block_height); + } + + int get_min_allowed_build_version_for_height(uint64_t upcoming_block_height) const + { + return min_build_numbers_for_hard_forks[hard_forks.get_the_most_recent_hardfork_id_for_height(upcoming_block_height)]; } static uint64_t _default_core_time_function() @@ -123,7 +129,7 @@ namespace currency inline core_runtime_config get_default_core_runtime_config() { - core_runtime_config pc = AUTO_VAL_INIT(pc); + core_runtime_config pc{}; pc.min_coinstake_age = POS_MINIMUM_COINSTAKE_AGE; pc.pos_minimum_heigh = POS_START_HEIGHT; pc.tx_pool_min_fee = TX_MINIMUM_FEE; @@ -137,7 +143,9 @@ namespace currency pc.hard_forks.set_hardfork_height(2, ZANO_HARDFORK_02_AFTER_HEIGHT); pc.hard_forks.set_hardfork_height(3, ZANO_HARDFORK_03_AFTER_HEIGHT); pc.hard_forks.set_hardfork_height(4, ZANO_HARDFORK_04_AFTER_HEIGHT); - + pc.hard_forks.set_hardfork_height(5, ZANO_HARDFORK_05_AFTER_HEIGHT); pc.min_build_numbers_for_hard_forks[5] = ZANO_HARDFORK_05_MIN_BUILD_VER; + static_assert(5 + 1 == ZANO_HARDFORKS_TOTAL); + pc.get_core_time = &core_runtime_config::_default_core_time_function; bool r = epee::string_tools::hex_to_pod(ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY, pc.alias_validation_pubkey); CHECK_AND_ASSERT_THROW_MES(r, "failed to parse alias_validation_pub_key"); diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index d189c2cd..c6581611 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -40,6 +40,7 @@ #include "crypto/hash.h" #include "crypto/range_proofs.h" #include "crypto/zarcanum.h" +#include "crypto/eth_signature.h" #include "misc_language.h" #include "block_flags.h" #include "etc_custom_serialization.h" @@ -695,6 +696,9 @@ namespace currency } }; +#define ASSET_DESCRIPTOR_BASE_STRUCTURE_VER 1 + + typedef boost::variant asset_owner_pub_key_v; struct asset_descriptor_base { @@ -706,9 +710,11 @@ namespace currency std::string meta_info; crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 bool hidden_supply = false; - uint8_t version = 0; + boost::optional owner_eth_pub_key; // note: the size is 33 bytes (if present) // NOTE: using boost::optional instead of std::optional because of the Boost compilation issue: https://github.com/boostorg/serialization/issues/319 -- sowle - BEGIN_VERSIONED_SERIALIZE(0, version) + uint8_t version = ASSET_DESCRIPTOR_BASE_STRUCTURE_VER; + + BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_BASE_STRUCTURE_VER, version) FIELD(total_max_supply) FIELD(current_supply) FIELD(decimal_point) @@ -717,9 +723,10 @@ namespace currency FIELD(meta_info) FIELD(owner) FIELD(hidden_supply) + END_VERSION_UNDER(1) + FIELD(owner_eth_pub_key) END_SERIALIZE() - BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(total_max_supply) BOOST_SERIALIZE(current_supply) @@ -729,17 +736,20 @@ namespace currency BOOST_SERIALIZE(meta_info) BOOST_SERIALIZE(owner) BOOST_SERIALIZE(hidden_supply) + BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(owner_eth_pub_key) END_BOOST_SERIALIZATION() BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for given asset, can't be changed after deployment") DOC_EXMP(1000000000000000000) DOC_END - KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for given asset (ignored for REGISTER operation)") DOC_EXMP(500000000000000000) DOC_END - KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point") DOC_EXMP(12) DOC_END - KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with asset") DOC_EXMP("ZUSD") DOC_END - KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset") DOC_EXMP("Zano wrapped USD") DOC_END - KV_SERIALIZE(meta_info) DOC_DSCR("Any other information assetiaded with asset in a free form") DOC_EXMP("Stable and private") DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, could be changed by transferring asset ownership") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END - KV_SERIALIZE(hidden_supply) DOC_DSCR("This one reserved for future use, will be documented later") DOC_END + KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for a given asset, cannot be changed after deployment.") DOC_EXMP(1000000000000000000) DOC_END + KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for the given asset (ignored for REGISTER operation).") DOC_EXMP(500000000000000000) DOC_END + KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point.") DOC_EXMP(12) DOC_END + KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with the asset.") DOC_EXMP("ZABC") DOC_END + KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset.") DOC_EXMP("Zano wrapped ABC") DOC_END + KV_SERIALIZE(meta_info) DOC_DSCR("Any other information associated with the asset in free form.") DOC_EXMP("Stable and private") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, can be changed by transferring asset ownership.") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE(hidden_supply) DOC_DSCR("This field is reserved for future use and will be documented later.") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(owner_eth_pub_key) DOC_DSCR("[Optional] Owner's key in the case when ETH signature is used.") DOC_END END_KV_SERIALIZE_MAP() }; @@ -840,6 +850,26 @@ namespace currency }; + struct asset_operation_ownership_proof_eth + { + crypto::eth_signature eth_sig; // 64 bytes + uint8_t version = 0; + + BEGIN_VERSIONED_SERIALIZE(0, version) + FIELD(eth_sig) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(eth_sig) + BOOST_SERIALIZE(version) + END_BOOST_SERIALIZATION() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_POD_AS_HEX_STRING(eth_sig) DOC_DSCR("HEX-encoded ETH signature (64 bytes)") DOC_EXMP("674bb56a5b4fa562e679ccacc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6add697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE(version) DOC_DSCR("Structure version") DOC_EXMP(0) DOC_END + END_KV_SERIALIZE_MAP() + }; + struct extra_padding { std::vector buff; //stub @@ -943,7 +973,7 @@ namespace currency typedef boost::variant signature_v; - typedef boost::variant proof_v; + typedef boost::variant proof_v; //include backward compatibility defintions @@ -1202,6 +1232,10 @@ SET_VARIANT_TAGS(currency::zc_balance_proof, 48, "zc_balance_proof"); SET_VARIANT_TAGS(currency::asset_descriptor_operation, 49, "asset_descriptor_base"); SET_VARIANT_TAGS(currency::asset_operation_proof, 50, "asset_operation_proof"); SET_VARIANT_TAGS(currency::asset_operation_ownership_proof, 51, "asset_operation_ownership_proof"); +SET_VARIANT_TAGS(currency::asset_operation_ownership_proof_eth, 52, "asset_operation_ownership_proof_eth"); + +SET_VARIANT_TAGS(crypto::eth_public_key, 60, "eth_public_key"); +//SET_VARIANT_TAGS(crypto::eth_signature, 61, "eth_signature");s diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index 4c0ebd56..f6a31839 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -258,25 +258,29 @@ #define CURRENT_MEMPOOL_ARCHIVE_VER (CURRENCY_FORMATION_VERSION+31) -//hard forks section #define BLOCK_MAJOR_VERSION_GENESIS 1 #define BLOCK_MINOR_VERSION_GENESIS 0 #define BLOCK_MAJOR_VERSION_INITIAL 0 + +/////// Hard forks setup ////////////////////////////// #ifndef TESTNET +// Mainnet #define ZANO_HARDFORK_01_AFTER_HEIGHT 194624 // 2019-09-21 20:25:16 #define ZANO_HARDFORK_02_AFTER_HEIGHT 999999 // 2021-04-05 09:11:45 #define ZANO_HARDFORK_03_AFTER_HEIGHT 1082577 // 2021-06-01 23:28:10 #define ZANO_HARDFORK_04_AFTER_HEIGHT 2555000 // 2024-03-21 11:49:55 -#define ZANO_HARDFORK_05_AFTER_HEIGHT 999999999999999999 #define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1711021795ull // block 2555000, 2024-03-21 11:49:55 UTC +#define ZANO_HARDFORK_05_AFTER_HEIGHT 999999999999999999 +#define ZANO_HARDFORK_05_MIN_BUILD_VER 343 #else -/////// Zarcanum Testnet ////////////////////////////// +// Testnet #define ZANO_HARDFORK_01_AFTER_HEIGHT 0 #define ZANO_HARDFORK_02_AFTER_HEIGHT 0 #define ZANO_HARDFORK_03_AFTER_HEIGHT 0 #define ZANO_HARDFORK_04_AFTER_HEIGHT 200 -#define ZANO_HARDFORK_05_AFTER_HEIGHT 200 #define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1712785801ull // block 200, 2024-04-10 21:50:01 UTC +#define ZANO_HARDFORK_05_AFTER_HEIGHT 241750 +#define ZANO_HARDFORK_05_MIN_BUILD_VER 343 #endif diff --git a/src/currency_core/currency_core.cpp b/src/currency_core/currency_core.cpp index 1c15498b..8d29c38d 100644 --- a/src/currency_core/currency_core.cpp +++ b/src/currency_core/currency_core.cpp @@ -552,6 +552,7 @@ namespace currency if (hardfork_id_for_prev_block != hardfork_id_for_curr_block) { LOG_PRINT_GREEN("Hardfork " << hardfork_id_for_curr_block << " has been activated after the block at height " << h, LOG_LEVEL_0); + m_pprotocol->on_hardfork_activated(hardfork_id_for_curr_block); } } diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index f2ed06d4..9dfae001 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -116,7 +116,7 @@ namespace currency secret_index = ring.size() - 1; } - CHECK_AND_ASSERT_MES(secret_index != SIZE_MAX, false, "out #" << j << ": can't find a corresponding asset id in inputs, asset id: " << H); + CHECK_AND_ASSERT_MES(secret_index != SIZE_MAX, false, "out #" << j << ": cannot find a corresponding asset id in inputs or asset operations; asset id: " << H); result.bge_proofs.emplace_back(crypto::BGE_proof_s{}); uint8_t err = 0; @@ -322,7 +322,7 @@ namespace currency CHECK_AND_ASSERT_MES(ogc.asset_id_blinding_mask_x_amount_sum.is_zero(), false, "it's expected that all asset ids for this tx are obvious and thus explicit"); // because this tx has no ZC inputs => all outs clearly have native asset id CHECK_AND_ASSERT_MES(ogc.ao_amount_blinding_mask.is_zero(), false, "asset emmission is not allowed for txs without ZC inputs"); - // (sum(bare inputs' amounts) - fee) * H + sum(pseudo out amount commitments) - sum(outputs' commitments) = lin(G) + // (sum(bare inputs' amounts) - fee) * H - sum(outputs' commitments) = lin(G) crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt - ogc.amount_commitments_sum; crypto::scalar_t secret_x = -ogc.amount_blinding_masks_sum; @@ -336,6 +336,8 @@ namespace currency { // there're ZC inputs => in main balance equation we only need to cancel out X-component, because G-component cancelled out by choosing blinding mask for the last pseudo out amount commitment + // (sum(bare inputs' amounts) - fee) * H + sum(pseudo out amount commitments) + asset_op_commitment - sum(outputs' commitments) = lin(X) + crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt + ogc.pseudo_out_amount_commitments_sum + (ogc.ao_commitment_in_outputs ? -ogc.ao_amount_commitment : ogc.ao_amount_commitment) - ogc.amount_commitments_sum; crypto::scalar_t secret_x = ogc.real_in_asset_id_blinding_mask_x_amount_sum - ogc.asset_id_blinding_mask_x_amount_sum; @@ -472,7 +474,7 @@ namespace currency } CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS || height == 0, false, "Too many outs (" << destinations.size() << ")! Miner tx can't be constructed."); - tx = AUTO_VAL_INIT_T(transaction); + // tx is not cleared intentionally to allow passing additional args in the extra/attachments tx.version = tx_version; tx_generation_context tx_gen_context{}; @@ -2194,6 +2196,8 @@ namespace currency hsc.add_scalar(crypto::scalar_t(ado.descriptor.total_max_supply)); hsc.add_scalar(crypto::scalar_t(ado.descriptor.decimal_point)); hsc.add_pub_key(ado.descriptor.owner); + if (ado.descriptor.owner_eth_pub_key.has_value()) + hsc.add_eth_pub_key(ado.descriptor.owner_eth_pub_key.value()); crypto::hash h = hsc.calc_hash_no_reduce(); // this hash function needs to be computationally expensive (s.a. the whitepaper) @@ -2246,7 +2250,8 @@ namespace currency // asset_control_key = Hs(CRYPTO_HDS_ASSET_CONTROL_KEY, 8 * tx_key.sec * sender_account_keys.account_address.spend_public_key, 0) // ado.descriptor.owner = asset_control_key * G - ado.descriptor.owner = sender_account_keys.account_address.spend_public_key; + if (!ado.descriptor.owner_eth_pub_key.has_value()) + ado.descriptor.owner = sender_account_keys.account_address.spend_public_key; CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ado, &gen_context.ao_asset_id_pt, &gen_context.ao_asset_id), false, "get_or_calculate_asset_id failed"); @@ -2335,25 +2340,6 @@ namespace currency } if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_handle_asset_descriptor_operation_before_seal{ &ado }); - ftp.need_to_generate_ado_proof = true; - /* - //seal it with owners signature - crypto::signature sig = currency::null_sig; - crypto::hash h = get_signature_hash_for_asset_operation(ado); - if (ftp.pthirdparty_sign_handler) - { - bool r = ftp.pthirdparty_sign_handler->sign(h, ftp.ado_current_asset_owner, sig); - CHECK_AND_ASSERT_MES(r, false, "asset thirparty sign failed"); - } - else - { - crypto::public_key pub_k = currency::null_pkey; - crypto::secret_key_to_public_key(sender_account_keys.spend_secret_key, pub_k); - CHECK_AND_ASSERT_MES(ftp.ado_current_asset_owner == pub_k, false, "asset owner key not matched with provided private key for asset operation signing"); - crypto::generate_signature(h, pub_k, account_keys.spend_secret_key, sig); - } - ado.opt_proof = sig; - */ } return true; } @@ -2603,8 +2589,7 @@ namespace currency // ASSET oprations handling if (tx.version > TRANSACTION_VERSION_PRE_HF4) { - asset_descriptor_operation* pado = nullptr; - pado = get_type_in_variant_container(tx.extra); + asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); if (pado) { bool r = construct_tx_handle_ado(sender_account_keys, ftp, *pado, gen_context, gen_context.tx_key, shuffled_dsts); @@ -2717,7 +2702,8 @@ namespace currency // generate proofs and signatures // (any changes made below should only affect the signatures/proofs and should not impact the prefix hash calculation) // - crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); + result.tx_id = get_transaction_prefix_hash(tx); + const crypto::hash &tx_prefix_hash = result.tx_id; // ring signatures (per-input proofs) r = false; @@ -2773,35 +2759,30 @@ namespace currency CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); tx.proofs.emplace_back(std::move(balance_proof)); - // asset operation proof (if necessary) + // optional asset operation proofs: amount commitment proof (required for register, emit, public burn) if (gen_context.ao_asset_id != currency::null_pkey) { - // construct the asset operation proof - // TODO @#@# add support for hidden supply + // asset amount commitment g proof (TODO @#@# add support for hidden supply) crypto::signature aop_g_sig{}; crypto::generate_signature(tx_prefix_hash, crypto::point_t(gen_context.ao_amount_blinding_mask * crypto::c_point_G).to_public_key(), gen_context.ao_amount_blinding_mask, aop_g_sig); asset_operation_proof aop{}; aop.opt_amount_commitment_g_proof = aop_g_sig; tx.proofs.emplace_back(std::move(aop)); } - if(ftp.need_to_generate_ado_proof) - { - asset_operation_ownership_proof aoop = AUTO_VAL_INIT(aoop); - if (ftp.pthirdparty_sign_handler) + // optional asset operation proofs: ownership proof for standard (non-eth) owner (using generic Shnorr signature with the spend secret key) + const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); + if (pado != nullptr) + { + if ((pado->operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || pado->operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) && + !pado->descriptor.owner_eth_pub_key.has_value()) { - //ask third party to generate proof - r = ftp.pthirdparty_sign_handler->sign(tx_prefix_hash, ftp.ado_current_asset_owner, aoop.gss); - CHECK_AND_ASSERT_MES(r, false, "Failed to sign ado by thirdparty"); - } - else - { - //generate signature by wallet account - r = crypto::generate_schnorr_sig(tx_prefix_hash, ftp.ado_current_asset_owner, sender_account_keys.spend_secret_key, aoop.gss); + asset_operation_ownership_proof aoop{}; + r = crypto::generate_schnorr_sig(tx_prefix_hash, sender_account_keys.spend_secret_key, aoop.gss); CHECK_AND_ASSERT_MES(r, false, "Failed to sign ado proof"); + if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_after_asset_ownership_proof_generated{ &aoop }); + tx.proofs.emplace_back(aoop); } - if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_after_asset_ownership_proof_generated{ &aoop }); - tx.proofs.emplace_back(aoop); } } diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 78668536..e1899ea1 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -139,11 +139,13 @@ namespace currency bool hltc_our_out_is_before_expiration; }; - struct thirdparty_sign_handler + struct asset_eth_signer_i { - virtual bool sign(const crypto::hash& h, const crypto::public_key& owner_public_key, crypto::generic_schnorr_sig& sig); + virtual bool sign(const crypto::hash& h, const crypto::eth_public_key& asset_owner, crypto::eth_signature& sig) = 0; }; + typedef boost::variant asset_owner_key_v; + struct finalize_tx_param { uint64_t unlock_time; @@ -165,10 +167,6 @@ namespace currency epee::misc_utils::events_dispatcher* pevents_dispatcher; tx_generation_context gen_context{}; // solely for consolidated txs - //crypto::secret_key asset_control_key = currency::null_skey; - crypto::public_key ado_current_asset_owner = null_pkey; - thirdparty_sign_handler* pthirdparty_sign_handler = nullptr; - mutable bool need_to_generate_ado_proof = false; BEGIN_SERIALIZE_OBJECT() @@ -191,14 +189,13 @@ namespace currency { FIELD(gen_context); } - FIELD(ado_current_asset_owner) - FIELD(need_to_generate_ado_proof) END_SERIALIZE() }; struct finalized_tx { currency::transaction tx; + crypto::hash tx_id; crypto::secret_key one_time_key; finalize_tx_param ftp; std::string htlc_origin; @@ -208,6 +205,7 @@ namespace currency BEGIN_SERIALIZE_OBJECT() FIELD(tx) + FIELD(tx_id) FIELD(one_time_key) FIELD(ftp) FIELD(htlc_origin) @@ -546,7 +544,7 @@ namespace currency { assets_list.push_back(currency::asset_descriptor_with_id()); assets_list.back().asset_id = pr.first; - epee::misc_utils::cast_assign_a_to_b(assets_list.back(), static_cast(pr.second)); + epee::misc_utils::cast_assign_a_to_b(static_cast(pr.second), assets_list.back()); //*static_cast(&assets_list.back()) = pr.second; } } @@ -948,6 +946,12 @@ namespace currency } } //--------------------------------------------------------------- + template + typename std::enable_if_t, std::ostream&> operator<<(std::ostream& o, invocable_t callee) + { + callee(o); + return o; + } //--------------------------------------------------------------- std::ostream& operator <<(std::ostream& o, const ref_by_id& r); std::ostream& operator <<(std::ostream& o, const std::type_info& ti); diff --git a/src/currency_protocol/currency_protocol_defs.h b/src/currency_protocol/currency_protocol_defs.h index 2a0eead6..b1ee93bc 100644 --- a/src/currency_protocol/currency_protocol_defs.h +++ b/src/currency_protocol/currency_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -135,7 +135,7 @@ namespace currency struct CORE_SYNC_DATA { - uint64_t current_height; + uint64_t current_height; // height of the top block + 1 crypto::hash top_id; uint64_t last_checkpoint_height; uint64_t core_time; diff --git a/src/currency_protocol/currency_protocol_handler.h b/src/currency_protocol/currency_protocol_handler.h index 1e420919..c21ac71a 100644 --- a/src/currency_protocol/currency_protocol_handler.h +++ b/src/currency_protocol/currency_protocol_handler.h @@ -74,6 +74,10 @@ namespace currency //----------------------------------------------------------------------------------- void set_to_debug_mode(uint32_t ip); + bool is_remote_client_version_allowed(int build_number, size_t min_allowed_build_number = SIZE_MAX) const; + bool is_remote_client_version_allowed(const std::string& client_version) const; + void check_all_client_versions_are_okay(); + private: //----------------- commands handlers ---------------------------------------------- int handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& context); @@ -86,11 +90,12 @@ namespace currency - //----------------- i_bc_protocol_layout --------------------------------------- - virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& exclude_context); - virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context); + //----------------- i_currency_protocol --------------------------------------- + virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& exclude_context) override; + virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context) override; + virtual void on_hardfork_activated(size_t hardfork_id) override; //---------------------------------------------------------------------------------- - //bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, currency_connection_context& context); + bool request_missing_objects(currency_connection_context& context, bool check_having_blocks); bool on_connection_synchronized(); void relay_que_worker(); diff --git a/src/currency_protocol/currency_protocol_handler.inl b/src/currency_protocol/currency_protocol_handler.inl index e6d75eb2..b9c7a524 100644 --- a/src/currency_protocol/currency_protocol_handler.inl +++ b/src/currency_protocol/currency_protocol_handler.inl @@ -1,13 +1,14 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - #include +#include "currency_protocol_handler.h" #include "currency_core/currency_format_utils.h" #include "profile_tools.h" #include + namespace currency { @@ -96,7 +97,7 @@ namespace currency ss << std::setw(29) << std::left << "Remote Host" << std::setw(20) << "Peer id" - << std::setw(25) << "Recv/Sent (idle,sec)" + << std::setw(27) << "Recv/Sent (idle,sec)" << std::setw(25) << "State" << std::setw(20) << "Livetime" << std::setw(20) << "Client version" << ENDL; @@ -110,7 +111,7 @@ namespace currency conn_ss << std::setw(29) << std::left << std::string(cntxt.m_is_income ? "[INC]":"[OUT]") + epst::get_ip_string_from_int32(cntxt.m_remote_ip) + ":" + std::to_string(cntxt.m_remote_port) << std::setw(20) << std::hex << peer_id - << std::setw(25) << std::to_string(cntxt.m_recv_cnt)+ "(" + std::to_string(time(NULL) - cntxt.m_last_recv) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(time(NULL) - cntxt.m_last_send) + ")" + << std::setw(27) << std::to_string(cntxt.m_recv_cnt)+ "(" + std::to_string(time(NULL) - cntxt.m_last_recv) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(time(NULL) - cntxt.m_last_send) + ")" << std::setw(25) << get_protocol_state_string(cntxt.m_state) << std::setw(20) << epee::misc_utils::get_time_interval_string(livetime) << std::setw(20) << cntxt.m_remote_version @@ -129,9 +130,12 @@ namespace currency template bool t_currency_protocol_handler::process_payload_sync_data(const CORE_SYNC_DATA& hshd, currency_connection_context& context, bool is_inital) { - - context.m_remote_version = hshd.client_version; + if (!tools::parse_client_version_build_number(context.m_remote_version, context.m_build_number)) + { + LOG_PRINT_RED_L0("Couldn't parse remote node's version: " << context.m_remote_version << ". Connection will be dropped."); + return false; + } if(context.m_state == currency_connection_context::state_befor_handshake && !is_inital) return true; @@ -989,7 +993,7 @@ namespace currency //------------------------------------------------------------------------------------------------------------------------ template bool t_currency_protocol_handler::relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context) - { + { #ifdef ASYNC_RELAY_MODE { CRITICAL_REGION_LOCAL(m_relay_que_lock); @@ -1003,4 +1007,52 @@ namespace currency return relay_post_notify(arg, exclude_context); #endif } -} + //------------------------------------------------------------------------------------------------------------------------ + template + void t_currency_protocol_handler::on_hardfork_activated(size_t hardfork_id) + { + check_all_client_versions_are_okay(); + } + //------------------------------------------------------------------------------------------------------------------------ + template + bool t_currency_protocol_handler::is_remote_client_version_allowed(int build_number, size_t min_allowed_build_number /*= SIZE_MAX*/) const + { + if (min_allowed_build_number == SIZE_MAX) + min_allowed_build_number = m_core.get_blockchain_storage().get_core_runtime_config().get_min_allowed_build_version_for_height(m_core.get_top_block_height() + 1); + + if (build_number < static_cast(min_allowed_build_number)) + return false; + + return true; + } + //------------------------------------------------------------------------------------------------------------------------ + template + bool t_currency_protocol_handler::is_remote_client_version_allowed(const std::string& client_version) const + { + int major = -1, minor = -1, revision = -1, build_number = -1; + std::string commit_id; + bool dirty = false; + if (!tools::parse_client_version(client_version, major, minor, revision, build_number, commit_id, dirty)) + return false; + + return is_remote_client_version_allowed(build_number); + } + //------------------------------------------------------------------------------------------------------------------------ + template + void t_currency_protocol_handler::check_all_client_versions_are_okay() + { + size_t min_allowed_build_number = m_core.get_blockchain_storage().get_core_runtime_config().get_min_allowed_build_version_for_height(m_core.get_top_block_height() + 1); + + m_p2p->for_each_connection([&](const connection_context& cc, nodetool::peerid_type peer_id) + { + if (!is_remote_client_version_allowed(cc.m_build_number, min_allowed_build_number)) + { + LOG_PRINT_CC_YELLOW(cc, "client's build number is " << cc.m_build_number << ", which is absolutely not okay in the current hardfork era, prompting us to adjust our connections accordingly.", LOG_LEVEL_0); + m_p2p->drop_connection(cc); + } + return true; // = continue + }); + } + + +} // namespace currency diff --git a/src/currency_protocol/currency_protocol_handler_common.h b/src/currency_protocol/currency_protocol_handler_common.h index d07153f5..ba640896 100644 --- a/src/currency_protocol/currency_protocol_handler_common.h +++ b/src/currency_protocol/currency_protocol_handler_common.h @@ -18,6 +18,7 @@ namespace currency { virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& exclude_context)=0; virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context)=0; + virtual void on_hardfork_activated(size_t hardfork_id) {} //virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, currency_connection_context& context)=0; }; diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 90f3b422..950c93af 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Boolberry developers // Distributed under the MIT/X11 software license, see the accompanying @@ -714,9 +714,12 @@ bool MainWindow::show_inital() { TRY_ENTRY(); if (load_app_config()) + { restore_pos(true); + } else { + LOG_PRINT_L1("set defaults values to config"); m_config = AUTO_VAL_INIT(m_config); this->show(); QSize sz = AUTO_VAL_INIT(sz); @@ -976,6 +979,11 @@ QString MainWindow::start_backend(const QString& params) CATCH_ENTRY_FAIL_API_RESPONCE(); } +void MainWindow::show_notification(const QString& title, const QString& message) +{ + show_notification(title.toStdString(), message.toStdString()); +} + QString MainWindow::sync_call(const QString& func_name, const QString& params) { if (func_name == "test_call") @@ -1107,6 +1115,7 @@ bool MainWindow::get_is_disabled_notifications(const QString& param) } bool MainWindow::set_is_disabled_notifications(const bool& param) { + LOG_PRINT_L1("set_is_disabled_notifications: notifications were " << (m_config.disable_notifications ? "DISABLED" : "ENABLED") << " -> now " << (param ? "DISABLED" : "ENABLED")); m_config.disable_notifications = param; return m_config.disable_notifications; } @@ -1712,7 +1721,7 @@ QString MainWindow::have_secure_app_data(const QString& param) CATCH_ENTRY_FAIL_API_RESPONCE(); } -QString MainWindow::drop_secure_app_data(const QString& param) +QString MainWindow::drop_secure_app_data() { TRY_ENTRY(); LOG_API_TIMING(); @@ -2468,6 +2477,10 @@ QString MainWindow::print_log(const QString& param) void MainWindow::show_notification(const std::string& title, const std::string& message) { TRY_ENTRY(); + + if (m_config.disable_notifications) + return; + LOG_PRINT_L1("system notification: \"" << title << "\", \"" << message << "\""); // it's expected that title and message are utf-8 encoded! @@ -2482,5 +2495,3 @@ void MainWindow::show_notification(const std::string& title, const std::string& #endif CATCH_ENTRY2(void()); } - - diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index 0f575b6b..30dc1b6c 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -112,7 +112,7 @@ public: QString get_network_type(const QString& param); QString transfer(const QString& param); QString have_secure_app_data(const QString& param); - QString drop_secure_app_data(const QString& param); + QString drop_secure_app_data(); QString get_secure_app_data(const QString& param); QString store_secure_app_data(const QString& param, const QString& password); QString set_master_password(const QString& param); @@ -190,6 +190,7 @@ public: void on_menu_show(const QString& param); QString is_remnotenode_mode_preconfigured(const QString& param); QString start_backend(const QString& params); + void show_notification(const QString& title, const QString& message); QString async_call(const QString& func_name, const QString& params); QString sync_call(const QString& func_name, const QString& params); diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 748e8e96..5c878005 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 748e8e96d8f2653e6e698a11f67c172c1f84c2b2 +Subproject commit 5c878005ace55484eafe2985d204cd51e90b203b diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 46a39ee5..70b906dc 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -523,7 +523,7 @@ namespace nodetool return; } - if (!tools::check_remote_client_version(rsp.payload_data.client_version)) + if (!m_payload_handler.is_remote_client_version_allowed(rsp.payload_data.client_version)) { LOG_PRINT_CC_YELLOW(context, "COMMAND_HANDSHAKE Failed, wrong client version: " << rsp.payload_data.client_version << ", closing connection.", LOG_LEVEL_1); return; @@ -1391,7 +1391,7 @@ namespace nodetool return 1; } - if (!tools::check_remote_client_version(arg.payload_data.client_version)) + if (!m_payload_handler.is_remote_client_version_allowed(arg.payload_data.client_version)) { LOG_PRINT_CCONTEXT_L2("COMMAND_HANDSHAKE: wrong client version: " << arg.payload_data.client_version << ", closing connection."); drop_connection(context); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 6d45766e..88938641 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -75,7 +75,8 @@ namespace currency return true; } #define check_core_ready() check_core_ready_(LOCAL_FUNCTION_DEF__) -#define CHECK_CORE_READY() if(!check_core_ready()){res.status = API_RETURN_CODE_BUSY;return true;} +#define CHECK_CORE_READY() if (!check_core_ready()) {res.status = API_RETURN_CODE_BUSY; return true; } +#define CHECK_CORE_READY_WE() if (!check_core_ready()) {error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; error_resp.message = "Core is busy."; return false; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res, connection_context& cntx) { @@ -750,6 +751,74 @@ namespace currency return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_decrypt_tx_details(const COMMAND_RPC_DECRYPT_TX_DETAILS::request& req, COMMAND_RPC_DECRYPT_TX_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) + { +#define LOCAL_CHECK(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; error_resp.message = msg; LOG_PRINT_L1("on_decrypt_tx_details: " << error_resp.message); return false; } +#define LOCAL_CHECK_INT_ERR(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = msg; LOG_PRINT_L1("on_decrypt_tx_details: " << error_resp.message); return false; } + + LOCAL_CHECK(req.tx_id.empty() != req.tx_blob.empty(), "One of either tx_id or tx_blob must be specified."); + + transaction tx{}; + if (!req.tx_id.empty()) + { + CHECK_CORE_READY_WE(); + + crypto::hash tx_id{}; + LOCAL_CHECK(crypto::parse_tpod_from_hex_string(req.tx_id, tx_id), "tx_id is given, but it's invalid"); + LOCAL_CHECK(m_core.get_transaction(tx_id, tx), "tx with the given tx_id could be found in the blockchain"); + } + else + { + blobdata decoded_blob = string_encoding::base64_decode(req.tx_blob); + if (!t_unserializable_object_from_blob(tx, decoded_blob)) + { + // unable to decode tx_blob as base64, try once again as hex-encoding + decoded_blob.clear(); + string_tools::parse_hexstr_to_binbuff(req.tx_blob, decoded_blob); + LOCAL_CHECK(t_unserializable_object_from_blob(tx, decoded_blob), "tx_id is not given, and tx_blob is invalid"); + } + } + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + crypto::point_t R{}; + LOCAL_CHECK(tx_pub_key != null_pkey && R.from_public_key(tx_pub_key) && R.is_in_main_subgroup(), "unsigned_tx: tx public key is missing or invalid"); + + LOCAL_CHECK(tx_pub_key == (crypto::scalar_t(req.tx_secret_key) * crypto::c_point_G).to_public_key(), "tx_secret_key doesn't match the transaction public key"); + + LOCAL_CHECK(req.outputs_addresses.size() == tx.vout.size(), "outputs_addresses count (" + epee::string_tools::num_to_string_fast(req.outputs_addresses.size()) + " doesn't match tx.vout size (" + epee::string_tools::num_to_string_fast(tx.vout.size()) + ")"); + + for(size_t i = 0; i < req.outputs_addresses.size(); ++i) + { + if (req.outputs_addresses[i].empty()) + continue; // skip this output if the given address is empty string + + account_public_address addr{}; + payment_id_t payment_id{}; + LOCAL_CHECK(currency::get_account_address_and_payment_id_from_str(addr, payment_id, req.outputs_addresses[i]) && payment_id.empty(), "output address #" + epee::string_tools::num_to_string_fast(i) + " couldn't be parsed or it is an integrated address (which is not supported)"); + + tx_out_v& out_v = tx.vout[i]; + LOCAL_CHECK(out_v.type() == typeid(tx_out_zarcanum), "tx output #" + epee::string_tools::num_to_string_fast(i) + " has wrong type"); + const tx_out_zarcanum& zo = boost::get(out_v); + + crypto::key_derivation derivation{}; + LOCAL_CHECK_INT_ERR(crypto::generate_key_derivation(addr.view_public_key, req.tx_secret_key, derivation), "output #" + epee::string_tools::num_to_string_fast(i) + ": generate_key_derivation failed"); + + auto& decoded_out = res.decoded_outputs.emplace_back(); + decoded_out.out_index = i; + decoded_out.address = req.outputs_addresses[i]; + crypto::scalar_t amount_blinding_mask{}, asset_id_blinding_mask{}; + LOCAL_CHECK(currency::decode_output_amount_and_asset_id(zo, derivation, i, decoded_out.amount, decoded_out.asset_id, amount_blinding_mask, asset_id_blinding_mask), "output #" + epee::string_tools::num_to_string_fast(i) + ": cannot be decoded"); + } + + res.tx_in_json = currency::obj_to_json_str(tx); + res.verified_tx_id = get_transaction_hash(tx); + + res.status = API_RETURN_CODE_OK; + return true; +#undef LOCAL_CHECK +#undef LOCAL_CHECK_INT_ERR + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_main_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { if (!m_core.get_blockchain_storage().get_main_block_rpc_details(req.id, res.block_details)) @@ -1001,7 +1070,7 @@ namespace currency //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { - CHECK_CORE_READY(); + CHECK_CORE_READY_WE(); if(req.size()!=1) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; @@ -1044,8 +1113,7 @@ namespace currency //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_submitblock2(const COMMAND_RPC_SUBMITBLOCK2::request& req, COMMAND_RPC_SUBMITBLOCK2::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { - CHECK_CORE_READY(); - + CHECK_CORE_READY_WE(); block b = AUTO_VAL_INIT(b); if (!parse_and_validate_block_from_blob(req.b, b)) @@ -1308,7 +1376,7 @@ namespace currency LOCAL_CHECK(req.address != account_public_address{}, "address is missing"); LOCAL_CHECK(req.viewkey != null_skey, "viewkey is missing"); - LOCAL_CHECK(0 <= req.blocks_limit && req.blocks_limit <= 5, "blocks_limit is out of allowed bounds"); + LOCAL_CHECK(req.blocks_limit <= 5, "blocks_limit is out of allowed bounds"); // verify addess keys crypto::point_t view_pk, spend_pk; @@ -1332,7 +1400,7 @@ namespace currency { uint64_t start_offset = resp.blockchain_top_block_height - req.blocks_limit + 1; std::list recent_blocks; - LOCAL_CHECK_INT_ERR(bcs.get_blocks(start_offset, req.blocks_limit, recent_blocks), "cannot get recent blocks"); + LOCAL_CHECK_INT_ERR(bcs.get_blocks(start_offset, static_cast(req.blocks_limit), recent_blocks), "cannot get recent blocks"); std::vector blockchain_tx_ids, missed_tx; for(auto& b : recent_blocks) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 8e44a31a..e95f5a41 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -89,6 +89,7 @@ namespace currency bool on_get_votes(const COMMAND_RPC_GET_VOTES::request& req, COMMAND_RPC_GET_VOTES::response& res, connection_context& cntx); bool on_get_asset_info(const COMMAND_RPC_GET_ASSET_INFO::request& req, COMMAND_RPC_GET_ASSET_INFO::response& res, connection_context& cntx); bool on_get_assets_list(const COMMAND_RPC_GET_ASSETS_LIST::request& req, COMMAND_RPC_GET_ASSETS_LIST::response& res, connection_context& cntx); + bool on_decrypt_tx_details(const COMMAND_RPC_DECRYPT_TX_DETAILS::request& req, COMMAND_RPC_DECRYPT_TX_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_main_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_alt_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); @@ -153,9 +154,11 @@ namespace currency MAP_JON_RPC ("getrandom_outs1", on_get_random_outs1, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) MAP_JON_RPC ("getrandom_outs3", on_get_random_outs3, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3) MAP_JON_RPC ("get_votes", on_get_votes, COMMAND_RPC_GET_VOTES) + //assets api MAP_JON_RPC ("get_asset_info", on_get_asset_info, COMMAND_RPC_GET_ASSET_INFO) MAP_JON_RPC ("get_assets_list", on_get_assets_list, COMMAND_RPC_GET_ASSETS_LIST) + MAP_JON_RPC_WE("decrypt_tx_details", on_decrypt_tx_details, COMMAND_RPC_DECRYPT_TX_DETAILS) MAP_JON_RPC_WE("get_main_block_details", on_get_main_block_details, COMMAND_RPC_GET_BLOCK_DETAILS) MAP_JON_RPC_WE("get_alt_block_details", on_get_alt_block_details, COMMAND_RPC_GET_BLOCK_DETAILS) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 452a8316..b5d6143d 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -179,6 +179,58 @@ namespace currency }; }; + + struct COMMAND_RPC_DECRYPT_TX_DETAILS + { + DOC_COMMAND("Decrypts transaction private information. Should be used only with your own local daemon for security reasons."); + + struct request + { + std::string tx_id; + currency::blobdata tx_blob; + crypto::secret_key tx_secret_key; + std::vector outputs_addresses; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_id) DOC_DSCR("[either] ID for a transaction if it is already in the blockchain. Can be ommited if tx_blob is provided.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END + KV_SERIALIZE(tx_blob) DOC_DSCR("[or] base64-encoded or hex-encoded tx blob. Can be ommited if tx_id is provided.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(tx_secret_key) DOC_DSCR("Hex-encoded transaction secret key.") DOC_EXMP("2e0b840e70dba386effd64c5d988622dea8c064040566e6bf035034cbb54a5c08") DOC_END + KV_SERIALIZE(outputs_addresses) DOC_DSCR("Address of each of tx's output. Order is important and should correspond to order of tx's outputs. Empty strings are ignored.") DOC_EXMP_AGGR("ZxDNaMeZjwCjnHuU5gUNyrP1pM3U5vckbakzzV6dEHyDYeCpW8XGLBFTshcaY8LkG9RQn7FsQx8w2JeJzJwPwuDm2NfixPAXf", "ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END + END_KV_SERIALIZE_MAP() + }; + + // TODO consider reusing existing structure transfer_destination -- sowle + struct decoded_output + { + uint64_t amount = 0; + std::string address; + crypto::public_key asset_id; + uint64_t out_index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) DOC_DSCR("Amount begin transferred.") DOC_EXMP(10000000000000) DOC_END + KV_SERIALIZE(address) DOC_DSCR("Destination address.") DOC_EXMP("ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(asset_id) DOC_DSCR("Asset id.") DOC_EXMP("cc608f59f8080e2fbfe3c8c80eb6e6a953d47cf2d6aebd345bada3a1cab99852") DOC_END + KV_SERIALIZE(out_index) DOC_DSCR("Index of the corresponding output in the transaction.") DOC_EXMP(1) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + std::vector decoded_outputs; + std::string tx_in_json; + crypto::hash verified_tx_id; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) DOC_DSCR("Status code of operation, OK if success") DOC_EXMP(API_RETURN_CODE_OK) DOC_END + KV_SERIALIZE(decoded_outputs) DOC_DSCR("Transaction's decoded outputs") DOC_EXMP_AUTO(1) DOC_END + KV_SERIALIZE_BLOB_AS_BASE64_STRING(tx_in_json) DOC_DSCR("Serialized transaction represented in JSON, encoded in Base64.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(verified_tx_id) DOC_DSCR("(Re)calculated transaction id. Can be used in third-party proof generation.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_HEIGHT { DOC_COMMAND("Return current blockchain height"); @@ -636,32 +688,32 @@ namespace currency }; //----------------------------------------------- - struct COMMAND_RPC_SEND_RAW_TX - { + struct COMMAND_RPC_SEND_RAW_TX + { DOC_COMMAND("Broadcasts a raw transaction encoded in hexadecimal format to the network."); struct request - { - std::string tx_as_hex; + { + std::string tx_as_hex; - request() {} - explicit request(const transaction &); + request() {} + explicit request(const transaction &); - BEGIN_KV_SERIALIZE_MAP() + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_as_hex) DOC_DSCR("The transaction data as a hexadecimal string, ready for network broadcast.") DOC_EXMP("00018ed1535b8b4862e.....368cdc5a86") DOC_END END_KV_SERIALIZE_MAP() - }; + }; - struct response - { - std::string status; + struct response + { + std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END - END_KV_SERIALIZE_MAP() - }; - }; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END + END_KV_SERIALIZE_MAP() + }; + }; //----------------------------------------------- @@ -672,7 +724,7 @@ namespace currency std::vector txs_as_hex; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(txs_as_hex) DOC_DSCR("List of transactions as a hexadecimal strings.") DOC_EXMP_AGGR("000535b8b2e.....3685a86", "00087368b2e.....349b77f") DOC_END + KV_SERIALIZE(txs_as_hex) DOC_DSCR("List of transactions as a hexadecimal strings.") DOC_EXMP_AGGR("000535b8b2e.....3685a86", "00087368b2e.....349b77f") DOC_END END_KV_SERIALIZE_MAP() }; diff --git a/src/serialization/boost_types.h b/src/serialization/boost_types.h index 154705b8..ca3e3d7b 100644 --- a/src/serialization/boost_types.h +++ b/src/serialization/boost_types.h @@ -1,3 +1,4 @@ +// Copyright (c) 2018-2024 Zano Project // Copyright (c) 2014-2017 The The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -7,7 +8,7 @@ #include - +// boost::optional template