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/CMakeLists.txt b/CMakeLists.txt index 04790815..e320fafc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,7 +226,12 @@ endif() message("CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") if(CMAKE_SYSTEM_NAME STREQUAL "iOS") set(CMAKE_OSX_DEPLOYMENT_TARGET 12.00) - set(Boost_LIBRARIES "libboost.a") + if(NOT DEFINED SKIP_BOOST_FATLIB_LIB OR NOT SKIP_BOOST_FATLIB_LIB) + message("Ios: libboost.a included as library") + set(Boost_LIBRARIES "libboost.a") + else() + message("Ios: libboost.a not included as library") + endif() #workaround for new XCode 12 policy for builds(now it includes a slice for the "arm64" when builds for simulator) set(__iphoneos_archs "arm64") #set(__iphonesimulator_archs "arm64,x86_64") 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/file_io_utils.h b/contrib/epee/include/file_io_utils.h index 9d5118cb..bb21ae99 100644 --- a/contrib/epee/include/file_io_utils.h +++ b/contrib/epee/include/file_io_utils.h @@ -35,7 +35,19 @@ #include #include #include -#include + + +#if __has_include() + #include + namespace stdfs = std::filesystem; +#else + #if TARGET_OS_IOS + #error "This should never happen on ios." + #endif + namespace stdfs = boost::filesystem; +#endif + +//#include #ifndef MAKE64 #define MAKE64(low,high) ((__int64)(((DWORD)(low)) | ((__int64)((DWORD)(high))) << 32)) @@ -562,10 +574,10 @@ namespace file_io_utils try { - std::filesystem::directory_iterator end_itr; // default construction yields past-the-end - for ( std::filesystem::directory_iterator itr( epee::string_encoding::utf8_to_wstring(path) ); itr != end_itr; ++itr ) + stdfs::directory_iterator end_itr; // default construction yields past-the-end + for (stdfs::directory_iterator itr( epee::string_encoding::utf8_to_wstring(path) ); itr != end_itr; ++itr ) { - if ( only_files && std::filesystem::is_directory(itr->status()) ) + if ( only_files && stdfs::is_directory(itr->status()) ) { continue; } 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 + 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/db_backend_lmdb.cpp b/src/common/db_backend_lmdb.cpp index 75e60ce4..d23fff80 100644 --- a/src/common/db_backend_lmdb.cpp +++ b/src/common/db_backend_lmdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -394,10 +394,67 @@ namespace tools } return true; } + const char* lmdb_db_backend::name() { return "lmdb"; } + + bool lmdb_db_backend::convert_db_4kb_page_to_16kb_page(const std::string& source_path, const std::string& destination_path) + { + #define MDB_CHECK(x, msg) {int rc = x; CHECK_AND_ASSERT_MES(rc == MDB_SUCCESS, false, "LMDB 4k->16k error: " << msg << ": " << mdb_strerror(rc));} + + MDB_env *env_src = nullptr, *env_dst = nullptr; + + // source + MDB_CHECK(mdb_env_create(&env_src), "failed to create LMDB environment"); + MDB_CHECK(mdb_env_set_mapsize(env_src, 4 * 1024 * 1024), "failed to set mapsize"); // mapsize ? + MDB_CHECK(mdb_env_open(env_src, source_path.c_str(), 0, 0664), "failed to open source LMDB"); + + // destination (16k page size) + MDB_CHECK(mdb_env_create(&env_dst), "failed to create LMDB environment"); + MDB_CHECK(mdb_env_set_mapsize(env_dst, 16 * 1024 * 1024), "failed to set mapsize"); // mapsize ? + + // TODO uncomment after mdb_env_set_pagesize is supported + // MDB_CHECK(mdb_env_set_pagesize(env_dst, 16 * 1024), "failed to set page size to 16K"); + + MDB_CHECK(mdb_env_open(env_dst, destination_path.c_str(), 0, 0664), "failed to open destination LMDB"); + + // begin transactions + MDB_txn *txn_src = nullptr, *txn_dst = nullptr; + MDB_dbi dbi_src, dbi_dst; + MDB_CHECK(mdb_txn_begin(env_src, nullptr, MDB_RDONLY, &txn_src), "failed to begin source transaction"); + MDB_CHECK(mdb_dbi_open(txn_src, nullptr, 0, &dbi_src), "failed to open source database"); + MDB_CHECK(mdb_txn_begin(env_dst, nullptr, 0, &txn_dst), "failed to begin destination transaction"); + MDB_CHECK(mdb_dbi_open(txn_dst, nullptr, MDB_CREATE, &dbi_dst), "failed to open destination database"); + + MDB_cursor *cursor; + MDB_val key, data; + + // Iterate over the source database and copy all key-value pairs to the destination database + MDB_CHECK(mdb_cursor_open(txn_src, dbi_src, &cursor), "failed to open cursor"); + + while (mdb_cursor_get(cursor, &key, &data, MDB_NEXT) == MDB_SUCCESS) + { + MDB_CHECK(mdb_put(txn_dst, dbi_dst, &key, &data, 0), "failed to put data in destination database"); + } + + mdb_cursor_close(cursor); + + // commit transactions + MDB_CHECK(mdb_txn_commit(txn_src), "failed to commit source transaction"); + MDB_CHECK(mdb_txn_commit(txn_dst), "failed to commit destination transaction"); + + mdb_dbi_close(env_src, dbi_src); + mdb_dbi_close(env_dst, dbi_dst); + mdb_env_close(env_src); + mdb_env_close(env_dst); + + return true; + + #undef MDB_CHECK + } + } } diff --git a/src/common/db_backend_lmdb.h b/src/common/db_backend_lmdb.h index ef56c346..c2920f0c 100644 --- a/src/common/db_backend_lmdb.h +++ b/src/common/db_backend_lmdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -36,6 +36,7 @@ namespace tools boost::recursive_mutex m_write_exclusive_lock; std::map m_txs; // size_t -> count of nested read_only transactions bool pop_tx_entry(tx_entry& txe); + public: lmdb_db_backend(); ~lmdb_db_backend(); @@ -60,6 +61,8 @@ namespace tools bool have_tx(); MDB_txn* get_current_tx(); + static bool convert_db_4kb_page_to_16kb_page(const std::string& source_path, const std::string& destination_path); + }; } } diff --git a/src/common/mnemonic-encoding.cpp b/src/common/mnemonic-encoding.cpp index c2e3e307..f52ece6b 100644 --- a/src/common/mnemonic-encoding.cpp +++ b/src/common/mnemonic-encoding.cpp @@ -3391,5 +3391,9 @@ namespace tools CHECK_AND_ASSERT_THROW_MES(it!= wordsMap.end(), "unable to find word \"" << w << "\" in mnemonic dictionary"); return it->second; } + const map& get_words_map() + { + return wordsMap; + } } } diff --git a/src/common/mnemonic-encoding.h b/src/common/mnemonic-encoding.h index 4ce6b9bb..5e235399 100644 --- a/src/common/mnemonic-encoding.h +++ b/src/common/mnemonic-encoding.h @@ -47,5 +47,6 @@ namespace tools std::string word_by_num(uint32_t n); uint64_t num_by_word(const std::string& w); bool valid_word(const std::string& w); + const std::map& get_words_map(); } } 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/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index 07528c56..a7c32892 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 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 @@ -25,6 +25,7 @@ using namespace epee; #include "storages/http_abstract_invoke.h" #include "net/http_client.h" #include "currency_core/genesis_acc.h" +#include "common/db_backend_lmdb.h" #include namespace po = boost::program_options; @@ -63,6 +64,7 @@ namespace const command_line::arg_descriptor arg_pack_file ("pack-file", "perform gzip-packing and calculate hash for a given file"); const command_line::arg_descriptor arg_unpack_file ("unpack-file", "Perform gzip-unpacking and calculate hash for a given file"); const command_line::arg_descriptor arg_target_file ("target-file", "Specify target file for pack-file and unpack-file commands"); + const command_line::arg_descriptor arg_lmdb_page_4to16 ("convert-lmdb-4to16", "Perform LMDB conversion from 4k page size to 16k page size"); //const command_line::arg_descriptor arg_send_ipc ("send-ipc", "Send IPC request to UI"); } @@ -1220,7 +1222,10 @@ bool handle_pack_file(po::variables_map& vm) } if (!command_line::has_arg(vm, arg_target_file)) + { std::cout << "Error: Parameter target_file is not set." << ENDL; + return false; + } path_target = command_line::get_arg(vm, arg_target_file); std::ifstream source; @@ -1251,6 +1256,34 @@ bool handle_pack_file(po::variables_map& vm) } } +bool handle_lmdb_page_4to16(po::variables_map& vm) +{ + std::string path_source; + std::string path_target; + + if (!command_line::has_arg(vm, arg_lmdb_page_4to16)) + return false; + + path_source = command_line::get_arg(vm, arg_lmdb_page_4to16); + + if (!command_line::has_arg(vm, arg_target_file)) + { + std::cout << "Error: Parameter target_file is not set." << ENDL; + return false; + } + path_target = command_line::get_arg(vm, arg_target_file); + + if (tools::db::lmdb_db_backend::convert_db_4kb_page_to_16kb_page(path_source, path_target)) + { + std::cout << "Conversion failed" << ENDL; + return false; + } + + std::cout << "Converted successfully" << ENDL; + return true; +} + + //--------------------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) @@ -1375,6 +1408,10 @@ int main(int argc, char* argv[]) { return handle_pack_file(vm) ? EXIT_SUCCESS : EXIT_FAILURE; } + else if (command_line::has_arg(vm, arg_lmdb_page_4to16)) + { + return handle_lmdb_page_4to16(vm) ? EXIT_SUCCESS : EXIT_FAILURE; + } /*else if (command_line::has_arg(vm, arg_send_ipc)) { handle_send_ipc(command_line::get_arg(vm, arg_send_ipc)) ? EXIT_SUCCESS : EXIT_FAILURE; 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..0e33c47f --- /dev/null +++ b/src/crypto/eth_signature.cpp @@ -0,0 +1,162 @@ +// 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); + } + + +} // namespace crypto diff --git a/src/crypto/eth_signature.h b/src/crypto/eth_signature.h new file mode 100644 index 00000000..b186114a --- /dev/null +++ b/src/crypto/eth_signature.h @@ -0,0 +1,66 @@ +// 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); + +} // namespace crypto diff --git a/src/crypto/one_out_of_many_proofs.cpp b/src/crypto/one_out_of_many_proofs.cpp index a30374c1..ab921f44 100644 --- a/src/crypto/one_out_of_many_proofs.cpp +++ b/src/crypto/one_out_of_many_proofs.cpp @@ -1,10 +1,11 @@ -// Copyright (c) 2023 Zano Project -// Copyright (c) 2023 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2023-2024 Zano Project +// Copyright (c) 2023-2024 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include "one_out_of_many_proofs.h" #include "../currency_core/crypto_config.h" +#include "../currency_core/currency_config.h" // for static asset checks #include "epee/include/misc_log_ex.h" //DISABLE_GCC_AND_CLANG_WARNING(unused-function) @@ -22,6 +23,8 @@ namespace crypto static const size_t N_max = 256; static const size_t mn_max = 16; + static_assert(CURRENCY_TX_MAX_ALLOWED_INPUTS <= N_max, "CURRENCY_TX_MAX_ALLOWED_INPUTS is inconsistent with one-out-of-many proof limits"); // TODO: consider moving this check out -- sowle + const point_t& get_BGE_generator(size_t index, bool& ok) { static std::vector precalculated_generators; diff --git a/src/crypto/random.c b/src/crypto/random.c index bdac1e7a..a54bdf74 100644 --- a/src/crypto/random.c +++ b/src/crypto/random.c @@ -43,7 +43,7 @@ void generate_system_random_bytes(size_t n, void *result) { void generate_system_random_bytes(size_t n, void *result) { int fd; if ((fd = open("/dev/urandom", O_RDONLY | O_NOCTTY | O_CLOEXEC)) < 0) { - err(EXIT_FAILURE, "open /dev/urandom"); + exit(EXIT_FAILURE); } for (;;) { ssize_t res = read(fd, result, n); @@ -52,17 +52,17 @@ void generate_system_random_bytes(size_t n, void *result) { } if (res < 0) { if (errno != EINTR) { - err(EXIT_FAILURE, "read /dev/urandom"); + exit(EXIT_FAILURE); } } else if (res == 0) { - errx(EXIT_FAILURE, "read /dev/urandom: end of file"); + exit(EXIT_FAILURE); } else { result = padd(result, (size_t) res); n -= (size_t) res; } } if (close(fd) < 0) { - err(EXIT_FAILURE, "close /dev/urandom"); + exit(EXIT_FAILURE); } } 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 def20255..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,28 +71,35 @@ 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); - std::string timestamp_word = currency::get_word_from_timstamp(m_creation_timestamp, !password.empty()); + std::string timestamp_word = currency::get_word_from_timestamp(m_creation_timestamp, !password.empty()); // floor creation time to WALLET_BRAIN_DATE_QUANTUM to make checksum calculation stable bool self_check_is_password_used = false; - uint64_t creation_timestamp_rounded = get_timstamp_from_word(timestamp_word, self_check_is_password_used); + uint64_t creation_timestamp_rounded = get_timestamp_from_word(timestamp_word, self_check_is_password_used); 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; @@ -127,11 +134,12 @@ namespace currency return true; } //----------------------------------------------------------------- - bool account_base::restore_from_seed_phrase(const std::string& seed_phrase, const std::string& seed_password) + bool account_base::restore_from_seed_phrase(const std::string& seed_phrase_, const std::string& seed_password) { //cut the last timestamp word from restore_dats std::list words; - boost::split(words, seed_phrase, boost::is_space()); + std::string seed_phrase = epee::string_tools::trim(seed_phrase_); + boost::split(words, seed_phrase, boost::is_space(), boost::token_compress_on); std::string keys_seed_text, timestamp_word, auditable_flag_and_checksum_word; if (words.size() == SEED_PHRASE_V1_WORDS_COUNT) @@ -176,7 +184,7 @@ namespace currency bool has_password = false; try { - m_creation_timestamp = get_timstamp_from_word(timestamp_word, has_password); + m_creation_timestamp = get_timestamp_from_word(timestamp_word, has_password); } catch (...) { @@ -230,11 +238,13 @@ namespace currency return seed_phrase.find(':') != std::string::npos; } //----------------------------------------------------------------- - bool account_base::is_seed_password_protected(const std::string& seed_phrase, bool& is_password_protected) + bool account_base::is_seed_password_protected(const std::string& seed_phrase_, bool& is_password_protected) { //cut the last timestamp word from restore_dats std::list words; - boost::split(words, seed_phrase, boost::is_space()); + + std::string seed_phrase = epee::string_tools::trim(seed_phrase_); + boost::split(words, seed_phrase, boost::is_space(), boost::token_compress_on); //let's validate each word for (const auto& w: words) @@ -260,7 +270,7 @@ namespace currency return false; } - get_timstamp_from_word(timestamp_word, is_password_protected); + get_timestamp_from_word(timestamp_word, is_password_protected); return true; } 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..c05ffc53 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,30 @@ 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"); + return crypto::verify_eth_signature(avc.tx_id, last_ado.descriptor.owner_eth_pub_key.value(), aoop_eth.eth_sig); + } + // 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 +4137,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 +4150,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 +5817,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 +5947,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 0d3e5891..976207ad 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(owner_eth_pub_key) DOC_DSCR("[Optional] Owner's key in the case when ETH signature is used.") DOC_END END_KV_SERIALIZE_MAP() }; @@ -793,6 +803,13 @@ namespace currency BOOST_END_VERSION_UNDER(1) BOOST_SERIALIZE(opt_asset_id) END_BOOST_SERIALIZATION() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END + KV_SERIALIZE(descriptor) DOC_DSCR("Asset descriptor") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(amount_commitment) DOC_DSCR("Amount commitment") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + END_KV_SERIALIZE_MAP() }; struct asset_operation_proof @@ -833,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 @@ -936,7 +973,7 @@ namespace currency typedef boost::variant signature_v; - typedef boost::variant proof_v; + typedef boost::variant proof_v; //include backward compatibility defintions @@ -1195,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 c7384510..ff666f50 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -19,6 +19,7 @@ #define CURRENCY_MAX_BLOCK_NUMBER 500000000 #define CURRENCY_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! +#define CURRENCY_TX_MAX_ALLOWED_INPUTS 256 // limited primarily by asset surjection proof #define CURRENCY_TX_MAX_ALLOWED_OUTS 2000 #define CURRENCY_TX_MIN_ALLOWED_OUTS 2 // effective starting HF4 Zarcanum #define CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX 0xc5 // addresses start with 'Zx' @@ -257,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 afd632d7..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{}; @@ -2182,8 +2184,10 @@ namespace currency return true; } - // otherwise, calculate asset id + // otherwise, it must be a register operation + CHECK_AND_ASSERT_MES(ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER, false, "unexpected asset operation type: " << (int)ado.operation_type << ", " << get_asset_operation_type_string(ado.operation_type)); + // calculate asset id crypto::hash_helper_t::hs_t hsc; hsc.add_32_chars(CRYPTO_HDS_ASSET_ID); hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.ticker)); @@ -2192,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) @@ -2244,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"); @@ -2333,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; } @@ -2374,7 +2362,8 @@ namespace currency crypto::secret_key& one_time_tx_secret_key = result.one_time_key; result.ftp = ftp; - CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS, false, "Too many outs (" << destinations.size() << ")! Tx can't be constructed."); + CHECK_AND_ASSERT_MES(sources.size() <= CURRENCY_TX_MAX_ALLOWED_INPUTS, false, "Too many inputs: " << sources.size() << ", max allowed: " << CURRENCY_TX_MAX_ALLOWED_INPUTS << ". Tx cannot be constructed."); + CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS, false, "Too many outputs: " << destinations.size() << ", max allowed: " << CURRENCY_TX_MAX_ALLOWED_OUTS << ". Tx cannot be constructed."); bool append_mode = false; if (flags&TX_FLAG_SIGNATURE_MODE_SEPARATE && tx.vin.size()) @@ -2600,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); @@ -2714,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; @@ -2770,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); } } @@ -2850,7 +2834,7 @@ namespace currency return reward; } //--------------------------------------------------------------- - std::string get_word_from_timstamp(uint64_t timestamp, bool use_password) + std::string get_word_from_timestamp(uint64_t timestamp, bool use_password) { uint64_t date_offset = timestamp > WALLET_BRAIN_DATE_OFFSET ? timestamp - WALLET_BRAIN_DATE_OFFSET : 0; uint64_t weeks_count = date_offset / WALLET_BRAIN_DATE_QUANTUM; @@ -2865,7 +2849,7 @@ namespace currency return tools::mnemonic_encoding::word_by_num(weeks_count_32); } //--------------------------------------------------------------- - uint64_t get_timstamp_from_word(std::string word, bool& password_used) + uint64_t get_timestamp_from_word(std::string word, bool& password_used) { uint64_t count_of_weeks = tools::mnemonic_encoding::num_by_word(word); if (count_of_weeks >= WALLET_BRAIN_DATE_MAX_WEEKS_COUNT) diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 3e875c08..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) @@ -457,9 +455,9 @@ namespace currency bool fill_block_rpc_details(block_rpc_extended_info& pei_rpc, const block_extended_info& bei_chain, const crypto::hash& h); void append_per_block_increments_for_tx(const transaction& tx, std::unordered_map& gindices); - std::string get_word_from_timstamp(uint64_t timestamp, bool use_password); - uint64_t get_timstamp_from_word(std::string word, bool& password_used, const std::string& buff); - uint64_t get_timstamp_from_word(std::string word, bool& password_used); + std::string get_word_from_timestamp(uint64_t timestamp, bool use_password); + uint64_t get_timestamp_from_word(std::string word, bool& password_used, const std::string& buff); + uint64_t get_timestamp_from_word(std::string word, bool& password_used); bool parse_vote(const std::string& buff, std::list>& votes); std::string generate_origin_for_htlc(const txout_htlc& htlc, const account_keys& acc_keys); @@ -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 9f4c22de..b9c7a524 100644 --- a/src/currency_protocol/currency_protocol_handler.inl +++ b/src/currency_protocol/currency_protocol_handler.inl @@ -1,12 +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 { @@ -95,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; @@ -109,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 @@ -128,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; @@ -988,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); @@ -1002,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/daemon/daemon.cpp b/src/daemon/daemon.cpp index 5f46b088..f5f768fc 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -61,7 +61,7 @@ struct core_critical_error_handler_t : public currency::i_critical_error_handler if (dont_stop_on_time_error) return false; // ignore such errors - LOG_ERROR(ENDL << ENDL << "Serious time sync problem detected, daemon will stop immediately" << ENDL << ENDL); + LOG_ERROR(ENDL << ENDL << "Serious TIME sync problem detected, daemon will stop immediately" << ENDL << ENDL); // stop handling dch.stop_handling(); 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 2fb143cc..748e8e96 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 2fb143cc67280f0e0cfcd3165e1c087ba8279edf +Subproject commit 748e8e96d8f2653e6e698a11f67c172c1f84c2b2 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 371d0a81..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) { @@ -575,7 +576,7 @@ namespace currency //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_pos_mining_details(const COMMAND_RPC_GET_POS_MINING_DETAILS::request& req, COMMAND_RPC_GET_POS_MINING_DETAILS::response& res, connection_context& cntx) { - if (!m_p2p.get_connections_count()) + if (!m_ignore_status && !m_p2p.get_connections_count()) { res.status = API_RETURN_CODE_DISCONNECTED; return true; @@ -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)) @@ -793,7 +862,7 @@ namespace currency return true; } - if (!m_p2p.get_payload_object().get_synchronized_connections_count()) + if (!m_ignore_status && !m_p2p.get_payload_object().get_synchronized_connections_count()) { LOG_PRINT_L0("[on_send_raw_tx]: Failed to send, daemon not connected to net"); res.status = API_RETURN_CODE_DISCONNECTED; @@ -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)) @@ -1301,6 +1369,110 @@ namespace currency return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_find_outs_in_recent_blocks(const COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::request& req, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::response& resp, 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_find_outs_in_recent_blocks: " << msg); 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_find_outs_in_recent_blocks: " << msg); return false; } + + LOCAL_CHECK(req.address != account_public_address{}, "address is missing"); + LOCAL_CHECK(req.viewkey != null_skey, "viewkey is missing"); + LOCAL_CHECK(req.blocks_limit <= 5, "blocks_limit is out of allowed bounds"); + + // verify addess keys + crypto::point_t view_pk, spend_pk; + LOCAL_CHECK(view_pk.from_public_key(req.address.view_public_key), "cannon load point from address.view_public_key"); + LOCAL_CHECK(view_pk.is_in_main_subgroup(), "address.view_public_key isn't in main subgroup"); + LOCAL_CHECK(spend_pk.from_public_key(req.address.spend_public_key), "cannon load point from address.spend_public_key"); + LOCAL_CHECK(spend_pk.is_in_main_subgroup(), "address.spend_public_key isn't in main subgroup"); + + // verify viewkey + crypto::scalar_t view_sc = req.viewkey; + LOCAL_CHECK(view_sc.is_reduced(), "viewkey is invalid"); + LOCAL_CHECK(view_sc * crypto::c_point_G == view_pk, "viewkey doesn't correspond to the given address"); + + const blockchain_storage& bcs = m_core.get_blockchain_storage(); + resp.blockchain_top_block_height = bcs.get_top_block_height(); + resp.blocks_limit = req.blocks_limit; + + // get blockchain transactions + std::unordered_map, std::list>> blockchain_txs; // block height -> (vector of tx_ids, list of txs) + if (req.blocks_limit > 0) + { + 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, static_cast(req.blocks_limit), recent_blocks), "cannot get recent blocks"); + + std::vector blockchain_tx_ids, missed_tx; + for(auto& b : recent_blocks) + { + blockchain_tx_ids.insert(blockchain_tx_ids.end(), b.tx_hashes.begin(), b.tx_hashes.end()); + uint64_t height = get_block_height(b); + auto& el = blockchain_txs[height]; + el.first = b.tx_hashes; + missed_tx.clear(); + LOCAL_CHECK_INT_ERR(bcs.get_transactions(b.tx_hashes, el.second, missed_tx), "bcs.get_transactions failed"); + LOCAL_CHECK_INT_ERR(missed_tx.empty(), "missed_tx is not empty"); + LOCAL_CHECK_INT_ERR(el.first.size() == el.second.size(), "el.first.size() != el.second.size()"); + } + } + + // get pool transactions + std::list pool_txs; + LOCAL_CHECK_INT_ERR(m_core.get_tx_pool().get_transactions(pool_txs), "cannot get txs from pool"); + + // processor lambda + auto process_tx = [&](const transaction& tx, const crypto::hash& tx_id, const int64_t height) { + crypto::key_derivation derivation{}; + LOCAL_CHECK(generate_key_derivation(get_tx_pub_key_from_extra(tx), req.viewkey, derivation), "generate_key_derivation failed"); + + for(size_t i = 0, sz = tx.vout.size(); i < sz; ++i) + { + const tx_out_v& outv = tx.vout[i]; + if (outv.type() != typeid(tx_out_zarcanum)) + continue; + + uint64_t decoded_amount = 0; + crypto::public_key decoded_asset_id{}; + crypto::scalar_t amount_blinding_mask{}, asset_id_blinding_mask{}; + if (!is_out_to_acc(req.address, boost::get(outv), derivation, i, decoded_amount, decoded_asset_id, amount_blinding_mask, asset_id_blinding_mask)) + continue; + + auto& el = resp.outputs.emplace_back(); + el.amount = decoded_amount; + el.asset_id = decoded_asset_id; + el.tx_id = tx_id; + el.tx_block_height = height; + el.output_tx_index = i; + } + return true; + }; + + // process blockchain txs + for(auto& [height, pair] : blockchain_txs) + { + LOCAL_CHECK(pair.first.size() == pair.second.size(), "container size inconsistency"); + auto tx_it = pair.second.begin(); + for(size_t i = 0, sz = pair.first.size(); i < sz; ++i, ++tx_it) + { + const crypto::hash& tx_id = pair.first[i]; + LOCAL_CHECK(process_tx(*tx_it, tx_id, height), "process blockchain tx failed for tx " + crypto::pod_to_hex(tx_id)); + } + } + + // process pool txs + for(auto& tx : pool_txs) + { + crypto::hash tx_id = get_transaction_hash(tx); + LOCAL_CHECK(process_tx(tx, tx_id, -1), "process pool tx failed for tx " + crypto::pod_to_hex(tx_id)); + } + + resp.status = resp.outputs.empty() ? API_RETURN_CODE_NOT_FOUND : API_RETURN_CODE_OK; + return true; + +#undef LOCAL_CHECK_INT_ERR +#undef LOCAL_CHECK + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_aliases_by_address(const COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request& req, COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { account_public_address addr = AUTO_VAL_INIT(addr); diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 02e7b478..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,11 +89,13 @@ 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); bool on_get_alt_blocks_details(const COMMAND_RPC_GET_ALT_BLOCKS_DETAILS::request& req, COMMAND_RPC_GET_ALT_BLOCKS_DETAILS::response& res, connection_context& cntx); bool on_get_est_height_from_date(const COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::request& req, COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::response& res, connection_context& cntx); + bool on_find_outs_in_recent_blocks(const COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::request& req, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_validate_signature(const COMMAND_VALIDATE_SIGNATURE::request& req, COMMAND_VALIDATE_SIGNATURE::response& res, epee::json_rpc::error& er, connection_context& cntx); @@ -133,6 +135,8 @@ namespace currency MAP_JON_RPC_WE("get_alias_by_address", on_aliases_by_address, COMMAND_RPC_GET_ALIASES_BY_ADDRESS) MAP_JON_RPC_WE("get_alias_reward", on_get_alias_reward, COMMAND_RPC_GET_ALIAS_REWARD) MAP_JON_RPC ("get_est_height_from_date", on_get_est_height_from_date, COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE) + MAP_JON_RPC_WE("find_outs_in_recent_blocks", on_find_outs_in_recent_blocks, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS) + //block explorer api MAP_JON_RPC ("get_blocks_details", on_rpc_get_blocks_details, COMMAND_RPC_GET_BLOCKS_DETAILS) MAP_JON_RPC_WE("get_tx_details", on_get_tx_details, COMMAND_RPC_GET_TX_DETAILS) @@ -150,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 9a89d043..b5d6143d 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -23,6 +23,7 @@ #include #include #include +#include //#include "currency_core/basic_api_response_codes.h" namespace currency @@ -178,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"); @@ -289,6 +342,58 @@ namespace currency }; }; //----------------------------------------------- + struct COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS + { + DOC_COMMAND("Retrieves information about outputs in recent blocks that are targeted for the given address with the corresponding secret view key.") + + static constexpr uint64_t blocks_limit_default = 5; + + struct request + { + account_public_address address; + crypto::secret_key viewkey; + uint64_t blocks_limit = blocks_limit_default; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_ADDRESS_AS_TEXT(address) DOC_DSCR("Target address for which outputs are being searched") DOC_EXMP("ZxCSpsGGeJsS8fwvQ4HktDU3qBeauoJTR6j73jAWWZxFXdF7XTbGm4YfS2kXJmAP4Rf5BVsSQ9iZ45XANXEYsrLN2L2W77dH7") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(viewkey) DOC_DSCR("Secret view key corresponding to the given address.") DOC_EXMP("5fa8eaaf231a305053260ff91d69c6ef1ecbd0f5") DOC_END + KV_SERIALIZE(blocks_limit) DOC_DSCR("Block count limit. If 0, only the transaction pool will be searched. Maximum and default is " + epee::string_tools::num_to_string_fast(blocks_limit_default) + ".") DOC_EXMP(1711021795) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct out_entry + { + uint64_t amount; + crypto::public_key asset_id; + crypto::hash tx_id; + int64_t tx_block_height; + uint64_t output_tx_index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) DOC_DSCR("The amount of the output.") DOC_EXMP(1000000000000) DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(asset_id) DOC_DSCR("Asset ID of the output.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(tx_id) DOC_DSCR("Transaction ID where the output is present, if found.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END + KV_SERIALIZE(tx_block_height) DOC_DSCR("Block height where the transaction is present.") DOC_EXMP(2555000) DOC_END + KV_SERIALIZE(output_tx_index) DOC_DSCR("Index of the output in the transaction.") DOC_EXMP(2) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector outputs; + uint64_t blockchain_top_block_height; + uint64_t blocks_limit; + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outputs) DOC_DSCR("List of found outputs.") DOC_EXMP_AUTO(1) DOC_END + KV_SERIALIZE(blockchain_top_block_height) DOC_DSCR("Height of the most recent block in the blockchain.") DOC_EXMP(2555000) DOC_END + KV_SERIALIZE(blocks_limit) DOC_DSCR("Used limit for block count.") DOC_EXMP(5) DOC_END + KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END + END_KV_SERIALIZE_MAP() + }; + }; + //----------------------------------------------- struct COMMAND_RPC_GET_TX_POOL { DOC_COMMAND("Retreives transactions from tx pool (and other information).") @@ -583,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() + }; + }; //----------------------------------------------- @@ -619,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