Merge pull request #2960
5cbcf0aa
wallet: support for multisig seeds (moneromooo-monero)
This commit is contained in:
commit
eb617be8f4
|
@ -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;
|
||||
}
|
||||
|
@ -1984,6 +1992,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;
|
||||
|
||||
|
@ -2001,20 +2011,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
|
||||
|
@ -2031,20 +2058,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;
|
||||
|
@ -2354,7 +2406,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)
|
||||
|
@ -2485,6 +2541,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);
|
||||
|
@ -2495,7 +2552,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;
|
||||
}
|
||||
|
@ -2692,6 +2750,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;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
@ -6473,6 +6574,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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
@ -306,6 +308,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;
|
||||
|
|
|
@ -733,6 +733,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
|
||||
*/
|
||||
|
@ -2642,6 +2706,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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue