From 5cbcf0aa2532f7123691c5ca414a4808fafbcf87 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 9 Nov 2017 10:59:41 +0000 Subject: [PATCH] wallet: support for multisig seeds They are hex rather than words, because they are a lot longer than "normal" seeds, as they have to embed a lot more information --- src/simplewallet/simplewallet.cpp | 192 +++++++++++++++++++++++------- src/simplewallet/simplewallet.h | 3 + src/wallet/wallet2.cpp | 155 ++++++++++++++++++++++++ src/wallet/wallet2.h | 10 ++ src/wallet/wallet_errors.h | 11 ++ 5 files changed, 326 insertions(+), 45 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 64e665fb3..e27d58d15 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -121,6 +121,7 @@ namespace const command_line::arg_descriptor arg_mnemonic_language = {"mnemonic-language", sw::tr("Language for mnemonic"), ""}; const command_line::arg_descriptor arg_electrum_seed = {"electrum-seed", sw::tr("Specify Electrum seed for wallet recovery/creation"), ""}; const command_line::arg_descriptor arg_restore_deterministic_wallet = {"restore-deterministic-wallet", sw::tr("Recover wallet using Electrum-style mnemonic seed"), false}; + const command_line::arg_descriptor arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor arg_non_deterministic = {"non-deterministic", sw::tr("Create non-deterministic view and spend keys"), false}; const command_line::arg_descriptor arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; @@ -516,48 +517,55 @@ bool simple_wallet::spendkey(const std::vector &args/* = std::vecto bool simple_wallet::print_seed(bool encrypted) { bool success = false; - std::string electrum_words; + std::string seed; + bool ready, multisig; - if (m_wallet->multisig()) - { - fail_msg_writer() << tr("wallet is multisig and has no seed"); - return true; - } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - if (m_wallet->is_deterministic()) - { - if (m_wallet->get_seed_language().empty()) - { - std::string mnemonic_language = get_mnemonic_language(); - if (mnemonic_language.empty()) - return true; - m_wallet->set_seed_language(mnemonic_language); - } - epee::wipeable_string seed_pass; - if (encrypted) + multisig = m_wallet->multisig(&ready); + if (multisig) + { + if (!ready) { - auto pwd_container = tools::password_container::prompt(true, tr("Enter optional seed encryption passphrase, empty to see raw seed")); - if (std::cin.eof() || !pwd_container) - return true; - seed_pass = pwd_container->password(); + fail_msg_writer() << tr("wallet is multisig but not yet finalized"); + return true; } + } + else if (!m_wallet->is_deterministic()) + { + fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); + return true; + } - success = m_wallet->get_seed(electrum_words, seed_pass); + epee::wipeable_string seed_pass; + if (encrypted) + { +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + auto pwd_container = tools::password_container::prompt(true, tr("Enter optional seed encryption passphrase, empty to see raw seed")); + if (std::cin.eof() || !pwd_container) + return true; + seed_pass = pwd_container->password(); } + if (multisig) + success = m_wallet->get_multisig_seed(seed, seed_pass); + else if (m_wallet->is_deterministic()) + success = m_wallet->get_seed(seed, seed_pass); + if (success) { - print_seed(electrum_words); + print_seed(seed); } else { - fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); + fail_msg_writer() << tr("Failed to retrieve seed"); } return true; } @@ -1972,6 +1980,8 @@ static bool might_be_partial_seed(std::string words) //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { + std::string multisig_keys; + if (!handle_command_line(vm)) return false; @@ -1989,49 +1999,91 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { std::string old_language; // check for recover flag. if present, require electrum word list (only recovery option for now). - if (m_restore_deterministic_wallet) + if (m_restore_deterministic_wallet || m_restore_multisig_wallet) { if (m_non_deterministic) { - fail_msg_writer() << tr("can't specify both --restore-deterministic-wallet and --non-deterministic"); + fail_msg_writer() << tr("can't specify both --restore-deterministic-wallet or --restore-multisig-wallet and --non-deterministic"); return false; } if (!m_wallet_file.empty()) { - fail_msg_writer() << tr("--restore-deterministic-wallet uses --generate-new-wallet, not --wallet-file"); + if (m_restore_multisig_wallet) + fail_msg_writer() << tr("--restore-multisig-wallet uses --generate-new-wallet, not --wallet-file"); + else + fail_msg_writer() << tr("--restore-deterministic-wallet uses --generate-new-wallet, not --wallet-file"); return false; } if (m_electrum_seed.empty()) { - m_electrum_seed = ""; - do + if (m_restore_multisig_wallet) { - const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed: " : "Electrum seed continued: "; - std::string electrum_seed = input_line(prompt); - if (std::cin.eof()) - return false; - if (electrum_seed.empty()) + const char *prompt = "Specify multisig seed: "; + m_electrum_seed = input_line(prompt); + if (std::cin.eof()) + return false; + if (m_electrum_seed.empty()) + { + fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"multisig seed here\""); + return false; + } + } + else + { + m_electrum_seed = ""; + do { - fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); - return false; - } - m_electrum_seed += electrum_seed + " "; - } while (might_be_partial_seed(m_electrum_seed)); + const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed: " : "Electrum seed continued: "; + std::string electrum_seed = input_line(prompt); + if (std::cin.eof()) + return false; + if (electrum_seed.empty()) + { + fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); + return false; + } + m_electrum_seed += electrum_seed + " "; + } while (might_be_partial_seed(m_electrum_seed)); + } } - if (!crypto::ElectrumWords::words_to_bytes(m_electrum_seed, m_recovery_key, old_language)) + if (m_restore_multisig_wallet) { - fail_msg_writer() << tr("Electrum-style word list failed verification"); - return false; + if (!epee::string_tools::parse_hexstr_to_binbuff(m_electrum_seed, multisig_keys)) + { + fail_msg_writer() << tr("Multisig seed failed verification"); + return false; + } + } + else + { + if (!crypto::ElectrumWords::words_to_bytes(m_electrum_seed, m_recovery_key, old_language)) + { + fail_msg_writer() << tr("Electrum-style word list failed verification"); + return false; + } } +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif auto pwd_container = tools::password_container::prompt(false, tr("Enter seed encryption passphrase, empty if none")); if (std::cin.eof() || !pwd_container) return false; epee::wipeable_string seed_pass = pwd_container->password(); if (!seed_pass.empty()) - m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); + { + if (m_restore_multisig_wallet) + { + crypto::secret_key key; + crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); + sc_reduce32((unsigned char*)key.data); + multisig_keys = m_wallet->decrypt(multisig_keys, key, true); + } + else + m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); + } } if (!m_generate_from_view_key.empty()) { @@ -2342,7 +2394,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } m_wallet_file = m_generate_new; - bool r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); + bool r; + if (m_restore_multisig_wallet) + r = new_wallet(vm, multisig_keys, old_language); + else + r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } if (!m_restore_height && m_restoring) @@ -2473,6 +2529,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_mnemonic_language = command_line::get_arg(vm, arg_mnemonic_language); m_electrum_seed = command_line::get_arg(vm, arg_electrum_seed); m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet); + m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); m_trusted_daemon = command_line::get_arg(vm, arg_trusted_daemon); m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version); @@ -2483,7 +2540,8 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ !m_generate_from_keys.empty() || !m_generate_from_multisig_keys.empty() || !m_generate_from_json.empty() || - m_restore_deterministic_wallet; + m_restore_deterministic_wallet || + m_restore_multisig_wallet; return true; } @@ -2680,6 +2738,49 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, + const std::string &multisig_keys, const std::string &old_language) +{ + auto rc = tools::wallet2::make_new(vm, password_prompter); + m_wallet = std::move(rc.first); + if (!m_wallet) + { + return false; + } + + std::string mnemonic_language = old_language; + + std::vector language_list; + crypto::ElectrumWords::get_language_list(language_list); + if (mnemonic_language.empty() && std::find(language_list.begin(), language_list.end(), m_mnemonic_language) != language_list.end()) + { + mnemonic_language = m_mnemonic_language; + } + + m_wallet->set_seed_language(mnemonic_language); + + try + { + m_wallet->generate(m_wallet_file, std::move(rc.second).password(), multisig_keys); + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total) || !ready) + { + fail_msg_writer() << tr("failed to generate new mutlisig wallet"); + return false; + } + message_writer(console_color_white, true) << boost::format(tr("Generated new %u/%u multisig wallet: ")) % threshold % total + << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + } + catch (const std::exception& e) + { + fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); + return false; + } + return true; } //---------------------------------------------------------------------------------------------------- @@ -6425,6 +6526,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_command); command_line::add_arg(desc_params, arg_restore_deterministic_wallet ); + command_line::add_arg(desc_params, arg_restore_multisig_wallet ); command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); command_line::add_arg(desc_params, arg_trusted_daemon); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index c0c33f6b8..c90cb1abf 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -92,6 +92,8 @@ namespace cryptonote bool recover, bool two_random, const std::string &old_language); bool new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, const boost::optional& spendkey, const crypto::secret_key& viewkey); + bool new_wallet(const boost::program_options::variables_map& vm, + const std::string &multisig_keys, const std::string &old_language); bool open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); @@ -305,6 +307,7 @@ namespace cryptonote crypto::secret_key m_recovery_key; // recovery key (used as random for wallet gen) bool m_restore_deterministic_wallet; // recover flag + bool m_restore_multisig_wallet; // recover flag bool m_non_deterministic; // old 2-random generation bool m_trusted_daemon; bool m_allow_mismatched_daemon_version; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 04c6ee236..68cc7a308 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -714,6 +714,70 @@ bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string return true; } +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase, bool raw) const +{ + bool ready; + uint32_t threshold, total; + if (!multisig(&ready, &threshold, &total)) + { + std::cout << "This is not a multisig wallet" << std::endl; + return false; + } + if (!ready) + { + std::cout << "This multisig wallet is not yet finalized" << std::endl; + return false; + } + if (!raw && seed_language.empty()) + { + std::cout << "seed_language not set" << std::endl; + return false; + } + + crypto::secret_key skey; + crypto::public_key pkey; + const account_keys &keys = get_account().get_keys(); + std::string data; + data.append((const char*)&threshold, sizeof(uint32_t)); + data.append((const char*)&total, sizeof(uint32_t)); + skey = keys.m_spend_secret_key; + data.append((const char*)&skey, sizeof(skey)); + pkey = keys.m_account_address.m_spend_public_key; + data.append((const char*)&pkey, sizeof(pkey)); + skey = keys.m_view_secret_key; + data.append((const char*)&skey, sizeof(skey)); + pkey = keys.m_account_address.m_view_public_key; + data.append((const char*)&pkey, sizeof(pkey)); + for (const auto &skey: keys.m_multisig_keys) + data.append((const char*)&skey, sizeof(skey)); + for (const auto &signer: m_multisig_signers) + data.append((const char*)&signer, sizeof(signer)); + + if (!passphrase.empty()) + { + crypto::secret_key key; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), (crypto::hash&)key); + sc_reduce32((unsigned char*)key.data); + data = encrypt(data, key, true); + } + + if (raw) + { + seed = epee::string_tools::buff_to_hex_nodelimer(data); + } + else + { + if (!crypto::ElectrumWords::bytes_to_words(data.data(), data.size(), seed, seed_language)) + { + std::cout << "Failed to encode seed"; + return false; + } + } + + return true; +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Gets the seed language */ @@ -2619,6 +2683,97 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip return r; } +/*! + * \brief Generates a wallet or restores one. + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + */ +void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, + const std::string& multisig_data) +{ + clear(); + prepare_file_names(wallet_); + + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } + + m_account.generate(rct::rct2sk(rct::zero()), true, false); + + THROW_WALLET_EXCEPTION_IF(multisig_data.size() < 32, error::invalid_multisig_seed); + size_t offset = 0; + uint32_t threshold = *(uint32_t*)(multisig_data.data() + offset); + offset += sizeof(uint32_t); + uint32_t total = *(uint32_t*)(multisig_data.data() + offset); + offset += sizeof(uint32_t); + THROW_WALLET_EXCEPTION_IF(threshold < 2, error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(total != threshold && total != threshold + 1, error::invalid_multisig_seed); + const size_t n_multisig_keys = total == threshold ? 1 : threshold; + THROW_WALLET_EXCEPTION_IF(multisig_data.size() != 8 + 32 * (4 + n_multisig_keys + total), error::invalid_multisig_seed); + + std::vector multisig_keys; + std::vector multisig_signers; + crypto::secret_key spend_secret_key = *(crypto::secret_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::secret_key); + crypto::public_key spend_public_key = *(crypto::public_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::public_key); + crypto::secret_key view_secret_key = *(crypto::secret_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::secret_key); + crypto::public_key view_public_key = *(crypto::public_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::public_key); + for (size_t n = 0; n < n_multisig_keys; ++n) + { + multisig_keys.push_back(*(crypto::secret_key*)(multisig_data.data() + offset)); + offset += sizeof(crypto::secret_key); + } + for (size_t n = 0; n < total; ++n) + { + multisig_signers.push_back(*(crypto::public_key*)(multisig_data.data() + offset)); + offset += sizeof(crypto::public_key); + } + + crypto::public_key calculated_view_public_key; + THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(view_secret_key, calculated_view_public_key), error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(view_public_key != calculated_view_public_key, error::invalid_multisig_seed); + crypto::public_key local_signer; + THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(spend_secret_key, local_signer), error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(std::find(multisig_signers.begin(), multisig_signers.end(), local_signer) == multisig_signers.end(), error::invalid_multisig_seed); + rct::key skey = rct::zero(); + for (const auto &msk: multisig_keys) + sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); + THROW_WALLET_EXCEPTION_IF(!(rct::rct2sk(skey) == spend_secret_key), error::invalid_multisig_seed); + + m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); + m_account.finalize_multisig(spend_public_key); + + m_account_public_address = m_account.get_keys().m_account_address; + m_watch_only = false; + m_multisig = true; + m_multisig_threshold = threshold; + m_multisig_signers = multisig_signers; + + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } + + cryptonote::block b; + generate_genesis(b); + m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); + + if (!wallet_.empty()) + store(); +} + /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 399287c3e..3dc215601 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -435,6 +435,15 @@ namespace tools typedef std::tuple get_outs_entry; + /*! + * \brief Generates a wallet or restores one. + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + */ + void generate(const std::string& wallet_, const epee::wipeable_string& password, + const std::string& multisig_data); + /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file @@ -610,6 +619,7 @@ namespace tools bool watch_only() const { return m_watch_only; } bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; + bool get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 234c22d85..023b53f28 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -60,6 +60,7 @@ namespace tools // file_save_error // invalid_password // invalid_priority + // invalid_multisig_seed // refresh_error * // acc_outs_lookup_error // block_parse_error @@ -266,6 +267,16 @@ namespace tools std::string to_string() const { return wallet_logic_error::to_string(); } }; + struct invalid_multisig_seed : public wallet_logic_error + { + explicit invalid_multisig_seed(std::string&& loc) + : wallet_logic_error(std::move(loc), "invalid multisig seed") + { + } + + std::string to_string() const { return wallet_logic_error::to_string(); } + }; + //---------------------------------------------------------------------------------------------------- struct invalid_pregenerated_random : public wallet_logic_error {