diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 171dd750c..690578651 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -70,6 +70,7 @@ namespace crypto namespace ElectrumWords { std::vector get_language_list(); + std::vector get_language_list_2(); } } @@ -89,31 +90,16 @@ namespace * \return true if all the words were present in some language false if not. */ bool find_seed_language(const std::vector &seed, - bool has_checksum, std::vector &matched_indices, Language::Base **language) + bool has_checksum, std::vector &matched_indices, const Language::Base **language) { - // If there's a new language added, add an instance of it here. - std::vector language_instances({ - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance(), - Language::Singleton::instance() - }); - Language::Base *fallback = NULL; + std::vector language_instances = crypto::ElectrumWords::get_language_list_2(); + const Language::Base *fallback = NULL; std::vector::const_iterator it2; matched_indices.reserve(seed.size()); // Iterate through all the languages and find a match - for (std::vector::iterator it1 = language_instances.begin(); + for (std::vector::const_iterator it1 = language_instances.begin(); it1 != language_instances.end(); it1++) { const std::unordered_map &word_map = (*it1)->get_word_map(); @@ -292,7 +278,7 @@ namespace crypto std::vector matched_indices; auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(matched_indices.data(), matched_indices.size() * sizeof(matched_indices[0]));}); - Language::Base *language; + const Language::Base *language; if (!find_seed_language(seed, has_checksum, matched_indices, &language)) { MERROR("Invalid seed: language not found"); @@ -371,6 +357,43 @@ namespace crypto return true; } + std::string get_invalid_word(const epee::wipeable_string &words) + { + std::vector language_instances = crypto::ElectrumWords::get_language_list_2(); + std::vector seed; + words.split(seed); + + for ( + std::vector::const_iterator seed_i = seed.begin(); + seed_i != seed.end(); + seed_i++ + ) + { + bool has_any = false; + + for ( + std::vector::const_iterator lang_i = language_instances.begin(); + lang_i != language_instances.end() && !has_any; + lang_i++ + ) + { + auto &word_map = (*lang_i)->get_word_map(); + + if (word_map.count(*seed_i) != 0) + { + has_any = true; + break; + } + } + + if (!has_any) { + return std::string(seed_i->data(), seed_i->size()); + } + } + + return ""; + } + /*! * \brief Converts bytes (secret key) to seed words. * \param src Secret key @@ -453,6 +476,26 @@ namespace crypto return language_instances; } + std::vector get_language_list_2() + { + static const std::vector language_instances_2({ + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance(), + Language::Singleton::instance() + }); + return language_instances_2; + } + /*! * \brief Gets a list of seed languages that are supported. * \param languages The vector is set to the list of languages. diff --git a/src/mnemonics/electrum-words.h b/src/mnemonics/electrum-words.h index 374ebef57..8231ffe5d 100644 --- a/src/mnemonics/electrum-words.h +++ b/src/mnemonics/electrum-words.h @@ -83,6 +83,8 @@ namespace crypto bool words_to_bytes(const epee::wipeable_string &words, crypto::secret_key& dst, std::string &language_name); + std::string get_invalid_word(const epee::wipeable_string &words); + /*! * \brief Converts bytes to seed words. * \param src Secret data diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index c8257919d..150cd33ae 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -749,7 +749,12 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c crypto::secret_key recovery_key; std::string old_language; if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { - setStatusError(tr("Electrum-style word list failed verification")); + std::string invalid_word = crypto::ElectrumWords::get_invalid_word(seed); + if (invalid_word != "") { + setStatusError((boost::format(tr("Electrum-style word list failed verification: %s")) % ("'" + invalid_word + "'")).str()); + } else { + setStatusError(tr("Electrum-style word list failed verification")); + } return false; } if (!seed_offset.empty()) diff --git a/tests/unit_tests/mnemonics.cpp b/tests/unit_tests/mnemonics.cpp index 8e8886f55..60e67cae4 100644 --- a/tests/unit_tests/mnemonics.cpp +++ b/tests/unit_tests/mnemonics.cpp @@ -255,3 +255,54 @@ TEST(mnemonics, partial_word_tolerance) ASSERT_EQ(true, res); ASSERT_STREQ(language_name_1.c_str(), "English"); } + +TEST(mnemonics, get_invalid_word) +{ + ASSERT_TRUE( + crypto::ElectrumWords::get_invalid_word( + "crim bam scamp gna limi woma wron tuit birth mundane donuts square cohesive dolphin titans narrate fue saved wrap aloof magic mirr toget upda wra" + ) == "crim" + ); + + ASSERT_TRUE( + crypto::ElectrumWords::get_invalid_word( + "criminal bamboo scamper gnaw limits womanly wrong tuition birth mundane donuts square cohesive dolphin titans narrate fue saved wrap aloof magically mirror together update wrap" + ) == "fue" + ); + + ASSERT_TRUE( + crypto::ElectrumWords::get_invalid_word( + "a a a a a a a a a a a a a a a a a a a a a a a a a" + ) == "a" + ); + + ASSERT_TRUE( + crypto::ElectrumWords::get_invalid_word( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandonnn abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon" + ) == "abandonnn" + ); + + ASSERT_TRUE( + crypto::ElectrumWords::get_invalid_word( + "abando abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandonnn abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon" + ) == "abando" + ); + + ASSERT_TRUE( + crypto::ElectrumWords::get_invalid_word( + "criminal bamboo scamper gnaw limits womanly wrong tuition birth mundane donuts square cohesive dolphin titans narrate fuel saved wrap aloof magically mirror together update wrap" + ) == "" + ); + + ASSERT_TRUE( + crypto::ElectrumWords::get_invalid_word( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon" + ) == "" + ); + + ASSERT_TRUE( + crypto::ElectrumWords::get_invalid_word( + "ㇴ" + ) == "ㇴ" + ); +}