Electroneum
mnemonics.cpp
Go to the documentation of this file.
1 // Copyrights(c) 2017-2021, The Electroneum Project
2 // Copyrights(c) 2014-2019, The Monero Project
3 //
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without modification, are
7 // permitted provided that the following conditions are met:
8 //
9 // 1. Redistributions of source code must retain the above copyright notice, this list of
10 // conditions and the following disclaimer.
11 //
12 // 2. Redistributions in binary form must reproduce the above copyright notice, this list
13 // of conditions and the following disclaimer in the documentation and/or other
14 // materials provided with the distribution.
15 //
16 // 3. Neither the name of the copyright holder nor the names of its contributors may be
17 // used to endorse or promote products derived from this software without specific
18 // prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
21 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
22 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
23 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
28 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "gtest/gtest.h"
31 #include "wipeable_string.h"
34 #include "crypto/crypto.h"
35 #include <stdlib.h>
36 #include <vector>
37 #include <time.h>
38 #include <iostream>
39 #include <boost/algorithm/string.hpp>
41 #include "mnemonics/english.h"
42 #include "mnemonics/spanish.h"
43 #include "mnemonics/portuguese.h"
44 #include "mnemonics/japanese.h"
45 #include "mnemonics/german.h"
46 #include "mnemonics/italian.h"
47 #include "mnemonics/russian.h"
48 #include "mnemonics/french.h"
49 #include "mnemonics/dutch.h"
50 #include "mnemonics/esperanto.h"
51 #include "mnemonics/lojban.h"
52 #include "mnemonics/english_old.h"
54 #include "mnemonics/singleton.h"
55 #include <boost/algorithm/string.hpp>
56 
57 namespace
58 {
64  void compare_vectors(const std::vector<std::string> &expected, const std::vector<std::string> &present)
65  {
66  std::vector<std::string>::const_iterator it1, it2;
67  for (it1 = expected.begin(), it2 = present.begin(); it1 != expected.end() && it2 != present.end();
68  it1++, it2++)
69  {
70  ASSERT_STREQ(it1->c_str(), it2->c_str());
71  }
72  }
73 
78  void test_language(const Language::Base &language)
79  {
80  epee::wipeable_string w_seed = "", w_return_seed = "";
81  std::string seed, return_seed;
82  // Generate a random seed without checksum
83  crypto::secret_key randkey;
84  for (size_t ii = 0; ii < sizeof(randkey); ++ii)
85  {
86  randkey.data[ii] = rand();
87  }
88  crypto::ElectrumWords::bytes_to_words(randkey, w_seed, language.get_language_name());
89  seed = std::string(w_seed.data(), w_seed.size());
90  // remove the checksum word
91  const char *space = strrchr(seed.c_str(), ' ');
92  ASSERT_TRUE(space != NULL);
93  seed = std::string(seed.c_str(), space-seed.c_str());
94 
95  std::cout << "Test seed without checksum:\n";
96  std::cout << seed << std::endl;
97 
99  std::string language_name;
100  bool res;
101  std::vector<std::string> seed_vector, return_seed_vector;
102  std::string checksum_word;
103 
104  // Convert it to secret key
105  res = crypto::ElectrumWords::words_to_bytes(seed, key, language_name);
106  ASSERT_EQ(true, res);
107  std::cout << "Detected language: " << language_name << std::endl;
108  ASSERT_STREQ(language.get_language_name().c_str(), language_name.c_str());
109 
110  // Convert the secret key back to seed
111  crypto::ElectrumWords::bytes_to_words(key, w_return_seed, language.get_language_name());
112  return_seed = std::string(w_return_seed.data(), w_return_seed.size());
113  ASSERT_EQ(true, res);
114  std::cout << "Returned seed:\n";
115  std::cout << return_seed << std::endl;
116  boost::split(seed_vector, seed, boost::is_any_of(" "));
117  boost::split(return_seed_vector, return_seed, boost::is_any_of(" "));
118 
119  // Extract the checksum word
120  checksum_word = return_seed_vector.back();
121  return_seed_vector.pop_back();
122  ASSERT_EQ(seed_vector.size(), return_seed_vector.size());
123  // Ensure that the rest of it is same
124  compare_vectors(seed_vector, return_seed_vector);
125 
126  // Append the checksum word to repeat the entire process with a seed with checksum
127  seed += (" " + checksum_word);
128  std::cout << "Test seed with checksum:\n";
129  std::cout << seed << std::endl;
130  res = crypto::ElectrumWords::words_to_bytes(seed, key, language_name);
131  ASSERT_EQ(true, res);
132  std::cout << "Detected language: " << language_name << std::endl;
133  ASSERT_STREQ(language.get_language_name().c_str(), language_name.c_str());
134 
135  w_return_seed = "";
136  crypto::ElectrumWords::bytes_to_words(key, w_return_seed, language.get_language_name());
137  return_seed = std::string(w_return_seed.data(), w_return_seed.size());
138  ASSERT_EQ(true, res);
139  std::cout << "Returned seed:\n";
140  std::cout << return_seed << std::endl;
141 
142  seed_vector.clear();
143  return_seed_vector.clear();
144  boost::split(seed_vector, seed, boost::is_any_of(" "));
145  boost::split(return_seed_vector, return_seed, boost::is_any_of(" "));
146  ASSERT_EQ(seed_vector.size(), return_seed_vector.size());
147  compare_vectors(seed_vector, return_seed_vector);
148  }
149 }
150 
151 TEST(mnemonics, consistency)
152 {
153  try {
154  std::vector<std::string> language_list;
156  }
157  catch(const std::exception &e)
158  {
159  std::cout << "Error initializing mnemonics: " << e.what() << std::endl;
160  ASSERT_TRUE(false);
161  }
162 }
163 
164 TEST(mnemonics, all_languages)
165 {
166  srand(time(NULL));
167  std::vector<Language::Base*> languages({
180  });
181 
182  for (std::vector<Language::Base*>::iterator it = languages.begin(); it != languages.end(); it++)
183  {
184  try {
185  test_language(*(*it));
186  }
187  catch (const std::exception &e) {
188  std::cout << "Error testing " << (*it)->get_language_name() << " language: " << e.what() << std::endl;
189  ASSERT_TRUE(false);
190  }
191  }
192 }
193 
194 TEST(mnemonics, language_detection_with_bad_checksum)
195 {
197  std::string language_name;
198  bool res;
199 
200  // This Portuguese (4-prefix) seed has all its words with 3-prefix that's also present in English
201  const std::string base_seed = "cinzento luxuriante leonardo gnostico digressao cupula fifa broxar iniquo louvor ovario dorsal ideologo besuntar decurso rosto susto lemure unheiro pagodeiro nitroglicerina eclusa mazurca bigorna";
202  const std::string real_checksum = "gnostico";
203 
204  res = crypto::ElectrumWords::words_to_bytes(base_seed, key, language_name);
205  ASSERT_EQ(true, res);
206  ASSERT_STREQ(language_name.c_str(), "Português");
207 
208  res = crypto::ElectrumWords::words_to_bytes(base_seed + " " + real_checksum, key, language_name);
209  ASSERT_EQ(true, res);
210  ASSERT_STREQ(language_name.c_str(), "Português");
211 }
212 
213 TEST(mnemonics, utf8prefix)
214 {
225 }
226 
227 TEST(mnemonics, case_tolerance)
228 {
229  bool res;
230  //
231  crypto::secret_key key_1;
232  std::string language_name_1;
233  const std::string seed_1 = "Neubau umarmen Abart umarmen Turban feilen Brett Bargeld Episode Milchkuh Substanz Jahr Armband Maibaum Tand Grünalge Tabak erziehen Federboa Lobrede Tenor Leuchter Curry Diskurs Tenor";
234  res = crypto::ElectrumWords::words_to_bytes(seed_1, key_1, language_name_1);
235  ASSERT_EQ(true, res);
236  ASSERT_STREQ(language_name_1.c_str(), "Deutsch");
237  //
238  crypto::secret_key key_2;
239  std::string language_name_2;
240  // neubau is capitalized in the word list, but the language detection code should be able to detect it as Deutsch
241  std::string seed_2 = "neubau umarmen Abart umarmen Turban feilen Brett Bargeld Episode Milchkuh Substanz Jahr Armband Maibaum Tand Grünalge Tabak erziehen Federboa Lobrede Tenor Leuchter Curry Diskurs tenor";
242  boost::algorithm::to_lower(seed_2);
243  res = crypto::ElectrumWords::words_to_bytes(seed_2, key_2, language_name_2);
244  ASSERT_EQ(true, res);
245  ASSERT_STREQ(language_name_2.c_str(), "Deutsch");
246  //
247  ASSERT_TRUE(key_1 == key_2);
248 }
249 
250 TEST(mnemonics, partial_word_tolerance)
251 {
252  bool res;
253  //
254  crypto::secret_key key_1;
255  std::string language_name_1;
256  const std::string seed_1 = "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";
257  res = crypto::ElectrumWords::words_to_bytes(seed_1, key_1, language_name_1);
258  ASSERT_EQ(true, res);
259  ASSERT_STREQ(language_name_1.c_str(), "English");
260 }
const char * res
Definition: hmac_keccak.cpp:41
Portuguese word list and map.
A singleton helper class based on template.
French word list and map.
const std::string & get_language_name() const
Returns the name of the language.
size_t size() const noexcept
std::vector< const Language::Base * > get_language_list()
New Dutch word list and map.
::std::string string
Definition: gtest-port.h:1097
const char * key
Definition: hmac_keccak.cpp:39
static T * instance()
Definition: singleton.h:57
bool words_to_bytes(const epee::wipeable_string &words, epee::wipeable_string &dst, size_t len, bool duplicate, std::string &language_name)
Converts seed words to bytes (secret key).
A base language class which all languages have to inherit from for Polymorphism.
Language Base class for Polymorphism.
#define ASSERT_EQ(val1, val2)
Definition: gtest.h:1956
Russian word list and map.
New Esperanto word list and map.
time_t time
Definition: blockchain.cpp:93
void rand(size_t N, uint8_t *bytes)
Definition: crypto.h:209
bool bytes_to_words(const char *src, size_t len, epee::wipeable_string &words, const std::string &language_name)
Converts bytes (secret key) to seed words.
Italian word list and map.
German word list and map.
Japanese word list and map.
Older version of English word list and map.
Spanish word list and map.
TEST(mnemonics, consistency)
Definition: mnemonics.cpp:151
#define ASSERT_STREQ(s1, s2)
Definition: gtest.h:2004
#define ASSERT_TRUE(condition)
Definition: gtest.h:1865
New English word list and map.
New Lojban word list and map.
Mnemonic seed generation and wallet restoration from them.
const char * data() const noexcept
Simplified Chinese word list and map.
T utf8prefix(const T &s, size_t count)
Returns a string made of (at most) the first count characters in s. Assumes well formedness. No check is made for this.
Definition: language_base.h:60