mirror of
https://github.com/monero-project/monero.git
synced 2024-12-27 10:09:26 -05:00
wallet: add multisig key generation
Scheme by luigi1111
This commit is contained in:
parent
a3a8343051
commit
6d219a9250
@ -123,6 +123,13 @@ DISABLE_VS_WARNINGS(4244 4345)
|
|||||||
create_from_keys(address, fake, viewkey);
|
create_from_keys(address, fake, viewkey);
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
|
bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::public_key &spend_public_key)
|
||||||
|
{
|
||||||
|
m_keys.m_account_address.m_spend_public_key = spend_public_key;
|
||||||
|
m_keys.m_view_secret_key = view_secret_key;
|
||||||
|
return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key);
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------------
|
||||||
const account_keys& account_base::get_keys() const
|
const account_keys& account_base::get_keys() const
|
||||||
{
|
{
|
||||||
return m_keys;
|
return m_keys;
|
||||||
|
@ -60,6 +60,7 @@ namespace cryptonote
|
|||||||
crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false);
|
crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false);
|
||||||
void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey);
|
void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey);
|
||||||
void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey);
|
void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey);
|
||||||
|
bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::public_key &spend_public_key);
|
||||||
const account_keys& get_keys() const;
|
const account_keys& get_keys() const;
|
||||||
std::string get_public_address_str(bool testnet) const;
|
std::string get_public_address_str(bool testnet) const;
|
||||||
std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const;
|
std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const;
|
||||||
|
@ -587,6 +587,133 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool simple_wallet::prepare_multisig(const std::vector<std::string> &args)
|
||||||
|
{
|
||||||
|
if (m_wallet->multisig())
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("This wallet is already multisig");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_wallet->get_num_transfer_details())
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto orig_pwd_container = get_and_verify_password();
|
||||||
|
if(orig_pwd_container == boost::none)
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("Your password is incorrect.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string multisig_info = m_wallet->get_multisig_info();
|
||||||
|
success_msg_writer() << multisig_info;
|
||||||
|
success_msg_writer() << "Send this multisig info to all other participants, then use make_multisig <info1> [<info2>...] with others' multisig info";
|
||||||
|
success_msg_writer() << "This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool simple_wallet::make_multisig(const std::vector<std::string> &args)
|
||||||
|
{
|
||||||
|
if (m_wallet->multisig())
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("This wallet is already multisig");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_wallet->get_num_transfer_details())
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto orig_pwd_container = get_and_verify_password();
|
||||||
|
if(orig_pwd_container == boost::none)
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("Your original password was incorrect.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size() < 2)
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("usage: make_multisig <threshold> <multisiginfo1> [<multisiginfo2>...]");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse threshold
|
||||||
|
uint32_t threshold;
|
||||||
|
if (!string_tools::get_xtype_from_string(threshold, args[0]))
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("Invalid threshold");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse all multisig info
|
||||||
|
std::vector<crypto::secret_key> secret_keys(args.size() - 1);
|
||||||
|
std::vector<crypto::public_key> public_keys(args.size() - 1);
|
||||||
|
for (size_t i = 1; i < args.size(); ++i)
|
||||||
|
{
|
||||||
|
if (!tools::wallet2::verify_multisig_info(args[i], secret_keys[i - 1], public_keys[i - 1]))
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("Bad multisig info: ") << args[i];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove duplicates
|
||||||
|
for (size_t i = 1; i < secret_keys.size(); ++i)
|
||||||
|
{
|
||||||
|
if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[0]))
|
||||||
|
{
|
||||||
|
message_writer() << tr("Duplicate key found, ignoring");
|
||||||
|
secret_keys[i] = secret_keys.back();
|
||||||
|
public_keys[i] = public_keys.back();
|
||||||
|
secret_keys.pop_back();
|
||||||
|
public_keys.pop_back();
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// people may include their own, weed it out
|
||||||
|
for (size_t i = 0; i < secret_keys.size(); ++i)
|
||||||
|
{
|
||||||
|
if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(m_wallet->get_account().get_keys().m_view_secret_key))
|
||||||
|
{
|
||||||
|
message_writer() << tr("Local key is present, ignoring");
|
||||||
|
secret_keys[i] = secret_keys.back();
|
||||||
|
public_keys[i] = public_keys.back();
|
||||||
|
secret_keys.pop_back();
|
||||||
|
public_keys.pop_back();
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
else if (rct::pk2rct(public_keys[i]) == rct::pk2rct(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key))
|
||||||
|
{
|
||||||
|
fail_msg_writer() << "Found local spend public key, but not local view secret key - something very weird";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK_IDLE_SCOPE();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
fail_msg_writer() << "Error creating multisig: " << e.what();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t total = secret_keys.size() + 1;
|
||||||
|
success_msg_writer() << std::to_string(threshold) << "/" << total << " multisig address: "
|
||||||
|
<< m_wallet->get_account().get_public_address_str(m_wallet->testnet());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||||
{
|
{
|
||||||
const auto pwd_container = get_and_verify_password();
|
const auto pwd_container = get_and_verify_password();
|
||||||
@ -1165,10 +1292,16 @@ simple_wallet::simple_wallet()
|
|||||||
m_cmd_binder.set_handler("fee",
|
m_cmd_binder.set_handler("fee",
|
||||||
boost::bind(&simple_wallet::print_fee_info, this, _1),
|
boost::bind(&simple_wallet::print_fee_info, this, _1),
|
||||||
tr("Print the information about the current fee and transaction backlog."));
|
tr("Print the information about the current fee and transaction backlog."));
|
||||||
|
m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::prepare_multisig, this, _1),
|
||||||
|
tr("Export data needed to create a multisig wallet"));
|
||||||
|
m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1),
|
||||||
|
tr("make_multisig <threshold> <string1> [<string>...]"),
|
||||||
|
tr("Turn this wallet into a multisig wallet"));
|
||||||
m_cmd_binder.set_handler("help",
|
m_cmd_binder.set_handler("help",
|
||||||
boost::bind(&simple_wallet::help, this, _1),
|
boost::bind(&simple_wallet::help, this, _1),
|
||||||
tr("help [<command>]"),
|
tr("help [<command>]"),
|
||||||
tr("Show the help section or the documentation about a <command>."));
|
tr("Show the help section or the documentation about a <command>."));
|
||||||
|
m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help"));
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
bool simple_wallet::set_variable(const std::vector<std::string> &args)
|
bool simple_wallet::set_variable(const std::vector<std::string> &args)
|
||||||
@ -2098,9 +2231,16 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string prefix;
|
||||||
|
uint32_t threshold, total;
|
||||||
|
if (m_wallet->watch_only())
|
||||||
|
prefix = tr("Opened watch-only wallet");
|
||||||
|
else if (m_wallet->multisig(&threshold, &total))
|
||||||
|
prefix = (boost::format(tr("Opened %u/%u multisig wallet")) % threshold % total).str();
|
||||||
|
else
|
||||||
|
prefix = tr("Opened wallet");
|
||||||
message_writer(console_color_white, true) <<
|
message_writer(console_color_white, true) <<
|
||||||
(m_wallet->watch_only() ? tr("Opened watch-only wallet") : tr("Opened wallet")) << ": "
|
prefix << ": " << m_wallet->get_account().get_public_address_str(m_wallet->testnet());
|
||||||
<< m_wallet->get_account().get_public_address_str(m_wallet->testnet());
|
|
||||||
// If the wallet file is deprecated, we should ask for mnemonic language again and store
|
// If the wallet file is deprecated, we should ask for mnemonic language again and store
|
||||||
// everything in the new format.
|
// everything in the new format.
|
||||||
// NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere.
|
// NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere.
|
||||||
|
@ -188,6 +188,8 @@ namespace cryptonote
|
|||||||
bool change_password(const std::vector<std::string>& args);
|
bool change_password(const std::vector<std::string>& args);
|
||||||
bool payment_id(const std::vector<std::string> &args);
|
bool payment_id(const std::vector<std::string> &args);
|
||||||
bool print_fee_info(const std::vector<std::string> &args);
|
bool print_fee_info(const std::vector<std::string> &args);
|
||||||
|
bool prepare_multisig(const std::vector<std::string>& args);
|
||||||
|
bool make_multisig(const std::vector<std::string>& args);
|
||||||
|
|
||||||
uint64_t get_daemon_blockchain_height(std::string& err);
|
uint64_t get_daemon_blockchain_height(std::string& err);
|
||||||
bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr);
|
bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr);
|
||||||
|
@ -2181,6 +2181,15 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
|
|||||||
value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ??
|
value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ??
|
||||||
json.AddMember("watch_only", value2, json.GetAllocator());
|
json.AddMember("watch_only", value2, json.GetAllocator());
|
||||||
|
|
||||||
|
value2.SetInt(m_multisig ? 1 :0);
|
||||||
|
json.AddMember("multisig", value2, json.GetAllocator());
|
||||||
|
|
||||||
|
value2.SetUint(m_multisig_threshold);
|
||||||
|
json.AddMember("multisig_threshold", value2, json.GetAllocator());
|
||||||
|
|
||||||
|
value2.SetUint(m_multisig_total);
|
||||||
|
json.AddMember("multisig_total", value2, json.GetAllocator());
|
||||||
|
|
||||||
value2.SetInt(m_always_confirm_transfers ? 1 :0);
|
value2.SetInt(m_always_confirm_transfers ? 1 :0);
|
||||||
json.AddMember("always_confirm_transfers", value2, json.GetAllocator());
|
json.AddMember("always_confirm_transfers", value2, json.GetAllocator());
|
||||||
|
|
||||||
@ -2292,6 +2301,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
|||||||
{
|
{
|
||||||
is_old_file_format = true;
|
is_old_file_format = true;
|
||||||
m_watch_only = false;
|
m_watch_only = false;
|
||||||
|
m_multisig = false;
|
||||||
|
m_multisig_threshold = 0;
|
||||||
|
m_multisig_total = 0;
|
||||||
m_always_confirm_transfers = false;
|
m_always_confirm_transfers = false;
|
||||||
m_print_ring_members = false;
|
m_print_ring_members = false;
|
||||||
m_default_mixin = 0;
|
m_default_mixin = 0;
|
||||||
@ -2328,6 +2340,12 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
|||||||
}
|
}
|
||||||
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false);
|
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false);
|
||||||
m_watch_only = field_watch_only;
|
m_watch_only = field_watch_only;
|
||||||
|
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig, int, Int, false, false);
|
||||||
|
m_multisig = field_multisig;
|
||||||
|
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_threshold, unsigned int, Uint, m_multisig, 0);
|
||||||
|
m_multisig_threshold = field_multisig_threshold;
|
||||||
|
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_total, unsigned int, Uint, m_multisig, 0);
|
||||||
|
m_multisig_total = field_multisig_total;
|
||||||
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true);
|
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true);
|
||||||
m_always_confirm_transfers = field_always_confirm_transfers;
|
m_always_confirm_transfers = field_always_confirm_transfers;
|
||||||
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true);
|
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true);
|
||||||
@ -2394,7 +2412,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
|||||||
const cryptonote::account_keys& keys = m_account.get_keys();
|
const cryptonote::account_keys& keys = m_account.get_keys();
|
||||||
r = epee::serialization::load_t_from_binary(m_account, account_data);
|
r = epee::serialization::load_t_from_binary(m_account, account_data);
|
||||||
r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
|
r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
|
||||||
if(!m_watch_only)
|
if(!m_watch_only && !m_multisig)
|
||||||
r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
|
r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
|
||||||
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
|
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
|
||||||
return true;
|
return true;
|
||||||
@ -2412,7 +2430,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
|||||||
*/
|
*/
|
||||||
bool wallet2::verify_password(const epee::wipeable_string& password) const
|
bool wallet2::verify_password(const epee::wipeable_string& password) const
|
||||||
{
|
{
|
||||||
return verify_password(m_keys_file, password, m_watch_only);
|
return verify_password(m_keys_file, password, m_watch_only || m_multisig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -2427,7 +2445,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const
|
|||||||
* can be used prior to rewriting wallet keys file, to ensure user has entered the correct password
|
* can be used prior to rewriting wallet keys file, to ensure user has entered the correct password
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only)
|
bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key)
|
||||||
{
|
{
|
||||||
wallet2::keys_file_data keys_file_data;
|
wallet2::keys_file_data keys_file_data;
|
||||||
std::string buf;
|
std::string buf;
|
||||||
@ -2461,7 +2479,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
|
|||||||
const cryptonote::account_keys& keys = account_data_check.get_keys();
|
const cryptonote::account_keys& keys = account_data_check.get_keys();
|
||||||
|
|
||||||
r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
|
r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
|
||||||
if(!watch_only)
|
if(!no_spend_key)
|
||||||
r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
|
r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@ -2489,6 +2507,9 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
|
|||||||
|
|
||||||
m_account_public_address = m_account.get_keys().m_account_address;
|
m_account_public_address = m_account.get_keys().m_account_address;
|
||||||
m_watch_only = false;
|
m_watch_only = false;
|
||||||
|
m_multisig = false;
|
||||||
|
m_multisig_threshold = 0;
|
||||||
|
m_multisig_total = 0;
|
||||||
|
|
||||||
// -1 month for fluctuations in block time and machine date/time setup.
|
// -1 month for fluctuations in block time and machine date/time setup.
|
||||||
// avg seconds per block
|
// avg seconds per block
|
||||||
@ -2569,6 +2590,9 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
|
|||||||
m_account.create_from_viewkey(account_public_address, viewkey);
|
m_account.create_from_viewkey(account_public_address, viewkey);
|
||||||
m_account_public_address = account_public_address;
|
m_account_public_address = account_public_address;
|
||||||
m_watch_only = true;
|
m_watch_only = true;
|
||||||
|
m_multisig = false;
|
||||||
|
m_multisig_threshold = 0;
|
||||||
|
m_multisig_total = 0;
|
||||||
|
|
||||||
bool r = store_keys(m_keys_file, password, true);
|
bool r = store_keys(m_keys_file, password, true);
|
||||||
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
||||||
@ -2605,6 +2629,69 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
|
|||||||
m_account.create_from_keys(account_public_address, spendkey, viewkey);
|
m_account.create_from_keys(account_public_address, spendkey, viewkey);
|
||||||
m_account_public_address = account_public_address;
|
m_account_public_address = account_public_address;
|
||||||
m_watch_only = false;
|
m_watch_only = false;
|
||||||
|
m_multisig = false;
|
||||||
|
m_multisig_threshold = 0;
|
||||||
|
m_multisig_total = 0;
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
void wallet2::make_multisig(const epee::wipeable_string &password,
|
||||||
|
const std::vector<crypto::secret_key> &view_keys,
|
||||||
|
const std::vector<crypto::public_key> &spend_keys,
|
||||||
|
uint32_t threshold)
|
||||||
|
{
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys");
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes");
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= spend_keys.size() + 1, "Invalid threshold");
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case");
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
MINFO("Creating spend key...");
|
||||||
|
rct::key spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key);
|
||||||
|
if (threshold == spend_keys.size() + 1)
|
||||||
|
{
|
||||||
|
// the multisig spend public key is the sum of all spend public keys
|
||||||
|
for (const auto &k: spend_keys)
|
||||||
|
rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the multisig spend public key is the sum of keys derived from all spend public keys
|
||||||
|
const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key);
|
||||||
|
for (const auto &k: spend_keys)
|
||||||
|
{
|
||||||
|
rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::cn_fast_hash(rct::scalarmultKey(rct::pk2rct(k), spend_skey))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the multisig view key is shared by all, make one all can derive
|
||||||
|
MINFO("Creating view key...");
|
||||||
|
rct::key view_skey = rct::sk2rct(get_account().get_keys().m_view_secret_key);
|
||||||
|
for (const auto &k: view_keys)
|
||||||
|
sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes);
|
||||||
|
sc_reduce32(view_skey.bytes);
|
||||||
|
|
||||||
|
MINFO("Creating multisig address...");
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2pk(spend_pkey)),
|
||||||
|
"Failed to create multisig wallet due to bad keys");
|
||||||
|
|
||||||
|
m_account_public_address = m_account.get_keys().m_account_address;
|
||||||
|
m_watch_only = false;
|
||||||
|
m_multisig = true;
|
||||||
|
m_multisig_threshold = threshold;
|
||||||
|
m_multisig_total = spend_keys.size() + 1;
|
||||||
|
|
||||||
bool r = store_keys(m_keys_file, password, false);
|
bool r = store_keys(m_keys_file, password, false);
|
||||||
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
||||||
@ -2620,6 +2707,74 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
|
|||||||
store();
|
store();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string wallet2::get_multisig_info() const
|
||||||
|
{
|
||||||
|
// It's a signed package of private view key and public spend key
|
||||||
|
const crypto::secret_key &skey = get_account().get_keys().m_view_secret_key;
|
||||||
|
const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key;
|
||||||
|
|
||||||
|
std::string data;
|
||||||
|
data += std::string((const char *)&skey, sizeof(crypto::secret_key));
|
||||||
|
data += std::string((const char *)&pkey, sizeof(crypto::public_key));
|
||||||
|
|
||||||
|
data.resize(data.size() + sizeof(crypto::signature));
|
||||||
|
crypto::hash hash;
|
||||||
|
crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash);
|
||||||
|
crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)];
|
||||||
|
crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature);
|
||||||
|
|
||||||
|
return std::string("MultisigV1") + tools::base58::encode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey)
|
||||||
|
{
|
||||||
|
const size_t header_len = strlen("MultisigV1");
|
||||||
|
if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1")
|
||||||
|
{
|
||||||
|
MERROR("Multisig info header check error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string decoded;
|
||||||
|
if (!tools::base58::decode(data.substr(header_len), decoded))
|
||||||
|
{
|
||||||
|
MERROR("Multisig info decoding error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))
|
||||||
|
{
|
||||||
|
MERROR("Multisig info is corrupt");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset = 0;
|
||||||
|
skey = *(const crypto::secret_key*)(decoded.data() + offset);
|
||||||
|
offset += sizeof(skey);
|
||||||
|
pkey = *(const crypto::public_key*)(decoded.data() + offset);
|
||||||
|
offset += sizeof(pkey);
|
||||||
|
const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset);
|
||||||
|
|
||||||
|
crypto::hash hash;
|
||||||
|
crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash);
|
||||||
|
if (!crypto::check_signature(hash, pkey, signature))
|
||||||
|
{
|
||||||
|
MERROR("Multisig info signature is invalid");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const
|
||||||
|
{
|
||||||
|
if (!m_multisig)
|
||||||
|
return false;
|
||||||
|
if (threshold)
|
||||||
|
*threshold = m_multisig_threshold;
|
||||||
|
if (total)
|
||||||
|
*total = m_multisig_total;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
|
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
|
||||||
* \param wallet_name Name of wallet file (should exist)
|
* \param wallet_name Name of wallet file (should exist)
|
||||||
|
@ -166,7 +166,7 @@ namespace tools
|
|||||||
//! Just parses variables.
|
//! Just parses variables.
|
||||||
static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
|
static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
|
||||||
|
|
||||||
static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only);
|
static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key);
|
||||||
|
|
||||||
wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {}
|
wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {}
|
||||||
|
|
||||||
@ -418,6 +418,21 @@ namespace tools
|
|||||||
void generate(const std::string& wallet, const epee::wipeable_string& password,
|
void generate(const std::string& wallet, const epee::wipeable_string& password,
|
||||||
const cryptonote::account_public_address &account_public_address,
|
const cryptonote::account_public_address &account_public_address,
|
||||||
const crypto::secret_key& viewkey = crypto::secret_key());
|
const crypto::secret_key& viewkey = crypto::secret_key());
|
||||||
|
/*!
|
||||||
|
* \brief Creates a multisig wallet
|
||||||
|
*/
|
||||||
|
void make_multisig(const epee::wipeable_string &password,
|
||||||
|
const std::vector<crypto::secret_key> &view_keys,
|
||||||
|
const std::vector<crypto::public_key> &spend_keys,
|
||||||
|
uint32_t threshold);
|
||||||
|
/*!
|
||||||
|
* Get a packaged multisig information string
|
||||||
|
*/
|
||||||
|
std::string get_multisig_info() const;
|
||||||
|
/*!
|
||||||
|
* Verifies and extracts keys from a packaged multisig information string
|
||||||
|
*/
|
||||||
|
static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey);
|
||||||
/*!
|
/*!
|
||||||
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
|
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
|
||||||
* \param wallet_name Name of wallet file (should exist)
|
* \param wallet_name Name of wallet file (should exist)
|
||||||
@ -512,6 +527,7 @@ namespace tools
|
|||||||
bool testnet() const { return m_testnet; }
|
bool testnet() const { return m_testnet; }
|
||||||
bool restricted() const { return m_restricted; }
|
bool restricted() const { return m_restricted; }
|
||||||
bool watch_only() const { return m_watch_only; }
|
bool watch_only() const { return m_watch_only; }
|
||||||
|
bool multisig(uint32_t *threshold = NULL, uint32_t *total = NULL) const;
|
||||||
|
|
||||||
// locked & unlocked balance of given or current subaddress account
|
// locked & unlocked balance of given or current subaddress account
|
||||||
uint64_t balance(uint32_t subaddr_index_major) const;
|
uint64_t balance(uint32_t subaddr_index_major) const;
|
||||||
@ -724,6 +740,7 @@ namespace tools
|
|||||||
bool delete_address_book_row(std::size_t row_id);
|
bool delete_address_book_row(std::size_t row_id);
|
||||||
|
|
||||||
uint64_t get_num_rct_outputs();
|
uint64_t get_num_rct_outputs();
|
||||||
|
size_t get_num_transfer_details() const { return m_transfers.size(); }
|
||||||
const transfer_details &get_transfer_details(size_t idx) const;
|
const transfer_details &get_transfer_details(size_t idx) const;
|
||||||
|
|
||||||
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
|
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
|
||||||
@ -921,6 +938,9 @@ namespace tools
|
|||||||
std::string seed_language; /*!< Language of the mnemonics (seed). */
|
std::string seed_language; /*!< Language of the mnemonics (seed). */
|
||||||
bool is_old_file_format; /*!< Whether the wallet file is of an old file format */
|
bool is_old_file_format; /*!< Whether the wallet file is of an old file format */
|
||||||
bool m_watch_only; /*!< no spend key */
|
bool m_watch_only; /*!< no spend key */
|
||||||
|
bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */
|
||||||
|
uint32_t m_multisig_threshold;
|
||||||
|
uint32_t m_multisig_total;
|
||||||
bool m_always_confirm_transfers;
|
bool m_always_confirm_transfers;
|
||||||
bool m_print_ring_members;
|
bool m_print_ring_members;
|
||||||
bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */
|
bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */
|
||||||
|
Loading…
Reference in New Issue
Block a user