diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 37701cd7..cfdfb866 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -294,6 +294,7 @@ namespace crypto { } void crypto_ops::generate_key_image(const public_key &pub, const secret_key &sec, key_image &image) { + // image = sec * 8 * ge_fromfe_frombytes_vartime(cn_fast_hash(pub)) = sec * Hp( pub ) ge_p3 point; ge_p2 point2; crypto_assert(sc_check(&sec) == 0); diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 8eb0412d..dbe4d82a 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -736,14 +736,15 @@ namespace currency //------------------------------------------------------------------ bool derive_ephemeral_key_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral) { + // TODO: re-implement this to avoid double Hs calculation -- sowle crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); - bool r = crypto::generate_key_derivation(tx_public_key, ack.view_secret_key, recv_derivation); + bool r = crypto::generate_key_derivation(tx_public_key, ack.view_secret_key, recv_derivation); // recv_derivation = 8 * ack.view_secret_key * tx_public_key CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.view_secret_key << ")"); - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.account_address.spend_public_key, in_ephemeral.pub); + r = crypto::derive_public_key(recv_derivation, real_output_index, ack.account_address.spend_public_key, in_ephemeral.pub); // = Hs(recv_derivation, real_output_index) * G + spend_public_key CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.account_address.spend_public_key << ")"); - crypto::derive_secret_key(recv_derivation, real_output_index, ack.spend_secret_key, in_ephemeral.sec); + crypto::derive_secret_key(recv_derivation, real_output_index, ack.spend_secret_key, in_ephemeral.sec); // = Hs(recv_derivation, real_output_index) + spend_secret_key return true; } //--------------------------------------------------------------- @@ -764,6 +765,8 @@ namespace currency //--------------------------------------------------------------- bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) { + // h = Hs(8 * ack.view_secret_key * tx_public_key, real_output_index) + // ki = sec * Hp( pub ) = (h + spend_secret_key) * Hp( h * G + spend_public_key ) bool r = derive_ephemeral_key_helper(ack, tx_public_key, real_output_index, in_ephemeral); CHECK_AND_ASSERT_MES(r, false, "Failed to call derive_ephemeral_key_helper(...)"); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 656856cb..b1c6ec19 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024 Zano Project +// Copyright (c) 2014-2025 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 @@ -96,22 +96,18 @@ namespace ph = boost::placeholders; } \ catch (const tools::error::transfer_error& e) \ { \ - LOG_ERROR("unknown transfer error: " << e.to_string()); \ - fail_msg_writer() << "unknown transfer error: " << e.what(); \ + fail_msg_writer() << "(transfer) " << e.what(); \ } \ catch (const tools::error::wallet_internal_error& e) \ { \ - LOG_ERROR("internal error: " << e.to_string()); \ - fail_msg_writer() << "internal error: " << e.what(); \ + fail_msg_writer() << "(internal) " << e.what(); \ } \ catch (const std::exception& e) \ { \ - LOG_ERROR("unexpected error: " << e.what()); \ - fail_msg_writer() << "unexpected error: " << e.what(); \ + fail_msg_writer() << "(unexpected) " << e.what(); \ } \ catch (...) \ { \ - LOG_ERROR("Unknown error"); \ fail_msg_writer() << "unknown error"; \ } \ @@ -521,7 +517,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) else { bool r = open_wallet(m_wallet_file, pwd_container.password()); - CHECK_AND_ASSERT_MES(r, false, "could not open account"); + CHECK_AND_ASSERT_MES(r, false, "wallet could not be opened"); was_open = true; } process_wallet_command_line_params(vm, *m_wallet, false); @@ -714,6 +710,10 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa if (!m_voting_config_file.empty()) m_wallet->set_votes_config_path(m_voting_config_file); + auto print_wallet_opened_msg = [&](){ + message_writer(epee::log_space::console_color_white, true) << "Opened" << (m_wallet->is_auditable() ? " auditable" : "") << (m_wallet->is_watch_only() ? " watch-only" : "") << " wallet: " << m_wallet->get_account().get_public_address_str(); + }; + while (true) { @@ -728,7 +728,7 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa } catch (const tools::error::wallet_load_notice_wallet_restored& /*e*/) { - message_writer(epee::log_space::console_color_white, true) << "Opened wallet: " << m_wallet->get_account().get_public_address_str(); + print_wallet_opened_msg(); message_writer(epee::log_space::console_color_red, true) << "NOTICE: Wallet file was damaged and restored."; break; } @@ -1797,6 +1797,12 @@ bool simple_wallet::print_address(const std::vector &args/* = std:: //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_seed(const std::vector &args) { + if (m_wallet->is_watch_only()) + { + fail_msg_writer() << "watch-only wallet doesn't have the full set of keys, hence no seed phrase can be generated"; + return false; + } + CONFIRM_WITH_PASSWORD(); success_msg_writer() << "Please enter a password to secure this seed. Securing your seed is HIGHLY recommended. Leave password blank to stay unsecured."; success_msg_writer(true) << "Remember, restoring a wallet from Secured Seed can only be done if you know its password."; @@ -1984,12 +1990,10 @@ bool simple_wallet::save_watch_only(const std::vector &args) } catch (const std::exception& e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << "unexpected error: " << e.what(); + fail_msg_writer() << e.what(); } catch (...) { - LOG_ERROR("Unknown error"); fail_msg_writer() << "unknown error"; } return true; @@ -2053,12 +2057,10 @@ bool simple_wallet::sign_transfer(const std::vector &args) } catch (const std::exception& e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << "unexpected error: " << e.what(); + fail_msg_writer() << e.what(); } catch (...) { - LOG_ERROR("Unknown error"); fail_msg_writer() << "unknown error"; } return true; @@ -2079,12 +2081,10 @@ bool simple_wallet::submit_transfer(const std::vector &args) } catch (const std::exception& e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << "unexpected error: " << e.what(); + fail_msg_writer() << e.what(); } catch (...) { - LOG_ERROR("Unknown error"); fail_msg_writer() << "unknown error"; } return true; @@ -3270,7 +3270,7 @@ int main(int argc, char* argv[]) } else if (command_line::get_arg(vm, command_line::arg_version)) { - success_msg_writer() << CURRENCY_NAME << " wallet v" << PROJECT_VERSION_LONG; + success_msg_writer() << CURRENCY_NAME << " simplewallet v" << PROJECT_VERSION_LONG; exit_requested = true; return true; } @@ -3296,6 +3296,7 @@ int main(int argc, char* argv[]) std::string log_dir; log_dir = log_file_path.has_parent_path() ? log_file_path.parent_path().string() : log_space::log_singletone::get_default_log_folder(); log_space::log_singletone::add_logger(LOGGER_FILE, log_file_path.filename().string().c_str(), log_dir.c_str(), LOG_LEVEL_4); + LOG_PRINT_L0(ENDL << ENDL); message_writer(epee::log_space::console_color_white, true, std::string(), LOG_LEVEL_0) << CURRENCY_NAME << " simplewallet v" << PROJECT_VERSION_LONG; if (command_line::has_arg(vm, command_line::arg_log_level)) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f777d86d..6e6b939f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -757,7 +757,7 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t { // normal wallet, calculate and store key images for own outs currency::keypair in_ephemeral = AUTO_VAL_INIT(in_ephemeral); - currency::generate_key_image_helper(m_account.get_keys(), ptc.tx_pub_key, o, in_ephemeral, ki); + currency::generate_key_image_helper(m_account.get_keys(), ptc.tx_pub_key, /* output index */ o, in_ephemeral, ki); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == out_key, "key_image generated ephemeral public key that does not match with output_key"); } @@ -4322,29 +4322,25 @@ void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& sig // assumed to be called from normal, non-watch-only wallet THROW_IF_FALSE_WALLET_EX(!m_watch_only, error::wallet_common_error, "watch-only wallet is unable to sign transfers, you need to use normal wallet for that"); - // decrypt the blob - std::string decrypted_src_blob = crypto::chacha_crypt(tx_sources_blob, m_account.get_keys().view_secret_key); + // decrypt the blob + std::string decrypted_src_blob = crypto::chacha_crypt(tx_sources_blob, m_account.get_keys().view_secret_key); // deserialize args - currency::finalized_tx ft = AUTO_VAL_INIT(ft); + currency::finalized_tx ft{}; bool r = t_unserializable_object_from_blob(ft.ftp, decrypted_src_blob); THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt tx sources blob"); // make sure unsigned tx was created with the same keys THROW_IF_FALSE_WALLET_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, error::wallet_common_error, "The was created in a different wallet, keys missmatch"); - finalize_transaction(ft.ftp, ft.tx, ft.one_time_key, false); + finalize_transaction(ft.ftp, ft, false, true); + + WLT_LOG_L0("sign_transfer: tx " << ft.tx_id << " has been successfully signed"); // calculate key images for each change output - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX( - crypto::generate_key_derivation( - m_account.get_keys().account_address.view_public_key, - ft.one_time_key, - derivation), - "internal error: sign_transfer: failed to generate key derivation(" - << m_account.get_keys().account_address.view_public_key - << ", view secret key: " << ft.one_time_key << ")"); + crypto::key_derivation derivation{}; + r = crypto::generate_key_derivation(m_account.get_keys().account_address.view_public_key, ft.one_time_key, derivation); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_transfer: generate_key_derivation failed, tx: " << ft.tx_id); for (size_t i = 0; i < ft.tx.vout.size(); ++i) { @@ -4354,7 +4350,7 @@ void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& sig crypto::public_key ephemeral_pub{}; if (!crypto::derive_public_key(derivation, i, m_account.get_keys().account_address.spend_public_key, ephemeral_pub)) { - WLT_LOG_ERROR("derive_public_key failed for tx " << get_transaction_hash(ft.tx) << ", out # " << i); + WLT_LOG_ERROR("derive_public_key failed for tx " << ft.tx_id << ", out # " << i); } if (out_pk == ephemeral_pub) @@ -4367,6 +4363,7 @@ void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& sig crypto::generate_key_image(ephemeral_pub, ephemeral_sec, ki); ft.outs_key_images.push_back(make_serializable_pair(static_cast(i), ki)); + WLT_LOG_L1("sign_transfer: tx " << ft.tx_id << ", out index: " << i << ", ki: " << ki); } } @@ -4495,6 +4492,9 @@ bool wallet2::attach_asset_descriptor(const wallet_public::COMMAND_ATTACH_ASSET_ //---------------------------------------------------------------------------------------------------- void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) { + // assumed to be called from watch-only wallet + THROW_IF_FALSE_WALLET_EX(m_watch_only, error::wallet_common_error, "submit_transfer should be called in watch-only wallet only"); + // decrypt sources std::string decrypted_src_blob = crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); @@ -4505,9 +4505,36 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans tx = ft.tx; crypto::hash tx_hash = get_transaction_hash(tx); - // foolproof + // foolproof check THROW_IF_FALSE_WALLET_CMN_ERR_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, "The given tx was created in a different wallet, keys missmatch, tx hash: " << tx_hash); + // prepare and check data for watch-only outkey2ki before sending the tx + std::vector> pk_ki_to_be_added; + std::vector> tri_ki_to_be_added; + if (m_watch_only) + { + for (auto& p : ft.outs_key_images) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); + std::list stub{}; + const crypto::public_key& pk = out_get_pub_key(tx.vout[p.first], stub); + pk_ki_to_be_added.push_back(std::make_pair(pk, p.second)); + } + + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); + for (size_t i = 0; i < tx.vin.size(); ++i) + { + const crypto::key_image& ki = get_key_image_from_txin_v(tx.vin[i]); + const auto& src = ft.ftp.sources[i]; + THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); + const crypto::public_key& out_key = src.outputs[src.real_output].stealth_address; + tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); + pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); + } + } + + // SEND the transaction + try { send_transaction_to_network(tx); @@ -4523,38 +4550,16 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans add_sent_tx_detailed_info(tx, ft.ftp.attachments, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); + // populate and store key images from own outputs, because otherwise a watch-only wallet cannot calculate it if (m_watch_only) { - std::vector> pk_ki_to_be_added; - std::vector> tri_ki_to_be_added; - - for (auto& p : ft.outs_key_images) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); - std::list stub{}; - const crypto::public_key& pk = out_get_pub_key(tx.vout[p.first], stub); - pk_ki_to_be_added.push_back(std::make_pair(pk, p.second)); - } - - THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); - for (size_t i = 0; i < tx.vin.size(); ++i) - { - const crypto::key_image& ki = get_key_image_from_txin_v(tx.vin[i]); - - const auto& src = ft.ftp.sources[i]; - THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); - const crypto::public_key& out_key = src.outputs[src.real_output].stealth_address; - - tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); - pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); - } - for (auto& p : pk_ki_to_be_added) { auto it = m_pending_key_images.find(p.first); if (it != m_pending_key_images.end()) { - LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); + if (it->second != p.second) + LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); } else { diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 7479159b..12581bc5 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -71,10 +71,10 @@ namespace tools std::string to_string() const { std::ostringstream ss; - ss << m_loc << '[' << boost::replace_all_copy(std::string(typeid(*this).name()), "struct ", ""); + ss << '[' << boost::replace_all_copy(std::string(typeid(*this).name()), "struct ", ""); if (!m_error_code.empty()) ss << "[" << m_error_code << "]"; - ss << "] " << Base::what(); + ss << "] " << m_loc << ENDL << " " << Base::what(); return ss.str(); }