diff options
Diffstat (limited to 'src/mnemonics/electrum-words.cpp')
-rw-r--r-- | src/mnemonics/electrum-words.cpp | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp new file mode 100644 index 000000000..d1fae84cb --- /dev/null +++ b/src/mnemonics/electrum-words.cpp @@ -0,0 +1,347 @@ +// Copyright (c) 2014, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/*! + * \file electrum-words.cpp + * + * \brief Mnemonic seed generation and wallet restoration from them. + * + * This file and its header file are for translating Electrum-style word lists + * into their equivalent byte representations for cross-compatibility with + * that method of "backing up" one's wallet keys. + */ + +#include <string> +#include <cassert> +#include <map> +#include <cstdint> +#include <vector> +#include <boost/algorithm/string.hpp> +#include "crypto/crypto.h" // for declaration of crypto::secret_key +#include <fstream> +#include "mnemonics/electrum-words.h" +#include <stdexcept> +#include <boost/filesystem.hpp> +#include <boost/crc.hpp> +#include <boost/algorithm/string/join.hpp> + +namespace +{ + int num_words = 0; + const int seed_length = 24; + + std::map<std::string,uint32_t> words_map; + std::vector<std::string> words_array; + + bool is_old_style_word_list = false; + + const std::string WORD_LISTS_DIRECTORY = "wordlists"; + const std::string LANGUAGES_DIRECTORY = "languages"; + const std::string OLD_WORD_FILE = "old-word-list"; + + /*! + * \brief Tells if the module hasn't been initialized with a word list file. + * \return true if the module hasn't been initialized with a word list file false otherwise. + */ + bool is_uninitialized() + { + return num_words == 0 ? true : false; + } + + /*! + * \brief Create word list map and array data structres for use during inter-conversion between + * words and secret key. + * \param word_file Path to the word list file from pwd. + */ + void create_data_structures(const std::string &word_file) + { + words_array.clear(); + words_map.clear(); + num_words = 0; + std::ifstream input_stream; + input_stream.open(word_file.c_str(), std::ifstream::in); + + if (!input_stream) + throw std::runtime_error("Word list file couldn't be opened."); + + std::string word; + while (input_stream >> word) + { + words_array.push_back(word); + words_map[word] = num_words; + num_words++; + } + input_stream.close(); + } + + /*! + * \brief Tells if all the words passed in wlist was present in current word list file. + * \param wlist List of words to match. + * \return true if all the words were present false if not. + */ + bool word_list_file_match(const std::vector<std::string> &wlist) + { + for (std::vector<std::string>::const_iterator it = wlist.begin(); it != wlist.end(); it++) + { + if (words_map.count(*it) == 0) + { + return false; + } + } + return true; + } + + /*! + * \brief Creates a checksum index in the word list array on the list of words. + * \param words Space separated list of words + * \return Checksum index + */ + uint32_t create_checksum_index(const std::string &words) + { + boost::crc_32_type result; + result.process_bytes(words.data(), words.length()); + return result.checksum() % seed_length; + } +} + +/*! + * \namespace crypto + * + * \brief crypto namespace. + */ +namespace crypto +{ + /*! + * \namespace crypto::ElectrumWords + * + * \brief Mnemonic seed word generation and wallet restoration helper functions. + */ + namespace ElectrumWords + { + /*! + * \brief Called to initialize it work with a word list file. + * \param language Language of the word list file. + * \param old_word_list true it is to use the old style word list file false if not. + */ + void init(const std::string &language, bool old_word_list) + { + if (old_word_list) + { + // Use the old word list file if told to. + create_data_structures(WORD_LISTS_DIRECTORY + '/' + OLD_WORD_FILE); + is_old_style_word_list = true; + } + else + { + create_data_structures(WORD_LISTS_DIRECTORY + '/' + LANGUAGES_DIRECTORY + '/' + language); + is_old_style_word_list = false; + } + if (num_words == 0) + { + throw std::runtime_error(std::string("Word list file is empty: ") + + (old_word_list ? OLD_WORD_FILE : (LANGUAGES_DIRECTORY + '/' + language))); + } + } + + /*! + * \brief Converts seed words to bytes (secret key). + * \param words String containing the words separated by spaces. + * \param dst To put the secret key restored from the words. + * \return false if not a multiple of 3 words, or if word is not in the words list + */ + bool words_to_bytes(const std::string& words, crypto::secret_key& dst) + { + std::vector<std::string> wlist; + + boost::split(wlist, words, boost::is_any_of(" ")); + + // If it is seed with a checksum. + if (wlist.size() == seed_length + 1) + { + // The last word is the checksum. + std::string last_word = wlist.back(); + wlist.pop_back(); + + std::string checksum = wlist[create_checksum_index(boost::algorithm::join(wlist, " "))]; + + if (checksum != last_word) + { + // Checksum fail + return false; + } + } + // Try to find a word list file that contains all the words in the word list. + std::vector<std::string> languages; + get_language_list(languages); + + std::vector<std::string>::iterator it; + for (it = languages.begin(); it != languages.end(); it++) + { + init(*it); + if (word_list_file_match(wlist)) + { + break; + } + } + // If no such file was found, see if the old style word list file has them all. + if (it == languages.end()) + { + init("", true); + if (!word_list_file_match(wlist)) + { + return false; + } + } + int n = num_words; + + // error on non-compliant word list + if (wlist.size() != 12 && wlist.size() != 24) return false; + + for (unsigned int i=0; i < wlist.size() / 3; i++) + { + uint32_t val; + uint32_t w1, w2, w3; + + w1 = words_map.at(wlist[i*3]); + w2 = words_map.at(wlist[i*3 + 1]); + w3 = words_map.at(wlist[i*3 + 2]); + + val = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n); + + if (!(val % n == w1)) return false; + + memcpy(dst.data + i * 4, &val, 4); // copy 4 bytes to position + } + + std::string wlist_copy = words; + if (wlist.size() == 12) + { + memcpy(dst.data, dst.data + 16, 16); // if electrum 12-word seed, duplicate + wlist_copy += ' '; + wlist_copy += words; + } + + return true; + } + + /*! + * \brief Converts bytes (secret key) to seed words. + * \param src Secret key + * \param words Space delimited concatenated words get written here. + * \return true if successful false if not. Unsuccessful if wrong key size. + */ + bool bytes_to_words(const crypto::secret_key& src, std::string& words) + { + if (is_uninitialized()) + { + init("english", true); + } + + // To store the words for random access to add the checksum word later. + std::vector<std::string> words_store; + int n = num_words; + + if (sizeof(src.data) % 4 != 0 || sizeof(src.data) == 0) return false; + + // 8 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 + for (unsigned int i=0; i < sizeof(src.data)/4; i++, words += ' ') + { + uint32_t w1, w2, w3; + + uint32_t val; + + memcpy(&val, (src.data) + (i * 4), 4); + + w1 = val % n; + w2 = ((val / n) + w1) % n; + w3 = (((val / n) / n) + w2) % n; + + words += words_array[w1]; + words += ' '; + words += words_array[w2]; + words += ' '; + words += words_array[w3]; + + words_store.push_back(words_array[w1]); + words_store.push_back(words_array[w2]); + words_store.push_back(words_array[w3]); + } + + words.pop_back(); + words += (' ' + words_store[create_checksum_index(words)]); + return false; + } + + /*! + * \brief Gets a list of seed languages that are supported. + * \param languages The vector is set to the list of languages. + */ + void get_language_list(std::vector<std::string> &languages) + { + languages.clear(); + boost::filesystem::path languages_directory("wordlists/languages"); + if (!boost::filesystem::exists(languages_directory) || + !boost::filesystem::is_directory(languages_directory)) + { + throw std::runtime_error("Word list languages directory is missing."); + } + boost::filesystem::directory_iterator end; + for (boost::filesystem::directory_iterator it(languages_directory); it != end; it++) + { + languages.push_back(it->path().filename().string()); + } + } + + /*! + * \brief Tells if the module is currenly using an old style word list. + * \return true if it is currenly using an old style word list false if not. + */ + bool get_is_old_style_word_list() + { + if (is_uninitialized()) + { + throw std::runtime_error("ElectrumWords hasn't been initialized with a word list yet."); + } + return is_old_style_word_list; + } + + /*! + * \brief Tells if the seed passed is an old style seed or not. + * \param seed The seed to check (a space delimited concatenated word list) + * \return true if the seed passed is a old style seed false if not. + */ + bool get_is_old_style_seed(const std::string &seed) + { + std::vector<std::string> wlist; + boost::split(wlist, seed, boost::is_any_of(" ")); + return wlist.size() != (seed_length + 1); + } + + } + +} |