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
This commit is contained in:
moneromooo-monero 2017-11-09 10:59:41 +00:00
parent 1cc7451130
commit 5cbcf0aa25
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
5 changed files with 327 additions and 46 deletions

View file

@ -121,6 +121,7 @@ namespace
const command_line::arg_descriptor<std::string> arg_mnemonic_language = {"mnemonic-language", sw::tr("Language for mnemonic"), ""};
const command_line::arg_descriptor<std::string> arg_electrum_seed = {"electrum-seed", sw::tr("Specify Electrum seed for wallet recovery/creation"), ""};
const command_line::arg_descriptor<bool> arg_restore_deterministic_wallet = {"restore-deterministic-wallet", sw::tr("Recover wallet using Electrum-style mnemonic seed"), false};
const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false};
const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Create non-deterministic view and spend keys"), false};
const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false};
const command_line::arg_descriptor<bool> 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<std::string> &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())
multisig = m_wallet->multisig(&ready);
if (multisig)
{
if (m_wallet->get_seed_language().empty())
if (!ready)
{
std::string mnemonic_language = get_mnemonic_language();
if (mnemonic_language.empty())
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;
m_wallet->set_seed_language(mnemonic_language);
}
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();
}
success = m_wallet->get_seed(electrum_words, seed_pass);
}
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,20 +1999,37 @@ 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())
{
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())
{
if (m_restore_multisig_wallet)
{
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
@ -2019,20 +2046,45 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
m_electrum_seed += electrum_seed + " ";
} while (might_be_partial_seed(m_electrum_seed));
}
}
if (m_restore_multisig_wallet)
{
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())
{
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())
{
m_wallet_file = m_generate_from_view_key;
@ -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<std::string> 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);

View file

@ -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<crypto::secret_key>& 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;

View file

@ -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<crypto::secret_key> multisig_keys;
std::vector<crypto::public_key> 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

View file

@ -435,6 +435,15 @@ namespace tools
typedef std::tuple<uint64_t, crypto::public_key, rct::key> 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;

View file

@ -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
{