mirror of
https://github.com/monero-project/monero.git
synced 2024-12-29 01:36:12 -05:00
N-1/N multisig
This commit is contained in:
parent
cd64c7990c
commit
f4eda44ce3
@ -64,6 +64,7 @@ DISABLE_VS_WARNINGS(4244 4345)
|
||||
void account_base::forget_spend_key()
|
||||
{
|
||||
m_keys.m_spend_secret_key = crypto::secret_key();
|
||||
m_keys.m_multisig_keys.clear();
|
||||
}
|
||||
//-----------------------------------------------------------------
|
||||
crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random)
|
||||
@ -123,13 +124,20 @@ DISABLE_VS_WARNINGS(4244 4345)
|
||||
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)
|
||||
bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys)
|
||||
{
|
||||
m_keys.m_account_address.m_spend_public_key = spend_public_key;
|
||||
m_keys.m_view_secret_key = view_secret_key;
|
||||
m_keys.m_spend_secret_key = spend_secret_key;
|
||||
m_keys.m_multisig_keys = multisig_keys;
|
||||
return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key);
|
||||
}
|
||||
//-----------------------------------------------------------------
|
||||
void account_base::finalize_multisig(const crypto::public_key &spend_public_key)
|
||||
{
|
||||
m_keys.m_account_address.m_spend_public_key = spend_public_key;
|
||||
}
|
||||
//-----------------------------------------------------------------
|
||||
const account_keys& account_base::get_keys() const
|
||||
{
|
||||
return m_keys;
|
||||
|
@ -42,11 +42,13 @@ namespace cryptonote
|
||||
account_public_address m_account_address;
|
||||
crypto::secret_key m_spend_secret_key;
|
||||
crypto::secret_key m_view_secret_key;
|
||||
std::vector<crypto::secret_key> m_multisig_keys;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(m_account_address)
|
||||
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key)
|
||||
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key)
|
||||
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
@ -60,7 +62,8 @@ namespace cryptonote
|
||||
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_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);
|
||||
bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys);
|
||||
void finalize_multisig(const crypto::public_key &spend_public_key);
|
||||
const account_keys& get_keys() 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;
|
||||
@ -72,6 +75,7 @@ namespace cryptonote
|
||||
bool store(const std::string& file_path);
|
||||
|
||||
void forget_spend_key();
|
||||
const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; }
|
||||
|
||||
template <class t_archive>
|
||||
inline void serialize(t_archive &a, const unsigned int /*ver*/)
|
||||
|
@ -99,10 +99,15 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
|
||||
std::vector<crypto::public_key> pk(total);
|
||||
for (size_t n = 0; n < total; ++n)
|
||||
{
|
||||
tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]);
|
||||
if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]))
|
||||
{
|
||||
tools::fail_msg_writer() << tr("Failed to verify multisig info");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// make the wallets multisig
|
||||
std::vector<std::string> extra_info(total);
|
||||
std::stringstream ss;
|
||||
for (size_t n = 0; n < total; ++n)
|
||||
{
|
||||
@ -117,10 +122,33 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
|
||||
pkn.push_back(pk[k]);
|
||||
}
|
||||
}
|
||||
wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold);
|
||||
extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold);
|
||||
ss << " " << name << std::endl;
|
||||
}
|
||||
|
||||
// finalize step if needed
|
||||
if (!extra_info[0].empty())
|
||||
{
|
||||
std::unordered_set<crypto::public_key> pkeys;
|
||||
std::vector<crypto::public_key> signers(total);
|
||||
for (size_t n = 0; n < total; ++n)
|
||||
{
|
||||
if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n]))
|
||||
{
|
||||
tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (size_t n = 0; n < total; ++n)
|
||||
{
|
||||
if (!wallets[n]->finalize_multisig(pwd_container->password(), pkeys, signers))
|
||||
{
|
||||
tools::fail_msg_writer() << genms::tr("Error finalizing multisig");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->testnet());
|
||||
tools::success_msg_writer() << genms::tr("Generated multisig wallets for address ") << address << std::endl << ss.str();
|
||||
}
|
||||
|
@ -517,6 +517,11 @@ inline std::ostream &operator <<(std::ostream &o, const rct::key &v) {
|
||||
}
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<> struct hash<rct::key> { std::size_t operator()(const rct::key &k) const { return reinterpret_cast<const std::size_t&>(k); } };
|
||||
}
|
||||
|
||||
BLOB_SERIALIZER(rct::key);
|
||||
BLOB_SERIALIZER(rct::key64);
|
||||
BLOB_SERIALIZER(rct::ctkey);
|
||||
|
@ -825,7 +825,14 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args)
|
||||
|
||||
try
|
||||
{
|
||||
m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold);
|
||||
std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold);
|
||||
if (!multisig_extra_info.empty())
|
||||
{
|
||||
success_msg_writer() << tr("Another step is needed");
|
||||
success_msg_writer() << multisig_extra_info;
|
||||
success_msg_writer() << tr("Send this multisig info to all other participants, then use finalize_multisig <info1> [<info2>...] with others' multisig info");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
@ -840,6 +847,57 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::finalize_multisig(const std::vector<std::string> &args)
|
||||
{
|
||||
if (!m_wallet->multisig())
|
||||
{
|
||||
fail_msg_writer() << tr("This wallet is not multisig");
|
||||
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: finalize_multisig <multisiginfo1> [<multisiginfo2>...]");
|
||||
return true;
|
||||
}
|
||||
|
||||
// parse all multisig info
|
||||
std::unordered_set<crypto::public_key> public_keys;
|
||||
std::vector<crypto::public_key> signers(args.size(), crypto::null_pkey);
|
||||
for (size_t i = 0; i < args.size(); ++i)
|
||||
{
|
||||
if (!tools::wallet2::verify_extra_multisig_info(args[i], public_keys, signers[i]))
|
||||
{
|
||||
fail_msg_writer() << tr("Bad multisig info: ") << args[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// we have all pubkeys now
|
||||
try
|
||||
{
|
||||
if (!m_wallet->finalize_multisig(orig_pwd_container->password(), public_keys, signers))
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to finalize multisig");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::export_multisig(const std::vector<std::string> &args)
|
||||
{
|
||||
if (!m_wallet->multisig())
|
||||
@ -869,9 +927,8 @@ bool simple_wallet::export_multisig(const std::vector<std::string> &args)
|
||||
std::string header;
|
||||
header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
|
||||
header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
|
||||
crypto::hash hash;
|
||||
cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash);
|
||||
header += std::string((const char *)&hash, sizeof(crypto::hash));
|
||||
crypto::public_key signer = m_wallet->get_multisig_signer_public_key();
|
||||
header += std::string((const char *)&signer, sizeof(crypto::public_key));
|
||||
std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str());
|
||||
bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext);
|
||||
if (!r)
|
||||
@ -908,7 +965,7 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args)
|
||||
return true;
|
||||
|
||||
std::vector<std::vector<tools::wallet2::multisig_info>> info;
|
||||
std::unordered_set<crypto::hash> seen;
|
||||
std::unordered_set<crypto::public_key> seen;
|
||||
for (size_t n = 0; n < args.size(); ++n)
|
||||
{
|
||||
const std::string filename = args[n];
|
||||
@ -944,26 +1001,24 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args)
|
||||
}
|
||||
const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
|
||||
const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
|
||||
const crypto::hash &hash = *(const crypto::hash*)&data[2*sizeof(crypto::public_key)];
|
||||
const crypto::public_key &signer = *(const crypto::public_key*)&data[2*sizeof(crypto::public_key)];
|
||||
const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address;
|
||||
if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
|
||||
{
|
||||
fail_msg_writer() << (boost::format(tr("Multisig info from %s is for a different account")) % filename).str();
|
||||
return true;
|
||||
}
|
||||
crypto::hash this_hash;
|
||||
cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&this_hash);
|
||||
if (this_hash == hash)
|
||||
if (m_wallet->get_multisig_signer_public_key() == signer)
|
||||
{
|
||||
message_writer() << (boost::format(tr("Multisig info from %s is from this wallet, ignored")) % filename).str();
|
||||
continue;
|
||||
}
|
||||
if (seen.find(hash) != seen.end())
|
||||
if (seen.find(signer) != seen.end())
|
||||
{
|
||||
message_writer() << (boost::format(tr("Multisig info from %s already seen, ignored")) % filename).str();
|
||||
continue;
|
||||
}
|
||||
seen.insert(hash);
|
||||
seen.insert(signer);
|
||||
|
||||
try
|
||||
{
|
||||
@ -1721,6 +1776,10 @@ simple_wallet::simple_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("finalize_multisig",
|
||||
boost::bind(&simple_wallet::finalize_multisig, this, _1),
|
||||
tr("finalize_multisig <string> [<string>...]"),
|
||||
tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets"));
|
||||
m_cmd_binder.set_handler("export_multisig_info",
|
||||
boost::bind(&simple_wallet::export_multisig, this, _1),
|
||||
tr("export_multisig <filename>"),
|
||||
|
@ -190,6 +190,7 @@ namespace cryptonote
|
||||
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);
|
||||
bool finalize_multisig(const std::vector<std::string> &args);
|
||||
bool export_multisig(const std::vector<std::string>& args);
|
||||
bool import_multisig(const std::vector<std::string>& args);
|
||||
bool accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs);
|
||||
|
@ -1123,8 +1123,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
||||
error::wallet_internal_error, "NULL m_multisig_rescan_k");
|
||||
if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size())
|
||||
update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1);
|
||||
else
|
||||
td.m_multisig_k = rct::skGen();
|
||||
}
|
||||
LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid);
|
||||
if (0 != m_callback)
|
||||
@ -1180,8 +1178,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
||||
error::wallet_internal_error, "NULL m_multisig_rescan_k");
|
||||
if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size())
|
||||
update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1);
|
||||
else
|
||||
td.m_multisig_k = rct::skGen();
|
||||
}
|
||||
THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys");
|
||||
THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status");
|
||||
@ -2252,6 +2248,7 @@ bool wallet2::clear()
|
||||
bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only)
|
||||
{
|
||||
std::string account_data;
|
||||
std::string multisig_signers;
|
||||
cryptonote::account_base account = m_account;
|
||||
|
||||
if (watch_only)
|
||||
@ -2282,8 +2279,13 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
|
||||
value2.SetUint(m_multisig_threshold);
|
||||
json.AddMember("multisig_threshold", value2, json.GetAllocator());
|
||||
|
||||
value2.SetUint(m_multisig_total);
|
||||
json.AddMember("multisig_total", value2, json.GetAllocator());
|
||||
if (m_multisig)
|
||||
{
|
||||
bool r = ::serialization::dump_binary(m_multisig_signers, multisig_signers);
|
||||
CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig signers");
|
||||
value.SetString(multisig_signers.c_str(), multisig_signers.length());
|
||||
json.AddMember("multisig_signers", value, json.GetAllocator());
|
||||
}
|
||||
|
||||
value2.SetInt(m_always_confirm_transfers ? 1 :0);
|
||||
json.AddMember("always_confirm_transfers", value2, json.GetAllocator());
|
||||
@ -2398,7 +2400,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
||||
m_watch_only = false;
|
||||
m_multisig = false;
|
||||
m_multisig_threshold = 0;
|
||||
m_multisig_total = 0;
|
||||
m_multisig_signers.clear();
|
||||
m_always_confirm_transfers = false;
|
||||
m_print_ring_members = false;
|
||||
m_default_mixin = 0;
|
||||
@ -2439,8 +2441,27 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
||||
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;
|
||||
if (m_multisig)
|
||||
{
|
||||
if (!json.HasMember("multisig_signers"))
|
||||
{
|
||||
LOG_ERROR("Field multisig_signers not found in JSON");
|
||||
return false;
|
||||
}
|
||||
if (!json["multisig_signers"].IsString())
|
||||
{
|
||||
LOG_ERROR("Field multisig_signers found in JSON, but not String");
|
||||
return false;
|
||||
}
|
||||
const char *field_multisig_signers = json["multisig_signers"].GetString();
|
||||
std::string multisig_signers = std::string(field_multisig_signers, field_multisig_signers + json["multisig_signers"].GetStringLength());
|
||||
r = ::serialization::parse_binary(multisig_signers, m_multisig_signers);
|
||||
if (!r)
|
||||
{
|
||||
LOG_ERROR("Field multisig_signers found in JSON, but failed to parse");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true);
|
||||
m_always_confirm_transfers = field_always_confirm_transfers;
|
||||
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true);
|
||||
@ -2607,7 +2628,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
|
||||
m_watch_only = false;
|
||||
m_multisig = false;
|
||||
m_multisig_threshold = 0;
|
||||
m_multisig_total = 0;
|
||||
m_multisig_signers.clear();
|
||||
|
||||
// -1 month for fluctuations in block time and machine date/time setup.
|
||||
// avg seconds per block
|
||||
@ -2698,7 +2719,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
|
||||
m_watch_only = true;
|
||||
m_multisig = false;
|
||||
m_multisig_threshold = 0;
|
||||
m_multisig_total = 0;
|
||||
m_multisig_signers.clear();
|
||||
|
||||
if (!wallet_.empty())
|
||||
{
|
||||
@ -2744,7 +2765,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
|
||||
m_watch_only = false;
|
||||
m_multisig = false;
|
||||
m_multisig_threshold = 0;
|
||||
m_multisig_total = 0;
|
||||
m_multisig_signers.clear();
|
||||
|
||||
if (!wallet_.empty())
|
||||
{
|
||||
@ -2763,7 +2784,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
|
||||
store();
|
||||
}
|
||||
|
||||
void wallet2::make_multisig(const epee::wipeable_string &password,
|
||||
std::string 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)
|
||||
@ -2771,45 +2792,92 @@ void wallet2::make_multisig(const epee::wipeable_string &password,
|
||||
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 > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold");
|
||||
CHECK_AND_ASSERT_THROW_MES(/*threshold == spend_keys.size() ||*/ threshold == spend_keys.size() + 1, "Unsupported threshold case");
|
||||
CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case");
|
||||
|
||||
std::string extra_multisig_info;
|
||||
crypto::hash hash;
|
||||
|
||||
clear();
|
||||
|
||||
MINFO("Creating spend key...");
|
||||
rct::key spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key);
|
||||
std::vector<crypto::secret_key> multisig_keys;
|
||||
rct::key spend_pkey, spend_skey;
|
||||
if (threshold == spend_keys.size() + 1)
|
||||
{
|
||||
// the multisig spend public key is the sum of all spend public keys
|
||||
spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key);
|
||||
for (const auto &k: spend_keys)
|
||||
rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k));
|
||||
multisig_keys.push_back(get_account().get_keys().m_spend_secret_key);
|
||||
spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key);
|
||||
}
|
||||
else if (threshold == spend_keys.size())
|
||||
{
|
||||
spend_pkey = rct::identity();
|
||||
spend_skey = rct::zero();
|
||||
|
||||
// create all our composite private keys
|
||||
for (const auto &k: spend_keys)
|
||||
{
|
||||
rct::keyV data;
|
||||
data.push_back(rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(get_account().get_keys().m_spend_secret_key)));
|
||||
static const rct::key salt = { {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g' , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } };
|
||||
data.push_back(salt);
|
||||
rct::key msk = rct::hash_to_scalar(data);
|
||||
multisig_keys.push_back(rct::rct2sk(msk));
|
||||
sc_add(spend_skey.bytes, spend_skey.bytes, msk.bytes);
|
||||
}
|
||||
|
||||
// We need an extra step, so we package all the composite public keys
|
||||
// we know about, and make a signed string out of them
|
||||
std::string data;
|
||||
const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key;
|
||||
data += std::string((const char *)&pkey, sizeof(crypto::public_key));
|
||||
const crypto::public_key signer = get_multisig_signer_public_key(rct::rct2sk(spend_skey));
|
||||
data += std::string((const char *)&signer, sizeof(crypto::public_key));
|
||||
|
||||
for (const auto &msk: multisig_keys)
|
||||
{
|
||||
rct::key pmsk = rct::scalarmultBase(rct::sk2rct(msk));
|
||||
data += std::string((const char *)&pmsk, sizeof(crypto::public_key));
|
||||
}
|
||||
|
||||
data.resize(data.size() + sizeof(crypto::signature));
|
||||
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);
|
||||
|
||||
extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data);
|
||||
}
|
||||
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); // WRONG
|
||||
for (const auto &k: spend_keys)
|
||||
{
|
||||
rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(k), spend_skey))));
|
||||
}
|
||||
CHECK_AND_ASSERT_THROW_MES(false, "Unsupported threshold case");
|
||||
}
|
||||
|
||||
// the multisig view key is shared by all, make one all can derive
|
||||
MINFO("Creating view key...");
|
||||
crypto::hash hash;
|
||||
crypto::cn_fast_hash(&get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash);
|
||||
rct::key view_skey = rct::hash2rct(hash);
|
||||
for (const auto &k: view_keys)
|
||||
sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes);
|
||||
|
||||
MINFO("Creating multisig address...");
|
||||
CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2pk(spend_pkey)),
|
||||
CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys),
|
||||
"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;
|
||||
if (threshold == spend_keys.size() + 1)
|
||||
{
|
||||
m_multisig_signers = spend_keys;
|
||||
m_multisig_signers.push_back(get_multisig_signer_public_key());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey);
|
||||
}
|
||||
|
||||
if (!m_wallet_file.empty())
|
||||
{
|
||||
@ -2827,6 +2895,65 @@ void wallet2::make_multisig(const epee::wipeable_string &password,
|
||||
|
||||
if (!m_wallet_file.empty())
|
||||
store();
|
||||
|
||||
return extra_multisig_info;
|
||||
}
|
||||
|
||||
bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys");
|
||||
|
||||
// add ours if not included
|
||||
const crypto::public_key local_signer = get_multisig_signer_public_key();
|
||||
if (std::find(signers.begin(), signers.end(), local_signer) == signers.end())
|
||||
{
|
||||
signers.push_back(local_signer);
|
||||
for (const auto &msk: get_account().get_multisig_keys())
|
||||
{
|
||||
pkeys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk))));
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size");
|
||||
|
||||
rct::key spend_public_key = rct::identity();
|
||||
for (const auto &pk: pkeys)
|
||||
{
|
||||
rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk));
|
||||
}
|
||||
m_multisig_signers = signers;
|
||||
std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); });
|
||||
m_account_public_address.m_spend_public_key = rct::rct2pk(spend_public_key);
|
||||
m_account.finalize_multisig(m_account_public_address.m_spend_public_key);
|
||||
|
||||
if (!m_wallet_file.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");
|
||||
}
|
||||
|
||||
m_subaddresses.clear();
|
||||
m_subaddresses_inv.clear();
|
||||
m_subaddress_labels.clear();
|
||||
add_subaddress_account(tr("Primary account"));
|
||||
|
||||
if (!m_wallet_file.empty())
|
||||
store();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wallet2::wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const
|
||||
{
|
||||
THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index");
|
||||
if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki))
|
||||
return false;
|
||||
// we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig
|
||||
crypto::generate_key_image(in_ephemeral.pub, ack.m_multisig_keys[multisig_key_index], ki);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string wallet2::get_multisig_info() const
|
||||
@ -2887,6 +3014,57 @@ bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer)
|
||||
{
|
||||
const size_t header_len = strlen("MultisigxV1");
|
||||
if (data.size() < header_len || data.substr(0, header_len) != "MultisigxV1")
|
||||
{
|
||||
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::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))
|
||||
{
|
||||
MERROR("Multisig info is corrupt");
|
||||
return false;
|
||||
}
|
||||
if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key))
|
||||
{
|
||||
MERROR("Multisig info is corrupt");
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key);
|
||||
size_t offset = 0;
|
||||
const crypto::public_key &pkey = *(const crypto::public_key*)(decoded.data() + offset);
|
||||
offset += sizeof(pkey);
|
||||
signer = *(const crypto::public_key*)(decoded.data() + offset);
|
||||
offset += sizeof(signer);
|
||||
const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for (size_t n = 0; n < n_keys; ++n)
|
||||
{
|
||||
crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset);
|
||||
pkeys.insert(mspk);
|
||||
offset += sizeof(mspk);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const
|
||||
{
|
||||
if (!m_multisig)
|
||||
@ -2894,7 +3072,7 @@ bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const
|
||||
if (threshold)
|
||||
*threshold = m_multisig_threshold;
|
||||
if (total)
|
||||
*total = m_multisig_total;
|
||||
*total = m_multisig_signers.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -3899,7 +4077,7 @@ void wallet2::commit_tx(pending_tx& ptx)
|
||||
|
||||
// tx generated, get rid of used k values
|
||||
for (size_t idx: ptx.selected_transfers)
|
||||
m_transfers[idx].m_multisig_k = rct::zero();
|
||||
m_transfers[idx].m_multisig_k.clear();
|
||||
|
||||
//fee includes dust if dust policy specified it.
|
||||
LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL
|
||||
@ -4084,7 +4262,6 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
|
||||
ptx.selected_transfers = sd.selected_transfers;
|
||||
ptx.tx_key = rct::rct2sk(rct::identity()); // don't send it back to the untrusted view wallet
|
||||
ptx.dests = sd.dests;
|
||||
ptx.msout = msout;
|
||||
ptx.construction_data = sd;
|
||||
|
||||
txs.push_back(ptx);
|
||||
@ -4241,7 +4418,7 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs)
|
||||
// txes generated, get rid of used k values
|
||||
for (size_t n = 0; n < txs.m_ptx.size(); ++n)
|
||||
for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers)
|
||||
m_transfers[idx].m_multisig_k = rct::zero();
|
||||
m_transfers[idx].m_multisig_k.clear();
|
||||
|
||||
// zero out some data we don't want to share
|
||||
for (auto &ptx: txs.m_ptx)
|
||||
@ -4284,9 +4461,15 @@ std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector)
|
||||
{
|
||||
multisig_tx_set txs;
|
||||
txs.m_ptx = ptx_vector;
|
||||
crypto::hash hash;
|
||||
cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash);
|
||||
txs.m_signers.insert(hash);
|
||||
|
||||
for (const auto &msk: get_account().get_multisig_keys())
|
||||
{
|
||||
crypto::public_key pkey = get_multisig_signing_public_key(msk);
|
||||
for (auto &ptx: txs.m_ptx) for (auto &sig: ptx.multisig_sigs) sig.signing_keys.insert(pkey);
|
||||
}
|
||||
|
||||
txs.m_signers.insert(get_multisig_signer_public_key());
|
||||
|
||||
return save_multisig_tx(txs);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
@ -4378,21 +4561,24 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids)
|
||||
bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids)
|
||||
{
|
||||
THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found");
|
||||
|
||||
const crypto::public_key local_signer = get_multisig_signer_public_key();
|
||||
|
||||
txids.clear();
|
||||
|
||||
// sign the transactions
|
||||
for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n)
|
||||
{
|
||||
tools::wallet2::pending_tx &ptx = exported_txs.m_ptx[n];
|
||||
THROW_WALLET_EXCEPTION_IF(ptx.multisig_sigs.empty(), error::wallet_internal_error, "No signatures found in multisig tx");
|
||||
tools::wallet2::tx_construction_data &sd = ptx.construction_data;
|
||||
LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1) <<
|
||||
", signed by " << exported_txs.m_signers.size() << "/" << m_multisig_threshold);
|
||||
cryptonote::transaction tx;
|
||||
rct::multisig_out msout = ptx.msout;
|
||||
rct::multisig_out msout = ptx.multisig_sigs.front().msout;
|
||||
auto sources = sd.sources;
|
||||
const bool bulletproof = sd.use_rct && (ptx.tx.rct_signatures.type == rct::RCTTypeFullBulletproof || ptx.tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof);
|
||||
bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, bulletproof, &msout);
|
||||
@ -4406,16 +4592,51 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string
|
||||
for (const auto &source: sources)
|
||||
indices.push_back(source.real_output);
|
||||
|
||||
rct::keyV k;
|
||||
for (size_t idx: sd.selected_transfers)
|
||||
k.push_back(get_multisig_k(idx));
|
||||
for (auto &sig: ptx.multisig_sigs)
|
||||
{
|
||||
if (sig.ignore != local_signer)
|
||||
{
|
||||
ptx.tx.rct_signatures = sig.sigs;
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, ptx.msout, rct::sk2rct(get_account().get_keys().m_spend_secret_key)),
|
||||
error::wallet_internal_error, "Failed signing, transaction likely malformed");
|
||||
rct::keyV k;
|
||||
for (size_t idx: sd.selected_transfers)
|
||||
k.push_back(get_multisig_k(idx, sig.used_L));
|
||||
|
||||
rct::key skey = rct::zero();
|
||||
for (const auto &msk: get_account().get_multisig_keys())
|
||||
{
|
||||
crypto::public_key pmsk = get_multisig_signing_public_key(msk);
|
||||
|
||||
if (sig.signing_keys.find(pmsk) == sig.signing_keys.end())
|
||||
{
|
||||
sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes);
|
||||
sig.signing_keys.insert(pmsk);
|
||||
}
|
||||
}
|
||||
THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, sig.msout, skey),
|
||||
error::wallet_internal_error, "Failed signing, transaction likely malformed");
|
||||
|
||||
sig.sigs = ptx.tx.rct_signatures;
|
||||
}
|
||||
}
|
||||
|
||||
const bool is_last = exported_txs.m_signers.size() + 1 >= m_multisig_threshold;
|
||||
if (is_last)
|
||||
{
|
||||
// when the last signature on a multisig tx is made, we select the right
|
||||
// signature to plug into the final tx
|
||||
bool found = false;
|
||||
for (const auto &sig: ptx.multisig_sigs)
|
||||
{
|
||||
if (sig.ignore != local_signer && exported_txs.m_signers.find(sig.ignore) == exported_txs.m_signers.end())
|
||||
{
|
||||
THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final");
|
||||
ptx.tx.rct_signatures = sig.sigs;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error,
|
||||
"Final signed transaction not found: this transaction was likely made without our export data, so we cannot sign it");
|
||||
const crypto::hash txid = get_transaction_hash(ptx.tx);
|
||||
if (store_tx_info())
|
||||
{
|
||||
@ -4429,12 +4650,18 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string
|
||||
// txes generated, get rid of used k values
|
||||
for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n)
|
||||
for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers)
|
||||
m_transfers[idx].m_multisig_k = rct::zero();
|
||||
m_transfers[idx].m_multisig_k.clear();
|
||||
|
||||
crypto::hash hash;
|
||||
cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash);
|
||||
exported_txs.m_signers.insert(hash);
|
||||
exported_txs.m_signers.insert(get_multisig_signer_public_key());
|
||||
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::sign_multisig_tx_from_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids)
|
||||
{
|
||||
bool r = sign_multisig_tx(exported_txs, txids);
|
||||
if (!r)
|
||||
return false;
|
||||
return save_multisig_tx(exported_txs, filename);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
@ -4444,9 +4671,8 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto
|
||||
if(!load_multisig_tx_from_file(filename, exported_txs))
|
||||
return false;
|
||||
|
||||
crypto::hash hash;
|
||||
cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash);
|
||||
THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(hash) != exported_txs.m_signers.end(),
|
||||
const crypto::public_key signer = get_multisig_signer_public_key();
|
||||
THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(signer) != exported_txs.m_signers.end(),
|
||||
error::wallet_internal_error, "Transaction already signed by this private key");
|
||||
THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold,
|
||||
error::wallet_internal_error, "Transaction was signed by too many signers");
|
||||
@ -4458,7 +4684,7 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto
|
||||
LOG_PRINT_L1("Transactions rejected by callback");
|
||||
return false;
|
||||
}
|
||||
return sign_multisig_tx(exported_txs, filename, txids);
|
||||
return sign_multisig_tx_from_file(exported_txs, filename, txids);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm)
|
||||
@ -5012,6 +5238,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
|
||||
// throw if attempting a transaction with no destinations
|
||||
THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(m_multisig, error::wallet_internal_error, "Multisig wallets cannot spend non rct outputs");
|
||||
|
||||
uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit();
|
||||
uint64_t needed_money = fee;
|
||||
LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money));
|
||||
@ -5084,10 +5312,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
|
||||
src.real_out_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
|
||||
src.real_output = it_to_replace - src.outputs.begin();
|
||||
src.real_output_in_tx_index = td.m_internal_output_index;
|
||||
if (m_multisig)
|
||||
src.multisig_kLRki = get_multisig_composite_LRki(idx, get_multisig_k(idx));
|
||||
else
|
||||
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
|
||||
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
|
||||
detail::print_source_entry(src);
|
||||
++out_index;
|
||||
}
|
||||
@ -5147,7 +5372,6 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
|
||||
ptx.tx_key = tx_key;
|
||||
ptx.additional_tx_keys = additional_tx_keys;
|
||||
ptx.dests = dsts;
|
||||
ptx.msout = msout;
|
||||
ptx.construction_data.sources = sources;
|
||||
ptx.construction_data.change_dts = change_dts;
|
||||
ptx.construction_data.splitted_dsts = splitted_dsts;
|
||||
@ -5187,6 +5411,36 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
||||
THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet);
|
||||
}
|
||||
|
||||
// if this is a multisig wallet, create a list of multisig signers we can use
|
||||
std::deque<crypto::public_key> multisig_signers;
|
||||
size_t n_multisig_txes = 0;
|
||||
if (m_multisig && !m_transfers.empty())
|
||||
{
|
||||
const crypto::public_key local_signer = get_multisig_signer_public_key();
|
||||
size_t n_available_signers = 1;
|
||||
for (const crypto::public_key &signer: m_multisig_signers)
|
||||
{
|
||||
if (signer == local_signer)
|
||||
continue;
|
||||
multisig_signers.push_front(signer);
|
||||
for (const auto &i: m_transfers[0].m_multisig_info)
|
||||
{
|
||||
if (i.m_signer == signer)
|
||||
{
|
||||
multisig_signers.pop_front();
|
||||
multisig_signers.push_back(signer);
|
||||
++n_available_signers;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
multisig_signers.push_back(local_signer);
|
||||
MDEBUG("We can use " << n_available_signers << "/" << m_multisig_signers.size() << " other signers");
|
||||
THROW_WALLET_EXCEPTION_IF(n_available_signers+1 < m_multisig_threshold, error::multisig_import_needed);
|
||||
n_multisig_txes = n_available_signers == m_multisig_signers.size() ? m_multisig_threshold : 1;
|
||||
MDEBUG("We will create " << n_multisig_txes << " txes");
|
||||
}
|
||||
|
||||
uint64_t found_money = 0;
|
||||
for(size_t idx: selected_transfers)
|
||||
{
|
||||
@ -5207,6 +5461,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
||||
LOG_PRINT_L2("preparing outputs");
|
||||
size_t i = 0, out_index = 0;
|
||||
std::vector<cryptonote::tx_source_entry> sources;
|
||||
std::unordered_set<rct::key> used_L;
|
||||
for(size_t idx: selected_transfers)
|
||||
{
|
||||
sources.resize(sources.size()+1);
|
||||
@ -5249,7 +5504,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
||||
src.real_output_in_tx_index = td.m_internal_output_index;
|
||||
src.mask = td.m_mask;
|
||||
if (m_multisig)
|
||||
src.multisig_kLRki = get_multisig_composite_LRki(idx, get_multisig_k(idx));
|
||||
{
|
||||
crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front();
|
||||
src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore, used_L, used_L);
|
||||
}
|
||||
else
|
||||
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
|
||||
detail::print_source_entry(src);
|
||||
@ -5307,6 +5565,45 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
||||
}
|
||||
THROW_WALLET_EXCEPTION_IF(ins_order.size() != sources.size(), error::wallet_internal_error, "Failed to work out sources permutation");
|
||||
|
||||
std::vector<tools::wallet2::multisig_sig> multisig_sigs;
|
||||
if (m_multisig)
|
||||
{
|
||||
crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front();
|
||||
multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, {}, msout});
|
||||
|
||||
if (m_multisig_threshold < m_multisig_signers.size())
|
||||
{
|
||||
const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx);
|
||||
|
||||
// create the other versions, one for every other participant (the first one's already done above)
|
||||
for (size_t signer_index = 1; signer_index < n_multisig_txes; ++signer_index)
|
||||
{
|
||||
std::unordered_set<rct::key> new_used_L;
|
||||
size_t src_idx = 0;
|
||||
THROW_WALLET_EXCEPTION_IF(selected_transfers.size() != sources.size(), error::wallet_internal_error, "mismatched selected_transfers and sources sixes");
|
||||
for(size_t idx: selected_transfers)
|
||||
{
|
||||
cryptonote::tx_source_entry& src = sources[src_idx];
|
||||
src.multisig_kLRki = get_multisig_composite_kLRki(idx, multisig_signers[signer_index], used_L, new_used_L);
|
||||
++src_idx;
|
||||
}
|
||||
|
||||
LOG_PRINT_L2("Creating supplementary multisig transaction");
|
||||
cryptonote::transaction ms_tx;
|
||||
auto sources_copy_copy = sources_copy;
|
||||
bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, &msout);
|
||||
LOG_PRINT_L2("constructed tx, r="<<r);
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet);
|
||||
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
|
||||
THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix");
|
||||
multisig_sigs.push_back({ms_tx.rct_signatures, multisig_signers[signer_index], new_used_L, {}, msout});
|
||||
|
||||
ms_tx.rct_signatures = tx.rct_signatures;
|
||||
THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_PRINT_L2("gathering key images");
|
||||
std::string key_images;
|
||||
bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool
|
||||
@ -5329,8 +5626,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
||||
ptx.tx_key = tx_key;
|
||||
ptx.additional_tx_keys = additional_tx_keys;
|
||||
ptx.dests = dsts;
|
||||
ptx.msout = msout;
|
||||
ptx.construction_data.sources = sources;
|
||||
ptx.multisig_sigs = multisig_sigs;
|
||||
ptx.construction_data.sources = sources_copy;
|
||||
ptx.construction_data.change_dts = change_dts;
|
||||
ptx.construction_data.splitted_dsts = splitted_dsts;
|
||||
ptx.construction_data.selected_transfers = ptx.selected_transfers;
|
||||
@ -8012,16 +8309,52 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
|
||||
return m_transfers.size();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
rct::key wallet2::get_multisig_k(size_t idx) const
|
||||
crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
|
||||
CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Index out of range");
|
||||
THROW_WALLET_EXCEPTION_IF(m_transfers[idx].m_multisig_k == rct::zero(), error::multisig_export_needed);
|
||||
return m_transfers[idx].m_multisig_k;
|
||||
crypto::public_key pkey;
|
||||
crypto::secret_key_to_public_key(spend_skey, pkey);
|
||||
return pkey;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
rct::multisig_kLRki wallet2::get_multisig_LRki(size_t n, const rct::key &k) const
|
||||
crypto::public_key wallet2::get_multisig_signer_public_key() const
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
|
||||
return get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
crypto::public_key wallet2::get_multisig_signing_public_key(const crypto::secret_key &msk) const
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
|
||||
crypto::public_key pkey;
|
||||
CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(msk, pkey), "Failed to derive public key");
|
||||
return pkey;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
crypto::public_key wallet2::get_multisig_signing_public_key(size_t idx) const
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
|
||||
CHECK_AND_ASSERT_THROW_MES(idx < get_account().get_multisig_keys().size(), "Multisig signing key index out of range");
|
||||
return get_multisig_signing_public_key(get_account().get_multisig_keys()[idx]);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
rct::key wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
|
||||
CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "idx out of range");
|
||||
for (const auto &k: m_transfers[idx].m_multisig_k)
|
||||
{
|
||||
rct::key L;
|
||||
rct::scalarmultBase(L, k);
|
||||
if (used_L.find(L) != used_L.end())
|
||||
return k;
|
||||
}
|
||||
THROW_WALLET_EXCEPTION(tools::error::multisig_export_needed);
|
||||
return rct::zero();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) const
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index");
|
||||
rct::multisig_kLRki kLRki;
|
||||
kLRki.k = k;
|
||||
rct::scalarmultBase(kLRki.L, kLRki.k);
|
||||
@ -8030,27 +8363,77 @@ rct::multisig_kLRki wallet2::get_multisig_LRki(size_t n, const rct::key &k) cons
|
||||
return kLRki;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
rct::multisig_kLRki wallet2::get_multisig_composite_LRki(size_t n, const rct::key &k) const
|
||||
rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const
|
||||
{
|
||||
rct::multisig_kLRki kLRki = get_multisig_LRki(n, k);
|
||||
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index");
|
||||
|
||||
const transfer_details &td = m_transfers[n];
|
||||
rct::multisig_kLRki kLRki = get_multisig_kLRki(n, rct::skGen());
|
||||
|
||||
// pick a L/R pair from every other participant but one
|
||||
size_t n_signers_used = 1;
|
||||
for (const auto &p: m_transfers[n].m_multisig_info)
|
||||
{
|
||||
if (p.m_signer == ignore)
|
||||
continue;
|
||||
for (const auto &lr: p.m_LR)
|
||||
{
|
||||
if (used_L.find(lr.m_L) != used_L.end())
|
||||
continue;
|
||||
used_L.insert(lr.m_L);
|
||||
new_used_L.insert(lr.m_L);
|
||||
rct::addKeys(kLRki.L, kLRki.L, lr.m_L);
|
||||
rct::addKeys(kLRki.R, kLRki.R, lr.m_R);
|
||||
++n_signers_used;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK_AND_ASSERT_THROW_MES(n_signers_used >= m_multisig_threshold, "LR not found for enough participants");
|
||||
|
||||
return kLRki;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad output index");
|
||||
|
||||
const transfer_details &td = m_transfers[n];
|
||||
crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td);
|
||||
cryptonote::keypair in_ephemeral;
|
||||
bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, (crypto::key_image&)kLRki.ki);
|
||||
crypto::key_image ki;
|
||||
bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image");
|
||||
std::unordered_set<crypto::key_image> used;
|
||||
|
||||
// insert the ones we start from
|
||||
for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m)
|
||||
{
|
||||
crypto::key_image pki;
|
||||
wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, pki, m);
|
||||
used.insert(pki);
|
||||
}
|
||||
|
||||
for (const auto &info: td.m_multisig_info)
|
||||
{
|
||||
rct::addKeys(kLRki.ki, kLRki.ki, rct::ki2rct(info.m_partial_key_image));
|
||||
rct::addKeys(kLRki.L, kLRki.L, info.m_L);
|
||||
rct::addKeys(kLRki.R, kLRki.R, info.m_R);
|
||||
for (const auto &pki: info.m_partial_key_images)
|
||||
{
|
||||
// don't add duplicates again
|
||||
if (used.find(pki) != used.end())
|
||||
continue;
|
||||
used.insert(pki);
|
||||
|
||||
rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki));
|
||||
}
|
||||
}
|
||||
return kLRki;
|
||||
return ki;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::vector<tools::wallet2::multisig_info> wallet2::export_multisig()
|
||||
{
|
||||
std::vector<tools::wallet2::multisig_info> info;
|
||||
|
||||
const crypto::public_key signer = get_multisig_signer_public_key();
|
||||
|
||||
info.resize(m_transfers.size());
|
||||
for (size_t n = 0; n < m_transfers.size(); ++n)
|
||||
{
|
||||
@ -8058,21 +8441,33 @@ std::vector<tools::wallet2::multisig_info> wallet2::export_multisig()
|
||||
crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td);
|
||||
cryptonote::keypair in_ephemeral;
|
||||
crypto::key_image ki;
|
||||
td.m_multisig_k = rct::skGen();
|
||||
const rct::multisig_kLRki kLRki = get_multisig_LRki(n, td.m_multisig_k);
|
||||
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
|
||||
// we want to export the partial key image, not the full one, so we can't use td.m_key_image
|
||||
bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, true);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image");
|
||||
// we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig
|
||||
crypto::generate_key_image(in_ephemeral.pub, get_account().get_keys().m_spend_secret_key, ki);
|
||||
info[n] = multisig_info({ki, kLRki.L, kLRki.R});
|
||||
td.m_multisig_k.clear();
|
||||
info[n].m_LR.clear();
|
||||
info[n].m_partial_key_images.clear();
|
||||
|
||||
for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m)
|
||||
{
|
||||
// we want to export the partial key image, not the full one, so we can't use td.m_key_image
|
||||
bool r = wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, m);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image");
|
||||
info[n].m_partial_key_images.push_back(ki);
|
||||
}
|
||||
|
||||
size_t nlr = m_multisig_threshold < m_multisig_signers.size() ? m_multisig_threshold - 1 : 1;
|
||||
for (size_t m = 0; m < nlr; ++m)
|
||||
{
|
||||
td.m_multisig_k.push_back(rct::skGen());
|
||||
const rct::multisig_kLRki kLRki = get_multisig_kLRki(n, td.m_multisig_k.back());
|
||||
info[n].m_LR.push_back({kLRki.L, kLRki.R});
|
||||
}
|
||||
|
||||
info[n].m_signer = signer;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::update_multisig_rescan_info(const std::vector<rct::key> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n)
|
||||
void wallet2::update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad index in update_multisig_info");
|
||||
CHECK_AND_ASSERT_THROW_MES(multisig_k.size() >= m_transfers.size(), "Mismatched sizes of multisig_k and info");
|
||||
@ -8086,7 +8481,7 @@ void wallet2::update_multisig_rescan_info(const std::vector<rct::key> &multisig_
|
||||
td.m_multisig_info.push_back(pi[n]);
|
||||
}
|
||||
m_key_images.erase(td.m_key_image);
|
||||
td.m_key_image = rct::rct2ki(get_multisig_composite_LRki(n, rct::skGen()).ki);
|
||||
td.m_key_image = get_multisig_composite_key_image(n);
|
||||
td.m_key_image_known = true;
|
||||
td.m_key_image_partial = false;
|
||||
td.m_multisig_k = multisig_k[n];
|
||||
@ -8096,9 +8491,9 @@ void wallet2::update_multisig_rescan_info(const std::vector<rct::key> &multisig_
|
||||
size_t wallet2::import_multisig(std::vector<std::vector<tools::wallet2::multisig_info>> info)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
|
||||
CHECK_AND_ASSERT_THROW_MES(info.size() + 1 == m_multisig_total, "Wrong number of multisig sources");
|
||||
CHECK_AND_ASSERT_THROW_MES(info.size() + 1 <= m_multisig_signers.size() && info.size() + 1 >= m_multisig_threshold, "Wrong number of multisig sources");
|
||||
|
||||
std::vector<rct::key> k;
|
||||
std::vector<std::vector<rct::key>> k;
|
||||
k.reserve(m_transfers.size());
|
||||
for (const auto &td: m_transfers)
|
||||
k.push_back(td.m_multisig_k);
|
||||
@ -8109,10 +8504,28 @@ size_t wallet2::import_multisig(std::vector<std::vector<tools::wallet2::multisig
|
||||
if (pi.size() < n_outputs)
|
||||
n_outputs = pi.size();
|
||||
|
||||
if (n_outputs == 0)
|
||||
return 0;
|
||||
|
||||
// check signers are consistent
|
||||
for (const auto &pi: info)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(std::find(m_multisig_signers.begin(), m_multisig_signers.end(), pi[0].m_signer) != m_multisig_signers.end(),
|
||||
"Signer is not a member of this multisig wallet");
|
||||
for (size_t n = 1; n < n_outputs; ++n)
|
||||
CHECK_AND_ASSERT_THROW_MES(pi[n].m_signer == pi[0].m_signer, "Mismatched signers in imported multisig info");
|
||||
}
|
||||
|
||||
// trim data we don't have info for from all participants
|
||||
for (auto &pi: info)
|
||||
pi.resize(n_outputs);
|
||||
|
||||
// sort by signer
|
||||
if (!info.empty() && !info.front().empty())
|
||||
{
|
||||
std::sort(info.begin(), info.end(), [](const std::vector<tools::wallet2::multisig_info> &i0, const std::vector<tools::wallet2::multisig_info> &i1){ return memcmp(&i0[0].m_signer, &i1[0].m_signer, sizeof(i0[0].m_signer)); });
|
||||
}
|
||||
|
||||
// first pass to determine where to detach the blockchain
|
||||
for (size_t n = 0; n < n_outputs; ++n)
|
||||
{
|
||||
|
@ -172,14 +172,25 @@ namespace tools
|
||||
|
||||
struct multisig_info
|
||||
{
|
||||
crypto::key_image m_partial_key_image;
|
||||
rct::key m_L;
|
||||
rct::key m_R;
|
||||
struct LR
|
||||
{
|
||||
rct::key m_L;
|
||||
rct::key m_R;
|
||||
|
||||
BEGIN_SERIALIZE_OBJECT()
|
||||
FIELD(m_L)
|
||||
FIELD(m_R)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
crypto::public_key m_signer;
|
||||
std::vector<LR> m_LR;
|
||||
std::vector<crypto::key_image> m_partial_key_images; // one per key the participant has
|
||||
|
||||
BEGIN_SERIALIZE_OBJECT()
|
||||
FIELD(m_partial_key_image)
|
||||
FIELD(m_L)
|
||||
FIELD(m_R)
|
||||
FIELD(m_signer)
|
||||
FIELD(m_LR)
|
||||
FIELD(m_partial_key_images)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
@ -213,8 +224,8 @@ namespace tools
|
||||
size_t m_pk_index;
|
||||
cryptonote::subaddress_index m_subaddr_index;
|
||||
bool m_key_image_partial;
|
||||
rct::key m_multisig_k;
|
||||
std::vector<multisig_info> m_multisig_info;
|
||||
std::vector<rct::key> m_multisig_k;
|
||||
std::vector<multisig_info> m_multisig_info; // one per other participant
|
||||
|
||||
bool is_rct() const { return m_rct; }
|
||||
uint64_t amount() const { return m_amount; }
|
||||
@ -327,6 +338,15 @@ namespace tools
|
||||
typedef std::vector<transfer_details> transfer_container;
|
||||
typedef std::unordered_multimap<crypto::hash, payment_details> payment_container;
|
||||
|
||||
struct multisig_sig
|
||||
{
|
||||
rct::rctSig sigs;
|
||||
crypto::public_key ignore;
|
||||
std::unordered_set<rct::key> used_L;
|
||||
std::unordered_set<crypto::public_key> signing_keys;
|
||||
rct::multisig_out msout;
|
||||
};
|
||||
|
||||
// The convention for destinations is:
|
||||
// dests does not include change
|
||||
// splitted_dsts (in construction_data) does
|
||||
@ -341,7 +361,7 @@ namespace tools
|
||||
crypto::secret_key tx_key;
|
||||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
std::vector<cryptonote::tx_destination_entry> dests;
|
||||
rct::multisig_out msout;
|
||||
std::vector<multisig_sig> multisig_sigs;
|
||||
|
||||
tx_construction_data construction_data;
|
||||
|
||||
@ -357,6 +377,7 @@ namespace tools
|
||||
FIELD(additional_tx_keys)
|
||||
FIELD(dests)
|
||||
FIELD(construction_data)
|
||||
FIELD(multisig_sigs)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
@ -377,7 +398,7 @@ namespace tools
|
||||
struct multisig_tx_set
|
||||
{
|
||||
std::vector<pending_tx> m_ptx;
|
||||
std::unordered_set<crypto::hash> m_signers;
|
||||
std::unordered_set<crypto::public_key> m_signers;
|
||||
};
|
||||
|
||||
struct keys_file_data
|
||||
@ -446,11 +467,17 @@ namespace tools
|
||||
const crypto::secret_key& viewkey = crypto::secret_key());
|
||||
/*!
|
||||
* \brief Creates a multisig wallet
|
||||
* \return empty if done, non empty if we need to send another string
|
||||
* to other participants
|
||||
*/
|
||||
void make_multisig(const epee::wipeable_string &password,
|
||||
std::string 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);
|
||||
/*!
|
||||
* \brief Finalizes creation of a multisig wallet
|
||||
*/
|
||||
bool finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers);
|
||||
/*!
|
||||
* Get a packaged multisig information string
|
||||
*/
|
||||
@ -459,6 +486,10 @@ namespace tools
|
||||
* 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);
|
||||
/*!
|
||||
* Verifies and extracts keys from a packaged multisig information string
|
||||
*/
|
||||
static bool verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer);
|
||||
/*!
|
||||
* Export multisig info
|
||||
* This will generate and remember new k values
|
||||
@ -610,7 +641,8 @@ namespace tools
|
||||
std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
|
||||
bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
|
||||
bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func);
|
||||
bool sign_multisig_tx(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids);
|
||||
bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids);
|
||||
bool sign_multisig_tx_from_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids);
|
||||
std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon);
|
||||
bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000);
|
||||
void get_transfers(wallet2::transfer_container& incoming_transfers) const;
|
||||
@ -890,6 +922,11 @@ namespace tools
|
||||
void set_attribute(const std::string &key, const std::string &value);
|
||||
std::string get_attribute(const std::string &key) const;
|
||||
|
||||
crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const;
|
||||
crypto::public_key get_multisig_signer_public_key() const;
|
||||
crypto::public_key get_multisig_signing_public_key(size_t idx) const;
|
||||
crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Stores wallet information to wallet file.
|
||||
@ -936,15 +973,17 @@ namespace tools
|
||||
void set_unspent(size_t idx);
|
||||
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count);
|
||||
bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
|
||||
bool wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const;
|
||||
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
|
||||
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
|
||||
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
|
||||
void scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs);
|
||||
void trim_hashchain();
|
||||
rct::multisig_kLRki get_multisig_composite_LRki(size_t n, const rct::key &k) const;
|
||||
rct::multisig_kLRki get_multisig_LRki(size_t n, const rct::key &k) const;
|
||||
rct::key get_multisig_k(size_t idx) const;
|
||||
void update_multisig_rescan_info(const std::vector<rct::key> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n);
|
||||
crypto::key_image get_multisig_composite_key_image(size_t n) const;
|
||||
rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const;
|
||||
rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const;
|
||||
rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const;
|
||||
void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n);
|
||||
|
||||
cryptonote::account_base m_account;
|
||||
boost::optional<epee::net_utils::http::login> m_daemon_login;
|
||||
@ -974,7 +1013,7 @@ namespace tools
|
||||
std::vector<tools::wallet2::address_book_row> m_address_book;
|
||||
uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value
|
||||
const std::vector<std::vector<tools::wallet2::multisig_info>> *m_multisig_rescan_info;
|
||||
const std::vector<rct::key> *m_multisig_rescan_k;
|
||||
const std::vector<std::vector<rct::key>> *m_multisig_rescan_k;
|
||||
|
||||
std::atomic<bool> m_run;
|
||||
|
||||
@ -988,7 +1027,7 @@ namespace tools
|
||||
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;
|
||||
std::vector<crypto::public_key> m_multisig_signers;
|
||||
bool m_always_confirm_transfers;
|
||||
bool m_print_ring_members;
|
||||
bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */
|
||||
@ -1026,6 +1065,7 @@ namespace tools
|
||||
BOOST_CLASS_VERSION(tools::wallet2, 22)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1)
|
||||
@ -1036,6 +1076,7 @@ BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0)
|
||||
|
||||
namespace boost
|
||||
{
|
||||
@ -1076,8 +1117,8 @@ namespace boost
|
||||
if (ver < 9)
|
||||
{
|
||||
x.m_key_image_partial = false;
|
||||
x.m_multisig_k.clear();
|
||||
x.m_multisig_info.clear();
|
||||
x.m_multisig_k = rct::zero();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1163,13 +1204,20 @@ namespace boost
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver)
|
||||
inline void serialize(Archive &a, tools::wallet2::multisig_info::LR &x, const boost::serialization::version_type ver)
|
||||
{
|
||||
a & x.m_partial_key_image;
|
||||
a & x.m_L;
|
||||
a & x.m_R;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver)
|
||||
{
|
||||
a & x.m_signer;
|
||||
a & x.m_LR;
|
||||
a & x.m_partial_key_images;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
inline void serialize(Archive &a, tools::wallet2::multisig_tx_set &x, const boost::serialization::version_type ver)
|
||||
{
|
||||
@ -1352,6 +1400,16 @@ namespace boost
|
||||
a & x.selected_transfers;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
inline void serialize(Archive &a, tools::wallet2::multisig_sig &x, const boost::serialization::version_type ver)
|
||||
{
|
||||
a & x.sigs;
|
||||
a & x.ignore;
|
||||
a & x.used_L;
|
||||
a & x.signing_keys;
|
||||
a & x.msout;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver)
|
||||
{
|
||||
@ -1382,7 +1440,7 @@ namespace boost
|
||||
a & x.selected_transfers;
|
||||
if (ver < 3)
|
||||
return;
|
||||
a & x.msout;
|
||||
a & x.multisig_sigs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1458,6 +1516,8 @@ namespace tools
|
||||
// throw if attempting a transaction with no destinations
|
||||
THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(m_multisig, error::wallet_internal_error, "Multisig wallets cannot spend non rct outputs");
|
||||
|
||||
uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit();
|
||||
uint64_t needed_money = fee;
|
||||
|
||||
@ -1560,10 +1620,7 @@ namespace tools
|
||||
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx);
|
||||
src.real_output = interted_it - src.outputs.begin();
|
||||
src.real_output_in_tx_index = td.m_internal_output_index;
|
||||
if (m_multisig)
|
||||
src.multisig_kLRki = get_multisig_composite_LRki(idx, get_multisig_k(idx));
|
||||
else
|
||||
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
|
||||
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
|
||||
detail::print_source_entry(src);
|
||||
++i;
|
||||
}
|
||||
@ -1619,7 +1676,6 @@ namespace tools
|
||||
ptx.tx_key = tx_key;
|
||||
ptx.additional_tx_keys = additional_tx_keys;
|
||||
ptx.dests = dsts;
|
||||
ptx.msout = msout;
|
||||
ptx.construction_data.sources = sources;
|
||||
ptx.construction_data.change_dts = change_dts;
|
||||
ptx.construction_data.splitted_dsts = splitted_dsts;
|
||||
|
@ -2446,7 +2446,7 @@ namespace tools
|
||||
|
||||
try
|
||||
{
|
||||
m_wallet->make_multisig(req.password, secret_keys, public_keys, req.threshold);
|
||||
res.multisig_info = m_wallet->make_multisig(req.password, secret_keys, public_keys, req.threshold);
|
||||
res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet());
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
@ -2490,9 +2490,16 @@ namespace tools
|
||||
res.info.resize(info.size());
|
||||
for (size_t n = 0; n < info.size(); ++n)
|
||||
{
|
||||
res.info[n].partial_key_image = epee::string_tools::pod_to_hex(info[n].m_partial_key_image);
|
||||
res.info[n].L = epee::string_tools::pod_to_hex(info[n].m_L);
|
||||
res.info[n].R = epee::string_tools::pod_to_hex(info[n].m_R);
|
||||
res.info[n].signer = epee::string_tools::pod_to_hex(info[n].m_signer);
|
||||
res.info[n].LR.resize(info[n].m_LR.size());
|
||||
for (size_t l = 0; l < info[n].m_LR.size(); ++l)
|
||||
{
|
||||
res.info[n].LR[l].L = epee::string_tools::pod_to_hex(info[n].m_LR[l].m_L);
|
||||
res.info[n].LR[l].R = epee::string_tools::pod_to_hex(info[n].m_LR[l].m_R);
|
||||
}
|
||||
res.info[n].partial_key_images.resize(info[n].m_partial_key_images.size());
|
||||
for (size_t l = 0; l < info[n].m_partial_key_images.size(); ++l)
|
||||
res.info[n].partial_key_images[l] = epee::string_tools::pod_to_hex(info[n].m_partial_key_images[l]);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -2529,17 +2536,34 @@ namespace tools
|
||||
info[n].resize(req.info[n].info.size());
|
||||
for (size_t i = 0; i < info[n].size(); ++i)
|
||||
{
|
||||
if (!epee::string_tools::hex_to_pod(req.info[n].info[i].partial_key_image, info[n][i].m_partial_key_image))
|
||||
const auto &src = req.info[n].info[i];
|
||||
auto &dst = info[n][i];
|
||||
|
||||
if (!epee::string_tools::hex_to_pod(src.signer, dst.m_signer))
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE;
|
||||
er.message = "Failed to parse partial key image from multisig info";
|
||||
er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS;
|
||||
er.message = "Failed to parse signer from multisig info";
|
||||
return false;
|
||||
}
|
||||
if (!epee::string_tools::hex_to_pod(req.info[n].info[i].L, info[n][i].m_L) || !epee::string_tools::hex_to_pod(req.info[n].info[i].R, info[n][i].m_R))
|
||||
dst.m_LR.resize(src.LR.size());
|
||||
for (size_t l = 0; l < src.LR.size(); ++l)
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_WRONG_LR;
|
||||
er.message = "Failed to parse L/R info from hex";
|
||||
return false;
|
||||
if (!epee::string_tools::hex_to_pod(src.LR[l].L, dst.m_LR[l].m_L) || !epee::string_tools::hex_to_pod(src.LR[l].R, dst.m_LR[l].m_R))
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_WRONG_LR;
|
||||
er.message = "Failed to parse L/R from multisig info";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
dst.m_partial_key_images.resize(src.partial_key_images.size());
|
||||
for (size_t l = 0; l < src.partial_key_images.size(); ++l)
|
||||
{
|
||||
if (!epee::string_tools::hex_to_pod(src.partial_key_images[l], dst.m_partial_key_images[l]))
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE;
|
||||
er.message = "Failed to parse partial key image from multisig info";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2573,6 +2597,64 @@ namespace tools
|
||||
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er)
|
||||
{
|
||||
if (!m_wallet) return not_open(er);
|
||||
if (m_wallet->restricted())
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_DENIED;
|
||||
er.message = "Command unavailable in restricted mode.";
|
||||
return false;
|
||||
}
|
||||
uint32_t threshold, total;
|
||||
if (!m_wallet->multisig(&threshold, &total))
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
|
||||
er.message = "This wallet is not multisig";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (req.multisig_info.size() < threshold - 1)
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
|
||||
er.message = "Needs multisig info from more participants";
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse all multisig info
|
||||
std::unordered_set<crypto::public_key> public_keys;
|
||||
std::vector<crypto::public_key> signers(req.multisig_info.size(), crypto::null_pkey);
|
||||
for (size_t i = 0; i < req.multisig_info.size(); ++i)
|
||||
{
|
||||
if (!m_wallet->verify_extra_multisig_info(req.multisig_info[i], public_keys, signers[i]))
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO;
|
||||
er.message = std::string("Bad multisig_info info: ") + req.multisig_info[i];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!m_wallet->finalize_multisig(req.password, public_keys, signers))
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
|
||||
er.message = "Error calling finalize_multisig";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
|
||||
er.message = std::string("Error calling finalize_multisig: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet());
|
||||
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
@ -181,6 +181,7 @@ namespace tools
|
||||
bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er);
|
||||
bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er);
|
||||
bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er);
|
||||
bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er);
|
||||
|
||||
//json rpc v2
|
||||
bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er);
|
||||
|
@ -1546,26 +1546,39 @@ namespace wallet_rpc
|
||||
struct response
|
||||
{
|
||||
std::string address;
|
||||
std::string multisig_info;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(multisig_info)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
struct multisig_info_entry
|
||||
struct LR_entry
|
||||
{
|
||||
std::string partial_key_image;
|
||||
std::string L;
|
||||
std::string R;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(partial_key_image)
|
||||
KV_SERIALIZE(L)
|
||||
KV_SERIALIZE(R)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct multisig_info_entry
|
||||
{
|
||||
std::string signer;
|
||||
std::vector<LR_entry> LR;
|
||||
std::vector<std::string> partial_key_images;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(signer)
|
||||
KV_SERIALIZE(LR)
|
||||
KV_SERIALIZE(partial_key_images)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_EXPORT_MULTISIG
|
||||
{
|
||||
struct request
|
||||
@ -1613,5 +1626,29 @@ namespace wallet_rpc
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_FINALIZE_MULTISIG
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::string password;
|
||||
std::vector<std::string> multisig_info;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(password)
|
||||
KV_SERIALIZE(multisig_info)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct response
|
||||
{
|
||||
std::string address;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(address)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -125,14 +125,26 @@ static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, to
|
||||
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk2[0], pk2[0]));
|
||||
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk2[1], pk2[1]));
|
||||
|
||||
// not implemented yet
|
||||
if (M < 3)
|
||||
return;
|
||||
|
||||
ASSERT_FALSE(wallet0.multisig() || wallet1.multisig() || wallet2.multisig());
|
||||
wallet0.make_multisig("", sk0, pk0, M);
|
||||
wallet1.make_multisig("", sk1, pk1, M);
|
||||
wallet2.make_multisig("", sk2, pk2, M);
|
||||
std::string mxi0 = wallet0.make_multisig("", sk0, pk0, M);
|
||||
std::string mxi1 = wallet1.make_multisig("", sk1, pk1, M);
|
||||
std::string mxi2 = wallet2.make_multisig("", sk2, pk2, M);
|
||||
|
||||
const size_t nset = !mxi0.empty() + !mxi1.empty() + !mxi2.empty();
|
||||
ASSERT_TRUE((M < 3 && nset == 3) || (M == 3 && nset == 0));
|
||||
|
||||
if (nset > 0)
|
||||
{
|
||||
std::unordered_set<crypto::public_key> pkeys;
|
||||
std::vector<crypto::public_key> signers(3, crypto::null_pkey);
|
||||
ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi0, pkeys, signers[0]));
|
||||
ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi1, pkeys, signers[1]));
|
||||
ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi2, pkeys, signers[2]));
|
||||
ASSERT_TRUE(pkeys.size() == 3);
|
||||
ASSERT_TRUE(wallet0.finalize_multisig("", pkeys, signers));
|
||||
ASSERT_TRUE(wallet1.finalize_multisig("", pkeys, signers));
|
||||
ASSERT_TRUE(wallet2.finalize_multisig("", pkeys, signers));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true));
|
||||
ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet2.get_account().get_public_address_str(true));
|
||||
|
Loading…
Reference in New Issue
Block a user