From 1cbb2eca1b60b74f9d09dedb02328e64dd2f9656 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 7 Aug 2024 19:18:44 +0400 Subject: [PATCH] seed doctor implemented --- src/common/mnemonic-encoding.cpp | 4 + src/common/mnemonic-encoding.h | 1 + src/simplewallet/simplewallet.cpp | 219 ++++++++++++++++++++- src/simplewallet/simplewallet.h | 1 + src/wallet/wallet2.cpp | 4 + src/wallet/wallet_rpc_server_error_codes.h | 15 +- 6 files changed, 235 insertions(+), 9 deletions(-) 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/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index cebf832e..9d842fdf 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "include_base_utils.h" #include "common/command_line.h" #include "common/util.h" @@ -26,7 +27,7 @@ #include "string_coding.h" #include "wallet/wrap_service.h" #include "common/general_purpose_commands_defs.h" - +#include "common/mnemonic-encoding.h" #include "wallet/wallet_helpers.h" @@ -142,6 +143,7 @@ namespace const command_line::arg_descriptor arg_set_timeout("set-timeout", "Set timeout for the wallet"); const command_line::arg_descriptor arg_voting_config_file("voting-config-file", "Set voting config instead of getting if from daemon", ""); const command_line::arg_descriptor arg_no_password_confirmations("no-password-confirmation", "Enable/Disable password confirmation for transactions", false); + const command_line::arg_descriptor arg_seed_doctor("seed-doctor", "Experimental: if your seed is not working for recovery this is likely because you've made a mistake whene you were doing back up(typo, wrong words order, missing word). This experimental code will attempt to recover seed phrase from with few approaches."); const command_line::arg_descriptor< std::vector > arg_command ("command", ""); @@ -2870,6 +2872,210 @@ bool search_for_wallet_file(const std::wstring &search_here/*, const std::string return false; } + + +int seed_doctor() +{ + success_msg_writer() << + "**********************************************************************\n" << + "This is experimental tool that might help you to recover your wallet's corrupted seed phrase. \n" << + "It might help to sort out only trivial situations where one word was written wrong or \n" << + "two word was written in wrong order\n" << + "**********************************************************************"; + + + success_msg_writer() << "Please enter problematic seed phrase:"; + std::string seed; + std::getline(std::cin, seed); + + success_msg_writer() << "Please enter wallet address if you have it(press enter if you don't know it):"; + std::string address; + std::getline(std::cin, address); + address = epee::string_tools::trim(address); + + std::string passphrase; + bool check_summ_available = false; + + //cut the last timestamp word from restore_dats + std::vector words; + boost::split(words, seed, boost::is_space()); + + std::set failed_words; + size_t i = 0; + //let's validate each word + for (const auto& w : words) + { + if (!tools::mnemonic_encoding::valid_word(w)) + { + success_msg_writer() << "Word " << i << " '" << w << "' is invalid, attempting to restore it"; + failed_words.insert(i); + } + i++; + } + + if (words.size() == SEED_PHRASE_V1_WORDS_COUNT) + { + // 24 seed words + one timestamp word = 25 total + success_msg_writer() << "SEED_PHRASE_V1_WORDS_COUNT, checksum is unavailable"; + if (address.empty()) + { + success_msg_writer() << "With SEED_PHRASE_V1_WORDS_COUNT and address unknown it's not enough information to recover it, sorry"; + return EXIT_FAILURE; + } + } + else if (words.size() == SEED_PHRASE_V2_WORDS_COUNT) + { + // 24 seed words + one timestamp word + one flags & checksum = 26 total + + success_msg_writer() << "SEED_PHRASE_V2_WORDS_COUNT, checksum is available"; + check_summ_available = true; + } + else if (words.size() == 24) + { + //seed only with no date, add date to it + std::string zero_word = tools::mnemonic_encoding::word_by_num(0); //zero_word is likely to be "like" + words.push_back(zero_word); // + } + else + { + success_msg_writer() << "Impossible to recover something with " << words.size() << " words only"; + return EXIT_FAILURE; + } + + bool pass_protected = false; + bool success = account_base::is_seed_password_protected(seed, pass_protected); + success_msg_writer() << "SECURED_SEED: " << (pass_protected ? "true" : "false"); + + if (pass_protected) + { + success_msg_writer() << "Please enter seed passphrase(if passphrase is wrong then chances to correct seed recovery is nearly zero):"; + std::getline(std::cin, passphrase); + } + + size_t global_candidates_count = 0; + + auto brute_force_func = [&](size_t word_index) -> bool { + const map& all_words = tools::mnemonic_encoding::get_words_map(); + success_msg_writer() << "Brute forcing word " << word_index << " ...."; + size_t candidates_count = 0; + std::vector words_local = words; + + for (auto it = all_words.begin(); it != all_words.end(); it++) + { + words_local[word_index] = it->first; + + std::string result = boost::algorithm::join(words_local, " "); + account_base acc; + bool r = acc.restore_from_seed_phrase(result, passphrase); + if (r) + { + if (!address.empty()) + { + //check against address + if (acc.get_public_address_str() == address) + { + success_msg_writer(true) << "!!!SUCCESS!!!"; + success_msg_writer() << "Seed recovered, please write down recovered seed and use it to restore the wallet:\n" << result; + return true; + } + } + else + { + success_msg_writer() << "Potential seed candidate:\n" << result << "\nAddress: " << acc.get_public_address_str(); + candidates_count++; + } + } + } + global_candidates_count += candidates_count; + return false; + }; + + + auto swap_func = [&](size_t word_index) -> bool { + size_t candidates_count = 0; + std::vector words_local = words; + + std::string tmp = words_local[word_index]; + words_local[word_index] = words_local[word_index + 1]; + words_local[word_index + 1] = tmp; + std::string result = boost::algorithm::join(words_local, " "); + account_base acc; + bool r = acc.restore_from_seed_phrase(result, passphrase); + if (r) + { + if (!address.empty()) + { + //check against address + if (acc.get_public_address_str() == address) + { + success_msg_writer(true) << "!!!SUCCESS!!!"; + success_msg_writer() << "Seed recovered, please write down recovered seed and use it to restore the wallet:\n" << result; + return true; + } + } + else + { + success_msg_writer() << "Potential seed candidate:\n" << result << "\nAddress: " << acc.get_public_address_str(); + candidates_count++; + } + } + global_candidates_count += candidates_count; + return false; + }; + + + if (failed_words.size()) + { + if (failed_words.size() > 1) + { + success_msg_writer() << "Restoring more then 1 broken words not implemented yet, sorry"; + return EXIT_FAILURE; + } + if (!check_summ_available && address.empty()) + { + success_msg_writer() << "No address and no checksum, recovery is impossible, sorry"; + return EXIT_FAILURE; + } + + size_t broken_word_index = *failed_words.begin(); + bool r = brute_force_func(broken_word_index); + success_msg_writer() << "Brute forcing finished, " << global_candidates_count << " potential candidates found"; + return r ? EXIT_SUCCESS : EXIT_FAILURE; + } + else + { + if (!check_summ_available && address.empty()) + { + success_msg_writer() << "No address and no checksum, recovery is limited only to date reset"; + std::string result = boost::algorithm::join(words, " "); + account_base acc; + bool r = acc.restore_from_seed_phrase(result, passphrase); + success_msg_writer() << "Potential seed candidate:\n" << result << "\nAddress: " << acc.get_public_address_str(); + return EXIT_FAILURE; + } + success_msg_writer() << "Brute forcing all each word"; + for (size_t i = 0; i != words.size() && i != 24; i++) + { + bool r = brute_force_func(i); + if(r) + return EXIT_SUCCESS; + } + } + + success_msg_writer() << "Brute forcing finished, " << global_candidates_count << " potential candidates found"; + + success_msg_writer() << "Swap check..."; + + for (size_t i = 0; i != words.size() && i != 23; i++) + { + bool r = swap_func(i); + if (r) + return EXIT_SUCCESS; + } + + return EXIT_FAILURE; +} + //---------------------------------------------------------------------------------------------------- #ifdef WIN32 int wmain( int argc, wchar_t* argv_w[ ], wchar_t* envp[ ] ) @@ -2934,7 +3140,8 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_set_timeout); command_line::add_arg(desc_params, arg_voting_config_file); command_line::add_arg(desc_params, arg_no_password_confirmations); - command_line::add_arg(desc_params, command_line::arg_generate_rpc_autodoc); + command_line::add_arg(desc_params, command_line::arg_generate_rpc_autodoc); + command_line::add_arg(desc_params, arg_seed_doctor); tools::wallet_rpc_server::init_options(desc_params); @@ -3012,6 +3219,13 @@ int main(int argc, char* argv[]) bool offline_mode = command_line::get_arg(vm, arg_offline_mode); + + if (command_line::has_arg(vm, arg_seed_doctor)) + { + return seed_doctor(); + } + + if (command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port)) { // runs wallet as RPC server @@ -3222,3 +3436,4 @@ int main(int argc, char* argv[]) CATCH_ENTRY_L0(__func__, EXIT_FAILURE); return EXIT_SUCCESS; } + diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 5f260433..c2024a4b 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -194,6 +194,7 @@ namespace currency std::string m_restore_wallet; std::string m_voting_config_file; bool m_no_password_confirmations = false; + crypto::hash m_password_hash; uint64_t m_password_salt; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d70ebb57..da73a1f1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -7598,6 +7598,10 @@ bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx const currency::transaction& tx_for_mode_separate = msc.tx_for_mode_separate; assets_selection_context needed_money_map = get_needed_money(ctp.fee, ctp.dsts); + if (this->is_auditable() && ctp.fake_outputs_count > 0) + { + WLT_THROW_IF_FALSE_WITH_CODE(false, "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET", "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET"); + } ftp.ado_current_asset_owner = ctp.ado_current_asset_owner; ftp.pthirdparty_sign_handler = ctp.pthirdparty_sign_handler; // diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index d24302e0..676b64ea 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -7,10 +7,11 @@ #pragma once -#define WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR -1 -#define WALLET_RPC_ERROR_CODE_WRONG_ADDRESS -2 -#define WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY -3 -#define WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR -4 -#define WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID -5 -#define WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT -6 -#define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY -7 +#define WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR -1 +#define WALLET_RPC_ERROR_CODE_WRONG_ADDRESS -2 +#define WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY -3 +#define WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR -4 +#define WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID -5 +#define WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT -6 +#define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY -7 +#define WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET -8